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.
7 * @implements {WebInspector.SuggestBoxDelegate}
8 * @param {!WebInspector.CodeMirrorTextEditor} textEditor
9 * @param {!CodeMirror} codeMirror
11 WebInspector
.TextEditorAutocompleteController = function(textEditor
, codeMirror
)
13 this._textEditor
= textEditor
;
14 this._codeMirror
= codeMirror
;
16 this._onScroll
= this._onScroll
.bind(this);
17 this._onCursorActivity
= this._onCursorActivity
.bind(this);
18 this._changes
= this._changes
.bind(this);
19 this._blur
= this._blur
.bind(this);
20 this._codeMirror
.on("changes", this._changes
);
23 this._initialized
= false;
26 WebInspector
.TextEditorAutocompleteController
.prototype = {
27 _initializeIfNeeded: function()
29 if (this._initialized
)
31 this._initialized
= true;
32 this._codeMirror
.on("scroll", this._onScroll
);
33 this._codeMirror
.on("cursorActivity", this._onCursorActivity
);
34 this._codeMirror
.on("blur", this._blur
);
35 this._delegate
.initialize(this._textEditor
);
39 * @param {!WebInspector.TextEditorAutocompleteDelegate} delegate
41 setDelegate: function(delegate
)
44 this._delegate
.dispose();
45 this._delegate
= delegate
;
49 * @param {boolean} enabled
51 setEnabled: function(enabled
)
53 if (enabled
=== this._enabled
)
55 this._enabled
= enabled
;
59 this._delegate
.dispose();
61 this._delegate
.initialize();
65 * @param {!CodeMirror} codeMirror
66 * @param {!Array.<!CodeMirror.ChangeObject>} changes
68 _changes: function(codeMirror
, changes
)
70 if (!changes
.length
|| !this._enabled
|| !this._delegate
)
73 var singleCharInput
= false;
74 for (var changeIndex
= 0; changeIndex
< changes
.length
; ++changeIndex
) {
75 var changeObject
= changes
[changeIndex
];
76 singleCharInput
= (changeObject
.origin
=== "+input" && changeObject
.text
.length
=== 1 && changeObject
.text
[0].length
=== 1) ||
77 (this._suggestBox
&& changeObject
.origin
=== "+delete" && changeObject
.removed
.length
=== 1 && changeObject
.removed
[0].length
=== 1);
80 setImmediate(this.autocomplete
.bind(this));
85 this.finishAutocomplete();
89 * @param {!WebInspector.TextRange} mainSelection
92 _validateSelectionsContexts: function(mainSelection
)
94 var selections
= this._codeMirror
.listSelections();
95 if (selections
.length
<= 1)
97 var mainSelectionContext
= this._textEditor
.copyRange(mainSelection
);
98 for (var i
= 0; i
< selections
.length
; ++i
) {
99 var wordRange
= this._delegate
.substituteRange(this._textEditor
, selections
[i
].head
.line
, selections
[i
].head
.ch
);
102 var context
= this._textEditor
.copyRange(wordRange
);
103 if (context
!== mainSelectionContext
)
109 autocomplete: function()
111 if (!this._enabled
|| !this._delegate
)
113 this._initializeIfNeeded();
114 if (this._codeMirror
.somethingSelected()) {
115 this.finishAutocomplete();
119 var cursor
= this._codeMirror
.getCursor("head");
120 var substituteRange
= this._delegate
.substituteRange(this._textEditor
, cursor
.line
, cursor
.ch
);
121 if (!substituteRange
|| !this._validateSelectionsContexts(substituteRange
)) {
122 this.finishAutocomplete();
126 var prefixRange
= substituteRange
.clone();
127 prefixRange
.endColumn
= cursor
.ch
;
129 var wordsWithPrefix
= this._delegate
.wordsWithPrefix(this._textEditor
, prefixRange
, substituteRange
);
130 if (!wordsWithPrefix
.length
) {
131 this.finishAutocomplete();
135 if (!this._suggestBox
)
136 this._suggestBox
= new WebInspector
.SuggestBox(this, 6);
137 var oldPrefixRange
= this._prefixRange
;
138 this._prefixRange
= prefixRange
;
139 if (!oldPrefixRange
|| prefixRange
.startLine
!== oldPrefixRange
.startLine
|| prefixRange
.startColumn
!== oldPrefixRange
.startColumn
)
140 this._updateAnchorBox();
141 this._suggestBox
.updateSuggestions(this._anchorBox
, wordsWithPrefix
, 0, true, this._textEditor
.copyRange(prefixRange
));
142 if (!this._suggestBox
.visible())
143 this.finishAutocomplete();
144 this._onSuggestionsShownForTest(wordsWithPrefix
);
148 * @param {!Array.<string>} suggestions
150 _onSuggestionsShownForTest: function(suggestions
) { },
152 finishAutocomplete: function()
154 if (!this._suggestBox
)
156 this._suggestBox
.hide();
157 this._suggestBox
= null;
158 this._prefixRange
= null;
159 this._anchorBox
= null;
168 if (!this._suggestBox
)
170 if (e
.keyCode
=== WebInspector
.KeyboardShortcut
.Keys
.Esc
.code
) {
171 this.finishAutocomplete();
174 if (e
.keyCode
=== WebInspector
.KeyboardShortcut
.Keys
.Tab
.code
) {
175 this._suggestBox
.acceptSuggestion();
176 this.finishAutocomplete();
179 return this._suggestBox
.keyPressed(e
);
184 * @param {string} suggestion
185 * @param {boolean=} isIntermediateSuggestion
187 applySuggestion: function(suggestion
, isIntermediateSuggestion
)
189 this._currentSuggestion
= suggestion
;
195 acceptSuggestion: function()
197 if (this._prefixRange
.endColumn
- this._prefixRange
.startColumn
=== this._currentSuggestion
.length
)
200 var selections
= this._codeMirror
.listSelections().slice();
201 var prefixLength
= this._prefixRange
.endColumn
- this._prefixRange
.startColumn
;
202 for (var i
= selections
.length
- 1; i
>= 0; --i
) {
203 var start
= selections
[i
].head
;
204 var end
= new CodeMirror
.Pos(start
.line
, start
.ch
- prefixLength
);
205 this._codeMirror
.replaceRange(this._currentSuggestion
, start
, end
, "+autocomplete");
209 _onScroll: function()
211 if (!this._suggestBox
)
213 var cursor
= this._codeMirror
.getCursor();
214 var scrollInfo
= this._codeMirror
.getScrollInfo();
215 var topmostLineNumber
= this._codeMirror
.lineAtHeight(scrollInfo
.top
, "local");
216 var bottomLine
= this._codeMirror
.lineAtHeight(scrollInfo
.top
+ scrollInfo
.clientHeight
, "local");
217 if (cursor
.line
< topmostLineNumber
|| cursor
.line
> bottomLine
)
218 this.finishAutocomplete();
220 this._updateAnchorBox();
221 this._suggestBox
.setPosition(this._anchorBox
);
225 _onCursorActivity: function()
227 if (!this._suggestBox
)
229 var cursor
= this._codeMirror
.getCursor();
230 if (cursor
.line
!== this._prefixRange
.startLine
|| cursor
.ch
> this._prefixRange
.endColumn
|| cursor
.ch
<= this._prefixRange
.startColumn
)
231 this.finishAutocomplete();
234 _updateAnchorBox: function()
236 var line
= this._prefixRange
.startLine
;
237 var column
= this._prefixRange
.startColumn
;
238 var metrics
= this._textEditor
.cursorPositionToCoordinates(line
, column
);
239 this._anchorBox
= metrics
? new AnchorBox(metrics
.x
, metrics
.y
, 0, metrics
.height
) : null;
246 WebInspector
.TextEditorAutocompleteDelegate = function() {}
248 WebInspector
.TextEditorAutocompleteDelegate
.prototype = {
250 * @param {!WebInspector.CodeMirrorTextEditor} editor
251 * @param {number} lineNumber
252 * @param {number} columnNumber
253 * @return {?WebInspector.TextRange}
255 substituteRange: function(editor
, lineNumber
, columnNumber
) {},
258 * @param {!WebInspector.CodeMirrorTextEditor} editor
259 * @param {!WebInspector.TextRange} prefixRange
260 * @param {!WebInspector.TextRange} substituteRange
261 * @return {!Array.<string>}
263 wordsWithPrefix: function(editor
, prefixRange
, substituteRange
) {},
266 * @param {!WebInspector.CodeMirrorTextEditor} editor
268 initialize: function(editor
) {},
270 dispose: function() {}
275 * @implements {WebInspector.TextEditorAutocompleteDelegate}
276 * @param {string=} additionalWordChars
278 WebInspector
.SimpleAutocompleteDelegate = function(additionalWordChars
)
280 this._additionalWordChars
= additionalWordChars
;
283 WebInspector
.SimpleAutocompleteDelegate
.prototype = {
286 * @param {!WebInspector.CodeMirrorTextEditor} editor
288 initialize: function(editor
)
290 if (this._dictionary
)
291 this._dictionary
.dispose();
292 this._dictionary
= editor
.createTextDictionary(this._additionalWordChars
);
300 if (this._dictionary
) {
301 this._dictionary
.dispose();
302 delete this._dictionary
;
308 * @param {!WebInspector.CodeMirrorTextEditor} editor
309 * @param {number} lineNumber
310 * @param {number} columnNumber
311 * @return {?WebInspector.TextRange}
313 substituteRange: function(editor
, lineNumber
, columnNumber
)
315 return editor
.wordRangeForCursorPosition(lineNumber
, columnNumber
, this._dictionary
.isWordChar
.bind(this._dictionary
));
320 * @param {!WebInspector.CodeMirrorTextEditor} editor
321 * @param {!WebInspector.TextRange} prefixRange
322 * @param {!WebInspector.TextRange} substituteRange
323 * @return {!Array.<string>}
325 wordsWithPrefix: function(editor
, prefixRange
, substituteRange
)
327 if (prefixRange
.startColumn
=== prefixRange
.endColumn
)
330 var dictionary
= this._dictionary
;
331 var completions
= dictionary
.wordsWithPrefix(editor
.copyRange(prefixRange
));
332 var substituteWord
= editor
.copyRange(substituteRange
);
333 if (dictionary
.wordCount(substituteWord
) === 1)
334 completions
= completions
.filter(excludeFilter
.bind(null, substituteWord
));
336 completions
.sort(sortSuggestions
);
339 function sortSuggestions(a
, b
)
341 return dictionary
.wordCount(b
) - dictionary
.wordCount(a
) || a
.length
- b
.length
;
344 function excludeFilter(excludeWord
, word
)
346 return word
!== excludeWord
;