OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2008 Apple Inc. All rights reserved. | 2 * Copyright (C) 2008 Apple Inc. All rights reserved. |
3 * Copyright (C) 2011 Google Inc. All rights reserved. | 3 * Copyright (C) 2011 Google Inc. All rights reserved. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
7 * are met: | 7 * are met: |
8 * | 8 * |
9 * 1. Redistributions of source code must retain the above copyright | 9 * 1. Redistributions of source code must retain the above copyright |
10 * notice, this list of conditions and the following disclaimer. | 10 * notice, this list of conditions and the following disclaimer. |
11 * 2. Redistributions in binary form must reproduce the above copyright | 11 * 2. Redistributions in binary form must reproduce the above copyright |
12 * notice, this list of conditions and the following disclaimer in the | 12 * notice, this list of conditions and the following disclaimer in the |
13 * documentation and/or other materials provided with the distribution. | 13 * documentation and/or other materials provided with the distribution. |
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of | 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
15 * its contributors may be used to endorse or promote products derived | 15 * its contributors may be used to endorse or promote products derived |
16 * from this software without specific prior written permission. | 16 * from this software without specific prior written permission. |
17 * | 17 * |
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 */ | 28 */ |
29 | |
30 /** | 29 /** |
31 * @constructor | |
32 * @extends {WebInspector.Object} | |
33 * @implements {WebInspector.SuggestBoxDelegate} | 30 * @implements {WebInspector.SuggestBoxDelegate} |
| 31 * @unrestricted |
34 */ | 32 */ |
35 WebInspector.TextPrompt = function() | 33 WebInspector.TextPrompt = class extends WebInspector.Object { |
36 { | 34 constructor() { |
| 35 super(); |
37 /** | 36 /** |
38 * @type {!Element|undefined} | 37 * @type {!Element|undefined} |
39 */ | 38 */ |
40 this._proxyElement; | 39 this._proxyElement; |
41 this._proxyElementDisplay = "inline-block"; | 40 this._proxyElementDisplay = 'inline-block'; |
42 this._autocompletionTimeout = WebInspector.TextPrompt.DefaultAutocompletionT
imeout; | 41 this._autocompletionTimeout = WebInspector.TextPrompt.DefaultAutocompletionT
imeout; |
43 this._title = ""; | 42 this._title = ''; |
44 this._prefixRange = null; | 43 this._prefixRange = null; |
45 this._previousText = ""; | 44 this._previousText = ''; |
46 this._currentSuggestion = ""; | 45 this._currentSuggestion = ''; |
47 this._completionRequestId = 0; | 46 this._completionRequestId = 0; |
48 this._ghostTextElement = createElementWithClass("span", "auto-complete-text"
); | 47 this._ghostTextElement = createElementWithClass('span', 'auto-complete-text'
); |
| 48 } |
| 49 |
| 50 /** |
| 51 * @param {function(!Element, !Range, boolean, function(!Array.<string>, numbe
r=))} completions |
| 52 * @param {string=} stopCharacters |
| 53 */ |
| 54 initialize(completions, stopCharacters) { |
| 55 this._loadCompletions = completions; |
| 56 this._completionStopCharacters = stopCharacters || ' =:[({;,!+-*/&|^<>.'; |
| 57 } |
| 58 |
| 59 /** |
| 60 * @param {number} timeout |
| 61 */ |
| 62 setAutocompletionTimeout(timeout) { |
| 63 this._autocompletionTimeout = timeout; |
| 64 } |
| 65 |
| 66 /** |
| 67 * @param {boolean} suggestBoxEnabled |
| 68 */ |
| 69 setSuggestBoxEnabled(suggestBoxEnabled) { |
| 70 this._suggestBoxEnabled = suggestBoxEnabled; |
| 71 } |
| 72 |
| 73 renderAsBlock() { |
| 74 this._proxyElementDisplay = 'block'; |
| 75 } |
| 76 |
| 77 /** |
| 78 * Clients should never attach any event listeners to the |element|. Instead, |
| 79 * they should use the result of this method to attach listeners for bubbling
events. |
| 80 * |
| 81 * @param {!Element} element |
| 82 * @return {!Element} |
| 83 */ |
| 84 attach(element) { |
| 85 return this._attachInternal(element); |
| 86 } |
| 87 |
| 88 /** |
| 89 * Clients should never attach any event listeners to the |element|. Instead, |
| 90 * they should use the result of this method to attach listeners for bubbling
events |
| 91 * or the |blurListener| parameter to register a "blur" event listener on the
|element| |
| 92 * (since the "blur" event does not bubble.) |
| 93 * |
| 94 * @param {!Element} element |
| 95 * @param {function(!Event)} blurListener |
| 96 * @return {!Element} |
| 97 */ |
| 98 attachAndStartEditing(element, blurListener) { |
| 99 var proxyElement = this._attachInternal(element); |
| 100 this._startEditing(blurListener); |
| 101 return proxyElement; |
| 102 } |
| 103 |
| 104 /** |
| 105 * @param {!Element} element |
| 106 * @return {!Element} |
| 107 */ |
| 108 _attachInternal(element) { |
| 109 if (this._proxyElement) |
| 110 throw 'Cannot attach an attached TextPrompt'; |
| 111 this._element = element; |
| 112 |
| 113 this._boundOnKeyDown = this.onKeyDown.bind(this); |
| 114 this._boundOnInput = this.onInput.bind(this); |
| 115 this._boundOnMouseWheel = this.onMouseWheel.bind(this); |
| 116 this._boundClearAutocomplete = this.clearAutocomplete.bind(this); |
| 117 this._proxyElement = element.ownerDocument.createElement('span'); |
| 118 var shadowRoot = WebInspector.createShadowRootWithCoreStyles(this._proxyElem
ent, 'ui/textPrompt.css'); |
| 119 this._contentElement = shadowRoot.createChild('div'); |
| 120 this._contentElement.createChild('content'); |
| 121 this._proxyElement.style.display = this._proxyElementDisplay; |
| 122 element.parentElement.insertBefore(this._proxyElement, element); |
| 123 this._proxyElement.appendChild(element); |
| 124 this._element.classList.add('text-prompt'); |
| 125 this._element.addEventListener('keydown', this._boundOnKeyDown, false); |
| 126 this._element.addEventListener('input', this._boundOnInput, false); |
| 127 this._element.addEventListener('mousewheel', this._boundOnMouseWheel, false)
; |
| 128 this._element.addEventListener('selectstart', this._boundClearAutocomplete,
false); |
| 129 this._element.addEventListener('blur', this._boundClearAutocomplete, false); |
| 130 this._element.ownerDocument.defaultView.addEventListener('resize', this._bou
ndClearAutocomplete, false); |
| 131 |
| 132 if (this._suggestBoxEnabled) |
| 133 this._suggestBox = new WebInspector.SuggestBox(this, 20, true); |
| 134 |
| 135 if (this._title) |
| 136 this._proxyElement.title = this._title; |
| 137 |
| 138 return this._proxyElement; |
| 139 } |
| 140 |
| 141 detach() { |
| 142 this._removeFromElement(); |
| 143 this._proxyElement.parentElement.insertBefore(this._element, this._proxyElem
ent); |
| 144 this._proxyElement.remove(); |
| 145 delete this._proxyElement; |
| 146 this._element.classList.remove('text-prompt'); |
| 147 this._focusRestorer.restore(); |
| 148 } |
| 149 |
| 150 /** |
| 151 * @return {string} |
| 152 */ |
| 153 textWithCurrentSuggestion() { |
| 154 return this._element.textContent; |
| 155 } |
| 156 |
| 157 /** |
| 158 * @return {string} |
| 159 */ |
| 160 text() { |
| 161 var text = this.textWithCurrentSuggestion(); |
| 162 if (this._ghostTextElement.parentNode) { |
| 163 var addition = this._ghostTextElement.textContent; |
| 164 text = text.substring(0, text.length - addition.length); |
| 165 } |
| 166 return text; |
| 167 } |
| 168 |
| 169 /** |
| 170 * @param {string} x |
| 171 */ |
| 172 setText(x) { |
| 173 this.clearAutocomplete(); |
| 174 if (!x) { |
| 175 // Append a break element instead of setting textContent to make sure the
selection is inside the prompt. |
| 176 this._element.removeChildren(); |
| 177 this._element.createChild('br'); |
| 178 } else { |
| 179 this._element.textContent = x; |
| 180 } |
| 181 this._previousText = this.text(); |
| 182 |
| 183 this.moveCaretToEndOfPrompt(); |
| 184 this._element.scrollIntoView(); |
| 185 } |
| 186 |
| 187 /** |
| 188 * @return {string} |
| 189 */ |
| 190 title() { |
| 191 return this._title; |
| 192 } |
| 193 |
| 194 /** |
| 195 * @param {string} title |
| 196 */ |
| 197 setTitle(title) { |
| 198 this._title = title; |
| 199 if (this._proxyElement) |
| 200 this._proxyElement.title = title; |
| 201 } |
| 202 |
| 203 _removeFromElement() { |
| 204 this.clearAutocomplete(); |
| 205 this._element.removeEventListener('keydown', this._boundOnKeyDown, false); |
| 206 this._element.removeEventListener('input', this._boundOnInput, false); |
| 207 this._element.removeEventListener('selectstart', this._boundClearAutocomplet
e, false); |
| 208 this._element.removeEventListener('blur', this._boundClearAutocomplete, fals
e); |
| 209 this._element.ownerDocument.defaultView.removeEventListener('resize', this._
boundClearAutocomplete, false); |
| 210 if (this._isEditing) |
| 211 this._stopEditing(); |
| 212 if (this._suggestBox) |
| 213 this._suggestBox.removeFromElement(); |
| 214 } |
| 215 |
| 216 /** |
| 217 * @param {function(!Event)=} blurListener |
| 218 */ |
| 219 _startEditing(blurListener) { |
| 220 this._isEditing = true; |
| 221 this._contentElement.classList.add('text-prompt-editing'); |
| 222 if (blurListener) { |
| 223 this._blurListener = blurListener; |
| 224 this._element.addEventListener('blur', this._blurListener, false); |
| 225 } |
| 226 this._oldTabIndex = this._element.tabIndex; |
| 227 if (this._element.tabIndex < 0) |
| 228 this._element.tabIndex = 0; |
| 229 this._focusRestorer = new WebInspector.ElementFocusRestorer(this._element); |
| 230 if (!this.text()) |
| 231 this.autoCompleteSoon(); |
| 232 } |
| 233 |
| 234 _stopEditing() { |
| 235 this._element.tabIndex = this._oldTabIndex; |
| 236 if (this._blurListener) |
| 237 this._element.removeEventListener('blur', this._blurListener, false); |
| 238 this._contentElement.classList.remove('text-prompt-editing'); |
| 239 delete this._isEditing; |
| 240 } |
| 241 |
| 242 /** |
| 243 * @param {!Event} event |
| 244 */ |
| 245 onMouseWheel(event) { |
| 246 // Subclasses can implement. |
| 247 } |
| 248 |
| 249 /** |
| 250 * @param {!Event} event |
| 251 */ |
| 252 onKeyDown(event) { |
| 253 var handled = false; |
| 254 |
| 255 switch (event.key) { |
| 256 case 'Tab': |
| 257 handled = this.tabKeyPressed(event); |
| 258 break; |
| 259 case 'ArrowLeft': |
| 260 case 'Home': |
| 261 this.clearAutocomplete(); |
| 262 break; |
| 263 case 'ArrowRight': |
| 264 case 'End': |
| 265 if (this._isCaretAtEndOfPrompt()) |
| 266 handled = this.acceptAutoComplete(); |
| 267 else |
| 268 this.clearAutocomplete(); |
| 269 break; |
| 270 case 'Escape': |
| 271 if (this._isSuggestBoxVisible()) { |
| 272 this.clearAutocomplete(); |
| 273 handled = true; |
| 274 } |
| 275 break; |
| 276 case ' ': // Space |
| 277 if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey)
{ |
| 278 this.autoCompleteSoon(true); |
| 279 handled = true; |
| 280 } |
| 281 break; |
| 282 case 'Alt': |
| 283 case 'Meta': |
| 284 case 'Shift': |
| 285 case 'Control': |
| 286 break; |
| 287 } |
| 288 |
| 289 if (!handled && this._isSuggestBoxVisible()) |
| 290 handled = this._suggestBox.keyPressed(event); |
| 291 |
| 292 if (handled) |
| 293 event.consume(true); |
| 294 } |
| 295 |
| 296 /** |
| 297 * @param {!Event} event |
| 298 */ |
| 299 onInput(event) { |
| 300 var text = this.text(); |
| 301 var hasCommonPrefix = text.startsWith(this._previousText) || this._previousT
ext.startsWith(text); |
| 302 if (this._prefixRange && hasCommonPrefix) |
| 303 this._prefixRange.endColumn += text.length - this._previousText.length; |
| 304 this._refreshGhostText(); |
| 305 this._previousText = text; |
| 306 |
| 307 this.autoCompleteSoon(); |
| 308 } |
| 309 |
| 310 /** |
| 311 * @return {boolean} |
| 312 */ |
| 313 acceptAutoComplete() { |
| 314 var result = false; |
| 315 if (this._isSuggestBoxVisible()) |
| 316 result = this._suggestBox.acceptSuggestion(); |
| 317 if (!result) |
| 318 result = this._acceptSuggestionInternal(); |
| 319 |
| 320 return result; |
| 321 } |
| 322 |
| 323 clearAutocomplete() { |
| 324 if (this._isSuggestBoxVisible()) |
| 325 this._suggestBox.hide(); |
| 326 this._clearAutocompleteTimeout(); |
| 327 this._prefixRange = null; |
| 328 this._refreshGhostText(); |
| 329 } |
| 330 |
| 331 _refreshGhostText() { |
| 332 if (this._prefixRange && this._isCaretAtEndOfPrompt()) { |
| 333 this._ghostTextElement.textContent = |
| 334 this._currentSuggestion.substring(this._prefixRange.endColumn - this._
prefixRange.startColumn); |
| 335 this._element.appendChild(this._ghostTextElement); |
| 336 } else { |
| 337 this._ghostTextElement.remove(); |
| 338 } |
| 339 } |
| 340 |
| 341 _clearAutocompleteTimeout() { |
| 342 if (this._completeTimeout) { |
| 343 clearTimeout(this._completeTimeout); |
| 344 delete this._completeTimeout; |
| 345 } |
| 346 this._completionRequestId++; |
| 347 } |
| 348 |
| 349 /** |
| 350 * @param {boolean=} force |
| 351 */ |
| 352 autoCompleteSoon(force) { |
| 353 var immediately = this._isSuggestBoxVisible() || force; |
| 354 if (!this._completeTimeout) |
| 355 this._completeTimeout = |
| 356 setTimeout(this.complete.bind(this, force), immediately ? 0 : this._au
tocompletionTimeout); |
| 357 } |
| 358 |
| 359 /** |
| 360 * @param {boolean=} force |
| 361 * @param {boolean=} reverse |
| 362 */ |
| 363 complete(force, reverse) { |
| 364 this._clearAutocompleteTimeout(); |
| 365 var selection = this._element.getComponentSelection(); |
| 366 var selectionRange = selection && selection.rangeCount ? selection.getRangeA
t(0) : null; |
| 367 if (!selectionRange) |
| 368 return; |
| 369 |
| 370 var shouldExit; |
| 371 |
| 372 if (!force && !this._isCaretAtEndOfPrompt() && !this._isSuggestBoxVisible()) |
| 373 shouldExit = true; |
| 374 else if (!selection.isCollapsed) |
| 375 shouldExit = true; |
| 376 else if (!force) { |
| 377 // BUG72018: Do not show suggest box if caret is followed by a non-stop ch
aracter. |
| 378 var wordSuffixRange = selectionRange.startContainer.rangeOfWord( |
| 379 selectionRange.endOffset, this._completionStopCharacters, this._elemen
t, 'forward'); |
| 380 var autocompleteTextLength = this._ghostTextElement.parentNode ? this._gho
stTextElement.textContent.length : 0; |
| 381 if (wordSuffixRange.toString().length !== autocompleteTextLength) |
| 382 shouldExit = true; |
| 383 } |
| 384 if (shouldExit) { |
| 385 this.clearAutocomplete(); |
| 386 return; |
| 387 } |
| 388 |
| 389 var wordPrefixRange = selectionRange.startContainer.rangeOfWord( |
| 390 selectionRange.startOffset, this._completionStopCharacters, this._elemen
t, 'backward'); |
| 391 this._loadCompletions( |
| 392 /** @type {!Element} */ (this._proxyElement), wordPrefixRange, force ||
false, |
| 393 this._completionsReady.bind(this, ++this._completionRequestId, selection
, wordPrefixRange, !!reverse, !!force)); |
| 394 } |
| 395 |
| 396 disableDefaultSuggestionForEmptyInput() { |
| 397 this._disableDefaultSuggestionForEmptyInput = true; |
| 398 } |
| 399 |
| 400 /** |
| 401 * @param {!Selection} selection |
| 402 * @param {!Range} textRange |
| 403 */ |
| 404 _boxForAnchorAtStart(selection, textRange) { |
| 405 var rangeCopy = selection.getRangeAt(0).cloneRange(); |
| 406 var anchorElement = createElement('span'); |
| 407 anchorElement.textContent = '\u200B'; |
| 408 textRange.insertNode(anchorElement); |
| 409 var box = anchorElement.boxInWindow(window); |
| 410 anchorElement.remove(); |
| 411 selection.removeAllRanges(); |
| 412 selection.addRange(rangeCopy); |
| 413 return box; |
| 414 } |
| 415 |
| 416 /** |
| 417 * @return {?Range} |
| 418 * @suppressGlobalPropertiesCheck |
| 419 */ |
| 420 _createRange() { |
| 421 return document.createRange(); |
| 422 } |
| 423 |
| 424 /** |
| 425 * @param {string} prefix |
| 426 * @return {!WebInspector.SuggestBox.Suggestions} |
| 427 */ |
| 428 additionalCompletions(prefix) { |
| 429 return []; |
| 430 } |
| 431 |
| 432 /** |
| 433 * @param {number} completionRequestId |
| 434 * @param {!Selection} selection |
| 435 * @param {!Range} originalWordPrefixRange |
| 436 * @param {boolean} reverse |
| 437 * @param {boolean} force |
| 438 * @param {!Array.<string>} completions |
| 439 * @param {number=} selectedIndex |
| 440 */ |
| 441 _completionsReady( |
| 442 completionRequestId, |
| 443 selection, |
| 444 originalWordPrefixRange, |
| 445 reverse, |
| 446 force, |
| 447 completions, |
| 448 selectedIndex) { |
| 449 if (this._completionRequestId !== completionRequestId) |
| 450 return; |
| 451 |
| 452 var prefix = originalWordPrefixRange.toString(); |
| 453 |
| 454 // Filter out dupes. |
| 455 var store = new Set(); |
| 456 completions = completions.filter(item => !store.has(item) && !!store.add(ite
m)); |
| 457 var annotatedCompletions = completions.map(item => ({title: item})); |
| 458 |
| 459 if (prefix || force) { |
| 460 if (prefix) |
| 461 annotatedCompletions = annotatedCompletions.concat(this.additionalComple
tions(prefix)); |
| 462 else |
| 463 annotatedCompletions = this.additionalCompletions(prefix).concat(annotat
edCompletions); |
| 464 } |
| 465 |
| 466 if (!annotatedCompletions.length) { |
| 467 this.clearAutocomplete(); |
| 468 return; |
| 469 } |
| 470 |
| 471 var selectionRange = selection.getRangeAt(0); |
| 472 |
| 473 var fullWordRange = this._createRange(); |
| 474 fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordP
refixRange.startOffset); |
| 475 fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset); |
| 476 |
| 477 if (prefix + selectionRange.toString() !== fullWordRange.toString()) |
| 478 return; |
| 479 |
| 480 selectedIndex = (this._disableDefaultSuggestionForEmptyInput && !this.text()
) ? -1 : (selectedIndex || 0); |
| 481 |
| 482 if (this._suggestBox) |
| 483 this._suggestBox.updateSuggestions( |
| 484 this._boxForAnchorAtStart(selection, fullWordRange), annotatedCompleti
ons, selectedIndex, |
| 485 !this._isCaretAtEndOfPrompt(), this.text()); |
| 486 |
| 487 var beforeRange = this._createRange(); |
| 488 beforeRange.setStart(this._element, 0); |
| 489 beforeRange.setEnd(fullWordRange.startContainer, fullWordRange.startOffset); |
| 490 this._prefixRange = new WebInspector.TextRange( |
| 491 0, beforeRange.toString().length, 0, beforeRange.toString().length + ful
lWordRange.toString().length); |
| 492 |
| 493 if (selectedIndex === -1) |
| 494 return; |
| 495 this.applySuggestion(annotatedCompletions[selectedIndex].title, true); |
| 496 } |
| 497 |
| 498 /** |
| 499 * @override |
| 500 * @param {string} suggestion |
| 501 * @param {boolean=} isIntermediateSuggestion |
| 502 */ |
| 503 applySuggestion(suggestion, isIntermediateSuggestion) { |
| 504 if (!this._prefixRange) |
| 505 return; |
| 506 this._currentSuggestion = suggestion; |
| 507 this._refreshGhostText(); |
| 508 if (isIntermediateSuggestion) |
| 509 this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemApplied); |
| 510 } |
| 511 |
| 512 /** |
| 513 * @override |
| 514 */ |
| 515 acceptSuggestion() { |
| 516 this._acceptSuggestionInternal(); |
| 517 } |
| 518 |
| 519 /** |
| 520 * @return {boolean} |
| 521 */ |
| 522 _acceptSuggestionInternal() { |
| 523 if (!this._prefixRange) |
| 524 return false; |
| 525 |
| 526 var text = this.text(); |
| 527 this._element.textContent = text.substring(0, this._prefixRange.startColumn)
+ this._currentSuggestion + |
| 528 text.substring(this._prefixRange.endColumn); |
| 529 this._setDOMSelection( |
| 530 this._prefixRange.startColumn + this._currentSuggestion.length, |
| 531 this._prefixRange.startColumn + this._currentSuggestion.length); |
| 532 |
| 533 this.clearAutocomplete(); |
| 534 this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemAccepted); |
| 535 |
| 536 return true; |
| 537 } |
| 538 |
| 539 /** |
| 540 * @param {number} startColumn |
| 541 * @param {number} endColumn |
| 542 */ |
| 543 _setDOMSelection(startColumn, endColumn) { |
| 544 this._element.normalize(); |
| 545 var node = this._element.childNodes[0]; |
| 546 if (!node || node === this._ghostTextElement) |
| 547 return; |
| 548 var range = this._createRange(); |
| 549 range.setStart(node, startColumn); |
| 550 range.setEnd(node, endColumn); |
| 551 var selection = this._element.getComponentSelection(); |
| 552 selection.removeAllRanges(); |
| 553 selection.addRange(range); |
| 554 } |
| 555 |
| 556 /** |
| 557 * @return {boolean} |
| 558 */ |
| 559 _isSuggestBoxVisible() { |
| 560 return this._suggestBox && this._suggestBox.visible(); |
| 561 } |
| 562 |
| 563 /** |
| 564 * @return {boolean} |
| 565 */ |
| 566 isCaretInsidePrompt() { |
| 567 var selection = this._element.getComponentSelection(); |
| 568 // @see crbug.com/602541 |
| 569 var selectionRange = selection && selection.rangeCount ? selection.getRangeA
t(0) : null; |
| 570 if (!selectionRange || !selection.isCollapsed) |
| 571 return false; |
| 572 return selectionRange.startContainer.isSelfOrDescendant(this._element); |
| 573 } |
| 574 |
| 575 /** |
| 576 * @return {boolean} |
| 577 */ |
| 578 _isCaretAtEndOfPrompt() { |
| 579 var selection = this._element.getComponentSelection(); |
| 580 var selectionRange = selection && selection.rangeCount ? selection.getRangeA
t(0) : null; |
| 581 if (!selectionRange || !selection.isCollapsed) |
| 582 return false; |
| 583 |
| 584 var node = selectionRange.startContainer; |
| 585 if (!node.isSelfOrDescendant(this._element)) |
| 586 return false; |
| 587 |
| 588 if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.no
deValue.length) |
| 589 return false; |
| 590 |
| 591 var foundNextText = false; |
| 592 while (node) { |
| 593 if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) { |
| 594 if (foundNextText && !this._ghostTextElement.isAncestor(node)) |
| 595 return false; |
| 596 foundNextText = true; |
| 597 } |
| 598 |
| 599 node = node.traverseNextNode(this._element); |
| 600 } |
| 601 |
| 602 return true; |
| 603 } |
| 604 |
| 605 moveCaretToEndOfPrompt() { |
| 606 var selection = this._element.getComponentSelection(); |
| 607 var selectionRange = this._createRange(); |
| 608 |
| 609 var container = this._element; |
| 610 while (container.childNodes.length) |
| 611 container = container.lastChild; |
| 612 var offset = container.nodeType === Node.TEXT_NODE ? container.textContent.l
ength : 0; |
| 613 selectionRange.setStart(container, offset); |
| 614 selectionRange.setEnd(container, offset); |
| 615 |
| 616 selection.removeAllRanges(); |
| 617 selection.addRange(selectionRange); |
| 618 } |
| 619 |
| 620 /** |
| 621 * @param {!Event} event |
| 622 * @return {boolean} |
| 623 */ |
| 624 tabKeyPressed(event) { |
| 625 this.acceptAutoComplete(); |
| 626 |
| 627 // Consume the key. |
| 628 return true; |
| 629 } |
| 630 |
| 631 /** |
| 632 * @return {?Element} |
| 633 */ |
| 634 proxyElementForTests() { |
| 635 return this._proxyElement || null; |
| 636 } |
49 }; | 637 }; |
50 | 638 |
51 WebInspector.TextPrompt.DefaultAutocompletionTimeout = 250; | 639 WebInspector.TextPrompt.DefaultAutocompletionTimeout = 250; |
52 | 640 |
53 /** @enum {symbol} */ | 641 /** @enum {symbol} */ |
54 WebInspector.TextPrompt.Events = { | 642 WebInspector.TextPrompt.Events = { |
55 ItemApplied: Symbol("text-prompt-item-applied"), | 643 ItemApplied: Symbol('text-prompt-item-applied'), |
56 ItemAccepted: Symbol("text-prompt-item-accepted") | 644 ItemAccepted: Symbol('text-prompt-item-accepted') |
57 }; | 645 }; |
58 | |
59 WebInspector.TextPrompt.prototype = { | |
60 /** | |
61 * @param {function(!Element, !Range, boolean, function(!Array.<string>, num
ber=))} completions | |
62 * @param {string=} stopCharacters | |
63 */ | |
64 initialize: function(completions, stopCharacters) | |
65 { | |
66 this._loadCompletions = completions; | |
67 this._completionStopCharacters = stopCharacters || " =:[({;,!+-*/&|^<>."
; | |
68 }, | |
69 | |
70 /** | |
71 * @param {number} timeout | |
72 */ | |
73 setAutocompletionTimeout: function(timeout) | |
74 { | |
75 this._autocompletionTimeout = timeout; | |
76 }, | |
77 | |
78 /** | |
79 * @param {boolean} suggestBoxEnabled | |
80 */ | |
81 setSuggestBoxEnabled: function(suggestBoxEnabled) | |
82 { | |
83 this._suggestBoxEnabled = suggestBoxEnabled; | |
84 }, | |
85 | |
86 renderAsBlock: function() | |
87 { | |
88 this._proxyElementDisplay = "block"; | |
89 }, | |
90 | |
91 /** | |
92 * Clients should never attach any event listeners to the |element|. Instead
, | |
93 * they should use the result of this method to attach listeners for bubblin
g events. | |
94 * | |
95 * @param {!Element} element | |
96 * @return {!Element} | |
97 */ | |
98 attach: function(element) | |
99 { | |
100 return this._attachInternal(element); | |
101 }, | |
102 | |
103 /** | |
104 * Clients should never attach any event listeners to the |element|. Instead
, | |
105 * they should use the result of this method to attach listeners for bubblin
g events | |
106 * or the |blurListener| parameter to register a "blur" event listener on th
e |element| | |
107 * (since the "blur" event does not bubble.) | |
108 * | |
109 * @param {!Element} element | |
110 * @param {function(!Event)} blurListener | |
111 * @return {!Element} | |
112 */ | |
113 attachAndStartEditing: function(element, blurListener) | |
114 { | |
115 var proxyElement = this._attachInternal(element); | |
116 this._startEditing(blurListener); | |
117 return proxyElement; | |
118 }, | |
119 | |
120 /** | |
121 * @param {!Element} element | |
122 * @return {!Element} | |
123 */ | |
124 _attachInternal: function(element) | |
125 { | |
126 if (this._proxyElement) | |
127 throw "Cannot attach an attached TextPrompt"; | |
128 this._element = element; | |
129 | |
130 this._boundOnKeyDown = this.onKeyDown.bind(this); | |
131 this._boundOnInput = this.onInput.bind(this); | |
132 this._boundOnMouseWheel = this.onMouseWheel.bind(this); | |
133 this._boundClearAutocomplete = this.clearAutocomplete.bind(this); | |
134 this._proxyElement = element.ownerDocument.createElement("span"); | |
135 var shadowRoot = WebInspector.createShadowRootWithCoreStyles(this._proxy
Element, "ui/textPrompt.css"); | |
136 this._contentElement = shadowRoot.createChild("div"); | |
137 this._contentElement.createChild("content"); | |
138 this._proxyElement.style.display = this._proxyElementDisplay; | |
139 element.parentElement.insertBefore(this._proxyElement, element); | |
140 this._proxyElement.appendChild(element); | |
141 this._element.classList.add("text-prompt"); | |
142 this._element.addEventListener("keydown", this._boundOnKeyDown, false); | |
143 this._element.addEventListener("input", this._boundOnInput, false); | |
144 this._element.addEventListener("mousewheel", this._boundOnMouseWheel, fa
lse); | |
145 this._element.addEventListener("selectstart", this._boundClearAutocomple
te, false); | |
146 this._element.addEventListener("blur", this._boundClearAutocomplete, fal
se); | |
147 this._element.ownerDocument.defaultView.addEventListener("resize", this.
_boundClearAutocomplete, false); | |
148 | |
149 if (this._suggestBoxEnabled) | |
150 this._suggestBox = new WebInspector.SuggestBox(this, 20, true); | |
151 | |
152 if (this._title) | |
153 this._proxyElement.title = this._title; | |
154 | |
155 return this._proxyElement; | |
156 }, | |
157 | |
158 detach: function() | |
159 { | |
160 this._removeFromElement(); | |
161 this._proxyElement.parentElement.insertBefore(this._element, this._proxy
Element); | |
162 this._proxyElement.remove(); | |
163 delete this._proxyElement; | |
164 this._element.classList.remove("text-prompt"); | |
165 this._focusRestorer.restore(); | |
166 }, | |
167 | |
168 /** | |
169 * @return {string} | |
170 */ | |
171 textWithCurrentSuggestion: function() | |
172 { | |
173 return this._element.textContent; | |
174 }, | |
175 | |
176 /** | |
177 * @return {string} | |
178 */ | |
179 text: function() | |
180 { | |
181 var text = this.textWithCurrentSuggestion(); | |
182 if (this._ghostTextElement.parentNode) { | |
183 var addition = this._ghostTextElement.textContent; | |
184 text = text.substring(0, text.length - addition.length); | |
185 } | |
186 return text; | |
187 }, | |
188 | |
189 /** | |
190 * @param {string} x | |
191 */ | |
192 setText: function(x) | |
193 { | |
194 this.clearAutocomplete(); | |
195 if (!x) { | |
196 // Append a break element instead of setting textContent to make sur
e the selection is inside the prompt. | |
197 this._element.removeChildren(); | |
198 this._element.createChild("br"); | |
199 } else { | |
200 this._element.textContent = x; | |
201 } | |
202 this._previousText = this.text(); | |
203 | |
204 this.moveCaretToEndOfPrompt(); | |
205 this._element.scrollIntoView(); | |
206 }, | |
207 | |
208 /** | |
209 * @return {string} | |
210 */ | |
211 title: function() | |
212 { | |
213 return this._title; | |
214 }, | |
215 | |
216 /** | |
217 * @param {string} title | |
218 */ | |
219 setTitle: function(title) | |
220 { | |
221 this._title = title; | |
222 if (this._proxyElement) | |
223 this._proxyElement.title = title; | |
224 }, | |
225 | |
226 _removeFromElement: function() | |
227 { | |
228 this.clearAutocomplete(); | |
229 this._element.removeEventListener("keydown", this._boundOnKeyDown, false
); | |
230 this._element.removeEventListener("input", this._boundOnInput, false); | |
231 this._element.removeEventListener("selectstart", this._boundClearAutocom
plete, false); | |
232 this._element.removeEventListener("blur", this._boundClearAutocomplete,
false); | |
233 this._element.ownerDocument.defaultView.removeEventListener("resize", th
is._boundClearAutocomplete, false); | |
234 if (this._isEditing) | |
235 this._stopEditing(); | |
236 if (this._suggestBox) | |
237 this._suggestBox.removeFromElement(); | |
238 }, | |
239 | |
240 /** | |
241 * @param {function(!Event)=} blurListener | |
242 */ | |
243 _startEditing: function(blurListener) | |
244 { | |
245 this._isEditing = true; | |
246 this._contentElement.classList.add("text-prompt-editing"); | |
247 if (blurListener) { | |
248 this._blurListener = blurListener; | |
249 this._element.addEventListener("blur", this._blurListener, false); | |
250 } | |
251 this._oldTabIndex = this._element.tabIndex; | |
252 if (this._element.tabIndex < 0) | |
253 this._element.tabIndex = 0; | |
254 this._focusRestorer = new WebInspector.ElementFocusRestorer(this._elemen
t); | |
255 if (!this.text()) | |
256 this.autoCompleteSoon(); | |
257 }, | |
258 | |
259 _stopEditing: function() | |
260 { | |
261 this._element.tabIndex = this._oldTabIndex; | |
262 if (this._blurListener) | |
263 this._element.removeEventListener("blur", this._blurListener, false)
; | |
264 this._contentElement.classList.remove("text-prompt-editing"); | |
265 delete this._isEditing; | |
266 }, | |
267 | |
268 /** | |
269 * @param {!Event} event | |
270 */ | |
271 onMouseWheel: function(event) | |
272 { | |
273 // Subclasses can implement. | |
274 }, | |
275 | |
276 /** | |
277 * @param {!Event} event | |
278 */ | |
279 onKeyDown: function(event) | |
280 { | |
281 var handled = false; | |
282 | |
283 switch (event.key) { | |
284 case "Tab": | |
285 handled = this.tabKeyPressed(event); | |
286 break; | |
287 case "ArrowLeft": | |
288 case "Home": | |
289 this.clearAutocomplete(); | |
290 break; | |
291 case "ArrowRight": | |
292 case "End": | |
293 if (this._isCaretAtEndOfPrompt()) | |
294 handled = this.acceptAutoComplete(); | |
295 else | |
296 this.clearAutocomplete(); | |
297 break; | |
298 case "Escape": | |
299 if (this._isSuggestBoxVisible()) { | |
300 this.clearAutocomplete(); | |
301 handled = true; | |
302 } | |
303 break; | |
304 case " ": // Space | |
305 if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shift
Key) { | |
306 this.autoCompleteSoon(true); | |
307 handled = true; | |
308 } | |
309 break; | |
310 case "Alt": | |
311 case "Meta": | |
312 case "Shift": | |
313 case "Control": | |
314 break; | |
315 } | |
316 | |
317 if (!handled && this._isSuggestBoxVisible()) | |
318 handled = this._suggestBox.keyPressed(event); | |
319 | |
320 if (handled) | |
321 event.consume(true); | |
322 }, | |
323 | |
324 /** | |
325 * @param {!Event} event | |
326 */ | |
327 onInput: function(event) | |
328 { | |
329 var text = this.text(); | |
330 var hasCommonPrefix = text.startsWith(this._previousText) || this._previ
ousText.startsWith(text); | |
331 if (this._prefixRange && hasCommonPrefix) | |
332 this._prefixRange.endColumn += text.length - this._previousText.leng
th; | |
333 this._refreshGhostText(); | |
334 this._previousText = text; | |
335 | |
336 this.autoCompleteSoon(); | |
337 }, | |
338 | |
339 /** | |
340 * @return {boolean} | |
341 */ | |
342 acceptAutoComplete: function() | |
343 { | |
344 var result = false; | |
345 if (this._isSuggestBoxVisible()) | |
346 result = this._suggestBox.acceptSuggestion(); | |
347 if (!result) | |
348 result = this._acceptSuggestionInternal(); | |
349 | |
350 return result; | |
351 }, | |
352 | |
353 clearAutocomplete: function() | |
354 { | |
355 if (this._isSuggestBoxVisible()) | |
356 this._suggestBox.hide(); | |
357 this._clearAutocompleteTimeout(); | |
358 this._prefixRange = null; | |
359 this._refreshGhostText(); | |
360 }, | |
361 | |
362 _refreshGhostText: function() | |
363 { | |
364 if (this._prefixRange && this._isCaretAtEndOfPrompt()) { | |
365 this._ghostTextElement.textContent = this._currentSuggestion.substri
ng(this._prefixRange.endColumn - this._prefixRange.startColumn); | |
366 this._element.appendChild(this._ghostTextElement); | |
367 } else { | |
368 this._ghostTextElement.remove(); | |
369 } | |
370 }, | |
371 | |
372 _clearAutocompleteTimeout: function() | |
373 { | |
374 if (this._completeTimeout) { | |
375 clearTimeout(this._completeTimeout); | |
376 delete this._completeTimeout; | |
377 } | |
378 this._completionRequestId++; | |
379 }, | |
380 | |
381 /** | |
382 * @param {boolean=} force | |
383 */ | |
384 autoCompleteSoon: function(force) | |
385 { | |
386 var immediately = this._isSuggestBoxVisible() || force; | |
387 if (!this._completeTimeout) | |
388 this._completeTimeout = setTimeout(this.complete.bind(this, force),
immediately ? 0 : this._autocompletionTimeout); | |
389 }, | |
390 | |
391 /** | |
392 * @param {boolean=} force | |
393 * @param {boolean=} reverse | |
394 */ | |
395 complete: function(force, reverse) | |
396 { | |
397 this._clearAutocompleteTimeout(); | |
398 var selection = this._element.getComponentSelection(); | |
399 var selectionRange = selection && selection.rangeCount ? selection.getRa
ngeAt(0) : null; | |
400 if (!selectionRange) | |
401 return; | |
402 | |
403 var shouldExit; | |
404 | |
405 if (!force && !this._isCaretAtEndOfPrompt() && !this._isSuggestBoxVisibl
e()) | |
406 shouldExit = true; | |
407 else if (!selection.isCollapsed) | |
408 shouldExit = true; | |
409 else if (!force) { | |
410 // BUG72018: Do not show suggest box if caret is followed by a non-s
top character. | |
411 var wordSuffixRange = selectionRange.startContainer.rangeOfWord(sele
ctionRange.endOffset, this._completionStopCharacters, this._element, "forward"); | |
412 var autocompleteTextLength = this._ghostTextElement.parentNode ? thi
s._ghostTextElement.textContent.length : 0; | |
413 if (wordSuffixRange.toString().length !== autocompleteTextLength) | |
414 shouldExit = true; | |
415 } | |
416 if (shouldExit) { | |
417 this.clearAutocomplete(); | |
418 return; | |
419 } | |
420 | |
421 var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectio
nRange.startOffset, this._completionStopCharacters, this._element, "backward"); | |
422 this._loadCompletions(/** @type {!Element} */ (this._proxyElement), word
PrefixRange, force || false, this._completionsReady.bind(this, ++this._completio
nRequestId, selection, wordPrefixRange, !!reverse, !!force)); | |
423 }, | |
424 | |
425 disableDefaultSuggestionForEmptyInput: function() | |
426 { | |
427 this._disableDefaultSuggestionForEmptyInput = true; | |
428 }, | |
429 | |
430 /** | |
431 * @param {!Selection} selection | |
432 * @param {!Range} textRange | |
433 */ | |
434 _boxForAnchorAtStart: function(selection, textRange) | |
435 { | |
436 var rangeCopy = selection.getRangeAt(0).cloneRange(); | |
437 var anchorElement = createElement("span"); | |
438 anchorElement.textContent = "\u200B"; | |
439 textRange.insertNode(anchorElement); | |
440 var box = anchorElement.boxInWindow(window); | |
441 anchorElement.remove(); | |
442 selection.removeAllRanges(); | |
443 selection.addRange(rangeCopy); | |
444 return box; | |
445 }, | |
446 | |
447 /** | |
448 * @return {?Range} | |
449 * @suppressGlobalPropertiesCheck | |
450 */ | |
451 _createRange: function() | |
452 { | |
453 return document.createRange(); | |
454 }, | |
455 | |
456 /** | |
457 * @param {string} prefix | |
458 * @return {!WebInspector.SuggestBox.Suggestions} | |
459 */ | |
460 additionalCompletions: function(prefix) | |
461 { | |
462 return []; | |
463 }, | |
464 | |
465 /** | |
466 * @param {number} completionRequestId | |
467 * @param {!Selection} selection | |
468 * @param {!Range} originalWordPrefixRange | |
469 * @param {boolean} reverse | |
470 * @param {boolean} force | |
471 * @param {!Array.<string>} completions | |
472 * @param {number=} selectedIndex | |
473 */ | |
474 _completionsReady: function(completionRequestId, selection, originalWordPref
ixRange, reverse, force, completions, selectedIndex) | |
475 { | |
476 if (this._completionRequestId !== completionRequestId) | |
477 return; | |
478 | |
479 var prefix = originalWordPrefixRange.toString(); | |
480 | |
481 // Filter out dupes. | |
482 var store = new Set(); | |
483 completions = completions.filter(item => !store.has(item) && !!store.add
(item)); | |
484 var annotatedCompletions = completions.map(item => ({title: item})); | |
485 | |
486 if (prefix || force) { | |
487 if (prefix) | |
488 annotatedCompletions = annotatedCompletions.concat(this.addition
alCompletions(prefix)); | |
489 else | |
490 annotatedCompletions = this.additionalCompletions(prefix).concat
(annotatedCompletions); | |
491 } | |
492 | |
493 if (!annotatedCompletions.length) { | |
494 this.clearAutocomplete(); | |
495 return; | |
496 } | |
497 | |
498 var selectionRange = selection.getRangeAt(0); | |
499 | |
500 var fullWordRange = this._createRange(); | |
501 fullWordRange.setStart(originalWordPrefixRange.startContainer, originalW
ordPrefixRange.startOffset); | |
502 fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffs
et); | |
503 | |
504 if (prefix + selectionRange.toString() !== fullWordRange.toString()) | |
505 return; | |
506 | |
507 selectedIndex = (this._disableDefaultSuggestionForEmptyInput && !this.te
xt()) ? -1 : (selectedIndex || 0); | |
508 | |
509 if (this._suggestBox) | |
510 this._suggestBox.updateSuggestions(this._boxForAnchorAtStart(selecti
on, fullWordRange), annotatedCompletions, selectedIndex, !this._isCaretAtEndOfPr
ompt(), this.text()); | |
511 | |
512 var beforeRange = this._createRange(); | |
513 beforeRange.setStart(this._element, 0); | |
514 beforeRange.setEnd(fullWordRange.startContainer, fullWordRange.startOffs
et); | |
515 this._prefixRange = new WebInspector.TextRange(0, beforeRange.toString()
.length, 0, beforeRange.toString().length + fullWordRange.toString().length); | |
516 | |
517 if (selectedIndex === -1) | |
518 return; | |
519 this.applySuggestion(annotatedCompletions[selectedIndex].title, true); | |
520 }, | |
521 | |
522 /** | |
523 * @override | |
524 * @param {string} suggestion | |
525 * @param {boolean=} isIntermediateSuggestion | |
526 */ | |
527 applySuggestion: function(suggestion, isIntermediateSuggestion) | |
528 { | |
529 if (!this._prefixRange) | |
530 return; | |
531 this._currentSuggestion = suggestion; | |
532 this._refreshGhostText(); | |
533 if (isIntermediateSuggestion) | |
534 this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemApp
lied); | |
535 }, | |
536 | |
537 /** | |
538 * @override | |
539 */ | |
540 acceptSuggestion: function() | |
541 { | |
542 this._acceptSuggestionInternal(); | |
543 }, | |
544 | |
545 /** | |
546 * @return {boolean} | |
547 */ | |
548 _acceptSuggestionInternal: function() | |
549 { | |
550 if (!this._prefixRange) | |
551 return false; | |
552 | |
553 var text = this.text(); | |
554 this._element.textContent = text.substring(0, this._prefixRange.startCol
umn) + this._currentSuggestion + text.substring(this._prefixRange.endColumn); | |
555 this._setDOMSelection(this._prefixRange.startColumn + this._currentSugge
stion.length, this._prefixRange.startColumn + this._currentSuggestion.length); | |
556 | |
557 this.clearAutocomplete(); | |
558 this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemAccepte
d); | |
559 | |
560 return true; | |
561 }, | |
562 | |
563 /** | |
564 * @param {number} startColumn | |
565 * @param {number} endColumn | |
566 */ | |
567 _setDOMSelection: function(startColumn, endColumn) | |
568 { | |
569 this._element.normalize(); | |
570 var node = this._element.childNodes[0]; | |
571 if (!node || node === this._ghostTextElement) | |
572 return; | |
573 var range = this._createRange(); | |
574 range.setStart(node, startColumn); | |
575 range.setEnd(node, endColumn); | |
576 var selection = this._element.getComponentSelection(); | |
577 selection.removeAllRanges(); | |
578 selection.addRange(range); | |
579 }, | |
580 | |
581 /** | |
582 * @return {boolean} | |
583 */ | |
584 _isSuggestBoxVisible: function() | |
585 { | |
586 return this._suggestBox && this._suggestBox.visible(); | |
587 }, | |
588 | |
589 /** | |
590 * @return {boolean} | |
591 */ | |
592 isCaretInsidePrompt: function() | |
593 { | |
594 var selection = this._element.getComponentSelection(); | |
595 // @see crbug.com/602541 | |
596 var selectionRange = selection && selection.rangeCount ? selection.getRa
ngeAt(0) : null; | |
597 if (!selectionRange || !selection.isCollapsed) | |
598 return false; | |
599 return selectionRange.startContainer.isSelfOrDescendant(this._element); | |
600 }, | |
601 | |
602 /** | |
603 * @return {boolean} | |
604 */ | |
605 _isCaretAtEndOfPrompt: function() | |
606 { | |
607 var selection = this._element.getComponentSelection(); | |
608 var selectionRange = selection && selection.rangeCount ? selection.getRa
ngeAt(0) : null; | |
609 if (!selectionRange || !selection.isCollapsed) | |
610 return false; | |
611 | |
612 var node = selectionRange.startContainer; | |
613 if (!node.isSelfOrDescendant(this._element)) | |
614 return false; | |
615 | |
616 if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < nod
e.nodeValue.length) | |
617 return false; | |
618 | |
619 var foundNextText = false; | |
620 while (node) { | |
621 if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) { | |
622 if (foundNextText && !this._ghostTextElement.isAncestor(node)) | |
623 return false; | |
624 foundNextText = true; | |
625 } | |
626 | |
627 node = node.traverseNextNode(this._element); | |
628 } | |
629 | |
630 return true; | |
631 }, | |
632 | |
633 moveCaretToEndOfPrompt: function() | |
634 { | |
635 var selection = this._element.getComponentSelection(); | |
636 var selectionRange = this._createRange(); | |
637 | |
638 var container = this._element; | |
639 while (container.childNodes.length) | |
640 container = container.lastChild; | |
641 var offset = container.nodeType === Node.TEXT_NODE ? container.textConte
nt.length : 0; | |
642 selectionRange.setStart(container, offset); | |
643 selectionRange.setEnd(container, offset); | |
644 | |
645 selection.removeAllRanges(); | |
646 selection.addRange(selectionRange); | |
647 }, | |
648 | |
649 /** | |
650 * @param {!Event} event | |
651 * @return {boolean} | |
652 */ | |
653 tabKeyPressed: function(event) | |
654 { | |
655 this.acceptAutoComplete(); | |
656 | |
657 // Consume the key. | |
658 return true; | |
659 }, | |
660 | |
661 /** | |
662 * @return {?Element} | |
663 */ | |
664 proxyElementForTests: function() | |
665 { | |
666 return this._proxyElement || null; | |
667 }, | |
668 | |
669 __proto__: WebInspector.Object.prototype | |
670 }; | |
OLD | NEW |