Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(87)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/source_frame/TextEditorAutocompleteController.js

Issue 2271683003: DevTools: Move CodeMirrorTextEditor into text_editor module (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Merge Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @constructor
7 * @implements {WebInspector.SuggestBoxDelegate}
8 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
9 * @param {!CodeMirror} codeMirror
10 * @param {!WebInspector.AutocompleteConfig} config
11 */
12 WebInspector.TextEditorAutocompleteController = function(textEditor, codeMirror, config)
13 {
14 this._textEditor = textEditor;
15 this._codeMirror = codeMirror;
16 this._config = config;
17 this._initialized = false;
18
19 this._onScroll = this._onScroll.bind(this);
20 this._onCursorActivity = this._onCursorActivity.bind(this);
21 this._changes = this._changes.bind(this);
22 this._blur = this._blur.bind(this);
23 this._beforeChange = this._beforeChange.bind(this);
24 this._mouseDown = this.finishAutocomplete.bind(this);
25 this._codeMirror.on("changes", this._changes);
26
27 this._hintElement = createElementWithClass("span", "auto-complete-text");
28 }
29
30 WebInspector.TextEditorAutocompleteController.HintBookmark = Symbol("hint");
31
32 WebInspector.TextEditorAutocompleteController.prototype = {
33 _initializeIfNeeded: function()
34 {
35 if (this._initialized)
36 return;
37 this._initialized = true;
38 this._codeMirror.on("scroll", this._onScroll);
39 this._codeMirror.on("cursorActivity", this._onCursorActivity);
40 this._codeMirror.on("mousedown", this._mouseDown);
41 this._codeMirror.on("blur", this._blur);
42 if (this._config.isWordChar) {
43 this._codeMirror.on("beforeChange", this._beforeChange);
44 this._dictionary = new WebInspector.TextDictionary();
45 this._addWordsFromText(this._codeMirror.getValue());
46 }
47 },
48
49 dispose: function()
50 {
51 this._codeMirror.off("changes", this._changes);
52 if (this._initialized) {
53 this._codeMirror.off("scroll", this._onScroll);
54 this._codeMirror.off("cursorActivity", this._onCursorActivity);
55 this._codeMirror.off("mousedown", this._mouseDown);
56 this._codeMirror.off("blur", this._blur);
57 }
58 if (this._dictionary) {
59 this._codeMirror.off("beforeChange", this._beforeChange);
60 this._dictionary.reset();
61 }
62 },
63
64 /**
65 * @param {!CodeMirror} codeMirror
66 * @param {!CodeMirror.BeforeChangeObject} changeObject
67 */
68 _beforeChange: function(codeMirror, changeObject)
69 {
70 this._updatedLines = this._updatedLines || {};
71 for (var i = changeObject.from.line; i <= changeObject.to.line; ++i)
72 this._updatedLines[i] = this._codeMirror.getLine(i);
73 },
74
75 /**
76 * @param {string} text
77 */
78 _addWordsFromText: function(text)
79 {
80 WebInspector.TextUtils.textToWords(text, /** @type {function(string):boo lean} */ (this._config.isWordChar), addWord.bind(this));
81
82 /**
83 * @param {string} word
84 * @this {WebInspector.TextEditorAutocompleteController}
85 */
86 function addWord(word)
87 {
88 if (word.length && (word[0] < "0" || word[0] > "9"))
89 this._dictionary.addWord(word);
90 }
91 },
92
93 /**
94 * @param {string} text
95 */
96 _removeWordsFromText: function(text)
97 {
98 WebInspector.TextUtils.textToWords(text, /** @type {function(string):boo lean} */ (this._config.isWordChar), (word) => this._dictionary.removeWord(word) );
99 },
100
101 /**
102 * @param {number} lineNumber
103 * @param {number} columnNumber
104 * @return {?WebInspector.TextRange}
105 */
106 _substituteRange: function(lineNumber, columnNumber)
107 {
108 var range = this._config.substituteRangeCallback ? this._config.substitu teRangeCallback(lineNumber, columnNumber) : null;
109 if (!range && this._config.isWordChar)
110 range = this._textEditor.wordRangeForCursorPosition(lineNumber, colu mnNumber, this._config.isWordChar);
111 return range;
112 },
113
114 /**
115 * @param {!WebInspector.TextRange} prefixRange
116 * @param {!WebInspector.TextRange} substituteRange
117 * @return {!Promise.<!WebInspector.SuggestBox.Suggestions>}
118 */
119 _wordsWithPrefix: function(prefixRange, substituteRange)
120 {
121 var external = this._config.suggestionsCallback ? this._config.suggestio nsCallback(prefixRange, substituteRange) : null;
122 if (external)
123 return external;
124
125 if (!this._dictionary || prefixRange.startColumn === prefixRange.endColu mn)
126 return Promise.resolve([]);
127
128 var completions = this._dictionary.wordsWithPrefix(this._textEditor.text (prefixRange));
129 var substituteWord = this._textEditor.text(substituteRange);
130 if (this._dictionary.wordCount(substituteWord) === 1)
131 completions = completions.filter((word) => word !== substituteWord);
132
133 completions.sort((a, b) => this._dictionary.wordCount(b) - this._diction ary.wordCount(a) || a.length - b.length);
134 return Promise.resolve(completions.map(item => ({ title: item })));
135 },
136
137 /**
138 * @param {!CodeMirror} codeMirror
139 * @param {!Array.<!CodeMirror.ChangeObject>} changes
140 */
141 _changes: function(codeMirror, changes)
142 {
143 if (!changes.length)
144 return;
145
146 if (this._dictionary && this._updatedLines) {
147 for (var lineNumber in this._updatedLines)
148 this._removeWordsFromText(this._updatedLines[lineNumber]);
149 delete this._updatedLines;
150
151 var linesToUpdate = {};
152 for (var changeIndex = 0; changeIndex < changes.length; ++changeInde x) {
153 var changeObject = changes[changeIndex];
154 var editInfo = WebInspector.CodeMirrorUtils.changeObjectToEditOp eration(changeObject);
155 for (var i = editInfo.newRange.startLine; i <= editInfo.newRange .endLine; ++i)
156 linesToUpdate[i] = this._codeMirror.getLine(i);
157 }
158 for (var lineNumber in linesToUpdate)
159 this._addWordsFromText(linesToUpdate[lineNumber]);
160 }
161
162 var singleCharInput = false;
163 var singleCharDelete = false;
164 var cursor = this._codeMirror.getCursor("head");
165 for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
166 var changeObject = changes[changeIndex];
167 if (changeObject.origin === "+input"
168 && changeObject.text.length === 1
169 && changeObject.text[0].length === 1
170 && changeObject.to.line === cursor.line
171 && changeObject.to.ch + 1 === cursor.ch) {
172 singleCharInput = true;
173 break;
174 }
175 if (this._suggestBox
176 && changeObject.origin === "+delete"
177 && changeObject.removed.length === 1
178 && changeObject.removed[0].length === 1
179 && changeObject.to.line === cursor.line
180 && changeObject.to.ch - 1 === cursor.ch) {
181 singleCharDelete = true;
182 break;
183 }
184 }
185 if (singleCharInput && this._hintMarker)
186 this._hintElement.textContent = this._hintElement.textContent.substr ing(1);
187
188 if (singleCharDelete && this._hintMarker && this._lastPrefix) {
189 this._hintElement.textContent = this._lastPrefix.charAt(this._lastPr efix.length - 1) + this._hintElement.textContent;
190 this._lastPrefix = this._lastPrefix.substring(0, this._lastPrefix.le ngth - 1);
191 }
192 if (singleCharInput || singleCharDelete)
193 setImmediate(this.autocomplete.bind(this));
194 else
195 this.finishAutocomplete();
196 },
197
198 _blur: function()
199 {
200 this.finishAutocomplete();
201 },
202
203 /**
204 * @param {!WebInspector.TextRange} mainSelection
205 * @return {boolean}
206 */
207 _validateSelectionsContexts: function(mainSelection)
208 {
209 var selections = this._codeMirror.listSelections();
210 if (selections.length <= 1)
211 return true;
212 var mainSelectionContext = this._textEditor.text(mainSelection);
213 for (var i = 0; i < selections.length; ++i) {
214 var wordRange = this._substituteRange(selections[i].head.line, selec tions[i].head.ch);
215 if (!wordRange)
216 return false;
217 var context = this._textEditor.text(wordRange);
218 if (context !== mainSelectionContext)
219 return false;
220 }
221 return true;
222 },
223
224 autocomplete: function()
225 {
226 this._initializeIfNeeded();
227 if (this._codeMirror.somethingSelected()) {
228 this.finishAutocomplete();
229 return;
230 }
231
232 var cursor = this._codeMirror.getCursor("head");
233 var substituteRange = this._substituteRange(cursor.line, cursor.ch);
234 if (!substituteRange || !this._validateSelectionsContexts(substituteRang e)) {
235 this.finishAutocomplete();
236 return;
237 }
238
239 var prefixRange = substituteRange.clone();
240 prefixRange.endColumn = cursor.ch;
241 var prefix = this._textEditor.text(prefixRange);
242 var hadSuggestBox = false;
243 if (this._suggestBox)
244 hadSuggestBox = true;
245
246 this._wordsWithPrefix(prefixRange, substituteRange).then(wordsAcquired.b ind(this));
247
248 /**
249 * @param {!WebInspector.SuggestBox.Suggestions} wordsWithPrefix
250 * @this {WebInspector.TextEditorAutocompleteController}
251 */
252 function wordsAcquired(wordsWithPrefix)
253 {
254 if (!wordsWithPrefix.length || (wordsWithPrefix.length === 1 && pref ix === wordsWithPrefix[0].title) || (!this._suggestBox && hadSuggestBox)) {
255 this.finishAutocomplete();
256 this._onSuggestionsShownForTest([]);
257 return;
258 }
259 if (!this._suggestBox)
260 this._suggestBox = new WebInspector.SuggestBox(this, 6);
261
262 var oldPrefixRange = this._prefixRange;
263 this._prefixRange = prefixRange;
264 if (!oldPrefixRange || prefixRange.startLine !== oldPrefixRange.star tLine || prefixRange.startColumn !== oldPrefixRange.startColumn)
265 this._updateAnchorBox();
266 this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, !this._isCursorAtEndOfLine(), prefix);
267 this._onSuggestionsShownForTest(wordsWithPrefix);
268 this._addHintMarker(wordsWithPrefix[0].title);
269 }
270 },
271
272 /**
273 * @param {string} hint
274 */
275 _addHintMarker: function(hint)
276 {
277 this._clearHintMarker();
278 if (!this._isCursorAtEndOfLine())
279 return;
280 var prefix = this._textEditor.text(this._prefixRange);
281 this._lastPrefix = prefix;
282 this._hintElement.textContent = hint.substring(prefix.length).split("\n" )[0];
283 var cursor = this._codeMirror.getCursor("to");
284 this._hintMarker = this._textEditor.addBookmark(cursor.line, cursor.ch, this._hintElement, WebInspector.TextEditorAutocompleteController.HintBookmark, t rue);
285 },
286
287 _clearHintMarker: function()
288 {
289 if (!this._hintMarker)
290 return;
291 this._hintMarker.clear();
292 delete this._hintMarker;
293 },
294
295 /**
296 * @param {!Array<{className: (string|undefined), title: string}>} suggestio ns
297 */
298 _onSuggestionsShownForTest: function(suggestions) { },
299
300 finishAutocomplete: function()
301 {
302 if (!this._suggestBox)
303 return;
304 this._suggestBox.hide();
305 this._suggestBox = null;
306 this._prefixRange = null;
307 this._anchorBox = null;
308 this._clearHintMarker();
309 },
310
311 /**
312 * @param {!Event} event
313 * @return {boolean}
314 */
315 keyDown: function(event)
316 {
317 if (!this._suggestBox)
318 return false;
319 switch (event.keyCode) {
320 case WebInspector.KeyboardShortcut.Keys.Tab.code:
321 this._suggestBox.acceptSuggestion();
322 this.finishAutocomplete();
323 return true;
324 case WebInspector.KeyboardShortcut.Keys.End.code:
325 case WebInspector.KeyboardShortcut.Keys.Right.code:
326 if (this._isCursorAtEndOfLine()) {
327 this._suggestBox.acceptSuggestion();
328 this.finishAutocomplete();
329 return true;
330 } else {
331 this.finishAutocomplete();
332 return false;
333 }
334 case WebInspector.KeyboardShortcut.Keys.Left.code:
335 case WebInspector.KeyboardShortcut.Keys.Home.code:
336 this.finishAutocomplete();
337 return false;
338 case WebInspector.KeyboardShortcut.Keys.Esc.code:
339 this.finishAutocomplete();
340 return true;
341 }
342 return this._suggestBox.keyPressed(event);
343 },
344
345 /**
346 * @return {boolean}
347 */
348 _isCursorAtEndOfLine: function()
349 {
350 var cursor = this._codeMirror.getCursor("to");
351 return cursor.ch === this._codeMirror.getLine(cursor.line).length;
352 },
353
354 /**
355 * @override
356 * @param {string} suggestion
357 * @param {boolean=} isIntermediateSuggestion
358 */
359 applySuggestion: function(suggestion, isIntermediateSuggestion)
360 {
361 this._currentSuggestion = suggestion;
362 this._addHintMarker(suggestion);
363 },
364
365 /**
366 * @override
367 */
368 acceptSuggestion: function()
369 {
370 if (this._prefixRange.endColumn - this._prefixRange.startColumn === this ._currentSuggestion.length)
371 return;
372
373 var selections = this._codeMirror.listSelections().slice();
374 var prefixLength = this._prefixRange.endColumn - this._prefixRange.start Column;
375 for (var i = selections.length - 1; i >= 0; --i) {
376 var start = selections[i].head;
377 var end = new CodeMirror.Pos(start.line, start.ch - prefixLength);
378 this._codeMirror.replaceRange(this._currentSuggestion, start, end, " +autocomplete");
379 }
380 },
381
382 _onScroll: function()
383 {
384 if (!this._suggestBox)
385 return;
386 var cursor = this._codeMirror.getCursor();
387 var scrollInfo = this._codeMirror.getScrollInfo();
388 var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "l ocal");
389 var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollIn fo.clientHeight, "local");
390 if (cursor.line < topmostLineNumber || cursor.line > bottomLine)
391 this.finishAutocomplete();
392 else {
393 this._updateAnchorBox();
394 this._suggestBox.setPosition(this._anchorBox);
395 }
396 },
397
398 _onCursorActivity: function()
399 {
400 if (!this._suggestBox)
401 return;
402 var cursor = this._codeMirror.getCursor();
403 if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._pre fixRange.endColumn + 1 || cursor.ch <= this._prefixRange.startColumn)
404 this.finishAutocomplete();
405 },
406
407 _updateAnchorBox: function()
408 {
409 var line = this._prefixRange.startLine;
410 var column = this._prefixRange.startColumn;
411 var metrics = this._textEditor.cursorPositionToCoordinates(line, column) ;
412 this._anchorBox = metrics ? new AnchorBox(metrics.x, metrics.y, 0, metri cs.height) : null;
413 },
414 }
415
416 /**
417 * @typedef {{
418 * substituteRangeCallback: ((function(number, number):?WebInspector.TextRan ge)|undefined),
419 * suggestionsCallback: ((function(!WebInspector.TextRange, !WebInspector.Te xtRange):?Promise.<!WebInspector.SuggestBox.Suggestions>)|undefined),
420 * isWordChar: ((function(string):boolean)|undefined)
421 * }}
422 **/
423 WebInspector.AutocompleteConfig;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698