Skip to content

Commit cd55932

Browse files
andredcoliveiranightwing
andauthoredJun 7, 2023
fix: don't throw unhandled errors in TabstopManager when EditSession becomes undefined (#5193)
* fix: don't throw unhandled errors in TabstopManager when EditSession becomes * refactor snippets and hash_handler to use classes * fix detaching tabstrops after sessionChange * backwards compatibility for HashHandler.call --------- Co-authored-by: nightwing <amirjanyan@gmail.com>
1 parent c90eaa1 commit cd55932

File tree

3 files changed

+304
-285
lines changed

3 files changed

+304
-285
lines changed
 

‎src/keyboard/hash_handler.js

+56-46
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,30 @@ var keyUtil = require("../lib/keys");
44
var useragent = require("../lib/useragent");
55
var KEY_MODS = keyUtil.KEY_MODS;
66

7-
function HashHandler(config, platform) {
8-
this.platform = platform || (useragent.isMac ? "mac" : "win");
9-
this.commands = {};
10-
this.commandKeyBinding = {};
11-
this.addCommands(config);
12-
this.$singleCommand = true;
13-
}
14-
15-
function MultiHashHandler(config, platform) {
16-
HashHandler.call(this, config, platform);
17-
this.$singleCommand = false;
18-
}
19-
20-
MultiHashHandler.prototype = HashHandler.prototype;
7+
class MultiHashHandler {
8+
constructor(config, platform) {
9+
this.$init(config, platform, false);
10+
}
2111

22-
(function() {
23-
12+
$init(config, platform, $singleCommand) {
13+
this.platform = platform || (useragent.isMac ? "mac" : "win");
14+
this.commands = {};
15+
this.commandKeyBinding = {};
16+
this.addCommands(config);
17+
this.$singleCommand = $singleCommand;
18+
}
2419

25-
this.addCommand = function(command) {
20+
addCommand(command) {
2621
if (this.commands[command.name])
2722
this.removeCommand(command);
2823

2924
this.commands[command.name] = command;
3025

3126
if (command.bindKey)
3227
this._buildKeyHash(command);
33-
};
28+
}
3429

35-
this.removeCommand = function(command, keepCommand) {
30+
removeCommand(command, keepCommand) {
3631
var name = command && (typeof command === 'string' ? command : command.name);
3732
command = this.commands[name];
3833
if (!keepCommand)
@@ -54,9 +49,9 @@ MultiHashHandler.prototype = HashHandler.prototype;
5449
}
5550
}
5651
}
57-
};
52+
}
5853

59-
this.bindKey = function(key, command, position) {
54+
bindKey(key, command, position) {
6055
if (typeof key == "object" && key) {
6156
if (position == undefined)
6257
position = key.position;
@@ -84,14 +79,9 @@ MultiHashHandler.prototype = HashHandler.prototype;
8479
var id = KEY_MODS[binding.hashId] + binding.key;
8580
this._addCommandToBinding(chain + id, command, position);
8681
}, this);
87-
};
88-
89-
function getPosition(command) {
90-
return typeof command == "object" && command.bindKey
91-
&& command.bindKey.position
92-
|| (command.isDefault ? -100 : 0);
9382
}
94-
this._addCommandToBinding = function(keyId, command, position) {
83+
84+
_addCommandToBinding(keyId, command, position) {
9585
var ckb = this.commandKeyBinding, i;
9686
if (!command) {
9787
delete ckb[keyId];
@@ -117,9 +107,9 @@ MultiHashHandler.prototype = HashHandler.prototype;
117107
}
118108
commands.splice(i, 0, command);
119109
}
120-
};
110+
}
121111

122-
this.addCommands = function(commands) {
112+
addCommands(commands) {
123113
commands && Object.keys(commands).forEach(function(name) {
124114
var command = commands[name];
125115
if (!command)
@@ -139,27 +129,27 @@ MultiHashHandler.prototype = HashHandler.prototype;
139129

140130
this.addCommand(command);
141131
}, this);
142-
};
132+
}
143133

144-
this.removeCommands = function(commands) {
134+
removeCommands(commands) {
145135
Object.keys(commands).forEach(function(name) {
146136
this.removeCommand(commands[name]);
147137
}, this);
148-
};
138+
}
149139

150-
this.bindKeys = function(keyList) {
140+
bindKeys(keyList) {
151141
Object.keys(keyList).forEach(function(key) {
152142
this.bindKey(key, keyList[key]);
153143
}, this);
154-
};
144+
}
155145

156-
this._buildKeyHash = function(command) {
146+
_buildKeyHash(command) {
157147
this.bindKey(command.bindKey, command);
158-
};
148+
}
159149

160150
// accepts keys in the form ctrl+Enter or ctrl-Enter
161151
// keys without modifiers or shift only
162-
this.parseKeys = function(keys) {
152+
parseKeys(keys) {
163153
var parts = keys.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(x){return x;});
164154
var key = parts.pop();
165155

@@ -182,14 +172,14 @@ MultiHashHandler.prototype = HashHandler.prototype;
182172
hashId |= modifier;
183173
}
184174
return {key: key, hashId: hashId};
185-
};
175+
}
186176

187-
this.findKeyCommand = function findKeyCommand(hashId, keyString) {
177+
findKeyCommand(hashId, keyString) {
188178
var key = KEY_MODS[hashId] + keyString;
189179
return this.commandKeyBinding[key];
190-
};
180+
}
191181

192-
this.handleKeyboard = function(data, hashId, keyString, keyCode) {
182+
handleKeyboard(data, hashId, keyString, keyCode) {
193183
if (keyCode < 0) return;
194184
var key = KEY_MODS[hashId] + keyString;
195185
var command = this.commandKeyBinding[key];
@@ -212,13 +202,33 @@ MultiHashHandler.prototype = HashHandler.prototype;
212202
data.$keyChain = ""; // reset keyChain
213203
}
214204
return {command: command};
215-
};
205+
}
216206

217-
this.getStatusText = function(editor, data) {
207+
getStatusText(editor, data) {
218208
return data.$keyChain || "";
219-
};
209+
}
210+
211+
}
212+
213+
function getPosition(command) {
214+
return typeof command == "object" && command.bindKey
215+
&& command.bindKey.position
216+
|| (command.isDefault ? -100 : 0);
217+
}
218+
219+
class HashHandler extends MultiHashHandler {
220+
constructor(config, platform) {
221+
super(config, platform);
222+
this.$singleCommand = true;
223+
}
224+
}
220225

221-
}).call(HashHandler.prototype);
226+
HashHandler.call = function(thisArg, config, platform) {
227+
MultiHashHandler.prototype.$init.call(thisArg, config, platform, true);
228+
};
229+
MultiHashHandler.call = function(thisArg, config, platform) {
230+
MultiHashHandler.prototype.$init.call(thisArg, config, platform, false);
231+
};
222232

223233
exports.HashHandler = HashHandler;
224234
exports.MultiHashHandler = MultiHashHandler;

‎src/snippets.js

+238-239
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,19 @@ function date(dateFormat) {
8787
return str.length == 1 ? "0" + str : str;
8888
}
8989

90-
var SnippetManager = function() {
91-
this.snippetMap = {};
92-
this.snippetNameMap = {};
93-
};
90+
class SnippetManager {
91+
constructor() {
92+
this.snippetMap = {};
93+
this.snippetNameMap = {};
94+
this.variables = VARIABLES;
95+
}
9496

95-
(function() {
96-
oop.implement(this, EventEmitter);
9797

98-
this.getTokenizer = function() {
98+
getTokenizer() {
9999
return SnippetManager.$tokenizer || this.createTokenizer();
100-
};
100+
}
101101

102-
this.createTokenizer = function() {
102+
createTokenizer() {
103103
function TabstopToken(str) {
104104
str = str.substr(1);
105105
if (/^\d+$/.test(str))
@@ -218,15 +218,15 @@ var SnippetManager = function() {
218218
]
219219
});
220220
return SnippetManager.$tokenizer;
221-
};
221+
}
222222

223-
this.tokenizeTmSnippet = function(str, startState) {
223+
tokenizeTmSnippet(str, startState) {
224224
return this.getTokenizer().getLineTokens(str, startState).tokens.map(function(x) {
225225
return x.value || x;
226226
});
227-
};
227+
}
228228

229-
this.getVariableValue = function(editor, name, indentation) {
229+
getVariableValue(editor, name, indentation) {
230230
if (/^\d+$/.test(name))
231231
return (this.variables.__ || {})[name] || "";
232232
if (/^[A-Z]\d+$/.test(name))
@@ -239,12 +239,10 @@ var SnippetManager = function() {
239239
if (typeof value == "function")
240240
value = this.variables[name](editor, name, indentation);
241241
return value == null ? "" : value;
242-
};
242+
}
243243

244-
this.variables = VARIABLES;
245-
246244
// returns string formatted according to http://manual.macromates.com/en/regular_expressions#replacement_string_syntax_format_strings
247-
this.tmStrFormat = function(str, ch, editor) {
245+
tmStrFormat(str, ch, editor) {
248246
if (!ch.fmt) return str;
249247
var flag = ch.flag || "";
250248
var re = ch.guard;
@@ -282,17 +280,17 @@ var SnippetManager = function() {
282280
return fmtParts.join("");
283281
});
284282
return formatted;
285-
};
283+
}
286284

287-
this.tmFormatFunction = function(str, ch, editor) {
285+
tmFormatFunction(str, ch, editor) {
288286
if (ch.formatFunction == "upcase")
289287
return str.toUpperCase();
290288
if (ch.formatFunction == "downcase")
291289
return str.toLowerCase();
292290
return str;
293-
};
291+
}
294292

295-
this.resolveVariables = function(snippet, editor) {
293+
resolveVariables(snippet, editor) {
296294
var result = [];
297295
var indentation = "";
298296
var afterNewLine = true;
@@ -347,139 +345,14 @@ var SnippetManager = function() {
347345
i = i1;
348346
}
349347
return result;
350-
};
351-
352-
var processSnippetText = function(editor, snippetText, options={}) {
353-
var cursor = editor.getCursorPosition();
354-
var line = editor.session.getLine(cursor.row);
355-
var tabString = editor.session.getTabString();
356-
var indentString = line.match(/^\s*/)[0];
357-
358-
if (cursor.column < indentString.length)
359-
indentString = indentString.slice(0, cursor.column);
348+
}
360349

361-
snippetText = snippetText.replace(/\r/g, "");
362-
var tokens = this.tokenizeTmSnippet(snippetText);
363-
tokens = this.resolveVariables(tokens, editor);
364-
// indent
365-
tokens = tokens.map(function(x) {
366-
if (x == "\n" && !options.excludeExtraIndent)
367-
return x + indentString;
368-
if (typeof x == "string")
369-
return x.replace(/\t/g, tabString);
370-
return x;
371-
});
372-
// tabstop values
373-
var tabstops = [];
374-
tokens.forEach(function(p, i) {
375-
if (typeof p != "object")
376-
return;
377-
var id = p.tabstopId;
378-
var ts = tabstops[id];
379-
if (!ts) {
380-
ts = tabstops[id] = [];
381-
ts.index = id;
382-
ts.value = "";
383-
ts.parents = {};
384-
}
385-
if (ts.indexOf(p) !== -1)
386-
return;
387-
if (p.choices && !ts.choices)
388-
ts.choices = p.choices;
389-
ts.push(p);
390-
var i1 = tokens.indexOf(p, i + 1);
391-
if (i1 === -1)
392-
return;
393-
394-
var value = tokens.slice(i + 1, i1);
395-
var isNested = value.some(function(t) {return typeof t === "object";});
396-
if (isNested && !ts.value) {
397-
ts.value = value;
398-
} else if (value.length && (!ts.value || typeof ts.value !== "string")) {
399-
ts.value = value.join("");
400-
}
401-
});
402-
403-
// expand tabstop values
404-
tabstops.forEach(function(ts) {ts.length = 0;});
405-
var expanding = {};
406-
function copyValue(val) {
407-
var copy = [];
408-
for (var i = 0; i < val.length; i++) {
409-
var p = val[i];
410-
if (typeof p == "object") {
411-
if (expanding[p.tabstopId])
412-
continue;
413-
var j = val.lastIndexOf(p, i - 1);
414-
p = copy[j] || {tabstopId: p.tabstopId};
415-
}
416-
copy[i] = p;
417-
}
418-
return copy;
419-
}
420-
for (var i = 0; i < tokens.length; i++) {
421-
var p = tokens[i];
422-
if (typeof p != "object")
423-
continue;
424-
var id = p.tabstopId;
425-
var ts = tabstops[id];
426-
var i1 = tokens.indexOf(p, i + 1);
427-
if (expanding[id]) {
428-
// if reached closing bracket clear expanding state
429-
if (expanding[id] === p) {
430-
delete expanding[id];
431-
Object.keys(expanding).forEach(function(parentId) {
432-
ts.parents[parentId] = true;
433-
});
434-
}
435-
// otherwise just ignore recursive tabstop
436-
continue;
437-
}
438-
expanding[id] = p;
439-
var value = ts.value;
440-
if (typeof value !== "string")
441-
value = copyValue(value);
442-
else if (p.fmt)
443-
value = this.tmStrFormat(value, p, editor);
444-
tokens.splice.apply(tokens, [i + 1, Math.max(0, i1 - i)].concat(value, p));
445-
446-
if (ts.indexOf(p) === -1)
447-
ts.push(p);
448-
}
449-
450-
// convert to plain text
451-
var row = 0, column = 0;
452-
var text = "";
453-
tokens.forEach(function(t) {
454-
if (typeof t === "string") {
455-
var lines = t.split("\n");
456-
if (lines.length > 1){
457-
column = lines[lines.length - 1].length;
458-
row += lines.length - 1;
459-
} else
460-
column += t.length;
461-
text += t;
462-
} else if (t) {
463-
if (!t.start)
464-
t.start = {row: row, column: column};
465-
else
466-
t.end = {row: row, column: column};
467-
}
468-
});
469-
470-
return {
471-
text,
472-
tabstops,
473-
tokens
474-
};
475-
};
476-
477-
this.getDisplayTextForSnippet = function(editor, snippetText) {
350+
getDisplayTextForSnippet(editor, snippetText) {
478351
var processedSnippet = processSnippetText.call(this, editor, snippetText);
479352
return processedSnippet.text;
480-
};
353+
}
481354

482-
this.insertSnippetForSelection = function(editor, snippetText, options={}) {
355+
insertSnippetForSelection(editor, snippetText, options={}) {
483356
var processedSnippet = processSnippetText.call(this, editor, snippetText, options);
484357

485358
var range = editor.getSelectionRange();
@@ -491,9 +364,9 @@ var SnippetManager = function() {
491364
var tabstopManager = new TabstopManager(editor);
492365
var selectionId = editor.inVirtualSelectionMode && editor.selection.index;
493366
tabstopManager.addTabstops(processedSnippet.tabstops, range.start, end, selectionId);
494-
};
367+
}
495368

496-
this.insertSnippet = function(editor, snippetText, options={}) {
369+
insertSnippet(editor, snippetText, options={}) {
497370
var self = this;
498371
if (options.range && !(options.range instanceof Range))
499372
options.range = Range.fromPoints(options.range.start, options.range.end);
@@ -507,9 +380,9 @@ var SnippetManager = function() {
507380

508381
if (editor.tabstopManager)
509382
editor.tabstopManager.tabNext();
510-
};
383+
}
511384

512-
this.$getScope = function(editor) {
385+
$getScope(editor) {
513386
var scope = editor.session.$mode.$id || "";
514387
scope = scope.split("/").pop();
515388
if (scope === "html" || scope === "php") {
@@ -532,9 +405,9 @@ var SnippetManager = function() {
532405
}
533406

534407
return scope;
535-
};
408+
}
536409

537-
this.getActiveScopes = function(editor) {
410+
getActiveScopes(editor) {
538411
var scope = this.$getScope(editor);
539412
var scopes = [scope];
540413
var snippetMap = this.snippetMap;
@@ -543,19 +416,19 @@ var SnippetManager = function() {
543416
}
544417
scopes.push("_");
545418
return scopes;
546-
};
419+
}
547420

548-
this.expandWithTab = function(editor, options) {
421+
expandWithTab(editor, options) {
549422
var self = this;
550423
var result = editor.forEachSelection(function() {
551424
return self.expandSnippetForSelection(editor, options);
552425
}, null, {keepOrder: true});
553426
if (result && editor.tabstopManager)
554427
editor.tabstopManager.tabNext();
555428
return result;
556-
};
429+
}
557430

558-
this.expandSnippetForSelection = function(editor, options) {
431+
expandSnippetForSelection(editor, options) {
559432
var cursor = editor.getCursorPosition();
560433
var line = editor.session.getLine(cursor.row);
561434
var before = line.substring(0, cursor.column);
@@ -584,9 +457,9 @@ var SnippetManager = function() {
584457

585458
this.variables.M__ = this.variables.T__ = null;
586459
return true;
587-
};
460+
}
588461

589-
this.findMatchingSnippet = function(snippetList, before, after) {
462+
findMatchingSnippet(snippetList, before, after) {
590463
for (var i = snippetList.length; i--;) {
591464
var s = snippetList[i];
592465
if (s.startRe && !s.startRe.test(before))
@@ -602,11 +475,9 @@ var SnippetManager = function() {
602475
s.replaceAfter = s.endTriggerRe ? s.endTriggerRe.exec(after)[0] : "";
603476
return s;
604477
}
605-
};
478+
}
606479

607-
this.snippetMap = {};
608-
this.snippetNameMap = {};
609-
this.register = function(snippets, scope) {
480+
register(snippets, scope) {
610481
var snippetMap = this.snippetMap;
611482
var snippetNameMap = this.snippetNameMap;
612483
var self = this;
@@ -684,8 +555,8 @@ var SnippetManager = function() {
684555
}
685556

686557
this._signal("registerSnippets", {scope: scope});
687-
};
688-
this.unregister = function(snippets, scope) {
558+
}
559+
unregister(snippets, scope) {
689560
var snippetMap = this.snippetMap;
690561
var snippetNameMap = this.snippetNameMap;
691562

@@ -703,8 +574,8 @@ var SnippetManager = function() {
703574
removeSnippet(snippets);
704575
else if (Array.isArray(snippets))
705576
snippets.forEach(removeSnippet);
706-
};
707-
this.parseSnippetFile = function(str) {
577+
}
578+
parseSnippetFile(str) {
708579
str = str.replace(/\r/g, "");
709580
var list = [], snippet = {};
710581
var re = /^#.*|^({[\s\S]*})\s*$|^(\S+) (.*)$|^((?:\n*\t.*)+)/gm;
@@ -737,8 +608,8 @@ var SnippetManager = function() {
737608
}
738609
}
739610
return list;
740-
};
741-
this.getSnippetByName = function(name, editor) {
611+
}
612+
getSnippetByName(name, editor) {
742613
var snippetMap = this.snippetNameMap;
743614
var snippet;
744615
this.getActiveScopes(editor).some(function(scope) {
@@ -748,55 +619,183 @@ var SnippetManager = function() {
748619
return !!snippet;
749620
}, this);
750621
return snippet;
751-
};
622+
}
623+
}
752624

753-
}).call(SnippetManager.prototype);
625+
oop.implement(SnippetManager.prototype, EventEmitter);
754626

627+
var processSnippetText = function(editor, snippetText, options={}) {
628+
var cursor = editor.getCursorPosition();
629+
var line = editor.session.getLine(cursor.row);
630+
var tabString = editor.session.getTabString();
631+
var indentString = line.match(/^\s*/)[0];
632+
633+
if (cursor.column < indentString.length)
634+
indentString = indentString.slice(0, cursor.column);
635+
636+
snippetText = snippetText.replace(/\r/g, "");
637+
var tokens = this.tokenizeTmSnippet(snippetText);
638+
tokens = this.resolveVariables(tokens, editor);
639+
// indent
640+
tokens = tokens.map(function(x) {
641+
if (x == "\n" && !options.excludeExtraIndent)
642+
return x + indentString;
643+
if (typeof x == "string")
644+
return x.replace(/\t/g, tabString);
645+
return x;
646+
});
647+
// tabstop values
648+
var tabstops = [];
649+
tokens.forEach(function(p, i) {
650+
if (typeof p != "object")
651+
return;
652+
var id = p.tabstopId;
653+
var ts = tabstops[id];
654+
if (!ts) {
655+
ts = tabstops[id] = [];
656+
ts.index = id;
657+
ts.value = "";
658+
ts.parents = {};
659+
}
660+
if (ts.indexOf(p) !== -1)
661+
return;
662+
if (p.choices && !ts.choices)
663+
ts.choices = p.choices;
664+
ts.push(p);
665+
var i1 = tokens.indexOf(p, i + 1);
666+
if (i1 === -1)
667+
return;
668+
669+
var value = tokens.slice(i + 1, i1);
670+
var isNested = value.some(function(t) {return typeof t === "object";});
671+
if (isNested && !ts.value) {
672+
ts.value = value;
673+
} else if (value.length && (!ts.value || typeof ts.value !== "string")) {
674+
ts.value = value.join("");
675+
}
676+
});
677+
678+
// expand tabstop values
679+
tabstops.forEach(function(ts) {ts.length = 0;});
680+
var expanding = {};
681+
function copyValue(val) {
682+
var copy = [];
683+
for (var i = 0; i < val.length; i++) {
684+
var p = val[i];
685+
if (typeof p == "object") {
686+
if (expanding[p.tabstopId])
687+
continue;
688+
var j = val.lastIndexOf(p, i - 1);
689+
p = copy[j] || {tabstopId: p.tabstopId};
690+
}
691+
copy[i] = p;
692+
}
693+
return copy;
694+
}
695+
for (var i = 0; i < tokens.length; i++) {
696+
var p = tokens[i];
697+
if (typeof p != "object")
698+
continue;
699+
var id = p.tabstopId;
700+
var ts = tabstops[id];
701+
var i1 = tokens.indexOf(p, i + 1);
702+
if (expanding[id]) {
703+
// if reached closing bracket clear expanding state
704+
if (expanding[id] === p) {
705+
delete expanding[id];
706+
Object.keys(expanding).forEach(function(parentId) {
707+
ts.parents[parentId] = true;
708+
});
709+
}
710+
// otherwise just ignore recursive tabstop
711+
continue;
712+
}
713+
expanding[id] = p;
714+
var value = ts.value;
715+
if (typeof value !== "string")
716+
value = copyValue(value);
717+
else if (p.fmt)
718+
value = this.tmStrFormat(value, p, editor);
719+
tokens.splice.apply(tokens, [i + 1, Math.max(0, i1 - i)].concat(value, p));
720+
721+
if (ts.indexOf(p) === -1)
722+
ts.push(p);
723+
}
724+
725+
// convert to plain text
726+
var row = 0, column = 0;
727+
var text = "";
728+
tokens.forEach(function(t) {
729+
if (typeof t === "string") {
730+
var lines = t.split("\n");
731+
if (lines.length > 1){
732+
column = lines[lines.length - 1].length;
733+
row += lines.length - 1;
734+
} else
735+
column += t.length;
736+
text += t;
737+
} else if (t) {
738+
if (!t.start)
739+
t.start = {row: row, column: column};
740+
else
741+
t.end = {row: row, column: column};
742+
}
743+
});
755744

756-
var TabstopManager = function(editor) {
757-
if (editor.tabstopManager)
758-
return editor.tabstopManager;
759-
editor.tabstopManager = this;
760-
this.$onChange = this.onChange.bind(this);
761-
this.$onChangeSelection = lang.delayedCall(this.onChangeSelection.bind(this)).schedule;
762-
this.$onChangeSession = this.onChangeSession.bind(this);
763-
this.$onAfterExec = this.onAfterExec.bind(this);
764-
this.attach(editor);
745+
return {
746+
text,
747+
tabstops,
748+
tokens
749+
};
765750
};
766-
(function() {
767-
this.attach = function(editor) {
751+
752+
class TabstopManager {
753+
constructor(editor) {
768754
this.index = 0;
769755
this.ranges = [];
770756
this.tabstops = [];
757+
if (editor.tabstopManager)
758+
return editor.tabstopManager;
759+
editor.tabstopManager = this;
760+
this.$onChange = this.onChange.bind(this);
761+
this.$onChangeSelection = lang.delayedCall(this.onChangeSelection.bind(this)).schedule;
762+
this.$onChangeSession = this.onChangeSession.bind(this);
763+
this.$onAfterExec = this.onAfterExec.bind(this);
764+
this.attach(editor);
765+
}
766+
767+
attach(editor) {
771768
this.$openTabstops = null;
772769
this.selectedTabstop = null;
773770

774771
this.editor = editor;
772+
this.session = editor.session;
775773
this.editor.on("change", this.$onChange);
776774
this.editor.on("changeSelection", this.$onChangeSelection);
777775
this.editor.on("changeSession", this.$onChangeSession);
778776
this.editor.commands.on("afterExec", this.$onAfterExec);
779777
this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
780-
};
781-
this.detach = function() {
778+
}
779+
detach() {
782780
this.tabstops.forEach(this.removeTabstopMarkers, this);
783-
this.ranges = null;
784-
this.tabstops = null;
781+
this.ranges.length = 0;
782+
this.tabstops.length = 0;
785783
this.selectedTabstop = null;
786-
this.editor.removeListener("change", this.$onChange);
787-
this.editor.removeListener("changeSelection", this.$onChangeSelection);
788-
this.editor.removeListener("changeSession", this.$onChangeSession);
789-
this.editor.commands.removeListener("afterExec", this.$onAfterExec);
784+
this.editor.off("change", this.$onChange);
785+
this.editor.off("changeSelection", this.$onChangeSelection);
786+
this.editor.off("changeSession", this.$onChangeSession);
787+
this.editor.commands.off("afterExec", this.$onAfterExec);
790788
this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
791789
this.editor.tabstopManager = null;
790+
this.session = null;
792791
this.editor = null;
793-
};
792+
}
794793

795-
this.onChange = function(delta) {
794+
onChange(delta) {
796795
var isRemove = delta.action[0] == "r";
797796
var selectedTabstop = this.selectedTabstop || {};
798797
var parents = selectedTabstop.parents || {};
799-
var tabstops = (this.tabstops || []).slice();
798+
var tabstops = this.tabstops.slice();
800799
for (var i = 0; i < tabstops.length; i++) {
801800
var ts = tabstops[i];
802801
var active = ts == selectedTabstop || parents[ts.index];
@@ -814,16 +813,16 @@ var TabstopManager = function(editor) {
814813
}
815814
ts.rangeList.$onChange(delta);
816815
}
817-
var session = this.editor.session;
816+
var session = this.session;
818817
if (!this.$inChange && isRemove && session.getLength() == 1 && !session.getValue())
819818
this.detach();
820-
};
821-
this.updateLinkedFields = function() {
819+
}
820+
updateLinkedFields() {
822821
var ts = this.selectedTabstop;
823822
if (!ts || !ts.hasLinkedRanges || !ts.firstNonLinked)
824823
return;
825824
this.$inChange = true;
826-
var session = this.editor.session;
825+
var session = this.session;
827826
var text = session.getTextRange(ts.firstNonLinked);
828827
for (var i = 0; i < ts.length; i++) {
829828
var range = ts[i];
@@ -834,12 +833,12 @@ var TabstopManager = function(editor) {
834833
session.replace(range, fmt);
835834
}
836835
this.$inChange = false;
837-
};
838-
this.onAfterExec = function(e) {
836+
}
837+
onAfterExec(e) {
839838
if (e.command && !e.command.readOnly)
840839
this.updateLinkedFields();
841-
};
842-
this.onChangeSelection = function() {
840+
}
841+
onChangeSelection() {
843842
if (!this.editor)
844843
return;
845844
var lead = this.editor.selection.lead;
@@ -854,11 +853,11 @@ var TabstopManager = function(editor) {
854853
return;
855854
}
856855
this.detach();
857-
};
858-
this.onChangeSession = function() {
856+
}
857+
onChangeSession() {
859858
this.detach();
860-
};
861-
this.tabNext = function(dir) {
859+
}
860+
tabNext(dir) {
862861
var max = this.tabstops.length;
863862
var index = this.index + (dir || 1);
864863
index = Math.min(Math.max(index, 1), max);
@@ -867,8 +866,8 @@ var TabstopManager = function(editor) {
867866
this.selectTabstop(index);
868867
if (index === 0)
869868
this.detach();
870-
};
871-
this.selectTabstop = function(index) {
869+
}
870+
selectTabstop(index) {
872871
this.$openTabstops = null;
873872
var ts = this.tabstops[this.index];
874873
if (ts)
@@ -896,8 +895,8 @@ var TabstopManager = function(editor) {
896895
this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
897896
if (this.selectedTabstop && this.selectedTabstop.choices)
898897
this.editor.execCommand("startAutocomplete", {matches: this.selectedTabstop.choices});
899-
};
900-
this.addTabstops = function(tabstops, start, end) {
898+
}
899+
addTabstops(tabstops, start, end) {
901900
var useLink = this.useLink || !this.editor.getOption("enableMultiselect");
902901

903902
if (!this.$openTabstops)
@@ -953,57 +952,57 @@ var TabstopManager = function(editor) {
953952
arg.push(arg.splice(2, 1)[0]);
954953
this.tabstops.splice.apply(this.tabstops, arg);
955954
}
956-
};
955+
}
957956

958-
this.addTabstopMarkers = function(ts) {
959-
var session = this.editor.session;
957+
addTabstopMarkers(ts) {
958+
var session = this.session;
960959
ts.forEach(function(range) {
961960
if (!range.markerId)
962961
range.markerId = session.addMarker(range, "ace_snippet-marker", "text");
963962
});
964-
};
965-
this.removeTabstopMarkers = function(ts) {
966-
var session = this.editor.session;
963+
}
964+
removeTabstopMarkers(ts) {
965+
var session = this.session;
967966
ts.forEach(function(range) {
968967
session.removeMarker(range.markerId);
969968
range.markerId = null;
970969
});
971-
};
972-
this.removeRange = function(range) {
970+
}
971+
removeRange(range) {
973972
var i = range.tabstop.indexOf(range);
974973
if (i != -1) range.tabstop.splice(i, 1);
975974
i = this.ranges.indexOf(range);
976975
if (i != -1) this.ranges.splice(i, 1);
977976
i = range.tabstop.rangeList.ranges.indexOf(range);
978977
if (i != -1) range.tabstop.splice(i, 1);
979-
this.editor.session.removeMarker(range.markerId);
978+
this.session.removeMarker(range.markerId);
980979
if (!range.tabstop.length) {
981980
i = this.tabstops.indexOf(range.tabstop);
982981
if (i != -1)
983982
this.tabstops.splice(i, 1);
984983
if (!this.tabstops.length)
985984
this.detach();
986985
}
987-
};
986+
}
987+
}
988988

989-
this.keyboardHandler = new HashHandler();
990-
this.keyboardHandler.bindKeys({
991-
"Tab": function(editor) {
992-
if (exports.snippetManager && exports.snippetManager.expandWithTab(editor))
993-
return;
994-
editor.tabstopManager.tabNext(1);
995-
editor.renderer.scrollCursorIntoView();
996-
},
997-
"Shift-Tab": function(editor) {
998-
editor.tabstopManager.tabNext(-1);
999-
editor.renderer.scrollCursorIntoView();
1000-
},
1001-
"Esc": function(editor) {
1002-
editor.tabstopManager.detach();
1003-
}
1004-
});
1005-
}).call(TabstopManager.prototype);
1006989

990+
TabstopManager.prototype.keyboardHandler = new HashHandler();
991+
TabstopManager.prototype.keyboardHandler.bindKeys({
992+
"Tab": function(editor) {
993+
if (exports.snippetManager && exports.snippetManager.expandWithTab(editor))
994+
return;
995+
editor.tabstopManager.tabNext(1);
996+
editor.renderer.scrollCursorIntoView();
997+
},
998+
"Shift-Tab": function(editor) {
999+
editor.tabstopManager.tabNext(-1);
1000+
editor.renderer.scrollCursorIntoView();
1001+
},
1002+
"Esc": function(editor) {
1003+
editor.tabstopManager.detach();
1004+
}
1005+
});
10071006

10081007

10091008
var movePoint = function(point, diff) {

‎src/snippets_test.js

+10
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,16 @@ module.exports = {
370370
editor.insertSnippet("hello $1 world $1");
371371
editor.onTextInput("!");
372372
assert.equal(editor.getValue(), "hello ! world !");
373+
},
374+
375+
"test: TabstopManager does not throw unhandled errors when session becomes `undefined`": function() {
376+
var editor = new Editor(new MockRenderer());
377+
var session = new EditSession("dummy content");
378+
editor.setSession(session);
379+
snippetManager.insertSnippet(editor, "snippet $1 with $2 tabstops");
380+
assert.equal(session.$backMarkers[5].clazz, "ace_snippet-marker");
381+
editor.setSession(undefined);
382+
assert.equal(session.$backMarkers[5], undefined);
373383
}
374384
};
375385

0 commit comments

Comments
 (0)
Please sign in to comment.