blazer 2.6.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +13 -28
  5. data/app/assets/javascripts/blazer/ace/ace.js +7235 -8906
  6. data/app/assets/javascripts/blazer/ace/ext-language_tools.js +762 -774
  7. data/app/assets/javascripts/blazer/ace/mode-sql.js +177 -72
  8. data/app/assets/javascripts/blazer/ace/snippets/sql.js +5 -29
  9. data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -6
  10. data/app/assets/javascripts/blazer/ace/theme-twilight.js +8 -106
  11. data/app/assets/javascripts/blazer/application.js +9 -6
  12. data/app/assets/javascripts/blazer/chart.umd.js +13 -0
  13. data/app/assets/javascripts/blazer/chartjs-adapter-date-fns.bundle.js +6322 -0
  14. data/app/assets/javascripts/blazer/chartkick.js +1020 -914
  15. data/app/assets/javascripts/blazer/highlight.min.js +466 -3
  16. data/app/assets/javascripts/blazer/mapkick.bundle.js +1029 -0
  17. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +39 -38
  18. data/app/assets/javascripts/blazer/moment.js +105 -88
  19. data/app/assets/javascripts/blazer/queries.js +10 -1
  20. data/app/assets/javascripts/blazer/rails-ujs.js +746 -0
  21. data/app/assets/javascripts/blazer/vue.global.prod.js +1 -0
  22. data/app/assets/stylesheets/blazer/bootstrap.css +1 -1
  23. data/app/assets/stylesheets/blazer/selectize.css +1 -1
  24. data/app/controllers/blazer/base_controller.rb +85 -84
  25. data/app/controllers/blazer/checks_controller.rb +6 -6
  26. data/app/controllers/blazer/dashboards_controller.rb +24 -24
  27. data/app/controllers/blazer/queries_controller.rb +208 -186
  28. data/app/controllers/blazer/uploads_controller.rb +49 -49
  29. data/app/helpers/blazer/base_helper.rb +0 -4
  30. data/app/models/blazer/query.rb +1 -12
  31. data/app/views/blazer/checks/index.html.erb +1 -1
  32. data/app/views/blazer/dashboards/_form.html.erb +11 -5
  33. data/app/views/blazer/queries/_form.html.erb +19 -14
  34. data/app/views/blazer/queries/docs.html.erb +11 -1
  35. data/app/views/blazer/queries/home.html.erb +9 -6
  36. data/app/views/blazer/queries/run.html.erb +17 -32
  37. data/app/views/blazer/queries/show.html.erb +12 -20
  38. data/app/views/layouts/blazer/application.html.erb +1 -5
  39. data/lib/blazer/adapters/sql_adapter.rb +1 -1
  40. data/lib/blazer/adapters.rb +17 -0
  41. data/lib/blazer/anomaly_detectors.rb +22 -0
  42. data/lib/blazer/data_source.rb +29 -40
  43. data/lib/blazer/engine.rb +11 -9
  44. data/lib/blazer/forecasters.rb +7 -0
  45. data/lib/blazer/result.rb +13 -71
  46. data/lib/blazer/result_cache.rb +71 -0
  47. data/lib/blazer/run_statement.rb +1 -1
  48. data/lib/blazer/run_statement_job.rb +2 -2
  49. data/lib/blazer/statement.rb +3 -1
  50. data/lib/blazer/version.rb +1 -1
  51. data/lib/blazer.rb +51 -53
  52. data/licenses/LICENSE-chart.js.txt +1 -1
  53. data/licenses/LICENSE-chartjs-adapter-date-fns.txt +9 -0
  54. data/licenses/LICENSE-chartkick.js.txt +1 -1
  55. data/licenses/LICENSE-date-fns.txt +21 -0
  56. data/licenses/LICENSE-kurkle-color.txt +9 -0
  57. data/licenses/LICENSE-mapkick-bundle.txt +1029 -0
  58. data/licenses/{LICENSE-jquery-ujs.txt → LICENSE-rails-ujs.txt} +1 -1
  59. data/licenses/LICENSE-vue.txt +1 -1
  60. metadata +26 -18
  61. data/app/assets/javascripts/blazer/Chart.js +0 -16172
  62. data/app/assets/javascripts/blazer/jquery-ujs.js +0 -555
  63. data/app/assets/javascripts/blazer/vue.js +0 -12014
  64. data/lib/blazer/adapters/mongodb_adapter.rb +0 -43
  65. data/lib/blazer/detect_anomalies.R +0 -19
@@ -1,170 +1,247 @@
1
- define("ace/snippets",["require","exports","module","ace/lib/oop","ace/lib/event_emitter","ace/lib/lang","ace/range","ace/anchor","ace/keyboard/hash_handler","ace/tokenizer","ace/lib/dom","ace/editor"], function(require, exports, module) {
2
- "use strict";
1
+ define("ace/snippets",["require","exports","module","ace/lib/dom","ace/lib/oop","ace/lib/event_emitter","ace/lib/lang","ace/range","ace/range_list","ace/keyboard/hash_handler","ace/tokenizer","ace/clipboard","ace/editor"], function(require, exports, module){"use strict";
2
+ var dom = require("./lib/dom");
3
3
  var oop = require("./lib/oop");
4
4
  var EventEmitter = require("./lib/event_emitter").EventEmitter;
5
5
  var lang = require("./lib/lang");
6
6
  var Range = require("./range").Range;
7
- var Anchor = require("./anchor").Anchor;
7
+ var RangeList = require("./range_list").RangeList;
8
8
  var HashHandler = require("./keyboard/hash_handler").HashHandler;
9
9
  var Tokenizer = require("./tokenizer").Tokenizer;
10
- var comparePoints = Range.comparePoints;
11
-
12
- var SnippetManager = function() {
10
+ var clipboard = require("./clipboard");
11
+ var VARIABLES = {
12
+ CURRENT_WORD: function (editor) {
13
+ return editor.session.getTextRange(editor.session.getWordRange());
14
+ },
15
+ SELECTION: function (editor, name, indentation) {
16
+ var text = editor.session.getTextRange();
17
+ if (indentation)
18
+ return text.replace(/\n\r?([ \t]*\S)/g, "\n" + indentation + "$1");
19
+ return text;
20
+ },
21
+ CURRENT_LINE: function (editor) {
22
+ return editor.session.getLine(editor.getCursorPosition().row);
23
+ },
24
+ PREV_LINE: function (editor) {
25
+ return editor.session.getLine(editor.getCursorPosition().row - 1);
26
+ },
27
+ LINE_INDEX: function (editor) {
28
+ return editor.getCursorPosition().row;
29
+ },
30
+ LINE_NUMBER: function (editor) {
31
+ return editor.getCursorPosition().row + 1;
32
+ },
33
+ SOFT_TABS: function (editor) {
34
+ return editor.session.getUseSoftTabs() ? "YES" : "NO";
35
+ },
36
+ TAB_SIZE: function (editor) {
37
+ return editor.session.getTabSize();
38
+ },
39
+ CLIPBOARD: function (editor) {
40
+ return clipboard.getText && clipboard.getText();
41
+ },
42
+ FILENAME: function (editor) {
43
+ return /[^/\\]*$/.exec(this.FILEPATH(editor))[0];
44
+ },
45
+ FILENAME_BASE: function (editor) {
46
+ return /[^/\\]*$/.exec(this.FILEPATH(editor))[0].replace(/\.[^.]*$/, "");
47
+ },
48
+ DIRECTORY: function (editor) {
49
+ return this.FILEPATH(editor).replace(/[^/\\]*$/, "");
50
+ },
51
+ FILEPATH: function (editor) { return "/not implemented.txt"; },
52
+ WORKSPACE_NAME: function () { return "Unknown"; },
53
+ FULLNAME: function () { return "Unknown"; },
54
+ BLOCK_COMMENT_START: function (editor) {
55
+ var mode = editor.session.$mode || {};
56
+ return mode.blockComment && mode.blockComment.start || "";
57
+ },
58
+ BLOCK_COMMENT_END: function (editor) {
59
+ var mode = editor.session.$mode || {};
60
+ return mode.blockComment && mode.blockComment.end || "";
61
+ },
62
+ LINE_COMMENT: function (editor) {
63
+ var mode = editor.session.$mode || {};
64
+ return mode.lineCommentStart || "";
65
+ },
66
+ CURRENT_YEAR: date.bind(null, { year: "numeric" }),
67
+ CURRENT_YEAR_SHORT: date.bind(null, { year: "2-digit" }),
68
+ CURRENT_MONTH: date.bind(null, { month: "numeric" }),
69
+ CURRENT_MONTH_NAME: date.bind(null, { month: "long" }),
70
+ CURRENT_MONTH_NAME_SHORT: date.bind(null, { month: "short" }),
71
+ CURRENT_DATE: date.bind(null, { day: "2-digit" }),
72
+ CURRENT_DAY_NAME: date.bind(null, { weekday: "long" }),
73
+ CURRENT_DAY_NAME_SHORT: date.bind(null, { weekday: "short" }),
74
+ CURRENT_HOUR: date.bind(null, { hour: "2-digit", hour12: false }),
75
+ CURRENT_MINUTE: date.bind(null, { minute: "2-digit" }),
76
+ CURRENT_SECOND: date.bind(null, { second: "2-digit" })
77
+ };
78
+ VARIABLES.SELECTED_TEXT = VARIABLES.SELECTION;
79
+ function date(dateFormat) {
80
+ var str = new Date().toLocaleString("en-us", dateFormat);
81
+ return str.length == 1 ? "0" + str : str;
82
+ }
83
+ var SnippetManager = function () {
13
84
  this.snippetMap = {};
14
85
  this.snippetNameMap = {};
15
86
  };
16
-
17
- (function() {
87
+ (function () {
18
88
  oop.implement(this, EventEmitter);
19
-
20
- this.getTokenizer = function() {
21
- function TabstopToken(str, _, stack) {
89
+ this.getTokenizer = function () {
90
+ return SnippetManager.$tokenizer || this.createTokenizer();
91
+ };
92
+ this.createTokenizer = function () {
93
+ function TabstopToken(str) {
22
94
  str = str.substr(1);
23
- if (/^\d+$/.test(str) && !stack.inFormatString)
24
- return [{tabstopId: parseInt(str, 10)}];
25
- return [{text: str}];
95
+ if (/^\d+$/.test(str))
96
+ return [{ tabstopId: parseInt(str, 10) }];
97
+ return [{ text: str }];
26
98
  }
27
99
  function escape(ch) {
28
100
  return "(?:[^\\\\" + ch + "]|\\\\.)";
29
101
  }
102
+ var formatMatcher = {
103
+ regex: "/(" + escape("/") + "+)/",
104
+ onMatch: function (val, state, stack) {
105
+ var ts = stack[0];
106
+ ts.fmtString = true;
107
+ ts.guard = val.slice(1, -1);
108
+ ts.flag = "";
109
+ return "";
110
+ },
111
+ next: "formatString"
112
+ };
30
113
  SnippetManager.$tokenizer = new Tokenizer({
31
114
  start: [
32
- {regex: /:/, onMatch: function(val, state, stack) {
33
- if (stack.length && stack[0].expectIf) {
34
- stack[0].expectIf = false;
35
- stack[0].elseBranch = stack[0];
36
- return [stack[0]];
37
- }
38
- return ":";
39
- }},
40
- {regex: /\\./, onMatch: function(val, state, stack) {
41
- var ch = val[1];
42
- if (ch == "}" && stack.length) {
43
- val = ch;
44
- }else if ("`$\\".indexOf(ch) != -1) {
45
- val = ch;
46
- } else if (stack.inFormatString) {
47
- if (ch == "n")
48
- val = "\n";
49
- else if (ch == "t")
50
- val = "\n";
51
- else if ("ulULE".indexOf(ch) != -1) {
52
- val = {changeCase: ch, local: ch > "a"};
115
+ { regex: /\\./, onMatch: function (val, state, stack) {
116
+ var ch = val[1];
117
+ if (ch == "}" && stack.length) {
118
+ val = ch;
53
119
  }
54
- }
55
-
56
- return [val];
57
- }},
58
- {regex: /}/, onMatch: function(val, state, stack) {
59
- return [stack.length ? stack.shift() : val];
60
- }},
61
- {regex: /\$(?:\d+|\w+)/, onMatch: TabstopToken},
62
- {regex: /\$\{[\dA-Z_a-z]+/, onMatch: function(str, state, stack) {
63
- var t = TabstopToken(str.substr(1), state, stack);
64
- stack.unshift(t[0]);
65
- return t;
66
- }, next: "snippetVar"},
67
- {regex: /\n/, token: "newline", merge: false}
120
+ else if ("`$\\".indexOf(ch) != -1) {
121
+ val = ch;
122
+ }
123
+ return [val];
124
+ } },
125
+ { regex: /}/, onMatch: function (val, state, stack) {
126
+ return [stack.length ? stack.shift() : val];
127
+ } },
128
+ { regex: /\$(?:\d+|\w+)/, onMatch: TabstopToken },
129
+ { regex: /\$\{[\dA-Z_a-z]+/, onMatch: function (str, state, stack) {
130
+ var t = TabstopToken(str.substr(1));
131
+ stack.unshift(t[0]);
132
+ return t;
133
+ }, next: "snippetVar" },
134
+ { regex: /\n/, token: "newline", merge: false }
68
135
  ],
69
136
  snippetVar: [
70
- {regex: "\\|" + escape("\\|") + "*\\|", onMatch: function(val, state, stack) {
71
- stack[0].choices = val.slice(1, -1).split(",");
72
- }, next: "start"},
73
- {regex: "/(" + escape("/") + "+)/(?:(" + escape("/") + "*)/)(\\w*):?",
74
- onMatch: function(val, state, stack) {
75
- var ts = stack[0];
76
- ts.fmtString = val;
77
-
78
- val = this.splitRegex.exec(val);
79
- ts.guard = val[1];
80
- ts.fmt = val[2];
81
- ts.flag = val[3];
82
- return "";
83
- }, next: "start"},
84
- {regex: "`" + escape("`") + "*`", onMatch: function(val, state, stack) {
85
- stack[0].code = val.splice(1, -1);
86
- return "";
87
- }, next: "start"},
88
- {regex: "\\?", onMatch: function(val, state, stack) {
89
- if (stack[0])
90
- stack[0].expectIf = true;
91
- }, next: "start"},
92
- {regex: "([^:}\\\\]|\\\\.)*:?", token: "", next: "start"}
137
+ { regex: "\\|" + escape("\\|") + "*\\|", onMatch: function (val, state, stack) {
138
+ var choices = val.slice(1, -1).replace(/\\[,|\\]|,/g, function (operator) {
139
+ return operator.length == 2 ? operator[1] : "\x00";
140
+ }).split("\x00").map(function (value) {
141
+ return { value: value };
142
+ });
143
+ stack[0].choices = choices;
144
+ return [choices[0]];
145
+ }, next: "start" },
146
+ formatMatcher,
147
+ { regex: "([^:}\\\\]|\\\\.)*:?", token: "", next: "start" }
93
148
  ],
94
149
  formatString: [
95
- {regex: "/(" + escape("/") + "+)/", token: "regex"},
96
- {regex: "", onMatch: function(val, state, stack) {
97
- stack.inFormatString = true;
98
- }, next: "start"}
150
+ { regex: /:/, onMatch: function (val, state, stack) {
151
+ if (stack.length && stack[0].expectElse) {
152
+ stack[0].expectElse = false;
153
+ stack[0].ifEnd = { elseEnd: stack[0] };
154
+ return [stack[0].ifEnd];
155
+ }
156
+ return ":";
157
+ } },
158
+ { regex: /\\./, onMatch: function (val, state, stack) {
159
+ var ch = val[1];
160
+ if (ch == "}" && stack.length)
161
+ val = ch;
162
+ else if ("`$\\".indexOf(ch) != -1)
163
+ val = ch;
164
+ else if (ch == "n")
165
+ val = "\n";
166
+ else if (ch == "t")
167
+ val = "\t";
168
+ else if ("ulULE".indexOf(ch) != -1)
169
+ val = { changeCase: ch, local: ch > "a" };
170
+ return [val];
171
+ } },
172
+ { regex: "/\\w*}", onMatch: function (val, state, stack) {
173
+ var next = stack.shift();
174
+ if (next)
175
+ next.flag = val.slice(1, -1);
176
+ this.next = next && next.tabstopId ? "start" : "";
177
+ return [next || val];
178
+ }, next: "start" },
179
+ { regex: /\$(?:\d+|\w+)/, onMatch: function (val, state, stack) {
180
+ return [{ text: val.slice(1) }];
181
+ } },
182
+ { regex: /\${\w+/, onMatch: function (val, state, stack) {
183
+ var token = { text: val.slice(2) };
184
+ stack.unshift(token);
185
+ return [token];
186
+ }, next: "formatStringVar" },
187
+ { regex: /\n/, token: "newline", merge: false },
188
+ { regex: /}/, onMatch: function (val, state, stack) {
189
+ var next = stack.shift();
190
+ this.next = next && next.tabstopId ? "start" : "";
191
+ return [next || val];
192
+ }, next: "start" }
193
+ ],
194
+ formatStringVar: [
195
+ { regex: /:\/\w+}/, onMatch: function (val, state, stack) {
196
+ var ts = stack[0];
197
+ ts.formatFunction = val.slice(2, -1);
198
+ return [stack.shift()];
199
+ }, next: "formatString" },
200
+ formatMatcher,
201
+ { regex: /:[\?\-+]?/, onMatch: function (val, state, stack) {
202
+ if (val[1] == "+")
203
+ stack[0].ifEnd = stack[0];
204
+ if (val[1] == "?")
205
+ stack[0].expectElse = true;
206
+ }, next: "formatString" },
207
+ { regex: "([^:}\\\\]|\\\\.)*:?", token: "", next: "formatString" }
99
208
  ]
100
209
  });
101
- SnippetManager.prototype.getTokenizer = function() {
102
- return SnippetManager.$tokenizer;
103
- };
104
210
  return SnippetManager.$tokenizer;
105
211
  };
106
-
107
- this.tokenizeTmSnippet = function(str, startState) {
108
- return this.getTokenizer().getLineTokens(str, startState).tokens.map(function(x) {
212
+ this.tokenizeTmSnippet = function (str, startState) {
213
+ return this.getTokenizer().getLineTokens(str, startState).tokens.map(function (x) {
109
214
  return x.value || x;
110
215
  });
111
216
  };
112
-
113
- this.$getDefaultValue = function(editor, name) {
114
- if (/^[A-Z]\d+$/.test(name)) {
115
- var i = name.substr(1);
116
- return (this.variables[name[0] + "__"] || {})[i];
117
- }
118
- if (/^\d+$/.test(name)) {
119
- return (this.variables.__ || {})[name];
120
- }
217
+ this.getVariableValue = function (editor, name, indentation) {
218
+ if (/^\d+$/.test(name))
219
+ return (this.variables.__ || {})[name] || "";
220
+ if (/^[A-Z]\d+$/.test(name))
221
+ return (this.variables[name[0] + "__"] || {})[name.substr(1)] || "";
121
222
  name = name.replace(/^TM_/, "");
122
-
123
- if (!editor)
124
- return;
125
- var s = editor.session;
126
- switch(name) {
127
- case "CURRENT_WORD":
128
- var r = s.getWordRange();
129
- case "SELECTION":
130
- case "SELECTED_TEXT":
131
- return s.getTextRange(r);
132
- case "CURRENT_LINE":
133
- return s.getLine(editor.getCursorPosition().row);
134
- case "PREV_LINE": // not possible in textmate
135
- return s.getLine(editor.getCursorPosition().row - 1);
136
- case "LINE_INDEX":
137
- return editor.getCursorPosition().column;
138
- case "LINE_NUMBER":
139
- return editor.getCursorPosition().row + 1;
140
- case "SOFT_TABS":
141
- return s.getUseSoftTabs() ? "YES" : "NO";
142
- case "TAB_SIZE":
143
- return s.getTabSize();
144
- case "FILENAME":
145
- case "FILEPATH":
146
- return "";
147
- case "FULLNAME":
148
- return "Ace";
149
- }
150
- };
151
- this.variables = {};
152
- this.getVariableValue = function(editor, varName) {
153
- if (this.variables.hasOwnProperty(varName))
154
- return this.variables[varName](editor, varName) || "";
155
- return this.$getDefaultValue(editor, varName) || "";
156
- };
157
- this.tmStrFormat = function(str, ch, editor) {
223
+ if (!this.variables.hasOwnProperty(name))
224
+ return "";
225
+ var value = this.variables[name];
226
+ if (typeof value == "function")
227
+ value = this.variables[name](editor, name, indentation);
228
+ return value == null ? "" : value;
229
+ };
230
+ this.variables = VARIABLES;
231
+ this.tmStrFormat = function (str, ch, editor) {
232
+ if (!ch.fmt)
233
+ return str;
158
234
  var flag = ch.flag || "";
159
235
  var re = ch.guard;
160
- re = new RegExp(re, flag.replace(/[^gi]/, ""));
161
- var fmtTokens = this.tokenizeTmSnippet(ch.fmt, "formatString");
236
+ re = new RegExp(re, flag.replace(/[^gim]/g, ""));
237
+ var fmtTokens = typeof ch.fmt == "string" ? this.tokenizeTmSnippet(ch.fmt, "formatString") : ch.fmt;
162
238
  var _self = this;
163
- var formatted = str.replace(re, function() {
164
- _self.variables.__ = arguments;
239
+ var formatted = str.replace(re, function () {
240
+ var oldArgs = _self.variables.__;
241
+ _self.variables.__ = [].slice.call(arguments);
165
242
  var fmtParts = _self.resolveVariables(fmtTokens, editor);
166
243
  var gChangeCase = "E";
167
- for (var i = 0; i < fmtParts.length; i++) {
244
+ for (var i = 0; i < fmtParts.length; i++) {
168
245
  var ch = fmtParts[i];
169
246
  if (typeof ch == "object") {
170
247
  fmtParts[i] = "";
@@ -177,52 +254,79 @@ var SnippetManager = function() {
177
254
  fmtParts[i] = next[0].toLowerCase();
178
255
  fmtParts[i + 1] = next.substr(1);
179
256
  }
180
- } else if (ch.changeCase) {
257
+ }
258
+ else if (ch.changeCase) {
181
259
  gChangeCase = ch.changeCase;
182
260
  }
183
- } else if (gChangeCase == "U") {
261
+ }
262
+ else if (gChangeCase == "U") {
184
263
  fmtParts[i] = ch.toUpperCase();
185
- } else if (gChangeCase == "L") {
264
+ }
265
+ else if (gChangeCase == "L") {
186
266
  fmtParts[i] = ch.toLowerCase();
187
267
  }
188
268
  }
269
+ _self.variables.__ = oldArgs;
189
270
  return fmtParts.join("");
190
271
  });
191
- this.variables.__ = null;
192
272
  return formatted;
193
273
  };
194
-
195
- this.resolveVariables = function(snippet, editor) {
274
+ this.tmFormatFunction = function (str, ch, editor) {
275
+ if (ch.formatFunction == "upcase")
276
+ return str.toUpperCase();
277
+ if (ch.formatFunction == "downcase")
278
+ return str.toLowerCase();
279
+ return str;
280
+ };
281
+ this.resolveVariables = function (snippet, editor) {
196
282
  var result = [];
283
+ var indentation = "";
284
+ var afterNewLine = true;
197
285
  for (var i = 0; i < snippet.length; i++) {
198
286
  var ch = snippet[i];
199
287
  if (typeof ch == "string") {
200
288
  result.push(ch);
201
- } else if (typeof ch != "object") {
289
+ if (ch == "\n") {
290
+ afterNewLine = true;
291
+ indentation = "";
292
+ }
293
+ else if (afterNewLine) {
294
+ indentation = /^\t*/.exec(ch)[0];
295
+ afterNewLine = /\S/.test(ch);
296
+ }
202
297
  continue;
203
- } else if (ch.skip) {
204
- gotoNext(ch);
205
- } else if (ch.processed < i) {
298
+ }
299
+ if (!ch)
206
300
  continue;
207
- } else if (ch.text) {
208
- var value = this.getVariableValue(editor, ch.text);
209
- if (value && ch.fmtString)
210
- value = this.tmStrFormat(value, ch);
211
- ch.processed = i;
212
- if (ch.expectIf == null) {
213
- if (value) {
214
- result.push(value);
215
- gotoNext(ch);
216
- }
217
- } else {
218
- if (value) {
219
- ch.skip = ch.elseBranch;
220
- } else
221
- gotoNext(ch);
301
+ afterNewLine = false;
302
+ if (ch.fmtString) {
303
+ var j = snippet.indexOf(ch, i + 1);
304
+ if (j == -1)
305
+ j = snippet.length;
306
+ ch.fmt = snippet.slice(i + 1, j);
307
+ i = j;
308
+ }
309
+ if (ch.text) {
310
+ var value = this.getVariableValue(editor, ch.text, indentation) + "";
311
+ if (ch.fmtString)
312
+ value = this.tmStrFormat(value, ch, editor);
313
+ if (ch.formatFunction)
314
+ value = this.tmFormatFunction(value, ch, editor);
315
+ if (value && !ch.ifEnd) {
316
+ result.push(value);
317
+ gotoNext(ch);
222
318
  }
223
- } else if (ch.tabstopId != null) {
319
+ else if (!value && ch.ifEnd) {
320
+ gotoNext(ch.ifEnd);
321
+ }
322
+ }
323
+ else if (ch.elseEnd) {
324
+ gotoNext(ch.elseEnd);
325
+ }
326
+ else if (ch.tabstopId != null) {
224
327
  result.push(ch);
225
- } else if (ch.changeCase != null) {
328
+ }
329
+ else if (ch.changeCase != null) {
226
330
  result.push(ch);
227
331
  }
228
332
  }
@@ -233,20 +337,17 @@ var SnippetManager = function() {
233
337
  }
234
338
  return result;
235
339
  };
236
-
237
- this.insertSnippetForSelection = function(editor, snippetText) {
340
+ this.insertSnippetForSelection = function (editor, snippetText) {
238
341
  var cursor = editor.getCursorPosition();
239
342
  var line = editor.session.getLine(cursor.row);
240
343
  var tabString = editor.session.getTabString();
241
344
  var indentString = line.match(/^\s*/)[0];
242
-
243
345
  if (cursor.column < indentString.length)
244
346
  indentString = indentString.slice(0, cursor.column);
245
-
246
347
  snippetText = snippetText.replace(/\r/g, "");
247
348
  var tokens = this.tokenizeTmSnippet(snippetText);
248
349
  tokens = this.resolveVariables(tokens, editor);
249
- tokens = tokens.map(function(x) {
350
+ tokens = tokens.map(function (x) {
250
351
  if (x == "\n")
251
352
  return x + indentString;
252
353
  if (typeof x == "string")
@@ -254,7 +355,7 @@ var SnippetManager = function() {
254
355
  return x;
255
356
  });
256
357
  var tabstops = [];
257
- tokens.forEach(function(p, i) {
358
+ tokens.forEach(function (p, i) {
258
359
  if (typeof p != "object")
259
360
  return;
260
361
  var id = p.tabstopId;
@@ -263,23 +364,26 @@ var SnippetManager = function() {
263
364
  ts = tabstops[id] = [];
264
365
  ts.index = id;
265
366
  ts.value = "";
367
+ ts.parents = {};
266
368
  }
267
369
  if (ts.indexOf(p) !== -1)
268
370
  return;
371
+ if (p.choices && !ts.choices)
372
+ ts.choices = p.choices;
269
373
  ts.push(p);
270
374
  var i1 = tokens.indexOf(p, i + 1);
271
375
  if (i1 === -1)
272
376
  return;
273
-
274
377
  var value = tokens.slice(i + 1, i1);
275
- var isNested = value.some(function(t) {return typeof t === "object";});
378
+ var isNested = value.some(function (t) { return typeof t === "object"; });
276
379
  if (isNested && !ts.value) {
277
380
  ts.value = value;
278
- } else if (value.length && (!ts.value || typeof ts.value !== "string")) {
381
+ }
382
+ else if (value.length && (!ts.value || typeof ts.value !== "string")) {
279
383
  ts.value = value.join("");
280
384
  }
281
385
  });
282
- tabstops.forEach(function(ts) {ts.length = 0;});
386
+ tabstops.forEach(function (ts) { ts.length = 0; });
283
387
  var expanding = {};
284
388
  function copyValue(val) {
285
389
  var copy = [];
@@ -289,7 +393,7 @@ var SnippetManager = function() {
289
393
  if (expanding[p.tabstopId])
290
394
  continue;
291
395
  var j = val.lastIndexOf(p, i - 1);
292
- p = copy[j] || {tabstopId: p.tabstopId};
396
+ p = copy[j] || { tabstopId: p.tabstopId };
293
397
  }
294
398
  copy[i] = p;
295
399
  }
@@ -300,67 +404,68 @@ var SnippetManager = function() {
300
404
  if (typeof p != "object")
301
405
  continue;
302
406
  var id = p.tabstopId;
407
+ var ts = tabstops[id];
303
408
  var i1 = tokens.indexOf(p, i + 1);
304
409
  if (expanding[id]) {
305
- if (expanding[id] === p)
306
- expanding[id] = null;
410
+ if (expanding[id] === p) {
411
+ delete expanding[id];
412
+ Object.keys(expanding).forEach(function (parentId) {
413
+ ts.parents[parentId] = true;
414
+ });
415
+ }
307
416
  continue;
308
417
  }
309
-
310
- var ts = tabstops[id];
311
- var arg = typeof ts.value == "string" ? [ts.value] : copyValue(ts.value);
312
- arg.unshift(i + 1, Math.max(0, i1 - i));
313
- arg.push(p);
314
418
  expanding[id] = p;
315
- tokens.splice.apply(tokens, arg);
316
-
419
+ var value = ts.value;
420
+ if (typeof value !== "string")
421
+ value = copyValue(value);
422
+ else if (p.fmt)
423
+ value = this.tmStrFormat(value, p, editor);
424
+ tokens.splice.apply(tokens, [i + 1, Math.max(0, i1 - i)].concat(value, p));
317
425
  if (ts.indexOf(p) === -1)
318
426
  ts.push(p);
319
427
  }
320
428
  var row = 0, column = 0;
321
429
  var text = "";
322
- tokens.forEach(function(t) {
430
+ tokens.forEach(function (t) {
323
431
  if (typeof t === "string") {
324
432
  var lines = t.split("\n");
325
- if (lines.length > 1){
433
+ if (lines.length > 1) {
326
434
  column = lines[lines.length - 1].length;
327
435
  row += lines.length - 1;
328
- } else
436
+ }
437
+ else
329
438
  column += t.length;
330
439
  text += t;
331
- } else {
440
+ }
441
+ else if (t) {
332
442
  if (!t.start)
333
- t.start = {row: row, column: column};
443
+ t.start = { row: row, column: column };
334
444
  else
335
- t.end = {row: row, column: column};
445
+ t.end = { row: row, column: column };
336
446
  }
337
447
  });
338
448
  var range = editor.getSelectionRange();
339
449
  var end = editor.session.replace(range, text);
340
-
341
450
  var tabstopManager = new TabstopManager(editor);
342
451
  var selectionId = editor.inVirtualSelectionMode && editor.selection.index;
343
452
  tabstopManager.addTabstops(tabstops, range.start, end, selectionId);
344
453
  };
345
-
346
- this.insertSnippet = function(editor, snippetText) {
454
+ this.insertSnippet = function (editor, snippetText) {
347
455
  var self = this;
348
456
  if (editor.inVirtualSelectionMode)
349
457
  return self.insertSnippetForSelection(editor, snippetText);
350
-
351
- editor.forEachSelection(function() {
458
+ editor.forEachSelection(function () {
352
459
  self.insertSnippetForSelection(editor, snippetText);
353
- }, null, {keepOrder: true});
354
-
460
+ }, null, { keepOrder: true });
355
461
  if (editor.tabstopManager)
356
462
  editor.tabstopManager.tabNext();
357
463
  };
358
-
359
- this.$getScope = function(editor) {
464
+ this.$getScope = function (editor) {
360
465
  var scope = editor.session.$mode.$id || "";
361
466
  scope = scope.split("/").pop();
362
467
  if (scope === "html" || scope === "php") {
363
- if (scope === "php" && !editor.session.$mode.inlinePhp)
468
+ if (scope === "php" && !editor.session.$mode.inlinePhp)
364
469
  scope = "html";
365
470
  var c = editor.getCursorPosition();
366
471
  var state = editor.session.getState(c.row);
@@ -376,11 +481,9 @@ var SnippetManager = function() {
376
481
  scope = "php";
377
482
  }
378
483
  }
379
-
380
484
  return scope;
381
485
  };
382
-
383
- this.getActiveScopes = function(editor) {
486
+ this.getActiveScopes = function (editor) {
384
487
  var scope = this.$getScope(editor);
385
488
  var scopes = [scope];
386
489
  var snippetMap = this.snippetMap;
@@ -390,26 +493,23 @@ var SnippetManager = function() {
390
493
  scopes.push("_");
391
494
  return scopes;
392
495
  };
393
-
394
- this.expandWithTab = function(editor, options) {
496
+ this.expandWithTab = function (editor, options) {
395
497
  var self = this;
396
- var result = editor.forEachSelection(function() {
498
+ var result = editor.forEachSelection(function () {
397
499
  return self.expandSnippetForSelection(editor, options);
398
- }, null, {keepOrder: true});
500
+ }, null, { keepOrder: true });
399
501
  if (result && editor.tabstopManager)
400
502
  editor.tabstopManager.tabNext();
401
503
  return result;
402
504
  };
403
-
404
- this.expandSnippetForSelection = function(editor, options) {
505
+ this.expandSnippetForSelection = function (editor, options) {
405
506
  var cursor = editor.getCursorPosition();
406
507
  var line = editor.session.getLine(cursor.row);
407
508
  var before = line.substring(0, cursor.column);
408
509
  var after = line.substr(cursor.column);
409
-
410
510
  var snippetMap = this.snippetMap;
411
511
  var snippet;
412
- this.getActiveScopes(editor).some(function(scope) {
512
+ this.getActiveScopes(editor).some(function (scope) {
413
513
  var snippets = snippetMap[scope];
414
514
  if (snippets)
415
515
  snippet = this.findMatchingSnippet(snippets, before, after);
@@ -419,20 +519,14 @@ var SnippetManager = function() {
419
519
  return false;
420
520
  if (options && options.dryRun)
421
521
  return true;
422
- editor.session.doc.removeInLine(cursor.row,
423
- cursor.column - snippet.replaceBefore.length,
424
- cursor.column + snippet.replaceAfter.length
425
- );
426
-
522
+ editor.session.doc.removeInLine(cursor.row, cursor.column - snippet.replaceBefore.length, cursor.column + snippet.replaceAfter.length);
427
523
  this.variables.M__ = snippet.matchBefore;
428
524
  this.variables.T__ = snippet.matchAfter;
429
525
  this.insertSnippetForSelection(editor, snippet.content);
430
-
431
526
  this.variables.M__ = this.variables.T__ = null;
432
527
  return true;
433
528
  };
434
-
435
- this.findMatchingSnippet = function(snippetList, before, after) {
529
+ this.findMatchingSnippet = function (snippetList, before, after) {
436
530
  for (var i = snippetList.length; i--;) {
437
531
  var s = snippetList[i];
438
532
  if (s.startRe && !s.startRe.test(before))
@@ -441,7 +535,6 @@ var SnippetManager = function() {
441
535
  continue;
442
536
  if (!s.startRe && !s.endRe)
443
537
  continue;
444
-
445
538
  s.matchBefore = s.startRe ? s.startRe.exec(before) : [""];
446
539
  s.matchAfter = s.endRe ? s.endRe.exec(after) : [""];
447
540
  s.replaceBefore = s.triggerRe ? s.triggerRe.exec(before)[0] : "";
@@ -449,21 +542,17 @@ var SnippetManager = function() {
449
542
  return s;
450
543
  }
451
544
  };
452
-
453
545
  this.snippetMap = {};
454
546
  this.snippetNameMap = {};
455
- this.register = function(snippets, scope) {
547
+ this.register = function (snippets, scope) {
456
548
  var snippetMap = this.snippetMap;
457
549
  var snippetNameMap = this.snippetNameMap;
458
550
  var self = this;
459
-
460
- if (!snippets)
551
+ if (!snippets)
461
552
  snippets = [];
462
-
463
553
  function wrapRegexp(src) {
464
554
  if (src && !/^\^?\(.*\)\$?$|^\\b$/.test(src))
465
555
  src = "(?:" + src + ")";
466
-
467
556
  return src || "";
468
557
  }
469
558
  function guardedRegexp(re, guard, opening) {
@@ -473,14 +562,14 @@ var SnippetManager = function() {
473
562
  re = guard + re;
474
563
  if (re && re[re.length - 1] != "$")
475
564
  re = re + "$";
476
- } else {
565
+ }
566
+ else {
477
567
  re = re + guard;
478
568
  if (re && re[0] != "^")
479
569
  re = "^" + re;
480
570
  }
481
571
  return new RegExp(re);
482
572
  }
483
-
484
573
  function addSnippet(s) {
485
574
  if (!s.scope)
486
575
  s.scope = scope || "_";
@@ -489,7 +578,6 @@ var SnippetManager = function() {
489
578
  snippetMap[scope] = [];
490
579
  snippetNameMap[scope] = {};
491
580
  }
492
-
493
581
  var map = snippetNameMap[scope];
494
582
  if (s.name) {
495
583
  var old = map[s.name];
@@ -498,39 +586,40 @@ var SnippetManager = function() {
498
586
  map[s.name] = s;
499
587
  }
500
588
  snippetMap[scope].push(s);
501
-
589
+ if (s.prefix)
590
+ s.tabTrigger = s.prefix;
591
+ if (!s.content && s.body)
592
+ s.content = Array.isArray(s.body) ? s.body.join("\n") : s.body;
502
593
  if (s.tabTrigger && !s.trigger) {
503
594
  if (!s.guard && /^\w/.test(s.tabTrigger))
504
595
  s.guard = "\\b";
505
596
  s.trigger = lang.escapeRegExp(s.tabTrigger);
506
597
  }
507
-
508
598
  if (!s.trigger && !s.guard && !s.endTrigger && !s.endGuard)
509
599
  return;
510
-
511
600
  s.startRe = guardedRegexp(s.trigger, s.guard, true);
512
601
  s.triggerRe = new RegExp(s.trigger);
513
-
514
602
  s.endRe = guardedRegexp(s.endTrigger, s.endGuard, true);
515
603
  s.endTriggerRe = new RegExp(s.endTrigger);
516
604
  }
517
-
518
- if (snippets && snippets.content)
519
- addSnippet(snippets);
520
- else if (Array.isArray(snippets))
605
+ if (Array.isArray(snippets)) {
521
606
  snippets.forEach(addSnippet);
522
-
523
- this._signal("registerSnippets", {scope: scope});
607
+ }
608
+ else {
609
+ Object.keys(snippets).forEach(function (key) {
610
+ addSnippet(snippets[key]);
611
+ });
612
+ }
613
+ this._signal("registerSnippets", { scope: scope });
524
614
  };
525
- this.unregister = function(snippets, scope) {
615
+ this.unregister = function (snippets, scope) {
526
616
  var snippetMap = this.snippetMap;
527
617
  var snippetNameMap = this.snippetNameMap;
528
-
529
618
  function removeSnippet(s) {
530
- var nameMap = snippetNameMap[s.scope||scope];
619
+ var nameMap = snippetNameMap[s.scope || scope];
531
620
  if (nameMap && nameMap[s.name]) {
532
621
  delete nameMap[s.name];
533
- var map = snippetMap[s.scope||scope];
622
+ var map = snippetMap[s.scope || scope];
534
623
  var i = map && map.indexOf(s);
535
624
  if (i >= 0)
536
625
  map.splice(i, 1);
@@ -541,7 +630,7 @@ var SnippetManager = function() {
541
630
  else if (Array.isArray(snippets))
542
631
  snippets.forEach(removeSnippet);
543
632
  };
544
- this.parseSnippetFile = function(str) {
633
+ this.parseSnippetFile = function (str) {
545
634
  str = str.replace(/\r/g, "");
546
635
  var list = [], snippet = {};
547
636
  var re = /^#.*|^({[\s\S]*})\s*$|^(\S+) (.*)$|^((?:\n*\t.*)+)/gm;
@@ -551,12 +640,15 @@ var SnippetManager = function() {
551
640
  try {
552
641
  snippet = JSON.parse(m[1]);
553
642
  list.push(snippet);
554
- } catch (e) {}
555
- } if (m[4]) {
643
+ }
644
+ catch (e) { }
645
+ }
646
+ if (m[4]) {
556
647
  snippet.content = m[4].replace(/^\t/gm, "");
557
648
  list.push(snippet);
558
649
  snippet = {};
559
- } else {
650
+ }
651
+ else {
560
652
  var key = m[2], val = m[3];
561
653
  if (key == "regex") {
562
654
  var guardRe = /\/((?:[^\/\\]|\\.)*)|$/g;
@@ -564,21 +656,23 @@ var SnippetManager = function() {
564
656
  snippet.trigger = guardRe.exec(val)[1];
565
657
  snippet.endTrigger = guardRe.exec(val)[1];
566
658
  snippet.endGuard = guardRe.exec(val)[1];
567
- } else if (key == "snippet") {
659
+ }
660
+ else if (key == "snippet") {
568
661
  snippet.tabTrigger = val.match(/^\S*/)[0];
569
662
  if (!snippet.name)
570
663
  snippet.name = val;
571
- } else {
664
+ }
665
+ else if (key) {
572
666
  snippet[key] = val;
573
667
  }
574
668
  }
575
669
  }
576
670
  return list;
577
671
  };
578
- this.getSnippetByName = function(name, editor) {
672
+ this.getSnippetByName = function (name, editor) {
579
673
  var snippetMap = this.snippetNameMap;
580
674
  var snippet;
581
- this.getActiveScopes(editor).some(function(scope) {
675
+ this.getActiveScopes(editor).some(function (scope) {
582
676
  var snippets = snippetMap[scope];
583
677
  if (snippets)
584
678
  snippet = snippets[name];
@@ -586,11 +680,8 @@ var SnippetManager = function() {
586
680
  }, this);
587
681
  return snippet;
588
682
  };
589
-
590
683
  }).call(SnippetManager.prototype);
591
-
592
-
593
- var TabstopManager = function(editor) {
684
+ var TabstopManager = function (editor) {
594
685
  if (editor.tabstopManager)
595
686
  return editor.tabstopManager;
596
687
  editor.tabstopManager = this;
@@ -600,14 +691,13 @@ var TabstopManager = function(editor) {
600
691
  this.$onAfterExec = this.onAfterExec.bind(this);
601
692
  this.attach(editor);
602
693
  };
603
- (function() {
604
- this.attach = function(editor) {
694
+ (function () {
695
+ this.attach = function (editor) {
605
696
  this.index = 0;
606
697
  this.ranges = [];
607
698
  this.tabstops = [];
608
699
  this.$openTabstops = null;
609
700
  this.selectedTabstop = null;
610
-
611
701
  this.editor = editor;
612
702
  this.editor.on("change", this.$onChange);
613
703
  this.editor.on("changeSelection", this.$onChangeSelection);
@@ -615,7 +705,7 @@ var TabstopManager = function(editor) {
615
705
  this.editor.commands.on("afterExec", this.$onAfterExec);
616
706
  this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
617
707
  };
618
- this.detach = function() {
708
+ this.detach = function () {
619
709
  this.tabstops.forEach(this.removeTabstopMarkers, this);
620
710
  this.ranges = null;
621
711
  this.tabstops = null;
@@ -628,83 +718,59 @@ var TabstopManager = function(editor) {
628
718
  this.editor.tabstopManager = null;
629
719
  this.editor = null;
630
720
  };
631
-
632
- this.onChange = function(delta) {
633
- var changeRange = delta;
721
+ this.onChange = function (delta) {
634
722
  var isRemove = delta.action[0] == "r";
635
- var start = delta.start;
636
- var end = delta.end;
637
- var startRow = start.row;
638
- var endRow = end.row;
639
- var lineDif = endRow - startRow;
640
- var colDiff = end.column - start.column;
641
-
642
- if (isRemove) {
643
- lineDif = -lineDif;
644
- colDiff = -colDiff;
645
- }
646
- if (!this.$inChange && isRemove) {
647
- var ts = this.selectedTabstop;
648
- var changedOutside = ts && !ts.some(function(r) {
649
- return comparePoints(r.start, start) <= 0 && comparePoints(r.end, end) >= 0;
650
- });
651
- if (changedOutside)
652
- return this.detach();
653
- }
654
- var ranges = this.ranges;
655
- for (var i = 0; i < ranges.length; i++) {
656
- var r = ranges[i];
657
- if (r.end.row < start.row)
658
- continue;
659
-
660
- if (isRemove && comparePoints(start, r.start) < 0 && comparePoints(end, r.end) > 0) {
661
- this.removeRange(r);
662
- i--;
663
- continue;
723
+ var selectedTabstop = this.selectedTabstop || {};
724
+ var parents = selectedTabstop.parents || {};
725
+ var tabstops = (this.tabstops || []).slice();
726
+ for (var i = 0; i < tabstops.length; i++) {
727
+ var ts = tabstops[i];
728
+ var active = ts == selectedTabstop || parents[ts.index];
729
+ ts.rangeList.$bias = active ? 0 : 1;
730
+ if (delta.action == "remove" && ts !== selectedTabstop) {
731
+ var parentActive = ts.parents && ts.parents[selectedTabstop.index];
732
+ var startIndex = ts.rangeList.pointIndex(delta.start, parentActive);
733
+ startIndex = startIndex < 0 ? -startIndex - 1 : startIndex + 1;
734
+ var endIndex = ts.rangeList.pointIndex(delta.end, parentActive);
735
+ endIndex = endIndex < 0 ? -endIndex - 1 : endIndex - 1;
736
+ var toRemove = ts.rangeList.ranges.slice(startIndex, endIndex);
737
+ for (var j = 0; j < toRemove.length; j++)
738
+ this.removeRange(toRemove[j]);
664
739
  }
665
-
666
- if (r.start.row == startRow && r.start.column > start.column)
667
- r.start.column += colDiff;
668
- if (r.end.row == startRow && r.end.column >= start.column)
669
- r.end.column += colDiff;
670
- if (r.start.row >= startRow)
671
- r.start.row += lineDif;
672
- if (r.end.row >= startRow)
673
- r.end.row += lineDif;
674
-
675
- if (comparePoints(r.start, r.end) > 0)
676
- this.removeRange(r);
740
+ ts.rangeList.$onChange(delta);
677
741
  }
678
- if (!ranges.length)
742
+ var session = this.editor.session;
743
+ if (!this.$inChange && isRemove && session.getLength() == 1 && !session.getValue())
679
744
  this.detach();
680
745
  };
681
- this.updateLinkedFields = function() {
746
+ this.updateLinkedFields = function () {
682
747
  var ts = this.selectedTabstop;
683
- if (!ts || !ts.hasLinkedRanges)
748
+ if (!ts || !ts.hasLinkedRanges || !ts.firstNonLinked)
684
749
  return;
685
750
  this.$inChange = true;
686
751
  var session = this.editor.session;
687
752
  var text = session.getTextRange(ts.firstNonLinked);
688
- for (var i = ts.length; i--;) {
753
+ for (var i = 0; i < ts.length; i++) {
689
754
  var range = ts[i];
690
755
  if (!range.linked)
691
756
  continue;
692
- var fmt = exports.snippetManager.tmStrFormat(text, range.original);
757
+ var original = range.original;
758
+ var fmt = exports.snippetManager.tmStrFormat(text, original, this.editor);
693
759
  session.replace(range, fmt);
694
760
  }
695
761
  this.$inChange = false;
696
762
  };
697
- this.onAfterExec = function(e) {
763
+ this.onAfterExec = function (e) {
698
764
  if (e.command && !e.command.readOnly)
699
765
  this.updateLinkedFields();
700
766
  };
701
- this.onChangeSelection = function() {
767
+ this.onChangeSelection = function () {
702
768
  if (!this.editor)
703
769
  return;
704
770
  var lead = this.editor.selection.lead;
705
771
  var anchor = this.editor.selection.anchor;
706
772
  var isEmpty = this.editor.selection.isEmpty();
707
- for (var i = this.ranges.length; i--;) {
773
+ for (var i = 0; i < this.ranges.length; i++) {
708
774
  if (this.ranges[i].linked)
709
775
  continue;
710
776
  var containsLead = this.ranges[i].contains(lead.row, lead.column);
@@ -714,10 +780,10 @@ var TabstopManager = function(editor) {
714
780
  }
715
781
  this.detach();
716
782
  };
717
- this.onChangeSession = function() {
783
+ this.onChangeSession = function () {
718
784
  this.detach();
719
785
  };
720
- this.tabNext = function(dir) {
786
+ this.tabNext = function (dir) {
721
787
  var max = this.tabstops.length;
722
788
  var index = this.index + (dir || 1);
723
789
  index = Math.min(Math.max(index, 1), max);
@@ -727,7 +793,7 @@ var TabstopManager = function(editor) {
727
793
  if (index === 0)
728
794
  this.detach();
729
795
  };
730
- this.selectTabstop = function(index) {
796
+ this.selectTabstop = function (index) {
731
797
  this.$openTabstops = null;
732
798
  var ts = this.tabstops[this.index];
733
799
  if (ts)
@@ -736,25 +802,28 @@ var TabstopManager = function(editor) {
736
802
  ts = this.tabstops[this.index];
737
803
  if (!ts || !ts.length)
738
804
  return;
739
-
740
805
  this.selectedTabstop = ts;
741
- if (!this.editor.inVirtualSelectionMode) {
806
+ var range = ts.firstNonLinked || ts;
807
+ if (ts.choices)
808
+ range.cursor = range.start;
809
+ if (!this.editor.inVirtualSelectionMode) {
742
810
  var sel = this.editor.multiSelect;
743
- sel.toSingleRange(ts.firstNonLinked.clone());
744
- for (var i = ts.length; i--;) {
811
+ sel.toSingleRange(range);
812
+ for (var i = 0; i < ts.length; i++) {
745
813
  if (ts.hasLinkedRanges && ts[i].linked)
746
814
  continue;
747
815
  sel.addRange(ts[i].clone(), true);
748
816
  }
749
- if (sel.ranges[0])
750
- sel.addRange(sel.ranges[0].clone());
751
- } else {
752
- this.editor.selection.setRange(ts.firstNonLinked);
753
817
  }
754
-
818
+ else {
819
+ this.editor.selection.fromOrientedRange(range);
820
+ }
755
821
  this.editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
822
+ if (this.selectedTabstop && this.selectedTabstop.choices)
823
+ this.editor.execCommand("startAutocomplete", { matches: this.selectedTabstop.choices });
756
824
  };
757
- this.addTabstops = function(tabstops, start, end) {
825
+ this.addTabstops = function (tabstops, start, end) {
826
+ var useLink = this.useLink || !this.editor.getOption("enableMultiselect");
758
827
  if (!this.$openTabstops)
759
828
  this.$openTabstops = [];
760
829
  if (!tabstops[0]) {
@@ -764,14 +833,12 @@ var TabstopManager = function(editor) {
764
833
  tabstops[0] = [p];
765
834
  tabstops[0].index = 0;
766
835
  }
767
-
768
836
  var i = this.index;
769
837
  var arg = [i + 1, 0];
770
838
  var ranges = this.ranges;
771
- tabstops.forEach(function(ts, index) {
839
+ tabstops.forEach(function (ts, index) {
772
840
  var dest = this.$openTabstops[index] || ts;
773
-
774
- for (var i = ts.length; i--;) {
841
+ for (var i = 0; i < ts.length; i++) {
775
842
  var p = ts[i];
776
843
  var range = Range.fromPoints(p.start, p.end || p.start);
777
844
  movePoint(range.start, start);
@@ -783,10 +850,11 @@ var TabstopManager = function(editor) {
783
850
  dest.unshift(range);
784
851
  else
785
852
  dest[i] = range;
786
- if (p.fmtString) {
853
+ if (p.fmtString || (dest.firstNonLinked && useLink)) {
787
854
  range.linked = true;
788
855
  dest.hasLinkedRanges = true;
789
- } else if (!dest.firstNonLinked)
856
+ }
857
+ else if (!dest.firstNonLinked)
790
858
  dest.firstNonLinked = range;
791
859
  }
792
860
  if (!dest.firstNonLinked)
@@ -796,34 +864,40 @@ var TabstopManager = function(editor) {
796
864
  this.$openTabstops[index] = dest;
797
865
  }
798
866
  this.addTabstopMarkers(dest);
867
+ dest.rangeList = dest.rangeList || new RangeList();
868
+ dest.rangeList.$bias = 0;
869
+ dest.rangeList.addList(dest);
799
870
  }, this);
800
-
801
871
  if (arg.length > 2) {
802
872
  if (this.tabstops.length)
803
873
  arg.push(arg.splice(2, 1)[0]);
804
874
  this.tabstops.splice.apply(this.tabstops, arg);
805
875
  }
806
876
  };
807
-
808
- this.addTabstopMarkers = function(ts) {
877
+ this.addTabstopMarkers = function (ts) {
809
878
  var session = this.editor.session;
810
- ts.forEach(function(range) {
811
- if (!range.markerId)
879
+ ts.forEach(function (range) {
880
+ if (!range.markerId)
812
881
  range.markerId = session.addMarker(range, "ace_snippet-marker", "text");
813
882
  });
814
883
  };
815
- this.removeTabstopMarkers = function(ts) {
884
+ this.removeTabstopMarkers = function (ts) {
816
885
  var session = this.editor.session;
817
- ts.forEach(function(range) {
886
+ ts.forEach(function (range) {
818
887
  session.removeMarker(range.markerId);
819
888
  range.markerId = null;
820
889
  });
821
890
  };
822
- this.removeRange = function(range) {
891
+ this.removeRange = function (range) {
823
892
  var i = range.tabstop.indexOf(range);
824
- range.tabstop.splice(i, 1);
893
+ if (i != -1)
894
+ range.tabstop.splice(i, 1);
825
895
  i = this.ranges.indexOf(range);
826
- this.ranges.splice(i, 1);
896
+ if (i != -1)
897
+ this.ranges.splice(i, 1);
898
+ i = range.tabstop.rangeList.ranges.indexOf(range);
899
+ if (i != -1)
900
+ range.tabstop.splice(i, 1);
827
901
  this.editor.session.removeMarker(range.markerId);
828
902
  if (!range.tabstop.length) {
829
903
  i = this.tabstops.indexOf(range.tabstop);
@@ -833,156 +907,112 @@ var TabstopManager = function(editor) {
833
907
  this.detach();
834
908
  }
835
909
  };
836
-
837
910
  this.keyboardHandler = new HashHandler();
838
911
  this.keyboardHandler.bindKeys({
839
- "Tab": function(ed) {
840
- if (exports.snippetManager && exports.snippetManager.expandWithTab(ed)) {
912
+ "Tab": function (editor) {
913
+ if (exports.snippetManager && exports.snippetManager.expandWithTab(editor))
841
914
  return;
842
- }
843
-
844
- ed.tabstopManager.tabNext(1);
915
+ editor.tabstopManager.tabNext(1);
916
+ editor.renderer.scrollCursorIntoView();
845
917
  },
846
- "Shift-Tab": function(ed) {
847
- ed.tabstopManager.tabNext(-1);
918
+ "Shift-Tab": function (editor) {
919
+ editor.tabstopManager.tabNext(-1);
920
+ editor.renderer.scrollCursorIntoView();
848
921
  },
849
- "Esc": function(ed) {
850
- ed.tabstopManager.detach();
851
- },
852
- "Return": function(ed) {
853
- return false;
922
+ "Esc": function (editor) {
923
+ editor.tabstopManager.detach();
854
924
  }
855
925
  });
856
926
  }).call(TabstopManager.prototype);
857
-
858
-
859
-
860
- var changeTracker = {};
861
- changeTracker.onChange = Anchor.prototype.onChange;
862
- changeTracker.setPosition = function(row, column) {
863
- this.pos.row = row;
864
- this.pos.column = column;
865
- };
866
- changeTracker.update = function(pos, delta, $insertRight) {
867
- this.$insertRight = $insertRight;
868
- this.pos = pos;
869
- this.onChange(delta);
870
- };
871
-
872
- var movePoint = function(point, diff) {
927
+ var movePoint = function (point, diff) {
873
928
  if (point.row == 0)
874
929
  point.column += diff.column;
875
930
  point.row += diff.row;
876
931
  };
877
-
878
- var moveRelative = function(point, start) {
932
+ var moveRelative = function (point, start) {
879
933
  if (point.row == start.row)
880
934
  point.column -= start.column;
881
935
  point.row -= start.row;
882
936
  };
883
-
884
-
885
- require("./lib/dom").importCssString("\
886
- .ace_snippet-marker {\
887
- -moz-box-sizing: border-box;\
888
- box-sizing: border-box;\
889
- background: rgba(194, 193, 208, 0.09);\
890
- border: 1px dotted rgba(211, 208, 235, 0.62);\
891
- position: absolute;\
892
- }");
893
-
937
+ dom.importCssString("\n.ace_snippet-marker {\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n background: rgba(194, 193, 208, 0.09);\n border: 1px dotted rgba(211, 208, 235, 0.62);\n position: absolute;\n}", "snippets.css", false);
894
938
  exports.snippetManager = new SnippetManager();
895
-
896
-
897
939
  var Editor = require("./editor").Editor;
898
- (function() {
899
- this.insertSnippet = function(content, options) {
940
+ (function () {
941
+ this.insertSnippet = function (content, options) {
900
942
  return exports.snippetManager.insertSnippet(this, content, options);
901
943
  };
902
- this.expandSnippet = function(options) {
944
+ this.expandSnippet = function (options) {
903
945
  return exports.snippetManager.expandWithTab(this, options);
904
946
  };
905
947
  }).call(Editor.prototype);
906
948
 
907
949
  });
908
950
 
909
- define("ace/autocomplete/popup",["require","exports","module","ace/virtual_renderer","ace/editor","ace/range","ace/lib/event","ace/lib/lang","ace/lib/dom"], function(require, exports, module) {
910
- "use strict";
911
-
951
+ define("ace/autocomplete/popup",["require","exports","module","ace/virtual_renderer","ace/editor","ace/range","ace/lib/event","ace/lib/lang","ace/lib/dom"], function(require, exports, module){"use strict";
912
952
  var Renderer = require("../virtual_renderer").VirtualRenderer;
913
953
  var Editor = require("../editor").Editor;
914
954
  var Range = require("../range").Range;
915
955
  var event = require("../lib/event");
916
956
  var lang = require("../lib/lang");
917
957
  var dom = require("../lib/dom");
918
-
919
- var $singleLineEditor = function(el) {
958
+ var getAriaId = function (index) {
959
+ return "suggest-aria-id:".concat(index);
960
+ };
961
+ var $singleLineEditor = function (el) {
920
962
  var renderer = new Renderer(el);
921
-
922
963
  renderer.$maxLines = 4;
923
-
924
964
  var editor = new Editor(renderer);
925
-
926
965
  editor.setHighlightActiveLine(false);
927
966
  editor.setShowPrintMargin(false);
928
967
  editor.renderer.setShowGutter(false);
929
968
  editor.renderer.setHighlightGutterLine(false);
930
-
931
969
  editor.$mouseHandler.$focusTimeout = 0;
932
970
  editor.$highlightTagPending = true;
933
-
934
971
  return editor;
935
972
  };
936
-
937
- var AcePopup = function(parentNode) {
973
+ var AcePopup = function (parentNode) {
938
974
  var el = dom.createElement("div");
939
975
  var popup = new $singleLineEditor(el);
940
-
941
976
  if (parentNode)
942
977
  parentNode.appendChild(el);
943
978
  el.style.display = "none";
944
979
  popup.renderer.content.style.cursor = "default";
945
980
  popup.renderer.setStyle("ace_autocomplete");
946
-
981
+ popup.renderer.container.setAttribute("role", "listbox");
982
+ popup.renderer.container.setAttribute("aria-label", "Autocomplete suggestions");
947
983
  popup.setOption("displayIndentGuides", false);
948
984
  popup.setOption("dragDelay", 150);
949
-
950
- var noop = function(){};
951
-
985
+ var noop = function () { };
952
986
  popup.focus = noop;
953
987
  popup.$isFocused = true;
954
-
955
988
  popup.renderer.$cursorLayer.restartTimer = noop;
956
989
  popup.renderer.$cursorLayer.element.style.opacity = 0;
957
-
958
990
  popup.renderer.$maxLines = 8;
959
991
  popup.renderer.$keepTextAreaAtCursor = false;
960
-
961
992
  popup.setHighlightActiveLine(false);
962
993
  popup.session.highlight("");
963
994
  popup.session.$searchHighlight.clazz = "ace_highlight-marker";
964
-
965
- popup.on("mousedown", function(e) {
995
+ popup.on("mousedown", function (e) {
966
996
  var pos = e.getDocumentPosition();
967
997
  popup.selection.moveToPosition(pos);
968
998
  selectionMarker.start.row = selectionMarker.end.row = pos.row;
969
999
  e.stop();
970
1000
  });
971
-
972
1001
  var lastMouseEvent;
973
- var hoverMarker = new Range(-1,0,-1,Infinity);
974
- var selectionMarker = new Range(-1,0,-1,Infinity);
1002
+ var hoverMarker = new Range(-1, 0, -1, Infinity);
1003
+ var selectionMarker = new Range(-1, 0, -1, Infinity);
975
1004
  selectionMarker.id = popup.session.addMarker(selectionMarker, "ace_active-line", "fullLine");
976
- popup.setSelectOnHover = function(val) {
1005
+ popup.setSelectOnHover = function (val) {
977
1006
  if (!val) {
978
1007
  hoverMarker.id = popup.session.addMarker(hoverMarker, "ace_line-hover", "fullLine");
979
- } else if (hoverMarker.id) {
1008
+ }
1009
+ else if (hoverMarker.id) {
980
1010
  popup.session.removeMarker(hoverMarker.id);
981
1011
  hoverMarker.id = null;
982
1012
  }
983
1013
  };
984
1014
  popup.setSelectOnHover(false);
985
- popup.on("mousemove", function(e) {
1015
+ popup.on("mousemove", function (e) {
986
1016
  if (!lastMouseEvent) {
987
1017
  lastMouseEvent = e;
988
1018
  return;
@@ -999,7 +1029,7 @@ var AcePopup = function(parentNode) {
999
1029
  setHoverMarker(row);
1000
1030
  }
1001
1031
  });
1002
- popup.renderer.on("beforeRender", function() {
1032
+ popup.renderer.on("beforeRender", function () {
1003
1033
  if (lastMouseEvent && hoverMarker.start.row != -1) {
1004
1034
  lastMouseEvent.$pos = null;
1005
1035
  var row = lastMouseEvent.getDocumentPosition().row;
@@ -1008,18 +1038,28 @@ var AcePopup = function(parentNode) {
1008
1038
  setHoverMarker(row, true);
1009
1039
  }
1010
1040
  });
1011
- popup.renderer.on("afterRender", function() {
1041
+ popup.renderer.on("afterRender", function () {
1012
1042
  var row = popup.getRow();
1013
1043
  var t = popup.renderer.$textLayer;
1014
1044
  var selected = t.element.childNodes[row - t.config.firstRow];
1015
- if (selected !== t.selectedNode && t.selectedNode)
1045
+ var el = document.activeElement; // Active element is textarea of main editor
1046
+ if (selected !== t.selectedNode && t.selectedNode) {
1016
1047
  dom.removeCssClass(t.selectedNode, "ace_selected");
1048
+ el.removeAttribute("aria-activedescendant");
1049
+ t.selectedNode.removeAttribute("id");
1050
+ }
1017
1051
  t.selectedNode = selected;
1018
- if (selected)
1052
+ if (selected) {
1019
1053
  dom.addCssClass(selected, "ace_selected");
1054
+ var ariaId = getAriaId(row);
1055
+ selected.id = ariaId;
1056
+ popup.renderer.container.setAttribute("aria-activedescendant", ariaId);
1057
+ el.setAttribute("aria-activedescendant", ariaId);
1058
+ selected.setAttribute("aria-label", popup.getData(row).value);
1059
+ }
1020
1060
  });
1021
- var hideHoverMarker = function() { setHoverMarker(-1); };
1022
- var setHoverMarker = function(row, suppressRedraw) {
1061
+ var hideHoverMarker = function () { setHoverMarker(-1); };
1062
+ var setHoverMarker = function (row, suppressRedraw) {
1023
1063
  if (row !== hoverMarker.start.row) {
1024
1064
  hoverMarker.start.row = hoverMarker.end.row = row;
1025
1065
  if (!suppressRedraw)
@@ -1027,41 +1067,36 @@ var AcePopup = function(parentNode) {
1027
1067
  popup._emit("changeHoverMarker");
1028
1068
  }
1029
1069
  };
1030
- popup.getHoveredRow = function() {
1070
+ popup.getHoveredRow = function () {
1031
1071
  return hoverMarker.start.row;
1032
1072
  };
1033
-
1034
1073
  event.addListener(popup.container, "mouseout", hideHoverMarker);
1035
1074
  popup.on("hide", hideHoverMarker);
1036
1075
  popup.on("changeSelection", hideHoverMarker);
1037
-
1038
- popup.session.doc.getLength = function() {
1076
+ popup.session.doc.getLength = function () {
1039
1077
  return popup.data.length;
1040
1078
  };
1041
- popup.session.doc.getLine = function(i) {
1079
+ popup.session.doc.getLine = function (i) {
1042
1080
  var data = popup.data[i];
1043
1081
  if (typeof data == "string")
1044
1082
  return data;
1045
1083
  return (data && data.value) || "";
1046
1084
  };
1047
-
1048
1085
  var bgTokenizer = popup.session.bgTokenizer;
1049
- bgTokenizer.$tokenizeRow = function(row) {
1086
+ bgTokenizer.$tokenizeRow = function (row) {
1050
1087
  var data = popup.data[row];
1051
1088
  var tokens = [];
1052
1089
  if (!data)
1053
1090
  return tokens;
1054
1091
  if (typeof data == "string")
1055
- data = {value: data};
1092
+ data = { value: data };
1056
1093
  var caption = data.caption || data.value || data.name;
1057
-
1058
1094
  function addToken(value, className) {
1059
1095
  value && tokens.push({
1060
- type: (data.className || "") + (className || ""),
1096
+ type: (data.className || "") + (className || ""),
1061
1097
  value: value
1062
1098
  });
1063
1099
  }
1064
-
1065
1100
  var lower = caption.toLowerCase();
1066
1101
  var filterText = (popup.filterText || "").toLowerCase();
1067
1102
  var lastIndex = 0;
@@ -1071,45 +1106,43 @@ var AcePopup = function(parentNode) {
1071
1106
  var sub = filterText.slice(lastI, i);
1072
1107
  lastI = i;
1073
1108
  var index = lower.indexOf(sub, lastIndex);
1074
- if (index == -1) continue;
1109
+ if (index == -1)
1110
+ continue;
1075
1111
  addToken(caption.slice(lastIndex, index), "");
1076
1112
  lastIndex = index + sub.length;
1077
1113
  addToken(caption.slice(index, lastIndex), "completion-highlight");
1078
1114
  }
1079
1115
  }
1080
1116
  addToken(caption.slice(lastIndex, caption.length), "");
1081
-
1082
1117
  if (data.meta)
1083
- tokens.push({type: "completion-meta", value: data.meta});
1084
-
1118
+ tokens.push({ type: "completion-meta", value: data.meta });
1119
+ if (data.message)
1120
+ tokens.push({ type: "completion-message", value: data.message });
1085
1121
  return tokens;
1086
1122
  };
1087
1123
  bgTokenizer.$updateOnChange = noop;
1088
1124
  bgTokenizer.start = noop;
1089
-
1090
- popup.session.$computeWidth = function() {
1125
+ popup.session.$computeWidth = function () {
1091
1126
  return this.screenWidth = 0;
1092
1127
  };
1093
1128
  popup.isOpen = false;
1094
1129
  popup.isTopdown = false;
1095
1130
  popup.autoSelect = true;
1096
1131
  popup.filterText = "";
1097
-
1098
1132
  popup.data = [];
1099
- popup.setData = function(list, filterText) {
1133
+ popup.setData = function (list, filterText) {
1100
1134
  popup.filterText = filterText || "";
1101
1135
  popup.setValue(lang.stringRepeat("\n", list.length), -1);
1102
1136
  popup.data = list || [];
1103
1137
  popup.setRow(0);
1104
1138
  };
1105
- popup.getData = function(row) {
1139
+ popup.getData = function (row) {
1106
1140
  return popup.data[row];
1107
1141
  };
1108
-
1109
- popup.getRow = function() {
1142
+ popup.getRow = function () {
1110
1143
  return selectionMarker.start.row;
1111
1144
  };
1112
- popup.setRow = function(line) {
1145
+ popup.setRow = function (line) {
1113
1146
  line = Math.max(this.autoSelect ? 0 : -1, Math.min(this.data.length, line));
1114
1147
  if (selectionMarker.start.row != line) {
1115
1148
  popup.selection.clearSelection();
@@ -1120,19 +1153,17 @@ var AcePopup = function(parentNode) {
1120
1153
  popup._signal("select");
1121
1154
  }
1122
1155
  };
1123
-
1124
- popup.on("changeSelection", function() {
1156
+ popup.on("changeSelection", function () {
1125
1157
  if (popup.isOpen)
1126
1158
  popup.setRow(popup.selection.lead.row);
1127
1159
  popup.renderer.scrollCursorIntoView();
1128
1160
  });
1129
-
1130
- popup.hide = function() {
1161
+ popup.hide = function () {
1131
1162
  this.container.style.display = "none";
1132
1163
  this._signal("hide");
1133
1164
  popup.isOpen = false;
1134
1165
  };
1135
- popup.show = function(pos, lineHeight, topdownOnly) {
1166
+ popup.show = function (pos, lineHeight, topdownOnly) {
1136
1167
  var el = this.container;
1137
1168
  var screenHeight = window.innerHeight;
1138
1169
  var screenWidth = window.innerWidth;
@@ -1145,111 +1176,75 @@ var AcePopup = function(parentNode) {
1145
1176
  el.style.top = "";
1146
1177
  el.style.bottom = screenHeight - top + "px";
1147
1178
  popup.isTopdown = false;
1148
- } else {
1179
+ }
1180
+ else {
1149
1181
  top += lineHeight;
1150
1182
  renderer.$maxPixelHeight = screenHeight - top - 0.2 * lineHeight;
1151
1183
  el.style.top = top + "px";
1152
1184
  el.style.bottom = "";
1153
1185
  popup.isTopdown = true;
1154
1186
  }
1155
-
1156
1187
  el.style.display = "";
1157
-
1158
1188
  var left = pos.left;
1159
1189
  if (left + el.offsetWidth > screenWidth)
1160
1190
  left = screenWidth - el.offsetWidth;
1161
-
1162
1191
  el.style.left = left + "px";
1163
-
1164
1192
  this._signal("show");
1165
1193
  lastMouseEvent = null;
1166
1194
  popup.isOpen = true;
1167
1195
  };
1168
-
1169
- popup.getTextLeftOffset = function() {
1196
+ popup.goTo = function (where) {
1197
+ var row = this.getRow();
1198
+ var max = this.session.getLength() - 1;
1199
+ switch (where) {
1200
+ case "up":
1201
+ row = row <= 0 ? max : row - 1;
1202
+ break;
1203
+ case "down":
1204
+ row = row >= max ? -1 : row + 1;
1205
+ break;
1206
+ case "start":
1207
+ row = 0;
1208
+ break;
1209
+ case "end":
1210
+ row = max;
1211
+ break;
1212
+ }
1213
+ this.setRow(row);
1214
+ };
1215
+ popup.getTextLeftOffset = function () {
1170
1216
  return this.$borderSize + this.renderer.$padding + this.$imageSize;
1171
1217
  };
1172
-
1173
1218
  popup.$imageSize = 0;
1174
1219
  popup.$borderSize = 1;
1175
-
1176
1220
  return popup;
1177
1221
  };
1178
-
1179
- dom.importCssString("\
1180
- .ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\
1181
- background-color: #CAD6FA;\
1182
- z-index: 1;\
1183
- }\
1184
- .ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\
1185
- background-color: #3a674e;\
1186
- }\
1187
- .ace_editor.ace_autocomplete .ace_line-hover {\
1188
- border: 1px solid #abbffe;\
1189
- margin-top: -1px;\
1190
- background: rgba(233,233,253,0.4);\
1191
- position: absolute;\
1192
- z-index: 2;\
1193
- }\
1194
- .ace_dark.ace_editor.ace_autocomplete .ace_line-hover {\
1195
- border: 1px solid rgba(109, 150, 13, 0.8);\
1196
- background: rgba(58, 103, 78, 0.62);\
1197
- }\
1198
- .ace_completion-meta {\
1199
- opacity: 0.5;\
1200
- margin: 0.9em;\
1201
- }\
1202
- .ace_editor.ace_autocomplete .ace_completion-highlight{\
1203
- color: #2d69c7;\
1204
- }\
1205
- .ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight{\
1206
- color: #93ca12;\
1207
- }\
1208
- .ace_editor.ace_autocomplete {\
1209
- width: 300px;\
1210
- z-index: 200000;\
1211
- border: 1px lightgray solid;\
1212
- position: fixed;\
1213
- box-shadow: 2px 3px 5px rgba(0,0,0,.2);\
1214
- line-height: 1.4;\
1215
- background: #fefefe;\
1216
- color: #111;\
1217
- }\
1218
- .ace_dark.ace_editor.ace_autocomplete {\
1219
- border: 1px #484747 solid;\
1220
- box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);\
1221
- line-height: 1.4;\
1222
- background: #25282c;\
1223
- color: #c1c1c1;\
1224
- }", "autocompletion.css");
1225
-
1222
+ dom.importCssString("\n.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\n background-color: #CAD6FA;\n z-index: 1;\n}\n.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {\n background-color: #3a674e;\n}\n.ace_editor.ace_autocomplete .ace_line-hover {\n border: 1px solid #abbffe;\n margin-top: -1px;\n background: rgba(233,233,253,0.4);\n position: absolute;\n z-index: 2;\n}\n.ace_dark.ace_editor.ace_autocomplete .ace_line-hover {\n border: 1px solid rgba(109, 150, 13, 0.8);\n background: rgba(58, 103, 78, 0.62);\n}\n.ace_completion-meta {\n opacity: 0.5;\n margin: 0.9em;\n}\n.ace_completion-message {\n color: blue;\n}\n.ace_editor.ace_autocomplete .ace_completion-highlight{\n color: #2d69c7;\n}\n.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight{\n color: #93ca12;\n}\n.ace_editor.ace_autocomplete {\n width: 300px;\n z-index: 200000;\n border: 1px lightgray solid;\n position: fixed;\n box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n line-height: 1.4;\n background: #fefefe;\n color: #111;\n}\n.ace_dark.ace_editor.ace_autocomplete {\n border: 1px #484747 solid;\n box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.51);\n line-height: 1.4;\n background: #25282c;\n color: #c1c1c1;\n}", "autocompletion.css", false);
1226
1223
  exports.AcePopup = AcePopup;
1224
+ exports.$singleLineEditor = $singleLineEditor;
1225
+ exports.getAriaId = getAriaId;
1227
1226
 
1228
1227
  });
1229
1228
 
1230
- define("ace/autocomplete/util",["require","exports","module"], function(require, exports, module) {
1231
- "use strict";
1232
-
1233
- exports.parForEach = function(array, fn, callback) {
1229
+ define("ace/autocomplete/util",["require","exports","module"], function(require, exports, module){"use strict";
1230
+ exports.parForEach = function (array, fn, callback) {
1234
1231
  var completed = 0;
1235
1232
  var arLength = array.length;
1236
1233
  if (arLength === 0)
1237
1234
  callback();
1238
1235
  for (var i = 0; i < arLength; i++) {
1239
- fn(array[i], function(result, err) {
1236
+ fn(array[i], function (result, err) {
1240
1237
  completed++;
1241
1238
  if (completed === arLength)
1242
1239
  callback(result, err);
1243
1240
  });
1244
1241
  }
1245
1242
  };
1246
-
1247
- var ID_REGEX = /[a-zA-Z_0-9\$\-\u00A2-\uFFFF]/;
1248
-
1249
- exports.retrievePrecedingIdentifier = function(text, pos, regex) {
1243
+ var ID_REGEX = /[a-zA-Z_0-9\$\-\u00A2-\u2000\u2070-\uFFFF]/;
1244
+ exports.retrievePrecedingIdentifier = function (text, pos, regex) {
1250
1245
  regex = regex || ID_REGEX;
1251
1246
  var buf = [];
1252
- for (var i = pos-1; i >= 0; i--) {
1247
+ for (var i = pos - 1; i >= 0; i--) {
1253
1248
  if (regex.test(text[i]))
1254
1249
  buf.push(text[i]);
1255
1250
  else
@@ -1257,8 +1252,7 @@ exports.retrievePrecedingIdentifier = function(text, pos, regex) {
1257
1252
  }
1258
1253
  return buf.reverse().join("");
1259
1254
  };
1260
-
1261
- exports.retrieveFollowingIdentifier = function(text, pos, regex) {
1255
+ exports.retrieveFollowingIdentifier = function (text, pos, regex) {
1262
1256
  regex = regex || ID_REGEX;
1263
1257
  var buf = [];
1264
1258
  for (var i = pos; i < text.length; i++) {
@@ -1269,14 +1263,13 @@ exports.retrieveFollowingIdentifier = function(text, pos, regex) {
1269
1263
  }
1270
1264
  return buf;
1271
1265
  };
1272
-
1273
1266
  exports.getCompletionPrefix = function (editor) {
1274
1267
  var pos = editor.getCursorPosition();
1275
1268
  var line = editor.session.getLine(pos.row);
1276
1269
  var prefix;
1277
- editor.completers.forEach(function(completer) {
1270
+ editor.completers.forEach(function (completer) {
1278
1271
  if (completer.identifierRegexps) {
1279
- completer.identifierRegexps.forEach(function(identifierRegex) {
1272
+ completer.identifierRegexps.forEach(function (identifierRegex) {
1280
1273
  if (!prefix && identifierRegex)
1281
1274
  prefix = this.retrievePrecedingIdentifier(line, pos.column, identifierRegex);
1282
1275
  }.bind(this));
@@ -1287,42 +1280,35 @@ exports.getCompletionPrefix = function (editor) {
1287
1280
 
1288
1281
  });
1289
1282
 
1290
- define("ace/autocomplete",["require","exports","module","ace/keyboard/hash_handler","ace/autocomplete/popup","ace/autocomplete/util","ace/lib/event","ace/lib/lang","ace/lib/dom","ace/snippets"], function(require, exports, module) {
1291
- "use strict";
1292
-
1283
+ define("ace/autocomplete",["require","exports","module","ace/keyboard/hash_handler","ace/autocomplete/popup","ace/autocomplete/popup","ace/autocomplete/util","ace/lib/lang","ace/lib/dom","ace/snippets","ace/config"], function(require, exports, module){"use strict";
1293
1284
  var HashHandler = require("./keyboard/hash_handler").HashHandler;
1294
1285
  var AcePopup = require("./autocomplete/popup").AcePopup;
1286
+ var getAriaId = require("./autocomplete/popup").getAriaId;
1295
1287
  var util = require("./autocomplete/util");
1296
- var event = require("./lib/event");
1297
1288
  var lang = require("./lib/lang");
1298
1289
  var dom = require("./lib/dom");
1299
1290
  var snippetManager = require("./snippets").snippetManager;
1300
-
1301
- var Autocomplete = function() {
1291
+ var config = require("./config");
1292
+ var Autocomplete = function () {
1302
1293
  this.autoInsert = false;
1303
1294
  this.autoSelect = true;
1304
1295
  this.exactMatch = false;
1305
1296
  this.gatherCompletionsId = 0;
1306
1297
  this.keyboardHandler = new HashHandler();
1307
1298
  this.keyboardHandler.bindKeys(this.commands);
1308
-
1309
1299
  this.blurListener = this.blurListener.bind(this);
1310
1300
  this.changeListener = this.changeListener.bind(this);
1311
1301
  this.mousedownListener = this.mousedownListener.bind(this);
1312
1302
  this.mousewheelListener = this.mousewheelListener.bind(this);
1313
-
1314
- this.changeTimer = lang.delayedCall(function() {
1303
+ this.changeTimer = lang.delayedCall(function () {
1315
1304
  this.updateCompletions(true);
1316
1305
  }.bind(this));
1317
-
1318
1306
  this.tooltipTimer = lang.delayedCall(this.updateDocTooltip.bind(this), 50);
1319
1307
  };
1320
-
1321
- (function() {
1322
-
1323
- this.$init = function() {
1308
+ (function () {
1309
+ this.$init = function () {
1324
1310
  this.popup = new AcePopup(document.body || document.documentElement);
1325
- this.popup.on("click", function(e) {
1311
+ this.popup.on("click", function (e) {
1326
1312
  this.insertMatch();
1327
1313
  e.stop();
1328
1314
  }.bind(this));
@@ -1332,44 +1318,36 @@ var Autocomplete = function() {
1332
1318
  this.popup.on("changeHoverMarker", this.tooltipTimer.bind(null, null));
1333
1319
  return this.popup;
1334
1320
  };
1335
-
1336
- this.getPopup = function() {
1321
+ this.getPopup = function () {
1337
1322
  return this.popup || this.$init();
1338
1323
  };
1339
-
1340
- this.openPopup = function(editor, prefix, keepPopupPosition) {
1324
+ this.openPopup = function (editor, prefix, keepPopupPosition) {
1341
1325
  if (!this.popup)
1342
1326
  this.$init();
1343
-
1344
1327
  this.popup.autoSelect = this.autoSelect;
1345
-
1346
1328
  this.popup.setData(this.completions.filtered, this.completions.filterText);
1347
-
1329
+ this.editor.textInput.setAriaOptions({ activeDescendant: getAriaId(this.popup.getRow()) });
1348
1330
  editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
1349
-
1350
1331
  var renderer = editor.renderer;
1351
1332
  this.popup.setRow(this.autoSelect ? 0 : -1);
1352
1333
  if (!keepPopupPosition) {
1353
1334
  this.popup.setTheme(editor.getTheme());
1354
1335
  this.popup.setFontSize(editor.getFontSize());
1355
-
1356
1336
  var lineHeight = renderer.layerConfig.lineHeight;
1357
-
1358
1337
  var pos = renderer.$cursorLayer.getPixelPosition(this.base, true);
1359
1338
  pos.left -= this.popup.getTextLeftOffset();
1360
-
1361
1339
  var rect = editor.container.getBoundingClientRect();
1362
1340
  pos.top += rect.top - renderer.layerConfig.offset;
1363
1341
  pos.left += rect.left - editor.renderer.scrollLeft;
1364
1342
  pos.left += renderer.gutterWidth;
1365
-
1366
1343
  this.popup.show(pos, lineHeight);
1367
- } else if (keepPopupPosition && !prefix) {
1344
+ }
1345
+ else if (keepPopupPosition && !prefix) {
1368
1346
  this.detach();
1369
1347
  }
1348
+ this.changeTimer.cancel();
1370
1349
  };
1371
-
1372
- this.detach = function() {
1350
+ this.detach = function () {
1373
1351
  this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
1374
1352
  this.editor.off("changeSelection", this.changeListener);
1375
1353
  this.editor.off("blur", this.blurListener);
@@ -1377,18 +1355,15 @@ var Autocomplete = function() {
1377
1355
  this.editor.off("mousewheel", this.mousewheelListener);
1378
1356
  this.changeTimer.cancel();
1379
1357
  this.hideDocTooltip();
1380
-
1381
1358
  this.gatherCompletionsId += 1;
1382
1359
  if (this.popup && this.popup.isOpen)
1383
1360
  this.popup.hide();
1384
-
1385
1361
  if (this.base)
1386
1362
  this.base.detach();
1387
1363
  this.activated = false;
1388
1364
  this.completions = this.base = null;
1389
1365
  };
1390
-
1391
- this.changeListener = function(e) {
1366
+ this.changeListener = function (e) {
1392
1367
  var cursor = this.editor.selection.lead;
1393
1368
  if (cursor.row != this.base.row || cursor.column < this.base.column) {
1394
1369
  this.detach();
@@ -1398,54 +1373,42 @@ var Autocomplete = function() {
1398
1373
  else
1399
1374
  this.detach();
1400
1375
  };
1401
-
1402
- this.blurListener = function(e) {
1376
+ this.blurListener = function (e) {
1403
1377
  var el = document.activeElement;
1404
1378
  var text = this.editor.textInput.getElement();
1405
1379
  var fromTooltip = e.relatedTarget && this.tooltipNode && this.tooltipNode.contains(e.relatedTarget);
1406
1380
  var container = this.popup && this.popup.container;
1407
1381
  if (el != text && el.parentNode != container && !fromTooltip
1408
- && el != this.tooltipNode && e.relatedTarget != text
1409
- ) {
1382
+ && el != this.tooltipNode && e.relatedTarget != text) {
1410
1383
  this.detach();
1411
1384
  }
1412
1385
  };
1413
-
1414
- this.mousedownListener = function(e) {
1386
+ this.mousedownListener = function (e) {
1415
1387
  this.detach();
1416
1388
  };
1417
-
1418
- this.mousewheelListener = function(e) {
1389
+ this.mousewheelListener = function (e) {
1419
1390
  this.detach();
1420
1391
  };
1421
-
1422
- this.goTo = function(where) {
1423
- var row = this.popup.getRow();
1424
- var max = this.popup.session.getLength() - 1;
1425
-
1426
- switch(where) {
1427
- case "up": row = row <= 0 ? max : row - 1; break;
1428
- case "down": row = row >= max ? -1 : row + 1; break;
1429
- case "start": row = 0; break;
1430
- case "end": row = max; break;
1431
- }
1432
-
1433
- this.popup.setRow(row);
1392
+ this.goTo = function (where) {
1393
+ this.popup.goTo(where);
1434
1394
  };
1435
-
1436
- this.insertMatch = function(data, options) {
1395
+ this.insertMatch = function (data, options) {
1437
1396
  if (!data)
1438
1397
  data = this.popup.getData(this.popup.getRow());
1439
1398
  if (!data)
1440
1399
  return false;
1441
-
1400
+ var completions = this.completions;
1401
+ this.editor.startOperation({ command: { name: "insertMatch" } });
1442
1402
  if (data.completer && data.completer.insertMatch) {
1443
1403
  data.completer.insertMatch(this.editor, data);
1444
- } else {
1445
- if (this.completions.filterText) {
1404
+ }
1405
+ else {
1406
+ if (!completions)
1407
+ return false;
1408
+ if (completions.filterText) {
1446
1409
  var ranges = this.editor.selection.getAllRanges();
1447
1410
  for (var i = 0, range; range = ranges[i]; i++) {
1448
- range.start.column -= this.completions.filterText.length;
1411
+ range.start.column -= completions.filterText.length;
1449
1412
  this.editor.session.remove(range);
1450
1413
  }
1451
1414
  }
@@ -1454,44 +1417,38 @@ var Autocomplete = function() {
1454
1417
  else
1455
1418
  this.editor.execCommand("insertstring", data.value || data);
1456
1419
  }
1457
- this.detach();
1420
+ if (this.completions == completions)
1421
+ this.detach();
1422
+ this.editor.endOperation();
1458
1423
  };
1459
-
1460
-
1461
1424
  this.commands = {
1462
- "Up": function(editor) { editor.completer.goTo("up"); },
1463
- "Down": function(editor) { editor.completer.goTo("down"); },
1464
- "Ctrl-Up|Ctrl-Home": function(editor) { editor.completer.goTo("start"); },
1465
- "Ctrl-Down|Ctrl-End": function(editor) { editor.completer.goTo("end"); },
1466
-
1467
- "Esc": function(editor) { editor.completer.detach(); },
1468
- "Return": function(editor) { return editor.completer.insertMatch(); },
1469
- "Shift-Return": function(editor) { editor.completer.insertMatch(null, {deleteSuffix: true}); },
1470
- "Tab": function(editor) {
1425
+ "Up": function (editor) { editor.completer.goTo("up"); },
1426
+ "Down": function (editor) { editor.completer.goTo("down"); },
1427
+ "Ctrl-Up|Ctrl-Home": function (editor) { editor.completer.goTo("start"); },
1428
+ "Ctrl-Down|Ctrl-End": function (editor) { editor.completer.goTo("end"); },
1429
+ "Esc": function (editor) { editor.completer.detach(); },
1430
+ "Return": function (editor) { return editor.completer.insertMatch(); },
1431
+ "Shift-Return": function (editor) { editor.completer.insertMatch(null, { deleteSuffix: true }); },
1432
+ "Tab": function (editor) {
1471
1433
  var result = editor.completer.insertMatch();
1472
1434
  if (!result && !editor.tabstopManager)
1473
1435
  editor.completer.goTo("down");
1474
1436
  else
1475
1437
  return result;
1476
1438
  },
1477
-
1478
- "PageUp": function(editor) { editor.completer.popup.gotoPageUp(); },
1479
- "PageDown": function(editor) { editor.completer.popup.gotoPageDown(); }
1439
+ "PageUp": function (editor) { editor.completer.popup.gotoPageUp(); },
1440
+ "PageDown": function (editor) { editor.completer.popup.gotoPageDown(); }
1480
1441
  };
1481
-
1482
- this.gatherCompletions = function(editor, callback) {
1442
+ this.gatherCompletions = function (editor, callback) {
1483
1443
  var session = editor.getSession();
1484
1444
  var pos = editor.getCursorPosition();
1485
-
1486
1445
  var prefix = util.getCompletionPrefix(editor);
1487
-
1488
1446
  this.base = session.doc.createAnchor(pos.row, pos.column - prefix.length);
1489
1447
  this.base.$insertRight = true;
1490
-
1491
1448
  var matches = [];
1492
1449
  var total = editor.completers.length;
1493
- editor.completers.forEach(function(completer, i) {
1494
- completer.getCompletions(editor, session, pos, prefix, function(err, results) {
1450
+ editor.completers.forEach(function (completer, i) {
1451
+ completer.getCompletions(editor, session, pos, prefix, function (err, results) {
1495
1452
  if (!err && results)
1496
1453
  matches = matches.concat(results);
1497
1454
  callback(null, {
@@ -1503,104 +1460,113 @@ var Autocomplete = function() {
1503
1460
  });
1504
1461
  return true;
1505
1462
  };
1506
-
1507
- this.showPopup = function(editor) {
1463
+ this.showPopup = function (editor, options) {
1508
1464
  if (this.editor)
1509
1465
  this.detach();
1510
-
1511
1466
  this.activated = true;
1512
-
1513
1467
  this.editor = editor;
1514
1468
  if (editor.completer != this) {
1515
1469
  if (editor.completer)
1516
1470
  editor.completer.detach();
1517
1471
  editor.completer = this;
1518
1472
  }
1519
-
1520
1473
  editor.on("changeSelection", this.changeListener);
1521
1474
  editor.on("blur", this.blurListener);
1522
1475
  editor.on("mousedown", this.mousedownListener);
1523
1476
  editor.on("mousewheel", this.mousewheelListener);
1524
-
1525
- this.updateCompletions();
1477
+ this.updateCompletions(false, options);
1526
1478
  };
1527
-
1528
- this.updateCompletions = function(keepPopupPosition) {
1479
+ this.updateCompletions = function (keepPopupPosition, options) {
1529
1480
  if (keepPopupPosition && this.base && this.completions) {
1530
1481
  var pos = this.editor.getCursorPosition();
1531
- var prefix = this.editor.session.getTextRange({start: this.base, end: pos});
1482
+ var prefix = this.editor.session.getTextRange({ start: this.base, end: pos });
1532
1483
  if (prefix == this.completions.filterText)
1533
1484
  return;
1534
1485
  this.completions.setFilter(prefix);
1535
1486
  if (!this.completions.filtered.length)
1536
1487
  return this.detach();
1537
1488
  if (this.completions.filtered.length == 1
1538
- && this.completions.filtered[0].value == prefix
1539
- && !this.completions.filtered[0].snippet)
1489
+ && this.completions.filtered[0].value == prefix
1490
+ && !this.completions.filtered[0].snippet)
1540
1491
  return this.detach();
1541
1492
  this.openPopup(this.editor, prefix, keepPopupPosition);
1542
1493
  return;
1543
1494
  }
1495
+ if (options && options.matches) {
1496
+ var pos = this.editor.getSelectionRange().start;
1497
+ this.base = this.editor.session.doc.createAnchor(pos.row, pos.column);
1498
+ this.base.$insertRight = true;
1499
+ this.completions = new FilteredList(options.matches);
1500
+ return this.openPopup(this.editor, "", keepPopupPosition);
1501
+ }
1544
1502
  var _id = this.gatherCompletionsId;
1545
- this.gatherCompletions(this.editor, function(err, results) {
1546
- var detachIfFinished = function() {
1547
- if (!results.finished) return;
1548
- return this.detach();
1549
- }.bind(this);
1550
-
1551
- var prefix = results.prefix;
1552
- var matches = results && results.matches;
1553
-
1554
- if (!matches || !matches.length)
1555
- return detachIfFinished();
1556
- if (prefix.indexOf(results.prefix) !== 0 || _id != this.gatherCompletionsId)
1503
+ var detachIfFinished = function (results) {
1504
+ if (!results.finished)
1557
1505
  return;
1558
-
1506
+ return this.detach();
1507
+ }.bind(this);
1508
+ var processResults = function (results) {
1509
+ var prefix = results.prefix;
1510
+ var matches = results.matches;
1559
1511
  this.completions = new FilteredList(matches);
1560
-
1561
1512
  if (this.exactMatch)
1562
1513
  this.completions.exactMatch = true;
1563
-
1564
1514
  this.completions.setFilter(prefix);
1565
1515
  var filtered = this.completions.filtered;
1566
1516
  if (!filtered.length)
1567
- return detachIfFinished();
1517
+ return detachIfFinished(results);
1568
1518
  if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet)
1569
- return detachIfFinished();
1519
+ return detachIfFinished(results);
1570
1520
  if (this.autoInsert && filtered.length == 1 && results.finished)
1571
1521
  return this.insertMatch(filtered[0]);
1572
-
1573
1522
  this.openPopup(this.editor, prefix, keepPopupPosition);
1523
+ }.bind(this);
1524
+ var isImmediate = true;
1525
+ var immediateResults = null;
1526
+ this.gatherCompletions(this.editor, function (err, results) {
1527
+ var prefix = results.prefix;
1528
+ var matches = results && results.matches;
1529
+ if (!matches || !matches.length)
1530
+ return detachIfFinished(results);
1531
+ if (prefix.indexOf(results.prefix) !== 0 || _id != this.gatherCompletionsId)
1532
+ return;
1533
+ if (isImmediate) {
1534
+ immediateResults = results;
1535
+ return;
1536
+ }
1537
+ processResults(results);
1574
1538
  }.bind(this));
1539
+ isImmediate = false;
1540
+ if (immediateResults) {
1541
+ var results = immediateResults;
1542
+ immediateResults = null;
1543
+ processResults(results);
1544
+ }
1575
1545
  };
1576
-
1577
- this.cancelContextMenu = function() {
1546
+ this.cancelContextMenu = function () {
1578
1547
  this.editor.$mouseHandler.cancelContextMenu();
1579
1548
  };
1580
-
1581
- this.updateDocTooltip = function() {
1549
+ this.updateDocTooltip = function () {
1582
1550
  var popup = this.popup;
1583
1551
  var all = popup.data;
1584
1552
  var selected = all && (all[popup.getHoveredRow()] || all[popup.getRow()]);
1585
1553
  var doc = null;
1586
1554
  if (!selected || !this.editor || !this.popup.isOpen)
1587
1555
  return this.hideDocTooltip();
1588
- this.editor.completers.some(function(completer) {
1556
+ this.editor.completers.some(function (completer) {
1589
1557
  if (completer.getDocTooltip)
1590
1558
  doc = completer.getDocTooltip(selected);
1591
1559
  return doc;
1592
1560
  });
1593
- if (!doc)
1561
+ if (!doc && typeof selected != "string")
1594
1562
  doc = selected;
1595
-
1596
1563
  if (typeof doc == "string")
1597
- doc = {docText: doc};
1564
+ doc = { docText: doc };
1598
1565
  if (!doc || !(doc.docHTML || doc.docText))
1599
1566
  return this.hideDocTooltip();
1600
1567
  this.showDocTooltip(doc);
1601
1568
  };
1602
-
1603
- this.showDocTooltip = function(item) {
1569
+ this.showDocTooltip = function (item) {
1604
1570
  if (!this.tooltipNode) {
1605
1571
  this.tooltipNode = dom.createElement("div");
1606
1572
  this.tooltipNode.className = "ace_tooltip ace_doc-tooltip";
@@ -1610,48 +1576,49 @@ var Autocomplete = function() {
1610
1576
  this.tooltipNode.onblur = this.blurListener.bind(this);
1611
1577
  this.tooltipNode.onclick = this.onTooltipClick.bind(this);
1612
1578
  }
1613
-
1614
1579
  var tooltipNode = this.tooltipNode;
1615
1580
  if (item.docHTML) {
1616
1581
  tooltipNode.innerHTML = item.docHTML;
1617
- } else if (item.docText) {
1582
+ }
1583
+ else if (item.docText) {
1618
1584
  tooltipNode.textContent = item.docText;
1619
1585
  }
1620
-
1621
1586
  if (!tooltipNode.parentNode)
1622
1587
  document.body.appendChild(tooltipNode);
1623
1588
  var popup = this.popup;
1624
1589
  var rect = popup.container.getBoundingClientRect();
1625
1590
  tooltipNode.style.top = popup.container.style.top;
1626
1591
  tooltipNode.style.bottom = popup.container.style.bottom;
1627
-
1628
1592
  tooltipNode.style.display = "block";
1629
1593
  if (window.innerWidth - rect.right < 320) {
1630
1594
  if (rect.left < 320) {
1631
- if(popup.isTopdown) {
1595
+ if (popup.isTopdown) {
1632
1596
  tooltipNode.style.top = rect.bottom + "px";
1633
1597
  tooltipNode.style.left = rect.left + "px";
1634
1598
  tooltipNode.style.right = "";
1635
1599
  tooltipNode.style.bottom = "";
1636
- } else {
1600
+ }
1601
+ else {
1637
1602
  tooltipNode.style.top = popup.container.offsetTop - tooltipNode.offsetHeight + "px";
1638
1603
  tooltipNode.style.left = rect.left + "px";
1639
1604
  tooltipNode.style.right = "";
1640
1605
  tooltipNode.style.bottom = "";
1641
1606
  }
1642
- } else {
1607
+ }
1608
+ else {
1643
1609
  tooltipNode.style.right = window.innerWidth - rect.left + "px";
1644
1610
  tooltipNode.style.left = "";
1645
1611
  }
1646
- } else {
1612
+ }
1613
+ else {
1647
1614
  tooltipNode.style.left = (rect.right + 1) + "px";
1648
1615
  tooltipNode.style.right = "";
1649
1616
  }
1650
1617
  };
1651
-
1652
- this.hideDocTooltip = function() {
1618
+ this.hideDocTooltip = function () {
1653
1619
  this.tooltipTimer.cancel();
1654
- if (!this.tooltipNode) return;
1620
+ if (!this.tooltipNode)
1621
+ return;
1655
1622
  var el = this.tooltipNode;
1656
1623
  if (!this.editor.isFocused() && document.activeElement == el)
1657
1624
  this.editor.focus();
@@ -1659,8 +1626,7 @@ var Autocomplete = function() {
1659
1626
  if (el.parentNode)
1660
1627
  el.parentNode.removeChild(el);
1661
1628
  };
1662
-
1663
- this.onTooltipClick = function(e) {
1629
+ this.onTooltipClick = function (e) {
1664
1630
  var a = e.target;
1665
1631
  while (a && a != this.tooltipNode) {
1666
1632
  if (a.nodeName == "A" && a.href) {
@@ -1671,71 +1637,97 @@ var Autocomplete = function() {
1671
1637
  a = a.parentNode;
1672
1638
  }
1673
1639
  };
1674
-
1640
+ this.destroy = function () {
1641
+ this.detach();
1642
+ if (this.popup) {
1643
+ this.popup.destroy();
1644
+ var el = this.popup.container;
1645
+ if (el && el.parentNode)
1646
+ el.parentNode.removeChild(el);
1647
+ }
1648
+ if (this.editor && this.editor.completer == this)
1649
+ this.editor.completer == null;
1650
+ this.popup = null;
1651
+ };
1675
1652
  }).call(Autocomplete.prototype);
1676
-
1653
+ Autocomplete.for = function (editor) {
1654
+ if (editor.completer) {
1655
+ return editor.completer;
1656
+ }
1657
+ if (config.get("sharedPopups")) {
1658
+ if (!Autocomplete.$shared)
1659
+ Autocomplete.$sharedInstance = new Autocomplete();
1660
+ editor.completer = Autocomplete.$sharedInstance;
1661
+ }
1662
+ else {
1663
+ editor.completer = new Autocomplete();
1664
+ editor.once("destroy", function (e, editor) {
1665
+ editor.completer.destroy();
1666
+ });
1667
+ }
1668
+ return editor.completer;
1669
+ };
1677
1670
  Autocomplete.startCommand = {
1678
1671
  name: "startAutocomplete",
1679
- exec: function(editor) {
1680
- if (!editor.completer)
1681
- editor.completer = new Autocomplete();
1682
- editor.completer.autoInsert = false;
1683
- editor.completer.autoSelect = true;
1684
- editor.completer.showPopup(editor);
1685
- editor.completer.cancelContextMenu();
1672
+ exec: function (editor, options) {
1673
+ var completer = Autocomplete.for(editor);
1674
+ completer.autoInsert = false;
1675
+ completer.autoSelect = true;
1676
+ completer.showPopup(editor, options);
1677
+ completer.cancelContextMenu();
1686
1678
  },
1687
1679
  bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
1688
1680
  };
1689
-
1690
- var FilteredList = function(array, filterText) {
1681
+ var FilteredList = function (array, filterText) {
1691
1682
  this.all = array;
1692
1683
  this.filtered = array;
1693
1684
  this.filterText = filterText || "";
1694
1685
  this.exactMatch = false;
1695
1686
  };
1696
- (function(){
1697
- this.setFilter = function(str) {
1687
+ (function () {
1688
+ this.setFilter = function (str) {
1698
1689
  if (str.length > this.filterText && str.lastIndexOf(this.filterText, 0) === 0)
1699
1690
  var matches = this.filtered;
1700
1691
  else
1701
1692
  var matches = this.all;
1702
-
1703
1693
  this.filterText = str;
1704
1694
  matches = this.filterCompletions(matches, this.filterText);
1705
- matches = matches.sort(function(a, b) {
1706
- return b.exactMatch - a.exactMatch || b.$score - a.$score
1707
- || (a.caption || a.value) < (b.caption || b.value);
1695
+ matches = matches.sort(function (a, b) {
1696
+ return b.exactMatch - a.exactMatch || b.$score - a.$score
1697
+ || (a.caption || a.value).localeCompare(b.caption || b.value);
1708
1698
  });
1709
1699
  var prev = null;
1710
- matches = matches.filter(function(item){
1700
+ matches = matches.filter(function (item) {
1711
1701
  var caption = item.snippet || item.caption || item.value;
1712
- if (caption === prev) return false;
1702
+ if (caption === prev)
1703
+ return false;
1713
1704
  prev = caption;
1714
1705
  return true;
1715
1706
  });
1716
-
1717
1707
  this.filtered = matches;
1718
1708
  };
1719
- this.filterCompletions = function(items, needle) {
1709
+ this.filterCompletions = function (items, needle) {
1720
1710
  var results = [];
1721
1711
  var upper = needle.toUpperCase();
1722
1712
  var lower = needle.toLowerCase();
1723
1713
  loop: for (var i = 0, item; item = items[i]; i++) {
1724
1714
  var caption = item.caption || item.value || item.snippet;
1725
- if (!caption) continue;
1715
+ if (!caption)
1716
+ continue;
1726
1717
  var lastIndex = -1;
1727
1718
  var matchMask = 0;
1728
1719
  var penalty = 0;
1729
1720
  var index, distance;
1730
-
1731
1721
  if (this.exactMatch) {
1732
1722
  if (needle !== caption.substr(0, needle.length))
1733
1723
  continue loop;
1734
- } else {
1724
+ }
1725
+ else {
1735
1726
  var fullMatchIndex = caption.toLowerCase().indexOf(lower);
1736
1727
  if (fullMatchIndex > -1) {
1737
1728
  penalty = fullMatchIndex;
1738
- } else {
1729
+ }
1730
+ else {
1739
1731
  for (var j = 0; j < needle.length; j++) {
1740
1732
  var i1 = caption.indexOf(lower[j], lastIndex + 1);
1741
1733
  var i2 = caption.indexOf(upper[j], lastIndex + 1);
@@ -1761,68 +1753,60 @@ var FilteredList = function(array, filterText) {
1761
1753
  return results;
1762
1754
  };
1763
1755
  }).call(FilteredList.prototype);
1764
-
1765
1756
  exports.Autocomplete = Autocomplete;
1766
1757
  exports.FilteredList = FilteredList;
1767
1758
 
1768
1759
  });
1769
1760
 
1770
- define("ace/autocomplete/text_completer",["require","exports","module","ace/range"], function(require, exports, module) {
1771
- var Range = require("../range").Range;
1772
-
1773
- var splitRegex = /[^a-zA-Z_0-9\$\-\u00C0-\u1FFF\u2C00-\uD7FF\w]+/;
1774
-
1775
- function getWordIndex(doc, pos) {
1776
- var textBefore = doc.getTextRange(Range.fromPoints({row: 0, column:0}, pos));
1777
- return textBefore.split(splitRegex).length - 1;
1778
- }
1779
- function wordDistance(doc, pos) {
1780
- var prefixPos = getWordIndex(doc, pos);
1781
- var words = doc.getValue().split(splitRegex);
1782
- var wordScores = Object.create(null);
1783
-
1784
- var currentWord = words[prefixPos];
1785
-
1786
- words.forEach(function(word, idx) {
1787
- if (!word || word === currentWord) return;
1788
-
1789
- var distance = Math.abs(prefixPos - idx);
1790
- var score = words.length - distance;
1791
- if (wordScores[word]) {
1792
- wordScores[word] = Math.max(score, wordScores[word]);
1793
- } else {
1794
- wordScores[word] = score;
1795
- }
1796
- });
1797
- return wordScores;
1798
- }
1761
+ define("ace/autocomplete/text_completer",["require","exports","module","ace/range"], function(require, exports, module){var Range = require("../range").Range;
1762
+ var splitRegex = /[^a-zA-Z_0-9\$\-\u00C0-\u1FFF\u2C00-\uD7FF\w]+/;
1763
+ function getWordIndex(doc, pos) {
1764
+ var textBefore = doc.getTextRange(Range.fromPoints({ row: 0, column: 0 }, pos));
1765
+ return textBefore.split(splitRegex).length - 1;
1766
+ }
1767
+ function wordDistance(doc, pos) {
1768
+ var prefixPos = getWordIndex(doc, pos);
1769
+ var words = doc.getValue().split(splitRegex);
1770
+ var wordScores = Object.create(null);
1771
+ var currentWord = words[prefixPos];
1772
+ words.forEach(function (word, idx) {
1773
+ if (!word || word === currentWord)
1774
+ return;
1775
+ var distance = Math.abs(prefixPos - idx);
1776
+ var score = words.length - distance;
1777
+ if (wordScores[word]) {
1778
+ wordScores[word] = Math.max(score, wordScores[word]);
1779
+ }
1780
+ else {
1781
+ wordScores[word] = score;
1782
+ }
1783
+ });
1784
+ return wordScores;
1785
+ }
1786
+ exports.getCompletions = function (editor, session, pos, prefix, callback) {
1787
+ var wordScore = wordDistance(session, pos);
1788
+ var wordList = Object.keys(wordScore);
1789
+ callback(null, wordList.map(function (word) {
1790
+ return {
1791
+ caption: word,
1792
+ value: word,
1793
+ score: wordScore[word],
1794
+ meta: "local"
1795
+ };
1796
+ }));
1797
+ };
1799
1798
 
1800
- exports.getCompletions = function(editor, session, pos, prefix, callback) {
1801
- var wordScore = wordDistance(session, pos);
1802
- var wordList = Object.keys(wordScore);
1803
- callback(null, wordList.map(function(word) {
1804
- return {
1805
- caption: word,
1806
- value: word,
1807
- score: wordScore[word],
1808
- meta: "local"
1809
- };
1810
- }));
1811
- };
1812
1799
  });
1813
1800
 
1814
- define("ace/ext/language_tools",["require","exports","module","ace/snippets","ace/autocomplete","ace/config","ace/lib/lang","ace/autocomplete/util","ace/autocomplete/text_completer","ace/editor","ace/config"], function(require, exports, module) {
1815
- "use strict";
1816
-
1801
+ define("ace/ext/language_tools",["require","exports","module","ace/snippets","ace/autocomplete","ace/config","ace/lib/lang","ace/autocomplete/util","ace/autocomplete/text_completer","ace/editor","ace/config"], function(require, exports, module){"use strict";
1817
1802
  var snippetManager = require("../snippets").snippetManager;
1818
1803
  var Autocomplete = require("../autocomplete").Autocomplete;
1819
1804
  var config = require("../config");
1820
1805
  var lang = require("../lib/lang");
1821
1806
  var util = require("../autocomplete/util");
1822
-
1823
1807
  var textCompleter = require("../autocomplete/text_completer");
1824
1808
  var keyWordCompleter = {
1825
- getCompletions: function(editor, session, pos, prefix, callback) {
1809
+ getCompletions: function (editor, session, pos, prefix, callback) {
1826
1810
  if (session.$mode.completer) {
1827
1811
  return session.$mode.completer.getCompletions(editor, session, pos, prefix, callback);
1828
1812
  }
@@ -1831,19 +1815,25 @@ var keyWordCompleter = {
1831
1815
  callback(null, completions);
1832
1816
  }
1833
1817
  };
1834
-
1818
+ var transformSnippetTooltip = function (str) {
1819
+ var record = {};
1820
+ return str.replace(/\${(\d+)(:(.*?))?}/g, function (_, p1, p2, p3) {
1821
+ return (record[p1] = p3 || '');
1822
+ }).replace(/\$(\d+?)/g, function (_, p1) {
1823
+ return record[p1];
1824
+ });
1825
+ };
1835
1826
  var snippetCompleter = {
1836
- getCompletions: function(editor, session, pos, prefix, callback) {
1827
+ getCompletions: function (editor, session, pos, prefix, callback) {
1837
1828
  var scopes = [];
1838
1829
  var token = session.getTokenAt(pos.row, pos.column);
1839
1830
  if (token && token.type.match(/(tag-name|tag-open|tag-whitespace|attribute-name|attribute-value)\.xml$/))
1840
1831
  scopes.push('html-tag');
1841
1832
  else
1842
1833
  scopes = snippetManager.getActiveScopes(editor);
1843
-
1844
1834
  var snippetMap = snippetManager.snippetMap;
1845
1835
  var completions = [];
1846
- scopes.forEach(function(scope) {
1836
+ scopes.forEach(function (scope) {
1847
1837
  var snippets = snippetMap[scope] || [];
1848
1838
  for (var i = snippets.length; i--;) {
1849
1839
  var s = snippets[i];
@@ -1860,71 +1850,68 @@ var snippetCompleter = {
1860
1850
  }, this);
1861
1851
  callback(null, completions);
1862
1852
  },
1863
- getDocTooltip: function(item) {
1853
+ getDocTooltip: function (item) {
1864
1854
  if (item.type == "snippet" && !item.docHTML) {
1865
1855
  item.docHTML = [
1866
1856
  "<b>", lang.escapeHTML(item.caption), "</b>", "<hr></hr>",
1867
- lang.escapeHTML(item.snippet)
1857
+ lang.escapeHTML(transformSnippetTooltip(item.snippet))
1868
1858
  ].join("");
1869
1859
  }
1870
1860
  }
1871
1861
  };
1872
-
1873
1862
  var completers = [snippetCompleter, textCompleter, keyWordCompleter];
1874
- exports.setCompleters = function(val) {
1863
+ exports.setCompleters = function (val) {
1875
1864
  completers.length = 0;
1876
- if (val) completers.push.apply(completers, val);
1865
+ if (val)
1866
+ completers.push.apply(completers, val);
1877
1867
  };
1878
- exports.addCompleter = function(completer) {
1868
+ exports.addCompleter = function (completer) {
1879
1869
  completers.push(completer);
1880
1870
  };
1881
1871
  exports.textCompleter = textCompleter;
1882
1872
  exports.keyWordCompleter = keyWordCompleter;
1883
1873
  exports.snippetCompleter = snippetCompleter;
1884
-
1885
1874
  var expandSnippet = {
1886
1875
  name: "expandSnippet",
1887
- exec: function(editor) {
1876
+ exec: function (editor) {
1888
1877
  return snippetManager.expandWithTab(editor);
1889
1878
  },
1890
1879
  bindKey: "Tab"
1891
1880
  };
1892
-
1893
- var onChangeMode = function(e, editor) {
1881
+ var onChangeMode = function (e, editor) {
1894
1882
  loadSnippetsForMode(editor.session.$mode);
1895
1883
  };
1896
-
1897
- var loadSnippetsForMode = function(mode) {
1898
- var id = mode.$id;
1884
+ var loadSnippetsForMode = function (mode) {
1885
+ if (typeof mode == "string")
1886
+ mode = config.$modes[mode];
1887
+ if (!mode)
1888
+ return;
1899
1889
  if (!snippetManager.files)
1900
1890
  snippetManager.files = {};
1901
- loadSnippetFile(id);
1891
+ loadSnippetFile(mode.$id, mode.snippetFileId);
1902
1892
  if (mode.modes)
1903
1893
  mode.modes.forEach(loadSnippetsForMode);
1904
1894
  };
1905
-
1906
- var loadSnippetFile = function(id) {
1907
- if (!id || snippetManager.files[id])
1895
+ var loadSnippetFile = function (id, snippetFilePath) {
1896
+ if (!snippetFilePath || !id || snippetManager.files[id])
1908
1897
  return;
1909
- var snippetFilePath = id.replace("mode", "snippets");
1910
1898
  snippetManager.files[id] = {};
1911
- config.loadModule(snippetFilePath, function(m) {
1912
- if (m) {
1913
- snippetManager.files[id] = m;
1914
- if (!m.snippets && m.snippetText)
1915
- m.snippets = snippetManager.parseSnippetFile(m.snippetText);
1916
- snippetManager.register(m.snippets || [], m.scope);
1917
- if (m.includeScopes) {
1918
- snippetManager.snippetMap[m.scope].includeScopes = m.includeScopes;
1919
- m.includeScopes.forEach(function(x) {
1920
- loadSnippetFile("ace/mode/" + x);
1921
- });
1922
- }
1899
+ config.loadModule(snippetFilePath, function (m) {
1900
+ if (!m)
1901
+ return;
1902
+ snippetManager.files[id] = m;
1903
+ if (!m.snippets && m.snippetText)
1904
+ m.snippets = snippetManager.parseSnippetFile(m.snippetText);
1905
+ snippetManager.register(m.snippets || [], m.scope);
1906
+ if (m.includeScopes) {
1907
+ snippetManager.snippetMap[m.scope].includeScopes = m.includeScopes;
1908
+ m.includeScopes.forEach(function (x) {
1909
+ loadSnippetsForMode("ace/mode/" + x);
1910
+ });
1923
1911
  }
1924
1912
  });
1925
1913
  };
1926
-
1927
- var doLiveAutocomplete = function(e) {
1914
+ var doLiveAutocomplete = function (e) {
1928
1915
  var editor = e.editor;
1929
1916
  var hasCompleter = editor.completer && editor.completer.activated;
1930
1917
  if (e.command.name === "backspace") {
@@ -1934,48 +1921,48 @@ var doLiveAutocomplete = function(e) {
1934
1921
  else if (e.command.name === "insertstring") {
1935
1922
  var prefix = util.getCompletionPrefix(editor);
1936
1923
  if (prefix && !hasCompleter) {
1937
- if (!editor.completer) {
1938
- editor.completer = new Autocomplete();
1939
- }
1940
- editor.completer.autoInsert = false;
1941
- editor.completer.showPopup(editor);
1924
+ var completer = Autocomplete.for(editor);
1925
+ completer.autoInsert = false;
1926
+ completer.showPopup(editor);
1942
1927
  }
1943
1928
  }
1944
1929
  };
1945
-
1946
1930
  var Editor = require("../editor").Editor;
1947
1931
  require("../config").defineOptions(Editor.prototype, "editor", {
1948
1932
  enableBasicAutocompletion: {
1949
- set: function(val) {
1933
+ set: function (val) {
1950
1934
  if (val) {
1951
1935
  if (!this.completers)
1952
- this.completers = Array.isArray(val)? val: completers;
1936
+ this.completers = Array.isArray(val) ? val : completers;
1953
1937
  this.commands.addCommand(Autocomplete.startCommand);
1954
- } else {
1938
+ }
1939
+ else {
1955
1940
  this.commands.removeCommand(Autocomplete.startCommand);
1956
1941
  }
1957
1942
  },
1958
1943
  value: false
1959
1944
  },
1960
1945
  enableLiveAutocompletion: {
1961
- set: function(val) {
1946
+ set: function (val) {
1962
1947
  if (val) {
1963
1948
  if (!this.completers)
1964
- this.completers = Array.isArray(val)? val: completers;
1949
+ this.completers = Array.isArray(val) ? val : completers;
1965
1950
  this.commands.on('afterExec', doLiveAutocomplete);
1966
- } else {
1951
+ }
1952
+ else {
1967
1953
  this.commands.removeListener('afterExec', doLiveAutocomplete);
1968
1954
  }
1969
1955
  },
1970
1956
  value: false
1971
1957
  },
1972
1958
  enableSnippets: {
1973
- set: function(val) {
1959
+ set: function (val) {
1974
1960
  if (val) {
1975
1961
  this.commands.addCommand(expandSnippet);
1976
1962
  this.on("changeMode", onChangeMode);
1977
1963
  onChangeMode(null, this);
1978
- } else {
1964
+ }
1965
+ else {
1979
1966
  this.commands.removeCommand(expandSnippet);
1980
1967
  this.off("changeMode", onChangeMode);
1981
1968
  }
@@ -1983,6 +1970,7 @@ require("../config").defineOptions(Editor.prototype, "editor", {
1983
1970
  value: false
1984
1971
  }
1985
1972
  });
1973
+
1986
1974
  }); (function() {
1987
1975
  window.require(["ace/ext/language_tools"], function(m) {
1988
1976
  if (typeof module == "object" && typeof exports == "object" && module) {