Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / ui / TextPrompt.js
1 /*
2  * Copyright (C) 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2011 Google Inc.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
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
16  *     from this software without specific prior written permission.
17  *
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
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
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
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
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
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 /**
31  * @constructor
32  * @extends {WebInspector.Object}
33  * @implements {WebInspector.SuggestBoxDelegate}
34  * @param {function(!Element, !Range, boolean, function(!Array.<string>, number=))} completions
35  * @param {string=} stopCharacters
36  */
37 WebInspector.TextPrompt = function(completions, stopCharacters)
38 {
39     /**
40      * @type {!Element|undefined}
41      */
42     this._proxyElement;
43     this._proxyElementDisplay = "inline-block";
44     this._loadCompletions = completions;
45     this._completionStopCharacters = stopCharacters || " =:[({;,!+-*/&|^<>.";
46 }
47
48 WebInspector.TextPrompt.Events = {
49     ItemApplied: "text-prompt-item-applied",
50     ItemAccepted: "text-prompt-item-accepted"
51 };
52
53 WebInspector.TextPrompt.prototype = {
54     get proxyElement()
55     {
56         return this._proxyElement;
57     },
58
59     /**
60      * @param {boolean} suggestBoxEnabled
61      */
62     setSuggestBoxEnabled: function(suggestBoxEnabled)
63     {
64         this._suggestBoxEnabled = suggestBoxEnabled;
65     },
66
67     renderAsBlock: function()
68     {
69         this._proxyElementDisplay = "block";
70     },
71
72     /**
73      * Clients should never attach any event listeners to the |element|. Instead,
74      * they should use the result of this method to attach listeners for bubbling events.
75      *
76      * @param {!Element} element
77      * @return {!Element}
78      */
79     attach: function(element)
80     {
81         return this._attachInternal(element);
82     },
83
84     /**
85      * Clients should never attach any event listeners to the |element|. Instead,
86      * they should use the result of this method to attach listeners for bubbling events
87      * or the |blurListener| parameter to register a "blur" event listener on the |element|
88      * (since the "blur" event does not bubble.)
89      *
90      * @param {!Element} element
91      * @param {function(!Event)} blurListener
92      * @return {!Element}
93      */
94     attachAndStartEditing: function(element, blurListener)
95     {
96         this._attachInternal(element);
97         this._startEditing(blurListener);
98         return this.proxyElement;
99     },
100
101     /**
102      * @param {!Element} element
103      * @return {!Element}
104      */
105     _attachInternal: function(element)
106     {
107         if (this.proxyElement)
108             throw "Cannot attach an attached TextPrompt";
109         this._element = element;
110
111         this._boundOnKeyDown = this.onKeyDown.bind(this);
112         this._boundOnInput = this.onInput.bind(this);
113         this._boundOnMouseWheel = this.onMouseWheel.bind(this);
114         this._boundSelectStart = this._selectStart.bind(this);
115         this._boundRemoveSuggestionAids = this._removeSuggestionAids.bind(this);
116         this._proxyElement = element.ownerDocument.createElement("span");
117         this._proxyElement.style.display = this._proxyElementDisplay;
118         element.parentElement.insertBefore(this.proxyElement, element);
119         this.proxyElement.appendChild(element);
120         this._element.classList.add("text-prompt");
121         this._element.addEventListener("keydown", this._boundOnKeyDown, false);
122         this._element.addEventListener("input", this._boundOnInput, false);
123         this._element.addEventListener("mousewheel", this._boundOnMouseWheel, false);
124         this._element.addEventListener("selectstart", this._boundSelectStart, false);
125         this._element.addEventListener("blur", this._boundRemoveSuggestionAids, false);
126
127         if (this._suggestBoxEnabled)
128             this._suggestBox = new WebInspector.SuggestBox(this);
129
130         return this.proxyElement;
131     },
132
133     detach: function()
134     {
135         this._removeFromElement();
136         this.proxyElement.parentElement.insertBefore(this._element, this.proxyElement);
137         this.proxyElement.remove();
138         delete this._proxyElement;
139         this._element.classList.remove("text-prompt");
140         WebInspector.restoreFocusFromElement(this._element);
141     },
142
143     /**
144      * @type {string}
145      */
146     get text()
147     {
148         return this._element.textContent;
149     },
150
151     /**
152      * @param {string} x
153      */
154     set text(x)
155     {
156         this._removeSuggestionAids();
157         if (!x) {
158             // Append a break element instead of setting textContent to make sure the selection is inside the prompt.
159             this._element.removeChildren();
160             this._element.createChild("br");
161         } else {
162             this._element.textContent = x;
163         }
164
165         this.moveCaretToEndOfPrompt();
166         this._element.scrollIntoView();
167     },
168
169     _removeFromElement: function()
170     {
171         this.clearAutoComplete(true);
172         this._element.removeEventListener("keydown", this._boundOnKeyDown, false);
173         this._element.removeEventListener("input", this._boundOnInput, false);
174         this._element.removeEventListener("selectstart", this._boundSelectStart, false);
175         this._element.removeEventListener("blur", this._boundRemoveSuggestionAids, false);
176         if (this._isEditing)
177             this._stopEditing();
178         if (this._suggestBox)
179             this._suggestBox.removeFromElement();
180     },
181
182     /**
183      * @param {function(!Event)=} blurListener
184      */
185     _startEditing: function(blurListener)
186     {
187         this._isEditing = true;
188         this._element.classList.add("editing");
189         if (blurListener) {
190             this._blurListener = blurListener;
191             this._element.addEventListener("blur", this._blurListener, false);
192         }
193         this._oldTabIndex = this._element.tabIndex;
194         if (this._element.tabIndex < 0)
195             this._element.tabIndex = 0;
196         WebInspector.setCurrentFocusElement(this._element);
197         if (!this.text)
198             this._updateAutoComplete();
199     },
200
201     _stopEditing: function()
202     {
203         this._element.tabIndex = this._oldTabIndex;
204         if (this._blurListener)
205             this._element.removeEventListener("blur", this._blurListener, false);
206         this._element.classList.remove("editing");
207         delete this._isEditing;
208     },
209
210     _removeSuggestionAids: function()
211     {
212         this.clearAutoComplete();
213         this.hideSuggestBox();
214     },
215
216     _selectStart: function()
217     {
218         if (this._selectionTimeout)
219             clearTimeout(this._selectionTimeout);
220
221         this._removeSuggestionAids();
222
223         /**
224          * @this {WebInspector.TextPrompt}
225          */
226         function moveBackIfOutside()
227         {
228             delete this._selectionTimeout;
229             if (!this.isCaretInsidePrompt() && window.getSelection().isCollapsed) {
230                 this.moveCaretToEndOfPrompt();
231                 this.autoCompleteSoon();
232             }
233         }
234
235         this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
236     },
237
238     /**
239      * @param {boolean=} force
240      */
241     _updateAutoComplete: function(force)
242     {
243         this.clearAutoComplete();
244         this.autoCompleteSoon(force);
245     },
246
247     /**
248      * @param {!Event} event
249      */
250     onMouseWheel: function(event)
251     {
252         // Subclasses can implement.
253     },
254
255     /**
256      * @param {!Event} event
257      */
258     onKeyDown: function(event)
259     {
260         var handled = false;
261         delete this._needUpdateAutocomplete;
262
263         switch (event.keyIdentifier) {
264         case "U+0009": // Tab
265             handled = this.tabKeyPressed(event);
266             break;
267         case "Left":
268         case "Home":
269             this._removeSuggestionAids();
270             break;
271         case "Right":
272         case "End":
273             if (this.isCaretAtEndOfPrompt())
274                 handled = this.acceptAutoComplete();
275             else
276                 this._removeSuggestionAids();
277             break;
278         case "U+001B": // Esc
279             if (this.isSuggestBoxVisible()) {
280                 this._removeSuggestionAids();
281                 handled = true;
282             }
283             break;
284         case "U+0020": // Space
285             if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
286                 this._updateAutoComplete(true);
287                 handled = true;
288             }
289             break;
290         case "Alt":
291         case "Meta":
292         case "Shift":
293         case "Control":
294             break;
295         }
296
297         if (!handled && this.isSuggestBoxVisible())
298             handled = this._suggestBox.keyPressed(event);
299
300         if (!handled)
301             this._needUpdateAutocomplete = true;
302
303         if (handled)
304             event.consume(true);
305     },
306
307     /**
308      * @param {!Event} event
309      */
310     onInput: function(event)
311     {
312         if (this._needUpdateAutocomplete)
313             this._updateAutoComplete();
314     },
315
316     /**
317      * @return {boolean}
318      */
319     acceptAutoComplete: function()
320     {
321         var result = false;
322         if (this.isSuggestBoxVisible())
323             result = this._suggestBox.acceptSuggestion();
324         if (!result)
325             result = this._acceptSuggestionInternal();
326
327         return result;
328     },
329
330     /**
331      * @param {boolean=} includeTimeout
332      */
333     clearAutoComplete: function(includeTimeout)
334     {
335         if (includeTimeout && this._completeTimeout) {
336             clearTimeout(this._completeTimeout);
337             delete this._completeTimeout;
338         }
339         delete this._waitingForCompletions;
340
341         if (!this.autoCompleteElement)
342             return;
343
344         this.autoCompleteElement.remove();
345         delete this.autoCompleteElement;
346         delete this._userEnteredRange;
347         delete this._userEnteredText;
348     },
349
350     /**
351      * @param {boolean=} force
352      */
353     autoCompleteSoon: function(force)
354     {
355         var immediately = this.isSuggestBoxVisible() || force;
356         if (!this._completeTimeout)
357             this._completeTimeout = setTimeout(this.complete.bind(this, force), immediately ? 0 : 250);
358     },
359
360     /**
361      * @param {boolean=} force
362      * @param {boolean=} reverse
363      */
364     complete: function(force, reverse)
365     {
366         this.clearAutoComplete(true);
367         var selection = window.getSelection();
368         if (!selection.rangeCount)
369             return;
370
371         var selectionRange = selection.getRangeAt(0);
372         var shouldExit;
373
374         if (!force && !this.isCaretAtEndOfPrompt() && !this.isSuggestBoxVisible())
375             shouldExit = true;
376         else if (!selection.isCollapsed)
377             shouldExit = true;
378         else if (!force) {
379             // BUG72018: Do not show suggest box if caret is followed by a non-stop character.
380             var wordSuffixRange = selectionRange.startContainer.rangeOfWord(selectionRange.endOffset, this._completionStopCharacters, this._element, "forward");
381             if (wordSuffixRange.toString().length)
382                 shouldExit = true;
383         }
384         if (shouldExit) {
385             this.hideSuggestBox();
386             return;
387         }
388
389         var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this._completionStopCharacters, this._element, "backward");
390         this._waitingForCompletions = true;
391         this._loadCompletions(this.proxyElement, wordPrefixRange, force || false, this._completionsReady.bind(this, selection, wordPrefixRange, !!reverse));
392     },
393
394     disableDefaultSuggestionForEmptyInput: function()
395     {
396         this._disableDefaultSuggestionForEmptyInput = true;
397     },
398
399     /**
400      * @param {!Selection} selection
401      * @param {!Range} textRange
402      */
403     _boxForAnchorAtStart: function(selection, textRange)
404     {
405         var rangeCopy = selection.getRangeAt(0).cloneRange();
406         var anchorElement = document.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      * @param {!Array.<string>} completions
418      * @param {number} wordPrefixLength
419      */
420     _buildCommonPrefix: function(completions, wordPrefixLength)
421     {
422         var commonPrefix = completions[0];
423         for (var i = 0; i < completions.length; ++i) {
424             var completion = completions[i];
425             var lastIndex = Math.min(commonPrefix.length, completion.length);
426             for (var j = wordPrefixLength; j < lastIndex; ++j) {
427                 if (commonPrefix[j] !== completion[j]) {
428                     commonPrefix = commonPrefix.substr(0, j);
429                     break;
430                 }
431             }
432         }
433         return commonPrefix;
434     },
435
436     /**
437      * @param {!Selection} selection
438      * @param {!Range} originalWordPrefixRange
439      * @param {boolean} reverse
440      * @param {!Array.<string>} completions
441      * @param {number=} selectedIndex
442      */
443     _completionsReady: function(selection, originalWordPrefixRange, reverse, completions, selectedIndex)
444     {
445         if (!this._waitingForCompletions || !completions.length) {
446             this.hideSuggestBox();
447             return;
448         }
449         delete this._waitingForCompletions;
450
451         var selectionRange = selection.getRangeAt(0);
452
453         var fullWordRange = document.createRange();
454         fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordPrefixRange.startOffset);
455         fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset);
456
457         if (originalWordPrefixRange.toString() + selectionRange.toString() !== fullWordRange.toString())
458             return;
459
460         selectedIndex = (this._disableDefaultSuggestionForEmptyInput && !this.text) ? -1 : (selectedIndex || 0);
461
462         this._userEnteredRange = fullWordRange;
463         this._userEnteredText = fullWordRange.toString();
464
465         if (this._suggestBox)
466             this._suggestBox.updateSuggestions(this._boxForAnchorAtStart(selection, fullWordRange), completions, selectedIndex, !this.isCaretAtEndOfPrompt(), this._userEnteredText);
467
468         if (selectedIndex === -1)
469             return;
470
471         var wordPrefixLength = originalWordPrefixRange.toString().length;
472         this._commonPrefix = this._buildCommonPrefix(completions, wordPrefixLength);
473
474         if (this.isCaretAtEndOfPrompt()) {
475             var completionText = completions[selectedIndex];
476             var prefixText = this._userEnteredRange.toString();
477             var suffixText = completionText.substring(wordPrefixLength);
478             this._userEnteredRange.deleteContents();
479             this._element.normalize();
480             var finalSelectionRange = document.createRange();
481
482             var prefixTextNode = document.createTextNode(prefixText);
483             fullWordRange.insertNode(prefixTextNode);
484
485             this.autoCompleteElement = document.createElementWithClass("span", "auto-complete-text");
486             this.autoCompleteElement.textContent = suffixText;
487
488             prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling);
489
490             finalSelectionRange.setStart(prefixTextNode, wordPrefixLength);
491             finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength);
492             selection.removeAllRanges();
493             selection.addRange(finalSelectionRange);
494             this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemApplied);
495         }
496     },
497
498     _completeCommonPrefix: function()
499     {
500         if (!this.autoCompleteElement || !this._commonPrefix || !this._userEnteredText || !this._commonPrefix.startsWith(this._userEnteredText))
501             return;
502
503         if (!this.isSuggestBoxVisible()) {
504             this.acceptAutoComplete();
505             return;
506         }
507
508         this.autoCompleteElement.textContent = this._commonPrefix.substring(this._userEnteredText.length);
509         this._acceptSuggestionInternal(true);
510     },
511
512     /**
513      * @param {string} completionText
514      * @param {boolean=} isIntermediateSuggestion
515      */
516     applySuggestion: function(completionText, isIntermediateSuggestion)
517     {
518         this._applySuggestion(completionText, isIntermediateSuggestion);
519     },
520
521     /**
522      * @param {string} completionText
523      * @param {boolean=} isIntermediateSuggestion
524      * @param {!Range=} originalPrefixRange
525      */
526     _applySuggestion: function(completionText, isIntermediateSuggestion, originalPrefixRange)
527     {
528         var wordPrefixLength;
529         if (originalPrefixRange)
530             wordPrefixLength = originalPrefixRange.toString().length;
531         else
532             wordPrefixLength = this._userEnteredText ? this._userEnteredText.length : 0;
533
534         this._userEnteredRange.deleteContents();
535         this._element.normalize();
536         var finalSelectionRange = document.createRange();
537         var completionTextNode = document.createTextNode(completionText);
538         this._userEnteredRange.insertNode(completionTextNode);
539         if (this.autoCompleteElement) {
540             this.autoCompleteElement.remove();
541             delete this.autoCompleteElement;
542         }
543
544         if (isIntermediateSuggestion)
545             finalSelectionRange.setStart(completionTextNode, wordPrefixLength);
546         else
547             finalSelectionRange.setStart(completionTextNode, completionText.length);
548
549         finalSelectionRange.setEnd(completionTextNode, completionText.length);
550
551         var selection = window.getSelection();
552         selection.removeAllRanges();
553         selection.addRange(finalSelectionRange);
554         if (isIntermediateSuggestion)
555             this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemApplied, { itemText: completionText });
556     },
557
558     /**
559      * @override
560      */
561     acceptSuggestion: function()
562     {
563         this._acceptSuggestionInternal();
564     },
565
566     /**
567      * @param {boolean=} prefixAccepted
568      * @return {boolean}
569      */
570     _acceptSuggestionInternal: function(prefixAccepted)
571     {
572         if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode)
573             return false;
574
575         var text = this.autoCompleteElement.textContent;
576         var textNode = document.createTextNode(text);
577         this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement);
578         delete this.autoCompleteElement;
579
580         var finalSelectionRange = document.createRange();
581         finalSelectionRange.setStart(textNode, text.length);
582         finalSelectionRange.setEnd(textNode, text.length);
583
584         var selection = window.getSelection();
585         selection.removeAllRanges();
586         selection.addRange(finalSelectionRange);
587
588         if (!prefixAccepted) {
589             this.hideSuggestBox();
590             this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemAccepted);
591         } else
592             this.autoCompleteSoon(true);
593
594         return true;
595     },
596
597     hideSuggestBox: function()
598     {
599         if (this.isSuggestBoxVisible())
600             this._suggestBox.hide();
601     },
602
603     /**
604      * @return {boolean}
605      */
606     isSuggestBoxVisible: function()
607     {
608         return this._suggestBox && this._suggestBox.visible();
609     },
610
611     /**
612      * @return {boolean}
613      */
614     isCaretInsidePrompt: function()
615     {
616         return this._element.isInsertionCaretInside();
617     },
618
619     /**
620      * @return {boolean}
621      */
622     isCaretAtEndOfPrompt: function()
623     {
624         var selection = window.getSelection();
625         if (!selection.rangeCount || !selection.isCollapsed)
626             return false;
627
628         var selectionRange = selection.getRangeAt(0);
629         var node = selectionRange.startContainer;
630         if (!node.isSelfOrDescendant(this._element))
631             return false;
632
633         if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length)
634             return false;
635
636         var foundNextText = false;
637         while (node) {
638             if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) {
639                 if (foundNextText && (!this.autoCompleteElement || !this.autoCompleteElement.isAncestor(node)))
640                     return false;
641                 foundNextText = true;
642             }
643
644             node = node.traverseNextNode(this._element);
645         }
646
647         return true;
648     },
649
650     /**
651      * @return {boolean}
652      */
653     isCaretOnFirstLine: function()
654     {
655         var selection = window.getSelection();
656         var focusNode = selection.focusNode;
657         if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this._element)
658             return true;
659
660         if (focusNode.textContent.substring(0, selection.focusOffset).indexOf("\n") !== -1)
661             return false;
662         focusNode = focusNode.previousSibling;
663
664         while (focusNode) {
665             if (focusNode.nodeType !== Node.TEXT_NODE)
666                 return true;
667             if (focusNode.textContent.indexOf("\n") !== -1)
668                 return false;
669             focusNode = focusNode.previousSibling;
670         }
671
672         return true;
673     },
674
675     /**
676      * @return {boolean}
677      */
678     isCaretOnLastLine: function()
679     {
680         var selection = window.getSelection();
681         var focusNode = selection.focusNode;
682         if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this._element)
683             return true;
684
685         if (focusNode.textContent.substring(selection.focusOffset).indexOf("\n") !== -1)
686             return false;
687         focusNode = focusNode.nextSibling;
688
689         while (focusNode) {
690             if (focusNode.nodeType !== Node.TEXT_NODE)
691                 return true;
692             if (focusNode.textContent.indexOf("\n") !== -1)
693                 return false;
694             focusNode = focusNode.nextSibling;
695         }
696
697         return true;
698     },
699
700     moveCaretToEndOfPrompt: function()
701     {
702         var selection = window.getSelection();
703         var selectionRange = document.createRange();
704
705         var offset = this._element.childNodes.length;
706         selectionRange.setStart(this._element, offset);
707         selectionRange.setEnd(this._element, offset);
708
709         selection.removeAllRanges();
710         selection.addRange(selectionRange);
711     },
712
713     /**
714      * @param {!Event} event
715      * @return {boolean}
716      */
717     tabKeyPressed: function(event)
718     {
719         this._completeCommonPrefix();
720
721         // Consume the key.
722         return true;
723     },
724
725     __proto__: WebInspector.Object.prototype
726 }
727
728
729 /**
730  * @constructor
731  * @extends {WebInspector.TextPrompt}
732  * @param {function(!Element, !Range, boolean, function(!Array.<string>, number=))} completions
733  * @param {string=} stopCharacters
734  */
735 WebInspector.TextPromptWithHistory = function(completions, stopCharacters)
736 {
737     WebInspector.TextPrompt.call(this, completions, stopCharacters);
738
739     /**
740      * @type {!Array.<string>}
741      */
742     this._data = [];
743
744     /**
745      * 1-based entry in the history stack.
746      * @type {number}
747      */
748     this._historyOffset = 1;
749
750     /**
751      * Whether to coalesce duplicate items in the history, default is true.
752      * @type {boolean}
753      */
754     this._coalesceHistoryDupes = true;
755 }
756
757 WebInspector.TextPromptWithHistory.prototype = {
758     /**
759      * @return {!Array.<string>}
760      */
761     get historyData()
762     {
763         // FIXME: do we need to copy this?
764         return this._data;
765     },
766
767     /**
768      * @param {boolean} x
769      */
770     setCoalesceHistoryDupes: function(x)
771     {
772         this._coalesceHistoryDupes = x;
773     },
774
775     /**
776      * @param {!Array.<string>} data
777      */
778     setHistoryData: function(data)
779     {
780         this._data = [].concat(data);
781         this._historyOffset = 1;
782     },
783
784     /**
785      * Pushes a committed text into the history.
786      * @param {string} text
787      */
788     pushHistoryItem: function(text)
789     {
790         if (this._uncommittedIsTop) {
791             this._data.pop();
792             delete this._uncommittedIsTop;
793         }
794
795         this._historyOffset = 1;
796         if (this._coalesceHistoryDupes && text === this._currentHistoryItem())
797             return;
798         this._data.push(text);
799     },
800
801     /**
802      * Pushes the current (uncommitted) text into the history.
803      */
804     _pushCurrentText: function()
805     {
806         if (this._uncommittedIsTop)
807             this._data.pop(); // Throw away obsolete uncommitted text.
808         this._uncommittedIsTop = true;
809         this.clearAutoComplete(true);
810         this._data.push(this.text);
811     },
812
813     /**
814      * @return {string|undefined}
815      */
816     _previous: function()
817     {
818         if (this._historyOffset > this._data.length)
819             return undefined;
820         if (this._historyOffset === 1)
821             this._pushCurrentText();
822         ++this._historyOffset;
823         return this._currentHistoryItem();
824     },
825
826     /**
827      * @return {string|undefined}
828      */
829     _next: function()
830     {
831         if (this._historyOffset === 1)
832             return undefined;
833         --this._historyOffset;
834         return this._currentHistoryItem();
835     },
836
837     /**
838      * @return {string|undefined}
839      */
840     _currentHistoryItem: function()
841     {
842         return this._data[this._data.length - this._historyOffset];
843     },
844
845     /**
846      * @override
847      */
848     onKeyDown: function(event)
849     {
850         var newText;
851         var isPrevious;
852
853         switch (event.keyIdentifier) {
854         case "Up":
855             if (!this.isCaretOnFirstLine() || this.isSuggestBoxVisible())
856                 break;
857             newText = this._previous();
858             isPrevious = true;
859             break;
860         case "Down":
861             if (!this.isCaretOnLastLine() || this.isSuggestBoxVisible())
862                 break;
863             newText = this._next();
864             break;
865         case "U+0050": // Ctrl+P = Previous
866             if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
867                 newText = this._previous();
868                 isPrevious = true;
869             }
870             break;
871         case "U+004E": // Ctrl+N = Next
872             if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey)
873                 newText = this._next();
874             break;
875         }
876
877         if (newText !== undefined) {
878             event.consume(true);
879             this.text = newText;
880
881             if (isPrevious) {
882                 var firstNewlineIndex = this.text.indexOf("\n");
883                 if (firstNewlineIndex === -1)
884                     this.moveCaretToEndOfPrompt();
885                 else {
886                     var selection = window.getSelection();
887                     var selectionRange = document.createRange();
888
889                     selectionRange.setStart(this._element.firstChild, firstNewlineIndex);
890                     selectionRange.setEnd(this._element.firstChild, firstNewlineIndex);
891
892                     selection.removeAllRanges();
893                     selection.addRange(selectionRange);
894                 }
895             }
896
897             return;
898         }
899
900         WebInspector.TextPrompt.prototype.onKeyDown.apply(this, arguments);
901     },
902
903     __proto__: WebInspector.TextPrompt.prototype
904 }
905