From feb500c86e21186f7ed0c5f43bbf769a36a5176c Mon Sep 17 00:00:00 2001 From: nightwing Date: Sun, 4 May 2014 17:29:56 +0400 Subject: [PATCH] fix inserting snippets when multiple selections are present --- lib/ace/autocomplete.js | 12 +- lib/ace/commands/default_commands.js | 4 + lib/ace/editor.js | 3 +- lib/ace/lib/keys.js | 9 -- lib/ace/multi_select.js | 29 +++-- lib/ace/snippets.js | 168 ++++++++++++++++++++------- lib/ace/worker/worker_client.js | 4 +- 7 files changed, 159 insertions(+), 70 deletions(-) diff --git a/lib/ace/autocomplete.js b/lib/ace/autocomplete.js index 8aa8abb3..a2fea74f 100644 --- a/lib/ace/autocomplete.js +++ b/lib/ace/autocomplete.js @@ -51,7 +51,7 @@ var Autocomplete = function() { this.changeTimer = lang.delayedCall(function() { this.updateCompletions(true); - }.bind(this)) + }.bind(this)); }; (function() { @@ -270,7 +270,7 @@ var Autocomplete = function() { var _id = this.gatherCompletionsId; this.gatherCompletions(this.editor, function(err, results) { // Only detach if result gathering is finished - var doDetach = function() { + var detachIfFinished = function() { if (!results.finished) return; return this.detach(); }.bind(this); @@ -279,10 +279,10 @@ var Autocomplete = function() { var matches = results && results.matches; if (!matches || !matches.length) - return doDetach(); + return detachIfFinished(); // Wrong prefix or wrong session -> ignore - if (prefix.indexOf(results.prefix) != 0 || _id != this.gatherCompletionsId) + if (prefix.indexOf(results.prefix) !== 0 || _id != this.gatherCompletionsId) return; this.completions = new FilteredList(matches); @@ -291,11 +291,11 @@ var Autocomplete = function() { // No results if (!filtered.length) - return doDetach(); + return detachIfFinished(); // One result equals to the prefix if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet) - return doDetach(); + return detachIfFinished(); // Autoinsert if one result if (this.autoInsert && filtered.length == 1) diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index 1ced6916..6506b5cf 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -141,11 +141,15 @@ exports.commands = [{ name: "findnext", bindKey: bindKey("Ctrl-K", "Command-G"), exec: function(editor) { editor.findNext(); }, + multiSelectAction: "forEach", + scrollIntoView: "center", readOnly: true }, { name: "findprevious", bindKey: bindKey("Ctrl-Shift-K", "Command-Shift-G"), exec: function(editor) { editor.findPrevious(); }, + multiSelectAction: "forEach", + scrollIntoView: "center", readOnly: true }, { name: "selectOrFindNext", diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 1e94863a..ef2d9f1a 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -2363,8 +2363,9 @@ var Editor = function(renderer, session) { var cursorLayer = this.renderer.$cursorLayer; if (!cursorLayer) return; - cursorLayer.setSmoothBlinking(style == "smooth"); + cursorLayer.setSmoothBlinking(/smooth/.test(style)); cursorLayer.isBlinking = !this.$readOnly && style != "wide"; + dom.setCssClass(cursorLayer.element, "ace_slim-cursors", /slim/.test(style)); }; }).call(Editor.prototype); diff --git a/lib/ace/lib/keys.js b/lib/ace/lib/keys.js index a811d8d1..e708a5d4 100644 --- a/lib/ace/lib/keys.js +++ b/lib/ace/lib/keys.js @@ -143,15 +143,6 @@ var Keys = (function() { } })(); - (function() { - var mods = ["cmd", "ctrl", "alt", "shift"]; - for (var i = Math.pow(2, mods.length); i--;) { - ret.KEY_MODS[i] = mods.filter(function(x) { - return i & ret.KEY_MODS[x]; - }).join("-") + "-"; - } - })(); - return ret; })(); oop.mixin(exports, Keys); diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 6b11691e..b079e387 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -464,36 +464,41 @@ var Editor = require("./editor").Editor; /** * Executes a command for each selection range. - * @param {String} cmd The command to execute + * @param {Object} cmd The command to execute * @param {String} args Any arguments for the command * @method Editor.forEachSelection **/ - this.forEachSelection = function(cmd, args, $byLines) { + this.forEachSelection = function(cmd, args, options) { if (this.inVirtualSelectionMode) return; - + var keepOrder = options && options.keepOrder; + var $byLines = options == true || options && options.$byLines var session = this.session; var selection = this.selection; var rangeList = selection.rangeList; + var ranges = (keepOrder ? selection : rangeList).ranges; var result; + if (!ranges.length) + return cmd.exec ? cmd.exec(this, args || {}) : cmd(this, args || {}); + var reg = selection._eventRegistry; selection._eventRegistry = {}; var tmpSel = new Selection(session); this.inVirtualSelectionMode = true; - for (var i = rangeList.ranges.length; i--;) { + for (var i = ranges.length; i--;) { if ($byLines) { - while (i > 0 && rangeList.ranges[i].start.row == rangeList.ranges[i - 1].end.row) + while (i > 0 && ranges[i].start.row == ranges[i - 1].end.row) i--; } - tmpSel.fromOrientedRange(rangeList.ranges[i]); - tmpSel.id = rangeList.ranges[i].marker; + tmpSel.fromOrientedRange(ranges[i]); + tmpSel.index = i; this.selection = session.selection = tmpSel; - var cmdResult = cmd.exec(this, args || {}); - if (result !== undefined) + var cmdResult = cmd.exec ? cmd.exec(this, args || {}) : cmd(this, args || {}); + if (!result && cmdResult !== undefined) result = cmdResult; - tmpSel.toOrientedRange(rangeList.ranges[i]); + tmpSel.toOrientedRange(ranges[i]); } tmpSel.detach(); @@ -599,6 +604,10 @@ var Editor = require("./editor").Editor; for (var i = ranges.length; i--; ) selection.addRange(ranges[i], true); + // keep old selection as primary if possible + if (range && selection.rangeList.rangeAtPoint(range.start)) + selection.addRange(range, true); + this.$blockScrolling -= 1; return ranges.length; diff --git a/lib/ace/snippets.js b/lib/ace/snippets.js index 6cc855b1..4fd301c2 100644 --- a/lib/ace/snippets.js +++ b/lib/ace/snippets.js @@ -30,8 +30,11 @@ define(function(require, exports, module) { "use strict"; -var lang = require("./lib/lang") -var Range = require("./range").Range +var oop = require("./lib/oop"); +var EventEmitter = require("./lib/event_emitter").EventEmitter; +var lang = require("./lib/lang"); +var Range = require("./range").Range; +var Anchor = require("./anchor").Anchor; var HashHandler = require("./keyboard/hash_handler").HashHandler; var Tokenizer = require("./tokenizer").Tokenizer; var comparePoints = Range.comparePoints; @@ -42,12 +45,14 @@ var SnippetManager = function() { }; (function() { + oop.implement(this, EventEmitter); + this.getTokenizer = function() { function TabstopToken(str, _, stack) { str = str.substr(1); if (/^\d+$/.test(str) && !stack.inFormatString) return [{tabstopId: parseInt(str, 10)}]; - return [{text: str}] + return [{text: str}]; } function escape(ch) { return "(?:[^\\\\" + ch + "]|\\\\.)"; @@ -125,7 +130,7 @@ var SnippetManager = function() { }); SnippetManager.prototype.getTokenizer = function() { return SnippetManager.$tokenizer; - } + }; return SnippetManager.$tokenizer; }; @@ -150,7 +155,8 @@ var SnippetManager = function() { var s = editor.session; switch(name) { case "CURRENT_WORD": - var r = s.getWordRange(); + var r = s.getWordRange(); + /* falls through */ case "SELECTION": case "SELECTED_TEXT": return s.getTextRange(r); @@ -166,7 +172,7 @@ var SnippetManager = function() { return s.getUseSoftTabs() ? "YES" : "NO"; case "TAB_SIZE": return s.getTabSize(); - // defult but can't fill :( + // default but can't fill :( case "FILENAME": case "FILEPATH": return ""; @@ -262,7 +268,7 @@ var SnippetManager = function() { return result; }; - this.insertSnippet = function(editor, snippetText) { + this.insertSnippetForSelection = function(editor, snippetText) { var cursor = editor.getCursorPosition(); var line = editor.session.getLine(cursor.row); var tabString = editor.session.getTabString(); @@ -313,7 +319,7 @@ var SnippetManager = function() { tabstops.forEach(function(ts) {ts.length = 0}); var expanding = {}; function copyValue(val) { - var copy = [] + var copy = []; for (var i = 0; i < val.length; i++) { var p = val[i]; if (typeof p == "object") { @@ -349,7 +355,7 @@ var SnippetManager = function() { if (ts.indexOf(p) === -1) ts.push(p); - }; + } // convert to plain text var row = 0, column = 0; @@ -373,8 +379,21 @@ var SnippetManager = function() { var end = editor.session.replace(range, text); var tabstopManager = new TabstopManager(editor); - tabstopManager.addTabstops(tabstops, range.start, end); - tabstopManager.tabNext(); + var selectionId = editor.inVirtualSelectionMode && editor.selection.index; + tabstopManager.addTabstops(tabstops, range.start, end, selectionId); + }; + + this.insertSnippet = function(editor, snippetText) { + var self = this; + if (editor.inVirtualSelectionMode) + return self.insertSnippetForSelection(editor, snippetText); + + editor.forEachSelection(function() { + self.insertSnippetForSelection(editor, snippetText); + }, null, {keepOrder: true}); + + if (editor.tabstopManager) + editor.tabstopManager.tabNext(); }; this.$getScope = function(editor) { @@ -384,7 +403,7 @@ var SnippetManager = function() { // PHP is actually HTML if (scope === "php" && !editor.session.$mode.inlinePhp) scope = "html"; - var c = editor.getCursorPosition() + var c = editor.getCursorPosition(); var state = editor.session.getState(c.row); if (typeof state === "object") { state = state[0]; @@ -413,7 +432,17 @@ var SnippetManager = function() { return scopes; }; - this.expandWithTab = function(editor) { + this.expandWithTab = function(editor, options) { + var self = this; + var result = editor.forEachSelection(function() { + return self.expandSnippetForSelection(editor, options); + }, null, {keepOrder: true}); + if (result && editor.tabstopManager) + editor.tabstopManager.tabNext(); + return result; + }; + + this.expandSnippetForSelection = function(editor, options) { var cursor = editor.getCursorPosition(); var line = editor.session.getLine(cursor.row); var before = line.substring(0, cursor.column); @@ -429,7 +458,8 @@ var SnippetManager = function() { }, this); if (!snippet) return false; - + if (options && options.dryRun) + return true; editor.session.doc.removeInLine(cursor.row, cursor.column - snippet.replaceBefore.length, cursor.column + snippet.replaceAfter.length @@ -437,7 +467,7 @@ var SnippetManager = function() { this.variables.M__ = snippet.matchBefore; this.variables.T__ = snippet.matchAfter; - this.insertSnippet(editor, snippet.content); + this.insertSnippetForSelection(editor, snippet.content); this.variables.M__ = this.variables.T__ = null; return true; @@ -469,7 +499,7 @@ var SnippetManager = function() { var self = this; function wrapRegexp(src) { if (src && !/^\^?\(.*\)\$?$|^\\b$/.test(src)) - src = "(?:" + src + ")" + src = "(?:" + src + ")"; return src || ""; } @@ -491,7 +521,7 @@ var SnippetManager = function() { function addSnippet(s) { if (!s.scope) s.scope = scope || "_"; - scope = s.scope + scope = s.scope; if (!snippetMap[scope]) { snippetMap[scope] = []; snippetNameMap[scope] = {}; @@ -517,12 +547,14 @@ var SnippetManager = function() { s.endRe = guardedRegexp(s.endTrigger, s.endGuard, true); s.endTriggerRe = new RegExp(s.endTrigger, "", true); - }; + } if (snippets.content) addSnippet(snippets); else if (Array.isArray(snippets)) snippets.forEach(addSnippet); + + this._signal("registerSnippets", {scope: scope}); }; this.unregister = function(snippets, scope) { var snippetMap = this.snippetMap; @@ -551,7 +583,7 @@ var SnippetManager = function() { while (m = re.exec(str)) { if (m[1]) { try { - snippet = JSON.parse(m[1]) + snippet = JSON.parse(m[1]); list.push(snippet); } catch (e) {} } if (m[4]) { @@ -604,9 +636,10 @@ var TabstopManager = function(editor) { }; (function() { this.attach = function(editor) { - this.index = -1; + this.index = 0; this.ranges = []; this.tabstops = []; + this.$openTabstops = null; this.selectedTabstop = null; this.editor = editor; @@ -646,7 +679,7 @@ var TabstopManager = function(editor) { } if (!this.$inChange && isRemove) { var ts = this.selectedTabstop; - var changedOutside = !ts.some(function(r) { + var changedOutside = ts && !ts.some(function(r) { return comparePoints(r.start, start) <= 0 && comparePoints(r.end, end) >= 0; }); if (changedOutside) @@ -658,7 +691,7 @@ var TabstopManager = function(editor) { if (r.end.row < start.row) continue; - if (comparePoints(start, r.start) < 0 && comparePoints(end, r.end) > 0) { + if (isRemove && comparePoints(start, r.start) < 0 && comparePoints(end, r.end) > 0) { this.removeRange(r); i--; continue; @@ -681,7 +714,7 @@ var TabstopManager = function(editor) { }; this.updateLinkedFields = function() { var ts = this.selectedTabstop; - if (!ts.hasLinkedRanges) + if (!ts || !ts.hasLinkedRanges) return; this.$inChange = true; var session = this.editor.session; @@ -690,7 +723,7 @@ var TabstopManager = function(editor) { var range = ts[i]; if (!range.linked) continue; - var fmt = exports.snippetManager.tmStrFormat(text, range.original) + var fmt = exports.snippetManager.tmStrFormat(text, range.original); session.replace(range, fmt); } this.$inChange = false; @@ -701,7 +734,7 @@ var TabstopManager = function(editor) { }; this.onChangeSelection = function() { if (!this.editor) - return + return; var lead = this.editor.selection.lead; var anchor = this.editor.selection.anchor; var isEmpty = this.editor.selection.isEmpty(); @@ -719,14 +752,17 @@ var TabstopManager = function(editor) { this.detach(); }; this.tabNext = function(dir) { - var max = this.tabstops.length - 1; + var max = this.tabstops.length; var index = this.index + (dir || 1); - index = Math.min(Math.max(index, 0), max); - this.selectTabstop(index); + index = Math.min(Math.max(index, 1), max); if (index == max) + index = 0; + this.selectTabstop(index); + if (index === 0) this.detach(); }; this.selectTabstop = function(index) { + this.$openTabstops = null; var ts = this.tabstops[this.index]; if (ts) this.addTabstopMarkers(ts); @@ -744,6 +780,9 @@ var TabstopManager = function(editor) { continue; sel.addRange(ts[i].clone(), true); } + // todo investigate why is this needed + if (sel.ranges[0]) + sel.addRange(sel.ranges[0].clone()); } else { this.editor.selection.setRange(ts.firstNonLinked); } @@ -751,6 +790,8 @@ var TabstopManager = function(editor) { this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler); }; this.addTabstops = function(tabstops, start, end) { + if (!this.$openTabstops) + this.$openTabstops = []; // add final tabstop if missing if (!tabstops[0]) { var p = Range.fromPoints(end, end); @@ -761,33 +802,44 @@ var TabstopManager = function(editor) { } var i = this.index; - var arg = [i, 0]; + var arg = [i + 1, 0]; var ranges = this.ranges; - var editor = this.editor; - tabstops.forEach(function(ts) { + tabstops.forEach(function(ts, index) { + var dest = this.$openTabstops[index] || ts; + for (var i = ts.length; i--;) { var p = ts[i]; var range = Range.fromPoints(p.start, p.end || p.start); movePoint(range.start, start); movePoint(range.end, start); range.original = p; - range.tabstop = ts; + range.tabstop = dest; ranges.push(range); - ts[i] = range; + if (dest != ts) + dest.unshift(range); + else + dest[i] = range; if (p.fmtString) { range.linked = true; - ts.hasLinkedRanges = true; - } else if (!ts.firstNonLinked) - ts.firstNonLinked = range; + dest.hasLinkedRanges = true; + } else if (!dest.firstNonLinked) + dest.firstNonLinked = range; } - if (!ts.firstNonLinked) - ts.hasLinkedRanges = false; - arg.push(ts); - this.addTabstopMarkers(ts); + if (!dest.firstNonLinked) + dest.hasLinkedRanges = false; + if (dest === ts) { + arg.push(dest); + this.$openTabstops[index] = dest; + } + this.addTabstopMarkers(dest); }, this); - // tabstop 0 is the last one - arg.push(arg.splice(2, 1)[0]); - this.tabstops.splice.apply(this.tabstops, arg); + + if (arg.length > 2) { + // when adding new snippet inside existing one, make sure 0 tabstop is at the end + if (this.tabstops.length) + arg.push(arg.splice(2, 1)[0]); + this.tabstops.splice.apply(this.tabstops, arg); + } }; this.addTabstopMarkers = function(ts) { @@ -810,6 +862,13 @@ var TabstopManager = function(editor) { i = this.ranges.indexOf(range); this.ranges.splice(i, 1); this.editor.session.removeMarker(range.markerId); + if (!range.tabstop.length) { + i = this.tabstops.indexOf(range.tabstop); + if (i != -1) + this.tabstops.splice(i, 1); + if (!this.tabstops.length) + this.detach(); + } }; this.keyboardHandler = new HashHandler(); @@ -835,6 +894,19 @@ var TabstopManager = function(editor) { }).call(TabstopManager.prototype); + +var changeTracker = {}; +changeTracker.onChange = Anchor.prototype.onChange; +changeTracker.setPosition = function(row, column) { + this.pos.row = row; + this.pos.column = column; +}; +changeTracker.update = function(pos, delta, $insertRight) { + this.$insertRight = $insertRight; + this.pos = pos; + this.onChange(delta); +}; + var movePoint = function(point, diff) { if (point.row == 0) point.column += diff.column; @@ -860,4 +932,14 @@ require("./lib/dom").importCssString("\ exports.snippetManager = new SnippetManager(); +var Editor = require("./editor").Editor; +(function() { + this.insertSnippet = function(content, options) { + return exports.snippetManager.insertSnippet(this, content, options); + }; + this.expandSnippet = function(options) { + return exports.snippetManager.expandWithTab(this, options); + }; +}).call(Editor.prototype); + }); diff --git a/lib/ace/worker/worker_client.js b/lib/ace/worker/worker_client.js index 409b7202..1a2460aa 100644 --- a/lib/ace/worker/worker_client.js +++ b/lib/ace/worker/worker_client.js @@ -149,7 +149,9 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { // TODO: cleanup event this.$worker.postMessage({event: event, data: {data: data.data}}); } - catch(ex) {} + catch(ex) { + console.error(ex.stack); + } }; this.attachToDocument = function(doc) {