|
24 | 24 | return cm.showHint(newOpts); |
25 | 25 | }; |
26 | 26 |
|
27 | | - var asyncRunID = 0; |
28 | | - function retrieveHints(getter, cm, options, then) { |
29 | | - if (getter.async) { |
30 | | - var id = ++asyncRunID; |
31 | | - getter(cm, function(hints) { |
32 | | - if (asyncRunID == id) then(hints); |
33 | | - }, options); |
34 | | - } else { |
35 | | - then(getter(cm, options)); |
36 | | - } |
37 | | - } |
38 | | - |
39 | 27 | CodeMirror.defineExtension("showHint", function(options) { |
40 | 28 | // We want a single cursor position. |
41 | 29 | if (this.listSelections().length > 1 || this.somethingSelected()) return; |
42 | 30 |
|
43 | 31 | if (this.state.completionActive) this.state.completionActive.close(); |
44 | 32 | var completion = this.state.completionActive = new Completion(this, options); |
45 | | - var getHints = completion.options.hint; |
46 | | - if (!getHints) return; |
| 33 | + if (!completion.options.hint) return; |
47 | 34 |
|
48 | 35 | CodeMirror.signal(this, "startCompletion", this); |
49 | | - return retrieveHints(getHints, this, completion.options, function(hints) { completion.showHints(hints); }); |
| 36 | + completion.update(true); |
50 | 37 | }); |
51 | 38 |
|
52 | 39 | function Completion(cm, options) { |
53 | 40 | this.cm = cm; |
54 | 41 | this.options = this.buildOptions(options); |
55 | | - this.widget = this.onClose = null; |
| 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(); }); |
56 | 50 | } |
57 | 51 |
|
| 52 | + var requestAnimationFrame = window.requestAnimationFrame || function(fn) { |
| 53 | + return setTimeout(fn, 1000/60); |
| 54 | + }; |
| 55 | + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; |
| 56 | + |
58 | 57 | Completion.prototype = { |
59 | 58 | close: function() { |
60 | 59 | if (!this.active()) return; |
61 | 60 | this.cm.state.completionActive = null; |
| 61 | + this.tick = null; |
| 62 | + this.cm.off("cursorActivity", this.activityFunc); |
62 | 63 |
|
| 64 | + if (this.widget && this.data) CodeMirror.signal(this.data, "close"); |
63 | 65 | if (this.widget) this.widget.close(); |
64 | | - if (this.onClose) this.onClose(); |
65 | 66 | CodeMirror.signal(this.cm, "endCompletion", this.cm); |
66 | 67 | }, |
67 | 68 |
|
|
78 | 79 | this.close(); |
79 | 80 | }, |
80 | 81 |
|
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 | | - showWidget: function(data) { |
91 | | - this.widget = new Widget(this, data); |
92 | | - CodeMirror.signal(data, "shown"); |
93 | | - |
94 | | - var debounce = 0, completion = this, finished; |
95 | | - var closeOn = this.options.closeCharacters; |
96 | | - var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length; |
97 | | - |
98 | | - var requestAnimationFrame = window.requestAnimationFrame || function(fn) { |
99 | | - return setTimeout(fn, 1000/60); |
100 | | - }; |
101 | | - var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; |
102 | | - |
103 | | - function done() { |
104 | | - if (finished) return; |
105 | | - finished = true; |
106 | | - completion.close(); |
107 | | - completion.cm.off("cursorActivity", activity); |
108 | | - if (data) CodeMirror.signal(data, "close"); |
| 82 | + cursorActivity: function() { |
| 83 | + if (this.debounce) { |
| 84 | + cancelAnimationFrame(this.debounce); |
| 85 | + this.debounce = 0; |
109 | 86 | } |
110 | 87 |
|
111 | | - function update() { |
112 | | - if (finished) return; |
113 | | - CodeMirror.signal(data, "update"); |
114 | | - retrieveHints(completion.options.hint, completion.cm, completion.options, finishUpdate); |
115 | | - } |
116 | | - function finishUpdate(data_) { |
117 | | - data = data_; |
118 | | - if (finished) return; |
119 | | - if (!data || !data.list.length) return done(); |
120 | | - if (completion.widget) completion.widget.close(); |
121 | | - completion.widget = new Widget(completion, data); |
| 88 | + var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line); |
| 89 | + if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch || |
| 90 | + pos.ch < this.startPos.ch || this.cm.somethingSelected() || |
| 91 | + (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) { |
| 92 | + this.close(); |
| 93 | + } else { |
| 94 | + var self = this; |
| 95 | + this.debounce = requestAnimationFrame(function() {self.update();}); |
| 96 | + if (this.widget) this.widget.disable(); |
122 | 97 | } |
| 98 | + }, |
123 | 99 |
|
124 | | - function clearDebounce() { |
125 | | - if (debounce) { |
126 | | - cancelAnimationFrame(debounce); |
127 | | - debounce = 0; |
128 | | - } |
| 100 | + update: function(first) { |
| 101 | + if (this.tick == null) return; |
| 102 | + if (this.data) CodeMirror.signal(this.data, "update"); |
| 103 | + if (!this.options.hint.async) { |
| 104 | + this.finishUpdate(this.options.hint(this.cm, this.options), first); |
| 105 | + } else { |
| 106 | + var myTick = ++this.tick, self = this; |
| 107 | + this.options.hint(this.cm, function(data) { |
| 108 | + if (self.tick == myTick) self.finishUpdate(data, first); |
| 109 | + }, this.options); |
129 | 110 | } |
| 111 | + }, |
| 112 | + |
| 113 | + finishUpdate: function(data, first) { |
| 114 | + this.data = data; |
130 | 115 |
|
131 | | - function activity() { |
132 | | - clearDebounce(); |
133 | | - var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line); |
134 | | - if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch || |
135 | | - pos.ch < startPos.ch || completion.cm.somethingSelected() || |
136 | | - (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) { |
137 | | - completion.close(); |
| 116 | + var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle); |
| 117 | + if (this.widget) this.widget.close(); |
| 118 | + if (data && data.list.length) { |
| 119 | + if (picked && data.list.length == 1) { |
| 120 | + this.pick(data, 0); |
138 | 121 | } else { |
139 | | - debounce = requestAnimationFrame(update); |
140 | | - if (completion.widget) completion.widget.close(); |
| 122 | + this.widget = new Widget(this, data); |
| 123 | + CodeMirror.signal(data, "shown"); |
141 | 124 | } |
142 | 125 | } |
143 | | - this.cm.on("cursorActivity", activity); |
144 | | - this.onClose = done; |
145 | 126 | }, |
146 | 127 |
|
147 | 128 | buildOptions: function(options) { |
|
206 | 187 | function Widget(completion, data) { |
207 | 188 | this.completion = completion; |
208 | 189 | this.data = data; |
| 190 | + this.picked = false; |
209 | 191 | var widget = this, cm = completion.cm; |
210 | 192 |
|
211 | 193 | var hints = this.hints = document.createElement("ul"); |
|
320 | 302 | cm.off("scroll", this.onScroll); |
321 | 303 | }, |
322 | 304 |
|
| 305 | + disable: function() { |
| 306 | + this.completion.cm.removeKeyMap(this.keyMap); |
| 307 | + var widget = this; |
| 308 | + this.keyMap = {Enter: function() { widget.picked = true; }}; |
| 309 | + this.completion.cm.addKeyMap(this.keyMap); |
| 310 | + }, |
| 311 | + |
323 | 312 | pick: function() { |
324 | 313 | this.completion.pick(this.data, this.selectedHint); |
325 | 314 | }, |
|
0 commit comments