diff --git a/lib/ace/snippets.js b/lib/ace/snippets.js index 3e00008b..aa1d71b5 100644 --- a/lib/ace/snippets.js +++ b/lib/ace/snippets.js @@ -282,38 +282,73 @@ var SnippetManager = function() { if (typeof p != "object") return; var id = p.tabstopId; - if (!tabstops[id]) { - tabstops[id] = []; - tabstops[id].index = id; - tabstops[id].value = ""; + var ts = tabstops[id]; + if (!ts) { + ts = tabstops[id] = []; + ts.index = id; + ts.value = ""; } - if (tabstops[id].indexOf(p) != -1) + if (ts.indexOf(p) !== -1) return; - tabstops[id].push(p); + ts.push(p); var i1 = tokens.indexOf(p, i + 1); - if (i1 == -1) + if (i1 === -1) return; - var value = tokens.slice(i + 1, i1).join(""); - if (value) - tabstops[id].value = value; + + var value = tokens.slice(i + 1, i1); + 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) { - ts.value && ts.forEach(function(p) { - var i = tokens.indexOf(p); - var i1 = tokens.indexOf(p, i + 1); - if (i1 == -1) - tokens.splice(i + 1, 0, ts.value, p); - else if (i1 == i + 1) - tokens.splice(i + 1, 0, ts.value); - }); - }); + // expand tabstop values + tabstops.forEach(function(ts) {ts.length = 0}); + var expanding = {}; + function copyValue(val) { + var copy = [] + for (var i = 0; i < val.length; i++) { + var p = val[i]; + 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 var row = 0, column = 0; var text = ""; tokens.forEach(function(t) { - if (typeof t == "string") { - if (t[0] == "\n"){ + if (typeof t === "string") { + if (t[0] === "\n"){ column = t.length - 1; row ++; } else diff --git a/lib/ace/snippets_test.js b/lib/ace/snippets_test.js index 52aab4f0..1870d950 100644 --- a/lib/ace/snippets_test.js +++ b/lib/ace/snippets_test.js @@ -34,11 +34,20 @@ if (typeof process !== "undefined") { define(function(require, exports, module) { "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 assert = require("./test/assertions"); module.exports = { + setUp : function(next) { + this.editor = new Editor(new MockRenderer()); + next(); + }, + "test: textmate style format strings" : function() { var fmt = snippetManager.tmStrFormat; snippetManager.tmStrFormat("hello", { @@ -86,6 +95,32 @@ module.exports = { assert.equal(tokens[1].fmt, "\\ul\\/"); assert.equal(tokens[1].guard, "as\\/d"); 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(), ""); } };