1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
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
11 })(function(CodeMirror
) {
14 var HINT_ELEMENT_CLASS
= "CodeMirror-hint";
15 var ACTIVE_HINT_ELEMENT_CLASS
= "CodeMirror-hint-active";
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
);
27 CodeMirror
.defineExtension("showHint", function(options
) {
28 // We want a single cursor position.
29 if (this.listSelections().length
> 1 || this.somethingSelected()) return;
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;
35 CodeMirror
.signal(this, "startCompletion", this);
39 function Completion(cm
, options
) {
41 this.options
= this.buildOptions(options
);
45 this.startPos
= this.cm
.getCursor();
46 this.startLen
= this.cm
.getLine(this.startPos
.line
).length
;
49 cm
.on("cursorActivity", this.activityFunc = function() { self
.cursorActivity(); });
52 var requestAnimationFrame
= window
.requestAnimationFrame
|| function(fn
) {
53 return setTimeout(fn
, 1000/60);
55 var cancelAnimationFrame
= window
.cancelAnimationFrame
|| clearTimeout
;
57 Completion
.prototype = {
59 if (!this.active()) return;
60 this.cm
.state
.completionActive
= null;
62 this.cm
.off("cursorActivity", this.activityFunc
);
64 if (this.widget
) this.widget
.close();
65 CodeMirror
.signal(this.cm
, "endCompletion", this.cm
);
69 return this.cm
.state
.completionActive
== this;
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
);
81 showHints: function(data
) {
82 if (!data
|| !data
.list
.length
|| !this.active()) return this.close();
84 if (this.options
.completeSingle
&& data
.list
.length
== 1)
87 this.showWidget(data
);
90 cursorActivity: function() {
92 cancelAnimationFrame(this.debounce
);
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)))) {
103 this.debounce
= requestAnimationFrame(function() {self
.update();});
104 if (this.widget
) this.widget
.disable();
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
);
114 var myTick
= ++this.tick
, self
= this;
115 this.options
.hint(this.cm
, function(data
) {
116 if (self
.tick
== myTick
) self
.finishUpdate(data
);
121 finishUpdate: function(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
);
131 showWidget: function(data
) {
133 this.widget
= new Widget(this, data
);
134 CodeMirror
.signal(data
, "shown");
137 buildOptions: function(options
) {
138 var editor
= this.cm
.options
.hintOptions
;
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
];
149 function getText(completion
) {
150 if (typeof completion
== "string") return completion
;
151 else return completion
.text
;
154 function buildKeyMap(completion
, handle
) {
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);},
166 var custom
= completion
.options
.customKeys
;
167 var ourMap
= custom
? {} : baseMap
;
168 function addBinding(key
, val
) {
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
];
180 for (var key
in custom
) if (custom
.hasOwnProperty(key
))
181 addBinding(key
, custom
[key
]);
182 var extra
= completion
.options
.extraKeys
;
184 for (var key
in extra
) if (extra
.hasOwnProperty(key
))
185 addBinding(key
, extra
[key
]);
189 function getHintElement(hintsElement
, el
) {
190 while (el
&& el
!= hintsElement
) {
191 if (el
.nodeName
.toUpperCase() === "LI" && el
.parentNode
== hintsElement
) return el
;
196 function Widget(completion
, data
) {
197 this.completion
= completion
;
200 var widget
= this, cm
= completion
.cm
;
202 var hints
= this.hints
= document
.createElement("ul");
203 hints
.className
= "CodeMirror-hints";
204 this.selectedHint
= data
.selectedHint
|| 0;
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
)));
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
;
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";
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();
242 var overlapX
= box
.right
- winW
;
244 if (box
.right
- box
.left
> winW
) {
245 hints
.style
.width
= (winW
- 5) + "px";
246 overlapX
-= (box
.right
- box
.left
) - winW
;
248 hints
.style
.left
= (left
= pos
.left
- overlapX
) + "px";
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(); },
261 if (completion
.options
.closeOnUnfocus
) {
263 cm
.on("blur", this.onBlur = function() { closingOnBlur
= setTimeout(function() { completion
.close(); }, 100); });
264 cm
.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur
); });
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";
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();}
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();
291 CodeMirror
.on(hints
, "mousedown", function() {
292 setTimeout(function(){cm
.focus();}, 20);
295 CodeMirror
.signal(data
, "select", completions
[0], hints
.firstChild
);
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
);
306 var cm
= this.completion
.cm
;
307 if (this.completion
.options
.closeOnUnfocus
) {
308 cm
.off("blur", this.onBlur
);
309 cm
.off("focus", this.onFocus
);
311 cm
.off("scroll", this.onScroll
);
314 disable: function() {
315 this.completion
.cm
.removeKeyMap(this.keyMap
);
317 this.keyMap
= {Enter: function() { widget
.picked
= true; }};
318 this.completion
.cm
.addKeyMap(this.keyMap
);
322 this.completion
.pick(this.data
, this.selectedHint
);
325 changeActive: function(i
, avoidWrap
) {
326 if (i
>= this.data
.list
.length
)
327 i
= avoidWrap
? this.data
.list
.length
- 1 : 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
);
342 screenAmount: function() {
343 return Math
.floor(this.hints
.clientHeight
/ this.hints
.firstChild
.offsetHeight
) || 1;
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
;
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
);
361 CodeMirror
.registerHelper("hint", "fromList", function(cm
, options
) {
362 var cur
= cm
.getCursor(), token
= cm
.getTokenAt(cur
);
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
)
370 if (found
.length
) return {
372 from: CodeMirror
.Pos(cur
.line
, token
.start
),
373 to: CodeMirror
.Pos(cur
.line
, token
.end
)
377 CodeMirror
.commands
.autocomplete
= CodeMirror
.showHint
;
379 var defaultOptions
= {
380 hint: CodeMirror
.hint
.auto
,
381 completeSingle: true,
383 closeCharacters: /[\s()\[\]{};:>,]/,
384 closeOnUnfocus: true,
385 completeOnSingleClick: false,
391 CodeMirror
.defineOption("hintOptions", null);