Merge pull request #1583 from ajaxorg/autocomplete

improve autocomplete popup
This commit is contained in:
Zef Hemel 2013-09-06 02:03:58 -07:00
commit f860b88e51
10 changed files with 267 additions and 122 deletions

View file

@ -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() {

View file

@ -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,52 +172,19 @@ 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) {
var session = editor.getSession(); var session = editor.getSession();
var pos = editor.getCursorPosition(); var pos = editor.getCursorPosition();
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) {
@ -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;

View file

@ -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,12 +130,7 @@ 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;
@ -166,17 +187,15 @@ var AcePopup = function(parentNode) {
popup.getData = function(row) { popup.getData = function(row) {
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() {
@ -200,13 +219,18 @@ 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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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()];
}; };
/** /**

View file

@ -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;

View file

@ -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)
{ {

View file

@ -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(), "");
} }
}; };