2 if (typeof exports
== "object" && typeof module
== "object") // CommonJS
3 mod(require("../../lib/codemirror"));
4 else if (typeof define
== "function" && define
.amd
) // AMD
5 define(["../../lib/codemirror"], mod
);
6 else // Plain browser env
8 })(function(CodeMirror
) {
11 var HINT_ELEMENT_CLASS
= "CodeMirror-hint";
12 var ACTIVE_HINT_ELEMENT_CLASS
= "CodeMirror-hint-active";
14 CodeMirror
.showHint = function(cm
, getHints
, options
) {
15 // We want a single cursor position.
16 if (cm
.listSelections().length
> 1 || cm
.somethingSelected()) return;
17 if (getHints
== null) {
18 if (options
&& options
.async
) return;
19 else getHints
= CodeMirror
.hint
.auto
;
22 if (cm
.state
.completionActive
) cm
.state
.completionActive
.close();
24 var completion
= cm
.state
.completionActive
= new Completion(cm
, getHints
, options
|| {});
25 CodeMirror
.signal(cm
, "startCompletion", cm
);
26 if (completion
.options
.async
)
27 getHints(cm
, function(hints
) { completion
.showHints(hints
); }, completion
.options
);
29 return completion
.showHints(getHints(cm
, completion
.options
));
32 function Completion(cm
, getHints
, options
) {
34 this.getHints
= getHints
;
35 this.options
= options
;
36 this.widget
= this.onClose
= null;
39 Completion
.prototype = {
41 if (!this.active()) return;
42 this.cm
.state
.completionActive
= null;
44 if (this.widget
) this.widget
.close();
45 if (this.onClose
) this.onClose();
46 CodeMirror
.signal(this.cm
, "endCompletion", this.cm
);
50 return this.cm
.state
.completionActive
== this;
53 pick: function(data
, i
) {
54 var completion
= data
.list
[i
];
55 if (completion
.hint
) completion
.hint(this.cm
, data
, completion
);
56 else this.cm
.replaceRange(getText(completion
), completion
.from||data
.from, completion
.to
||data
.to
);
57 CodeMirror
.signal(data
, "pick", completion
);
61 showHints: function(data
) {
62 if (!data
|| !data
.list
.length
|| !this.active()) return this.close();
64 if (this.options
.completeSingle
!= false && data
.list
.length
== 1)
67 this.showWidget(data
);
70 showWidget: function(data
) {
71 this.widget
= new Widget(this, data
);
72 CodeMirror
.signal(data
, "shown");
74 var debounce
= 0, completion
= this, finished
;
75 var closeOn
= this.options
.closeCharacters
|| /[\s()\[\]{};:>,]/;
76 var startPos
= this.cm
.getCursor(), startLen
= this.cm
.getLine(startPos
.line
).length
;
78 var requestAnimationFrame
= window
.requestAnimationFrame
|| function(fn
) {
79 return setTimeout(fn
, 1000/60);
81 var cancelAnimationFrame
= window
.cancelAnimationFrame
|| clearTimeout
;
87 completion
.cm
.off("cursorActivity", activity
);
88 if (data
) CodeMirror
.signal(data
, "close");
93 CodeMirror
.signal(data
, "update");
94 if (completion
.options
.async
)
95 completion
.getHints(completion
.cm
, finishUpdate
, completion
.options
);
97 finishUpdate(completion
.getHints(completion
.cm
, completion
.options
));
99 function finishUpdate(data_
) {
101 if (finished
) return;
102 if (!data
|| !data
.list
.length
) return done();
103 completion
.widget
= new Widget(completion
, data
);
106 function clearDebounce() {
108 cancelAnimationFrame(debounce
);
113 function activity() {
115 var pos
= completion
.cm
.getCursor(), line
= completion
.cm
.getLine(pos
.line
);
116 if (pos
.line
!= startPos
.line
|| line
.length
- pos
.ch
!= startLen
- startPos
.ch
||
117 pos
.ch
< startPos
.ch
|| completion
.cm
.somethingSelected() ||
118 (pos
.ch
&& closeOn
.test(line
.charAt(pos
.ch
- 1)))) {
121 debounce
= requestAnimationFrame(update
);
122 if (completion
.widget
) completion
.widget
.close();
125 this.cm
.on("cursorActivity", activity
);
130 function getText(completion
) {
131 if (typeof completion
== "string") return completion
;
132 else return completion
.text
;
135 function buildKeyMap(options
, handle
) {
137 Up: function() {handle
.moveFocus(-1);},
138 Down: function() {handle
.moveFocus(1);},
139 PageUp: function() {handle
.moveFocus(-handle
.menuSize() + 1, true);},
140 PageDown: function() {handle
.moveFocus(handle
.menuSize() - 1, true);},
141 Home: function() {handle
.setFocus(0);},
142 End: function() {handle
.setFocus(handle
.length
- 1);},
147 var ourMap
= options
.customKeys
? {} : baseMap
;
148 function addBinding(key
, val
) {
150 if (typeof val
!= "string")
151 bound = function(cm
) { return val(cm
, handle
); };
152 // This mechanism is deprecated
153 else if (baseMap
.hasOwnProperty(val
))
154 bound
= baseMap
[val
];
159 if (options
.customKeys
)
160 for (var key
in options
.customKeys
) if (options
.customKeys
.hasOwnProperty(key
))
161 addBinding(key
, options
.customKeys
[key
]);
162 if (options
.extraKeys
)
163 for (var key
in options
.extraKeys
) if (options
.extraKeys
.hasOwnProperty(key
))
164 addBinding(key
, options
.extraKeys
[key
]);
168 function getHintElement(hintsElement
, el
) {
169 while (el
&& el
!= hintsElement
) {
170 if (el
.nodeName
.toUpperCase() === "LI" && el
.parentNode
== hintsElement
) return el
;
175 function Widget(completion
, data
) {
176 this.completion
= completion
;
178 var widget
= this, cm
= completion
.cm
, options
= completion
.options
;
180 var hints
= this.hints
= document
.createElement("ul");
181 hints
.className
= "CodeMirror-hints";
182 this.selectedHint
= options
.getDefaultSelection
? options
.getDefaultSelection(cm
,options
,data
) : 0;
184 var completions
= data
.list
;
185 for (var i
= 0; i
< completions
.length
; ++i
) {
186 var elt
= hints
.appendChild(document
.createElement("li")), cur
= completions
[i
];
187 var className
= HINT_ELEMENT_CLASS
+ (i
!= this.selectedHint
? "" : " " + ACTIVE_HINT_ELEMENT_CLASS
);
188 if (cur
.className
!= null) className
= cur
.className
+ " " + className
;
189 elt
.className
= className
;
190 if (cur
.render
) cur
.render(elt
, data
, cur
);
191 else elt
.appendChild(document
.createTextNode(cur
.displayText
|| getText(cur
)));
195 var pos
= cm
.cursorCoords(options
.alignWithWord
!== false ? data
.from : null);
196 var left
= pos
.left
, top
= pos
.bottom
, below
= true;
197 hints
.style
.left
= left
+ "px";
198 hints
.style
.top
= top
+ "px";
199 // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
200 var winW
= window
.innerWidth
|| Math
.max(document
.body
.offsetWidth
, document
.documentElement
.offsetWidth
);
201 var winH
= window
.innerHeight
|| Math
.max(document
.body
.offsetHeight
, document
.documentElement
.offsetHeight
);
202 (options
.container
|| document
.body
).appendChild(hints
);
203 var box
= hints
.getBoundingClientRect(), overlapY
= box
.bottom
- winH
;
205 var height
= box
.bottom
- box
.top
, curTop
= box
.top
- (pos
.bottom
- pos
.top
);
206 if (curTop
- height
> 0) { // Fits above cursor
207 hints
.style
.top
= (top
= curTop
- height
) + "px";
209 } else if (height
> winH
) {
210 hints
.style
.height
= (winH
- 5) + "px";
211 hints
.style
.top
= (top
= pos
.bottom
- box
.top
) + "px";
212 var cursor
= cm
.getCursor();
213 if (data
.from.ch
!= cursor
.ch
) {
214 pos
= cm
.cursorCoords(cursor
);
215 hints
.style
.left
= (left
= pos
.left
) + "px";
216 box
= hints
.getBoundingClientRect();
220 var overlapX
= box
.left
- winW
;
222 if (box
.right
- box
.left
> winW
) {
223 hints
.style
.width
= (winW
- 5) + "px";
224 overlapX
-= (box
.right
- box
.left
) - winW
;
226 hints
.style
.left
= (left
= pos
.left
- overlapX
) + "px";
229 cm
.addKeyMap(this.keyMap
= buildKeyMap(options
, {
230 moveFocus: function(n
, avoidWrap
) { widget
.changeActive(widget
.selectedHint
+ n
, avoidWrap
); },
231 setFocus: function(n
) { widget
.changeActive(n
); },
232 menuSize: function() { return widget
.screenAmount(); },
233 length
: completions
.length
,
234 close: function() { completion
.close(); },
235 pick: function() { widget
.pick(); },
239 if (options
.closeOnUnfocus
!== false) {
241 cm
.on("blur", this.onBlur = function() { closingOnBlur
= setTimeout(function() { completion
.close(); }, 100); });
242 cm
.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur
); });
245 var startScroll
= cm
.getScrollInfo();
246 cm
.on("scroll", this.onScroll = function() {
247 var curScroll
= cm
.getScrollInfo(), editor
= cm
.getWrapperElement().getBoundingClientRect();
248 var newTop
= top
+ startScroll
.top
- curScroll
.top
;
249 var point
= newTop
- (window
.pageYOffset
|| (document
.documentElement
|| document
.body
).scrollTop
);
250 if (!below
) point
+= hints
.offsetHeight
;
251 if (point
<= editor
.top
|| point
>= editor
.bottom
) return completion
.close();
252 hints
.style
.top
= newTop
+ "px";
253 hints
.style
.left
= (left
+ startScroll
.left
- curScroll
.left
) + "px";
256 CodeMirror
.on(hints
, "dblclick", function(e
) {
257 var t
= getHintElement(hints
, e
.target
|| e
.srcElement
);
258 if (t
&& t
.hintId
!= null) {widget
.changeActive(t
.hintId
); widget
.pick();}
261 CodeMirror
.on(hints
, "click", function(e
) {
262 var t
= getHintElement(hints
, e
.target
|| e
.srcElement
);
263 if (t
&& t
.hintId
!= null) {
264 widget
.changeActive(t
.hintId
);
265 if (options
.completeOnSingleClick
) widget
.pick();
269 CodeMirror
.on(hints
, "mousedown", function() {
270 setTimeout(function(){cm
.focus();}, 20);
273 CodeMirror
.signal(data
, "select", completions
[0], hints
.firstChild
);
279 if (this.completion
.widget
!= this) return;
280 this.completion
.widget
= null;
281 this.hints
.parentNode
.removeChild(this.hints
);
282 this.completion
.cm
.removeKeyMap(this.keyMap
);
284 var cm
= this.completion
.cm
;
285 if (this.completion
.options
.closeOnUnfocus
!== false) {
286 cm
.off("blur", this.onBlur
);
287 cm
.off("focus", this.onFocus
);
289 cm
.off("scroll", this.onScroll
);
293 this.completion
.pick(this.data
, this.selectedHint
);
296 changeActive: function(i
, avoidWrap
) {
297 if (i
>= this.data
.list
.length
)
298 i
= avoidWrap
? this.data
.list
.length
- 1 : 0;
300 i
= avoidWrap
? 0 : this.data
.list
.length
- 1;
301 if (this.selectedHint
== i
) return;
302 var node
= this.hints
.childNodes
[this.selectedHint
];
303 node
.className
= node
.className
.replace(" " + ACTIVE_HINT_ELEMENT_CLASS
, "");
304 node
= this.hints
.childNodes
[this.selectedHint
= i
];
305 node
.className
+= " " + ACTIVE_HINT_ELEMENT_CLASS
;
306 if (node
.offsetTop
< this.hints
.scrollTop
)
307 this.hints
.scrollTop
= node
.offsetTop
- 3;
308 else if (node
.offsetTop
+ node
.offsetHeight
> this.hints
.scrollTop
+ this.hints
.clientHeight
)
309 this.hints
.scrollTop
= node
.offsetTop
+ node
.offsetHeight
- this.hints
.clientHeight
+ 3;
310 CodeMirror
.signal(this.data
, "select", this.data
.list
[this.selectedHint
], node
);
313 screenAmount: function() {
314 return Math
.floor(this.hints
.clientHeight
/ this.hints
.firstChild
.offsetHeight
) || 1;
318 CodeMirror
.registerHelper("hint", "auto", function(cm
, options
) {
319 var helpers
= cm
.getHelpers(cm
.getCursor(), "hint"), words
;
320 if (helpers
.length
) {
321 for (var i
= 0; i
< helpers
.length
; i
++) {
322 var cur
= helpers
[i
](cm
, options
);
323 if (cur
&& cur
.list
.length
) return cur
;
325 } else if (words
= cm
.getHelper(cm
.getCursor(), "hintWords")) {
326 if (words
) return CodeMirror
.hint
.fromList(cm
, {words
: words
});
327 } else if (CodeMirror
.hint
.anyword
) {
328 return CodeMirror
.hint
.anyword(cm
, options
);
332 CodeMirror
.registerHelper("hint", "fromList", function(cm
, options
) {
333 var cur
= cm
.getCursor(), token
= cm
.getTokenAt(cur
);
335 for (var i
= 0; i
< options
.words
.length
; i
++) {
336 var word
= options
.words
[i
];
337 if (word
.slice(0, token
.string
.length
) == token
.string
)
341 if (found
.length
) return {
343 from: CodeMirror
.Pos(cur
.line
, token
.start
),
344 to
: CodeMirror
.Pos(cur
.line
, token
.end
)
348 CodeMirror
.commands
.autocomplete
= CodeMirror
.showHint
;