]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blob - sources/samples/toolbarconfigurator/lib/codemirror/show-hint.js
Validation initiale
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / samples / toolbarconfigurator / lib / codemirror / show-hint.js
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
3
4 (function(mod) {
5 if (typeof exports == "object" && typeof module == "object") // CommonJS
6 mod(require("../../lib/codemirror"));
7 else if (typeof define == "function" && define.amd) // AMD
8 define(["../../lib/codemirror"], mod);
9 else // Plain browser env
10 mod(CodeMirror);
11 })(function(CodeMirror) {
12 "use strict";
13
14 var HINT_ELEMENT_CLASS = "CodeMirror-hint";
15 var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
16
17 // This is the old interface, kept around for now to stay
18 // backwards-compatible.
19 CodeMirror.showHint = function(cm, getHints, options) {
20 if (!getHints) return cm.showHint(options);
21 if (options && options.async) getHints.async = true;
22 var newOpts = {hint: getHints};
23 if (options) for (var prop in options) newOpts[prop] = options[prop];
24 return cm.showHint(newOpts);
25 };
26
27 CodeMirror.defineExtension("showHint", function(options) {
28 // We want a single cursor position.
29 if (this.listSelections().length > 1 || this.somethingSelected()) return;
30
31 if (this.state.completionActive) this.state.completionActive.close();
32 var completion = this.state.completionActive = new Completion(this, options);
33 if (!completion.options.hint) return;
34
35 CodeMirror.signal(this, "startCompletion", this);
36 completion.update();
37 });
38
39 function Completion(cm, options) {
40 this.cm = cm;
41 this.options = this.buildOptions(options);
42 this.widget = null;
43 this.debounce = 0;
44 this.tick = 0;
45 this.startPos = this.cm.getCursor();
46 this.startLen = this.cm.getLine(this.startPos.line).length;
47
48 var self = this;
49 cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
50 }
51
52 var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
53 return setTimeout(fn, 1000/60);
54 };
55 var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
56
57 Completion.prototype = {
58 close: function() {
59 if (!this.active()) return;
60 this.cm.state.completionActive = null;
61 this.tick = null;
62 this.cm.off("cursorActivity", this.activityFunc);
63
64 if (this.widget) this.widget.close();
65 CodeMirror.signal(this.cm, "endCompletion", this.cm);
66 },
67
68 active: function() {
69 return this.cm.state.completionActive == this;
70 },
71
72 pick: function(data, i) {
73 var completion = data.list[i];
74 if (completion.hint) completion.hint(this.cm, data, completion);
75 else this.cm.replaceRange(getText(completion), completion.from || data.from,
76 completion.to || data.to, "complete");
77 CodeMirror.signal(data, "pick", completion);
78 this.close();
79 },
80
81 showHints: function(data) {
82 if (!data || !data.list.length || !this.active()) return this.close();
83
84 if (this.options.completeSingle && data.list.length == 1)
85 this.pick(data, 0);
86 else
87 this.showWidget(data);
88 },
89
90 cursorActivity: function() {
91 if (this.debounce) {
92 cancelAnimationFrame(this.debounce);
93 this.debounce = 0;
94 }
95
96 var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
97 if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
98 pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
99 (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
100 this.close();
101 } else {
102 var self = this;
103 this.debounce = requestAnimationFrame(function() {self.update();});
104 if (this.widget) this.widget.disable();
105 }
106 },
107
108 update: function() {
109 if (this.tick == null) return;
110 if (this.data) CodeMirror.signal(this.data, "update");
111 if (!this.options.hint.async) {
112 this.finishUpdate(this.options.hint(this.cm, this.options), myTick);
113 } else {
114 var myTick = ++this.tick, self = this;
115 this.options.hint(this.cm, function(data) {
116 if (self.tick == myTick) self.finishUpdate(data);
117 }, this.options);
118 }
119 },
120
121 finishUpdate: function(data) {
122 this.data = data;
123 var picked = this.widget && this.widget.picked;
124 if (this.widget) this.widget.close();
125 if (data && data.list.length) {
126 if (picked && data.list.length == 1) this.pick(data, 0);
127 else this.widget = new Widget(this, data);
128 }
129 },
130
131 showWidget: function(data) {
132 this.data = data;
133 this.widget = new Widget(this, data);
134 CodeMirror.signal(data, "shown");
135 },
136
137 buildOptions: function(options) {
138 var editor = this.cm.options.hintOptions;
139 var out = {};
140 for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
141 if (editor) for (var prop in editor)
142 if (editor[prop] !== undefined) out[prop] = editor[prop];
143 if (options) for (var prop in options)
144 if (options[prop] !== undefined) out[prop] = options[prop];
145 return out;
146 }
147 };
148
149 function getText(completion) {
150 if (typeof completion == "string") return completion;
151 else return completion.text;
152 }
153
154 function buildKeyMap(completion, handle) {
155 var baseMap = {
156 Up: function() {handle.moveFocus(-1);},
157 Down: function() {handle.moveFocus(1);},
158 PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
159 PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
160 Home: function() {handle.setFocus(0);},
161 End: function() {handle.setFocus(handle.length - 1);},
162 Enter: handle.pick,
163 Tab: handle.pick,
164 Esc: handle.close
165 };
166 var custom = completion.options.customKeys;
167 var ourMap = custom ? {} : baseMap;
168 function addBinding(key, val) {
169 var bound;
170 if (typeof val != "string")
171 bound = function(cm) { return val(cm, handle); };
172 // This mechanism is deprecated
173 else if (baseMap.hasOwnProperty(val))
174 bound = baseMap[val];
175 else
176 bound = val;
177 ourMap[key] = bound;
178 }
179 if (custom)
180 for (var key in custom) if (custom.hasOwnProperty(key))
181 addBinding(key, custom[key]);
182 var extra = completion.options.extraKeys;
183 if (extra)
184 for (var key in extra) if (extra.hasOwnProperty(key))
185 addBinding(key, extra[key]);
186 return ourMap;
187 }
188
189 function getHintElement(hintsElement, el) {
190 while (el && el != hintsElement) {
191 if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
192 el = el.parentNode;
193 }
194 }
195
196 function Widget(completion, data) {
197 this.completion = completion;
198 this.data = data;
199 this.picked = false;
200 var widget = this, cm = completion.cm;
201
202 var hints = this.hints = document.createElement("ul");
203 hints.className = "CodeMirror-hints";
204 this.selectedHint = data.selectedHint || 0;
205
206 var completions = data.list;
207 for (var i = 0; i < completions.length; ++i) {
208 var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
209 var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
210 if (cur.className != null) className = cur.className + " " + className;
211 elt.className = className;
212 if (cur.render) cur.render(elt, data, cur);
213 else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
214 elt.hintId = i;
215 }
216
217 var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
218 var left = pos.left, top = pos.bottom, below = true;
219 hints.style.left = left + "px";
220 hints.style.top = top + "px";
221 // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
222 var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
223 var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
224 (completion.options.container || document.body).appendChild(hints);
225 var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
226 if (overlapY > 0) {
227 var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
228 if (curTop - height > 0) { // Fits above cursor
229 hints.style.top = (top = pos.top - height) + "px";
230 below = false;
231 } else if (height > winH) {
232 hints.style.height = (winH - 5) + "px";
233 hints.style.top = (top = pos.bottom - box.top) + "px";
234 var cursor = cm.getCursor();
235 if (data.from.ch != cursor.ch) {
236 pos = cm.cursorCoords(cursor);
237 hints.style.left = (left = pos.left) + "px";
238 box = hints.getBoundingClientRect();
239 }
240 }
241 }
242 var overlapX = box.right - winW;
243 if (overlapX > 0) {
244 if (box.right - box.left > winW) {
245 hints.style.width = (winW - 5) + "px";
246 overlapX -= (box.right - box.left) - winW;
247 }
248 hints.style.left = (left = pos.left - overlapX) + "px";
249 }
250
251 cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
252 moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
253 setFocus: function(n) { widget.changeActive(n); },
254 menuSize: function() { return widget.screenAmount(); },
255 length: completions.length,
256 close: function() { completion.close(); },
257 pick: function() { widget.pick(); },
258 data: data
259 }));
260
261 if (completion.options.closeOnUnfocus) {
262 var closingOnBlur;
263 cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
264 cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
265 }
266
267 var startScroll = cm.getScrollInfo();
268 cm.on("scroll", this.onScroll = function() {
269 var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
270 var newTop = top + startScroll.top - curScroll.top;
271 var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
272 if (!below) point += hints.offsetHeight;
273 if (point <= editor.top || point >= editor.bottom) return completion.close();
274 hints.style.top = newTop + "px";
275 hints.style.left = (left + startScroll.left - curScroll.left) + "px";
276 });
277
278 CodeMirror.on(hints, "dblclick", function(e) {
279 var t = getHintElement(hints, e.target || e.srcElement);
280 if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
281 });
282
283 CodeMirror.on(hints, "click", function(e) {
284 var t = getHintElement(hints, e.target || e.srcElement);
285 if (t && t.hintId != null) {
286 widget.changeActive(t.hintId);
287 if (completion.options.completeOnSingleClick) widget.pick();
288 }
289 });
290
291 CodeMirror.on(hints, "mousedown", function() {
292 setTimeout(function(){cm.focus();}, 20);
293 });
294
295 CodeMirror.signal(data, "select", completions[0], hints.firstChild);
296 return true;
297 }
298
299 Widget.prototype = {
300 close: function() {
301 if (this.completion.widget != this) return;
302 this.completion.widget = null;
303 this.hints.parentNode.removeChild(this.hints);
304 this.completion.cm.removeKeyMap(this.keyMap);
305
306 var cm = this.completion.cm;
307 if (this.completion.options.closeOnUnfocus) {
308 cm.off("blur", this.onBlur);
309 cm.off("focus", this.onFocus);
310 }
311 cm.off("scroll", this.onScroll);
312 },
313
314 disable: function() {
315 this.completion.cm.removeKeyMap(this.keyMap);
316 var widget = this;
317 this.keyMap = {Enter: function() { widget.picked = true; }};
318 this.completion.cm.addKeyMap(this.keyMap);
319 },
320
321 pick: function() {
322 this.completion.pick(this.data, this.selectedHint);
323 },
324
325 changeActive: function(i, avoidWrap) {
326 if (i >= this.data.list.length)
327 i = avoidWrap ? this.data.list.length - 1 : 0;
328 else if (i < 0)
329 i = avoidWrap ? 0 : this.data.list.length - 1;
330 if (this.selectedHint == i) return;
331 var node = this.hints.childNodes[this.selectedHint];
332 node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
333 node = this.hints.childNodes[this.selectedHint = i];
334 node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
335 if (node.offsetTop < this.hints.scrollTop)
336 this.hints.scrollTop = node.offsetTop - 3;
337 else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
338 this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
339 CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
340 },
341
342 screenAmount: function() {
343 return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
344 }
345 };
346
347 CodeMirror.registerHelper("hint", "auto", function(cm, options) {
348 var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
349 if (helpers.length) {
350 for (var i = 0; i < helpers.length; i++) {
351 var cur = helpers[i](cm, options);
352 if (cur && cur.list.length) return cur;
353 }
354 } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
355 if (words) return CodeMirror.hint.fromList(cm, {words: words});
356 } else if (CodeMirror.hint.anyword) {
357 return CodeMirror.hint.anyword(cm, options);
358 }
359 });
360
361 CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
362 var cur = cm.getCursor(), token = cm.getTokenAt(cur);
363 var found = [];
364 for (var i = 0; i < options.words.length; i++) {
365 var word = options.words[i];
366 if (word.slice(0, token.string.length) == token.string)
367 found.push(word);
368 }
369
370 if (found.length) return {
371 list: found,
372 from: CodeMirror.Pos(cur.line, token.start),
373 to: CodeMirror.Pos(cur.line, token.end)
374 };
375 });
376
377 CodeMirror.commands.autocomplete = CodeMirror.showHint;
378
379 var defaultOptions = {
380 hint: CodeMirror.hint.auto,
381 completeSingle: true,
382 alignWithWord: true,
383 closeCharacters: /[\s()\[\]{};:>,]/,
384 closeOnUnfocus: true,
385 completeOnSingleClick: false,
386 container: null,
387 customKeys: null,
388 extraKeys: null
389 };
390
391 CodeMirror.defineOption("hintOptions", null);
392 });