Merge pull request #1583 from ajaxorg/autocomplete
improve autocomplete popup
This commit is contained in:
commit
f860b88e51
10 changed files with 267 additions and 122 deletions
|
|
@ -600,7 +600,7 @@ env.editSnippets = function() {
|
||||||
editor.on("blur", function() {
|
editor.on("blur", function() {
|
||||||
m.snippetText = editor.getValue();
|
m.snippetText = editor.getValue();
|
||||||
snippetManager.unregister(m.snippets);
|
snippetManager.unregister(m.snippets);
|
||||||
m.snippets = snippetManager.parseSnippetFile(m.snippetText);
|
m.snippets = snippetManager.parseSnippetFile(m.snippetText, m.scope);
|
||||||
snippetManager.register(m.snippets);
|
snippetManager.register(m.snippets);
|
||||||
})
|
})
|
||||||
sp.$editors[0].once("changeMode", function() {
|
sp.$editors[0].once("changeMode", function() {
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ var Autocomplete = function() {
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.openPopup = function(editor, keepPopupPosition) {
|
this.openPopup = function(editor, prefix, keepPopupPosition) {
|
||||||
if (!this.popup)
|
if (!this.popup)
|
||||||
this.$init();
|
this.$init();
|
||||||
|
|
||||||
|
|
@ -70,7 +70,10 @@ var Autocomplete = function() {
|
||||||
var renderer = editor.renderer;
|
var renderer = editor.renderer;
|
||||||
if (!keepPopupPosition) {
|
if (!keepPopupPosition) {
|
||||||
var lineHeight = renderer.layerConfig.lineHeight;
|
var lineHeight = renderer.layerConfig.lineHeight;
|
||||||
var pos = renderer.$cursorLayer.getPixelPosition(null, true);
|
|
||||||
|
var pos = renderer.$cursorLayer.getPixelPosition(this.base, true);
|
||||||
|
pos.left -= this.popup.getTextLeftOffset();
|
||||||
|
|
||||||
var rect = editor.container.getBoundingClientRect();
|
var rect = editor.container.getBoundingClientRect();
|
||||||
pos.top += rect.top - renderer.layerConfig.offset;
|
pos.top += rect.top - renderer.layerConfig.offset;
|
||||||
pos.left += rect.left;
|
pos.left += rect.left;
|
||||||
|
|
@ -83,18 +86,24 @@ var Autocomplete = function() {
|
||||||
|
|
||||||
this.detach = function() {
|
this.detach = function() {
|
||||||
this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
|
this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
|
||||||
this.editor.removeEventListener("changeSelection", this.changeListener);
|
this.editor.off("changeSelection", this.changeListener);
|
||||||
this.editor.removeEventListener("blur", this.changeListener);
|
this.editor.off("blur", this.changeListener);
|
||||||
this.editor.removeEventListener("mousedown", this.changeListener);
|
this.editor.off("mousedown", this.changeListener);
|
||||||
|
this.editor.off("mousewheel", this.mousewheelListener);
|
||||||
this.changeTimer.cancel();
|
this.changeTimer.cancel();
|
||||||
|
|
||||||
if (this.popup)
|
if (this.popup)
|
||||||
this.popup.hide();
|
this.popup.hide();
|
||||||
|
|
||||||
this.activated = false;
|
this.activated = false;
|
||||||
|
this.completions = this.base = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.changeListener = function(e) {
|
this.changeListener = function(e) {
|
||||||
|
var cursor = this.editor.selection.lead;
|
||||||
|
if (cursor.row != this.base.row || cursor.column < this.base.column) {
|
||||||
|
this.detach();
|
||||||
|
}
|
||||||
if (this.activated)
|
if (this.activated)
|
||||||
this.changeTimer.schedule();
|
this.changeTimer.schedule();
|
||||||
else
|
else
|
||||||
|
|
@ -119,8 +128,8 @@ var Autocomplete = function() {
|
||||||
var max = this.popup.session.getLength() - 1;
|
var max = this.popup.session.getLength() - 1;
|
||||||
|
|
||||||
switch(where) {
|
switch(where) {
|
||||||
case "up": row = row <= 0 ? max : row - 1; break;
|
case "up": row = row < 0 ? max : row - 1; break;
|
||||||
case "down": row = row >= max ? 0 : row + 1; break;
|
case "down": row = row >= max ? -1 : row + 1; break;
|
||||||
case "start": row = 0; break;
|
case "start": row = 0; break;
|
||||||
case "end": row = max; break;
|
case "end": row = max; break;
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +138,6 @@ var Autocomplete = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.insertMatch = function(data) {
|
this.insertMatch = function(data) {
|
||||||
this.detach();
|
|
||||||
if (!data)
|
if (!data)
|
||||||
data = this.popup.getData(this.popup.getRow());
|
data = this.popup.getData(this.popup.getRow());
|
||||||
if (!data)
|
if (!data)
|
||||||
|
|
@ -138,15 +146,18 @@ var Autocomplete = function() {
|
||||||
data.completer.insertMatch(this.editor);
|
data.completer.insertMatch(this.editor);
|
||||||
} else {
|
} else {
|
||||||
if (this.completions.filterText) {
|
if (this.completions.filterText) {
|
||||||
var range = this.editor.selection.getRange();
|
var ranges = this.editor.selection.getAllRanges();
|
||||||
range.start.column -= this.completions.filterText.length;
|
for (var i = 0, range; range = ranges[i]; i++) {
|
||||||
this.editor.session.remove(range);
|
range.start.column -= this.completions.filterText.length;
|
||||||
|
this.editor.session.remove(range);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (data.snippet)
|
if (data.snippet)
|
||||||
snippetManager.insertSnippet(this.editor, data.snippet);
|
snippetManager.insertSnippet(this.editor, data.snippet);
|
||||||
else
|
else
|
||||||
this.editor.insert(data.value || data);
|
this.editor.execCommand("insertstring", data.value || data);
|
||||||
}
|
}
|
||||||
|
this.detach();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.commands = {
|
this.commands = {
|
||||||
|
|
@ -161,44 +172,8 @@ var Autocomplete = function() {
|
||||||
"Shift-Return": function(editor) { editor.completer.insertMatch(true); },
|
"Shift-Return": function(editor) { editor.completer.insertMatch(true); },
|
||||||
"Tab": function(editor) { editor.completer.insertMatch(); },
|
"Tab": function(editor) { editor.completer.insertMatch(); },
|
||||||
|
|
||||||
"PageUp": function(editor) { editor.completer.popup.gotoPageDown(); },
|
"PageUp": function(editor) { editor.completer.popup.gotoPageUp(); },
|
||||||
"PageDown": function(editor) { editor.completer.popup.gotoPageUp(); }
|
"PageDown": function(editor) { editor.completer.popup.gotoPageDown(); }
|
||||||
};
|
|
||||||
|
|
||||||
this.filterCompletions = function(items, needle) {
|
|
||||||
var results = [];
|
|
||||||
var upper = needle.toUpperCase();
|
|
||||||
var lower = needle.toLowerCase();
|
|
||||||
loop: for (var i = 0, item; item = items[i]; i++) {
|
|
||||||
var caption = item.value || item.caption;
|
|
||||||
if (!caption) continue;
|
|
||||||
var lastIndex = -1;
|
|
||||||
var matchMask = 0;
|
|
||||||
var penalty = 0;
|
|
||||||
var index, distance;
|
|
||||||
// caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf
|
|
||||||
for (var j = 0; j < needle.length; j++) {
|
|
||||||
// TODO add penalty on case mismatch
|
|
||||||
var i1 = caption.indexOf(lower[j], lastIndex + 1);
|
|
||||||
var i2 = caption.indexOf(upper[j], lastIndex + 1);
|
|
||||||
index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2;
|
|
||||||
if (index < 0)
|
|
||||||
continue loop;
|
|
||||||
distance = index - lastIndex - 1;
|
|
||||||
if (distance > 0) {
|
|
||||||
// first char mismatch should be more sensitive
|
|
||||||
if (lastIndex == -1)
|
|
||||||
penalty += 10;
|
|
||||||
penalty += distance;
|
|
||||||
}
|
|
||||||
matchMask = matchMask | (1 << index);
|
|
||||||
lastIndex = index;
|
|
||||||
}
|
|
||||||
item.matchMask = matchMask;
|
|
||||||
item.score = (item.score || 0) - penalty;
|
|
||||||
results.push(item);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.gatherCompletions = function(editor, callback) {
|
this.gatherCompletions = function(editor, callback) {
|
||||||
|
|
@ -208,6 +183,9 @@ var Autocomplete = function() {
|
||||||
var line = session.getLine(pos.row);
|
var line = session.getLine(pos.row);
|
||||||
var prefix = util.retrievePrecedingIdentifier(line, pos.column);
|
var prefix = util.retrievePrecedingIdentifier(line, pos.column);
|
||||||
|
|
||||||
|
this.base = editor.getCursorPosition();
|
||||||
|
this.base.column -= prefix.length;
|
||||||
|
|
||||||
var matches = [];
|
var matches = [];
|
||||||
util.parForEach(editor.completers, function(completer, next) {
|
util.parForEach(editor.completers, function(completer, next) {
|
||||||
completer.getCompletions(editor, session, pos, prefix, function(err, results) {
|
completer.getCompletions(editor, session, pos, prefix, function(err, results) {
|
||||||
|
|
@ -241,10 +219,22 @@ var Autocomplete = function() {
|
||||||
editor.on("changeSelection", this.changeListener);
|
editor.on("changeSelection", this.changeListener);
|
||||||
editor.on("blur", this.blurListener);
|
editor.on("blur", this.blurListener);
|
||||||
editor.on("mousedown", this.mousedownListener);
|
editor.on("mousedown", this.mousedownListener);
|
||||||
|
editor.on("mousewheel", this.mousewheelListener);
|
||||||
this.updateCompletions();
|
this.updateCompletions();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateCompletions = function(keepPopupPosition) {
|
this.updateCompletions = function(keepPopupPosition) {
|
||||||
|
if (keepPopupPosition && this.base && this.completions) {
|
||||||
|
var pos = this.editor.getCursorPosition();
|
||||||
|
var prefix = this.editor.session.getTextRange({start: this.base, end: pos});
|
||||||
|
if (prefix == this.completions.filterText)
|
||||||
|
return;
|
||||||
|
this.completions.setFilter(prefix);
|
||||||
|
if (!this.completions.filtered.length)
|
||||||
|
return this.detach();
|
||||||
|
this.openPopup(this.editor, prefix, keepPopupPosition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.gatherCompletions(this.editor, function(err, results) {
|
this.gatherCompletions(this.editor, function(err, results) {
|
||||||
var matches = results && results.matches;
|
var matches = results && results.matches;
|
||||||
if (!matches || !matches.length)
|
if (!matches || !matches.length)
|
||||||
|
|
@ -253,14 +243,11 @@ var Autocomplete = function() {
|
||||||
// if (matches.length == 1)
|
// if (matches.length == 1)
|
||||||
// return this.insertMatch(matches[0]);
|
// return this.insertMatch(matches[0]);
|
||||||
|
|
||||||
|
|
||||||
matches = this.filterCompletions(matches, results.prefix);
|
|
||||||
matches = matches.sort(function(a, b) {
|
|
||||||
return b.score - a.score;
|
|
||||||
});
|
|
||||||
this.completions = new FilteredList(matches);
|
this.completions = new FilteredList(matches);
|
||||||
this.completions.setFilter(results.prefix);
|
this.completions.setFilter(results.prefix);
|
||||||
this.openPopup(this.editor, keepPopupPosition);
|
if (!this.completions.filtered.length)
|
||||||
|
return this.detach();
|
||||||
|
this.openPopup(this.editor, results.prefix, keepPopupPosition);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -288,16 +275,71 @@ Autocomplete.startCommand = {
|
||||||
bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
|
bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
|
||||||
};
|
};
|
||||||
|
|
||||||
var FilteredList = function(array, mutateData) {
|
var FilteredList = function(array, filterText, mutateData) {
|
||||||
this.all = array;
|
this.all = array;
|
||||||
this.filtered = array.concat();
|
this.filtered = array;
|
||||||
this.filterText = "";
|
this.filterText = filterText || "";
|
||||||
};
|
};
|
||||||
(function(){
|
(function(){
|
||||||
this.setFilter = function(str) {
|
this.setFilter = function(str) {
|
||||||
this.filterText = str;
|
if (str.length > this.filterText && str.lastIndexOf(this.filterText, 0) === 0)
|
||||||
};
|
var matches = this.filtered;
|
||||||
|
else
|
||||||
|
var matches = this.all;
|
||||||
|
|
||||||
|
this.filterText = str;
|
||||||
|
matches = this.filterCompletions(matches, this.filterText);
|
||||||
|
matches = matches.sort(function(a, b) {
|
||||||
|
return b.exactMatch - a.exactMatch || b.score - a.score;
|
||||||
|
});
|
||||||
|
|
||||||
|
// make unique
|
||||||
|
var prev = null;
|
||||||
|
matches = matches.filter(function(item){
|
||||||
|
var caption = item.value || item.caption || item.snippet;
|
||||||
|
if (caption === prev) return false;
|
||||||
|
prev = caption;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filtered = matches;
|
||||||
|
};
|
||||||
|
this.filterCompletions = function(items, needle) {
|
||||||
|
var results = [];
|
||||||
|
var upper = needle.toUpperCase();
|
||||||
|
var lower = needle.toLowerCase();
|
||||||
|
loop: for (var i = 0, item; item = items[i]; i++) {
|
||||||
|
var caption = item.value || item.caption || item.snippet;
|
||||||
|
if (!caption) continue;
|
||||||
|
var lastIndex = -1;
|
||||||
|
var matchMask = 0;
|
||||||
|
var penalty = 0;
|
||||||
|
var index, distance;
|
||||||
|
// caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf
|
||||||
|
for (var j = 0; j < needle.length; j++) {
|
||||||
|
// TODO add penalty on case mismatch
|
||||||
|
var i1 = caption.indexOf(lower[j], lastIndex + 1);
|
||||||
|
var i2 = caption.indexOf(upper[j], lastIndex + 1);
|
||||||
|
index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2;
|
||||||
|
if (index < 0)
|
||||||
|
continue loop;
|
||||||
|
distance = index - lastIndex - 1;
|
||||||
|
if (distance > 0) {
|
||||||
|
// first char mismatch should be more sensitive
|
||||||
|
if (lastIndex === -1)
|
||||||
|
penalty += 10;
|
||||||
|
penalty += distance;
|
||||||
|
}
|
||||||
|
matchMask = matchMask | (1 << index);
|
||||||
|
lastIndex = index;
|
||||||
|
}
|
||||||
|
item.matchMask = matchMask;
|
||||||
|
item.exactMatch = penalty ? 0 : 1;
|
||||||
|
item.score = (item.score || 0) - penalty;
|
||||||
|
results.push(item);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
};
|
||||||
}).call(FilteredList.prototype);
|
}).call(FilteredList.prototype);
|
||||||
|
|
||||||
exports.Autocomplete = Autocomplete;
|
exports.Autocomplete = Autocomplete;
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ var AcePopup = function(parentNode) {
|
||||||
popup.renderer.$maxLines = 8;
|
popup.renderer.$maxLines = 8;
|
||||||
popup.renderer.$keepTextAreaAtCursor = false;
|
popup.renderer.$keepTextAreaAtCursor = false;
|
||||||
|
|
||||||
popup.setHighlightActiveLine(true);
|
popup.setHighlightActiveLine(false);
|
||||||
// set default highlight color
|
// set default highlight color
|
||||||
popup.session.highlight("");
|
popup.session.highlight("");
|
||||||
popup.session.$searchHighlight.clazz = "ace_highlight-marker";
|
popup.session.$searchHighlight.clazz = "ace_highlight-marker";
|
||||||
|
|
@ -86,16 +86,42 @@ var AcePopup = function(parentNode) {
|
||||||
var pos = e.getDocumentPosition();
|
var pos = e.getDocumentPosition();
|
||||||
popup.moveCursorToPosition(pos);
|
popup.moveCursorToPosition(pos);
|
||||||
popup.selection.clearSelection();
|
popup.selection.clearSelection();
|
||||||
|
selectionMarker.start.row = selectionMarker.end.row = pos.row;
|
||||||
e.stop();
|
e.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var lastMouseEvent;
|
||||||
var hoverMarker = new Range(-1,0,-1,Infinity);
|
var hoverMarker = new Range(-1,0,-1,Infinity);
|
||||||
hoverMarker.id = popup.session.addMarker(hoverMarker, "ace_line-hover", "fullLine");
|
var selectionMarker = new Range(-1,0,-1,Infinity);
|
||||||
|
selectionMarker.id = popup.session.addMarker(selectionMarker, "ace_active-line", "fullLine");
|
||||||
|
popup.setSelectOnHover = function(val) {
|
||||||
|
if (!val) {
|
||||||
|
hoverMarker.id = popup.session.addMarker(hoverMarker, "ace_line-hover", "fullLine");
|
||||||
|
} else if (hoverMarker.id) {
|
||||||
|
popup.session.removeMarker(hoverMarker.id);
|
||||||
|
hoverMarker.id = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
popup.setSelectOnHover(false)
|
||||||
popup.on("mousemove", function(e) {
|
popup.on("mousemove", function(e) {
|
||||||
//if (popup.lastOpened)
|
lastMouseEvent = e;
|
||||||
var row = e.getDocumentPosition().row;
|
lastMouseEvent.scrollTop = popup.renderer.scrollTop;
|
||||||
hoverMarker.start.row = hoverMarker.end.row = row;
|
var row = lastMouseEvent.getDocumentPosition().row;
|
||||||
popup.session._emit("changeBackMarker");
|
if (hoverMarker.start.row != row) {
|
||||||
|
popup.session._emit("changeBackMarker");
|
||||||
|
if (!hoverMarker.id)
|
||||||
|
popup.setRow(row);
|
||||||
|
hoverMarker.start.row = hoverMarker.end.row = row;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
popup.renderer.on("beforeRender", function() {
|
||||||
|
if (lastMouseEvent && hoverMarker.start.row != -1) {
|
||||||
|
lastMouseEvent.$pos = null;
|
||||||
|
var row = lastMouseEvent.getDocumentPosition().row;
|
||||||
|
if (!hoverMarker.id)
|
||||||
|
popup.setRow(row);
|
||||||
|
hoverMarker.start.row = hoverMarker.end.row = row;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
var hideHoverMarker = function() {
|
var hideHoverMarker = function() {
|
||||||
hoverMarker.start.row = hoverMarker.end.row = -1;
|
hoverMarker.start.row = hoverMarker.end.row = -1;
|
||||||
|
|
@ -104,11 +130,6 @@ var AcePopup = function(parentNode) {
|
||||||
event.addListener(popup.container, "mouseout", hideHoverMarker);
|
event.addListener(popup.container, "mouseout", hideHoverMarker);
|
||||||
popup.on("hide", hideHoverMarker);
|
popup.on("hide", hideHoverMarker);
|
||||||
popup.on("changeSelection", hideHoverMarker);
|
popup.on("changeSelection", hideHoverMarker);
|
||||||
popup.on("mousewheel", function(e) {
|
|
||||||
setTimeout(function() {
|
|
||||||
popup._signal("mousemove", e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
popup.session.doc.getLength = function() {
|
popup.session.doc.getLength = function() {
|
||||||
return popup.data.length;
|
return popup.data.length;
|
||||||
|
|
@ -137,7 +158,7 @@ var AcePopup = function(parentNode) {
|
||||||
c = data.caption[i];
|
c = data.caption[i];
|
||||||
flag = data.matchMask & (1 << i) ? 1 : 0;
|
flag = data.matchMask & (1 << i) ? 1 : 0;
|
||||||
if (last !== flag) {
|
if (last !== flag) {
|
||||||
tokens.push({type: data.className || "" + ( flag ? ".bold" : ""), value: c});
|
tokens.push({type: data.className || "" + ( flag ? "completion-highlight" : ""), value: c});
|
||||||
last = flag;
|
last = flag;
|
||||||
} else {
|
} else {
|
||||||
tokens[tokens.length - 1].value += c;
|
tokens[tokens.length - 1].value += c;
|
||||||
|
|
@ -167,16 +188,14 @@ var AcePopup = function(parentNode) {
|
||||||
return popup.data[row];
|
return popup.data[row];
|
||||||
};
|
};
|
||||||
|
|
||||||
popup.getRow = function() {
|
popup.getRow = function() {
|
||||||
var line = this.getCursorPosition().row;
|
return selectionMarker.start.row;
|
||||||
if (line == 0 && !this.getHighlightActiveLine())
|
|
||||||
line = -1;
|
|
||||||
return line;
|
|
||||||
};
|
};
|
||||||
popup.setRow = function(line) {
|
popup.setRow = function(line) {
|
||||||
popup.setHighlightActiveLine(line != -1);
|
|
||||||
popup.selection.clearSelection();
|
popup.selection.clearSelection();
|
||||||
popup.moveCursorTo(line, 0 || 0);
|
selectionMarker.start.row = selectionMarker.end.row = line || 0;
|
||||||
|
popup.session._emit("changeBackMarker");
|
||||||
|
popup.moveCursorTo(line || 0, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
popup.hide = function() {
|
popup.hide = function() {
|
||||||
|
|
@ -201,12 +220,17 @@ var AcePopup = function(parentNode) {
|
||||||
this._signal("show");
|
this._signal("show");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
popup.getTextLeftOffset = function() {
|
||||||
|
return 1 + this.renderer.layerConfig.padding;
|
||||||
|
}
|
||||||
|
|
||||||
return popup;
|
return popup;
|
||||||
};
|
};
|
||||||
|
|
||||||
dom.importCssString("\
|
dom.importCssString("\
|
||||||
.ace_autocomplete.ace-tm .ace_marker-layer .ace_active-line {\
|
.ace_autocomplete.ace-tm .ace_marker-layer .ace_active-line {\
|
||||||
background-color: #abbffe;\
|
background-color: #CAD6FA;\
|
||||||
|
z-index: 1;\
|
||||||
}\
|
}\
|
||||||
.ace_autocomplete.ace-tm .ace_line-hover {\
|
.ace_autocomplete.ace-tm .ace_line-hover {\
|
||||||
border: 1px solid #abbffe;\
|
border: 1px solid #abbffe;\
|
||||||
|
|
@ -223,13 +247,19 @@ dom.importCssString("\
|
||||||
text-align: right;\
|
text-align: right;\
|
||||||
z-index: -1;\
|
z-index: -1;\
|
||||||
}\
|
}\
|
||||||
|
.ace_autocomplete .ace_completion-highlight{\
|
||||||
|
color: #000;\
|
||||||
|
text-shadow: 0 0 0.01px;\
|
||||||
|
}\
|
||||||
.ace_autocomplete {\
|
.ace_autocomplete {\
|
||||||
width: 200px;\
|
width: 280px;\
|
||||||
z-index: 200000;\
|
z-index: 200000;\
|
||||||
background: #f8f8f8;\
|
background: #fbfbfb;\
|
||||||
|
color: #444;\
|
||||||
border: 1px lightgray solid;\
|
border: 1px lightgray solid;\
|
||||||
position: fixed;\
|
position: fixed;\
|
||||||
box-shadow: 2px 3px 5px rgba(0,0,0,.2);\
|
box-shadow: 2px 3px 5px rgba(0,0,0,.2);\
|
||||||
|
line-height: 1.4;\
|
||||||
}");
|
}");
|
||||||
|
|
||||||
exports.AcePopup = AcePopup;
|
exports.AcePopup = AcePopup;
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,7 @@
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 300;
|
z-index: 999999;
|
||||||
-moz-box-sizing: border-box;
|
-moz-box-sizing: border-box;
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
||||||
|
|
@ -992,7 +992,7 @@ var EditSession = function(text, mode) {
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
this.setScrollTop = function(scrollTop) {
|
this.setScrollTop = function(scrollTop) {
|
||||||
scrollTop = Math.round(scrollTop);
|
// TODO: should we force integer lineheight instead? scrollTop = Math.round(scrollTop);
|
||||||
if (this.$scrollTop === scrollTop || isNaN(scrollTop))
|
if (this.$scrollTop === scrollTop || isNaN(scrollTop))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -1013,7 +1013,7 @@ var EditSession = function(text, mode) {
|
||||||
* [Sets the value of the distance between the left of the editor and the leftmost part of the visible content.]{: #EditSession.setScrollLeft}
|
* [Sets the value of the distance between the left of the editor and the leftmost part of the visible content.]{: #EditSession.setScrollLeft}
|
||||||
**/
|
**/
|
||||||
this.setScrollLeft = function(scrollLeft) {
|
this.setScrollLeft = function(scrollLeft) {
|
||||||
scrollLeft = Math.round(scrollLeft);
|
// scrollLeft = Math.round(scrollLeft);
|
||||||
if (this.$scrollLeft === scrollLeft || isNaN(scrollLeft))
|
if (this.$scrollLeft === scrollLeft || isNaN(scrollLeft))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,13 @@ var snippetCompleter = {
|
||||||
var snippets = snippetMap[scope] || [];
|
var snippets = snippetMap[scope] || [];
|
||||||
for (var i = snippets.length; i--;) {
|
for (var i = snippets.length; i--;) {
|
||||||
var s = snippets[i];
|
var s = snippets[i];
|
||||||
|
var caption = s.name || s.tabTrigger;
|
||||||
|
if (!caption)
|
||||||
|
continue;
|
||||||
completions.push({
|
completions.push({
|
||||||
caption: s.tabTrigger,
|
caption: caption,
|
||||||
snippet: s.content,
|
snippet: s.content,
|
||||||
meta: "snippet"
|
meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, this);
|
}, this);
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ var EditSession = require("./edit_session").EditSession;
|
||||||
* @method Selection.getAllRanges
|
* @method Selection.getAllRanges
|
||||||
**/
|
**/
|
||||||
this.getAllRanges = function() {
|
this.getAllRanges = function() {
|
||||||
return this.rangeList.ranges.concat();
|
return this.rangeCount ? this.rangeList.ranges.concat() : [this.getRange()];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -282,38 +282,73 @@ var SnippetManager = function() {
|
||||||
if (typeof p != "object")
|
if (typeof p != "object")
|
||||||
return;
|
return;
|
||||||
var id = p.tabstopId;
|
var id = p.tabstopId;
|
||||||
if (!tabstops[id]) {
|
var ts = tabstops[id];
|
||||||
tabstops[id] = [];
|
if (!ts) {
|
||||||
tabstops[id].index = id;
|
ts = tabstops[id] = [];
|
||||||
tabstops[id].value = "";
|
ts.index = id;
|
||||||
|
ts.value = "";
|
||||||
}
|
}
|
||||||
if (tabstops[id].indexOf(p) != -1)
|
if (ts.indexOf(p) !== -1)
|
||||||
return;
|
return;
|
||||||
tabstops[id].push(p);
|
ts.push(p);
|
||||||
var i1 = tokens.indexOf(p, i + 1);
|
var i1 = tokens.indexOf(p, i + 1);
|
||||||
if (i1 == -1)
|
if (i1 === -1)
|
||||||
return;
|
return;
|
||||||
var value = tokens.slice(i + 1, i1).join("");
|
|
||||||
if (value)
|
var value = tokens.slice(i + 1, i1);
|
||||||
tabstops[id].value = value;
|
var isNested = value.some(function(t) {return typeof t === "object"});
|
||||||
|
if (isNested && !ts.value) {
|
||||||
|
ts.value = value;
|
||||||
|
} else if (value.length && (!ts.value || typeof ts.value !== "string")) {
|
||||||
|
ts.value = value.join("");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tabstops.forEach(function(ts) {
|
// expand tabstop values
|
||||||
ts.value && ts.forEach(function(p) {
|
tabstops.forEach(function(ts) {ts.length = 0});
|
||||||
var i = tokens.indexOf(p);
|
var expanding = {};
|
||||||
var i1 = tokens.indexOf(p, i + 1);
|
function copyValue(val) {
|
||||||
if (i1 == -1)
|
var copy = []
|
||||||
tokens.splice(i + 1, 0, ts.value, p);
|
for (var i = 0; i < val.length; i++) {
|
||||||
else if (i1 == i + 1)
|
var p = val[i];
|
||||||
tokens.splice(i + 1, 0, ts.value);
|
if (typeof p == "object") {
|
||||||
});
|
if (expanding[p.tabstopId])
|
||||||
});
|
continue;
|
||||||
|
var j = val.lastIndexOf(p, i - 1);
|
||||||
|
p = copy[j] || {tabstopId: p.tabstopId};
|
||||||
|
}
|
||||||
|
copy[i] = p;
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < tokens.length; i++) {
|
||||||
|
var p = tokens[i];
|
||||||
|
if (typeof p != "object")
|
||||||
|
continue;
|
||||||
|
var id = p.tabstopId;
|
||||||
|
var i1 = tokens.indexOf(p, i + 1);
|
||||||
|
if (expanding[id] == p) {
|
||||||
|
expanding[id] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ts = tabstops[id];
|
||||||
|
var arg = typeof ts.value == "string" ? [ts.value] : copyValue(ts.value);
|
||||||
|
arg.unshift(i + 1, Math.max(0, i1 - i));
|
||||||
|
arg.push(p);
|
||||||
|
expanding[id] = p;
|
||||||
|
tokens.splice.apply(tokens, arg);
|
||||||
|
|
||||||
|
if (ts.indexOf(p) === -1)
|
||||||
|
ts.push(p);
|
||||||
|
};
|
||||||
|
|
||||||
// convert to plain text
|
// convert to plain text
|
||||||
var row = 0, column = 0;
|
var row = 0, column = 0;
|
||||||
var text = "";
|
var text = "";
|
||||||
tokens.forEach(function(t) {
|
tokens.forEach(function(t) {
|
||||||
if (typeof t == "string") {
|
if (typeof t === "string") {
|
||||||
if (t[0] == "\n"){
|
if (t[0] === "\n"){
|
||||||
column = t.length - 1;
|
column = t.length - 1;
|
||||||
row ++;
|
row ++;
|
||||||
} else
|
} else
|
||||||
|
|
@ -484,7 +519,7 @@ var SnippetManager = function() {
|
||||||
snippets.forEach(removeSnippet);
|
snippets.forEach(removeSnippet);
|
||||||
};
|
};
|
||||||
this.parseSnippetFile = function(str) {
|
this.parseSnippetFile = function(str) {
|
||||||
str = str.replace(/\r/, "");
|
str = str.replace(/\r/g, "");
|
||||||
var list = [], snippet = {};
|
var list = [], snippet = {};
|
||||||
var re = /^#.*|^({[\s\S]*})\s*$|^(\S+) (.*)$|^((?:\n*\t.*)+)/gm;
|
var re = /^#.*|^({[\s\S]*})\s*$|^(\S+) (.*)$|^((?:\n*\t.*)+)/gm;
|
||||||
var m;
|
var m;
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@ snippet use
|
||||||
use ${1:Foo\Bar\Baz};
|
use ${1:Foo\Bar\Baz};
|
||||||
${2}
|
${2}
|
||||||
snippet c
|
snippet c
|
||||||
${1:abstract }class ${2:`Filename()`}
|
${1:abstract }class ${2:$FILENAME}
|
||||||
{
|
{
|
||||||
${3}
|
${3}
|
||||||
}
|
}
|
||||||
snippet i
|
snippet i
|
||||||
interface ${1:`Filename()`}
|
interface ${1:$FILENAME}
|
||||||
{
|
{
|
||||||
${2}
|
${2}
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +45,7 @@ snippet sm
|
||||||
*
|
*
|
||||||
* @param ${2:$1} $$1 ${3:description}
|
* @param ${2:$1} $$1 ${3:description}
|
||||||
*
|
*
|
||||||
* @return ${4:`Filename()`}
|
* @return ${4:$FILENAME}
|
||||||
*/
|
*/
|
||||||
${5:public} function set${6:$2}(${7:$2 }$$1)
|
${5:public} function set${6:$2}(${7:$2 }$$1)
|
||||||
{
|
{
|
||||||
|
|
@ -202,7 +202,7 @@ snippet interface
|
||||||
* @package ${3:default}
|
* @package ${3:default}
|
||||||
* @author ${4:`g:snips_author`}
|
* @author ${4:`g:snips_author`}
|
||||||
*/
|
*/
|
||||||
interface ${1:`Filename()`}
|
interface ${1:$FILENAME}
|
||||||
{
|
{
|
||||||
${5}
|
${5}
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +211,7 @@ snippet class
|
||||||
/**
|
/**
|
||||||
* ${1}
|
* ${1}
|
||||||
*/
|
*/
|
||||||
class ${2:`Filename()`}
|
class ${2:$FILENAME}
|
||||||
{
|
{
|
||||||
${3}
|
${3}
|
||||||
/**
|
/**
|
||||||
|
|
@ -345,7 +345,7 @@ snippet gs
|
||||||
*
|
*
|
||||||
* @param $2 $$1 ${5:description}
|
* @param $2 $$1 ${5:description}
|
||||||
*
|
*
|
||||||
* @return ${6:`Filename()`}
|
* @return ${6:$FILENAME}
|
||||||
*/
|
*/
|
||||||
public function set$3(${7:$2 }$$1)
|
public function set$3(${7:$2 }$$1)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,20 @@ if (typeof process !== "undefined") {
|
||||||
|
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
var EditSession = require("./edit_session").EditSession;
|
||||||
|
var Editor = require("./editor").Editor;
|
||||||
|
var MockRenderer = require("./test/mockrenderer").MockRenderer;
|
||||||
|
var MultiSelect = require("./multi_select").MultiSelect;
|
||||||
|
|
||||||
var snippetManager = require("./snippets").snippetManager;
|
var snippetManager = require("./snippets").snippetManager;
|
||||||
var assert = require("./test/assertions");
|
var assert = require("./test/assertions");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
setUp : function(next) {
|
||||||
|
this.editor = new Editor(new MockRenderer());
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
|
||||||
"test: textmate style format strings" : function() {
|
"test: textmate style format strings" : function() {
|
||||||
var fmt = snippetManager.tmStrFormat;
|
var fmt = snippetManager.tmStrFormat;
|
||||||
snippetManager.tmStrFormat("hello", {
|
snippetManager.tmStrFormat("hello", {
|
||||||
|
|
@ -86,6 +95,32 @@ module.exports = {
|
||||||
assert.equal(tokens[1].fmt, "\\ul\\/");
|
assert.equal(tokens[1].fmt, "\\ul\\/");
|
||||||
assert.equal(tokens[1].guard, "as\\/d");
|
assert.equal(tokens[1].guard, "as\\/d");
|
||||||
assert.equal(tokens[1].flag, "g");
|
assert.equal(tokens[1].flag, "g");
|
||||||
|
},
|
||||||
|
"test: expand snippet with nested tabstops": function() {
|
||||||
|
var content = "-${1}-${1:1}--${2:2 ${3} 2}-${3:3 $1 3}-${4:4 $2 4}";
|
||||||
|
this.editor.setValue("");
|
||||||
|
snippetManager.insertSnippet(this.editor, content);
|
||||||
|
assert.equal(this.editor.getValue(), "-1-1--2 3 1 3 2-3 1 3-4 2 3 1 3 2 4");
|
||||||
|
|
||||||
|
assert.equal(this.editor.getSelectedText(), "1\n1\n1\n1\n1");
|
||||||
|
this.editor.tabstopManager.tabNext();
|
||||||
|
assert.equal(this.editor.getSelectedText(), "2 3 1 3 2\n2 3 1 3 2");
|
||||||
|
this.editor.tabstopManager.tabNext();
|
||||||
|
assert.equal(this.editor.getSelectedText(), "3 1 3\n3 1 3\n3 1 3");
|
||||||
|
this.editor.tabstopManager.tabNext();
|
||||||
|
assert.equal(this.editor.getSelectedText(), "4 2 3 1 3 2 4");
|
||||||
|
this.editor.tabstopManager.tabNext();
|
||||||
|
assert.equal(this.editor.getSelectedText(), "");
|
||||||
|
|
||||||
|
this.editor.setValue("");
|
||||||
|
snippetManager.insertSnippet(this.editor, "-${1:a$2}-${2:b$1}");
|
||||||
|
assert.equal(this.editor.getValue(), "-ab-ba");
|
||||||
|
|
||||||
|
assert.equal(this.editor.getSelectedText(), "ab\na");
|
||||||
|
this.editor.tabstopManager.tabNext();
|
||||||
|
assert.equal(this.editor.getSelectedText(), "b\nba");
|
||||||
|
this.editor.tabstopManager.tabNext();
|
||||||
|
assert.equal(this.editor.getSelectedText(), "");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue