blob: 549cab4fdb82834f3649ff539f8606c896a070af [file] [log] [blame]
/*
* Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.ConsolePrompt = class ConsolePrompt extends WI.View
{
constructor(delegate, mimeType)
{
super();
mimeType = parseMIMEType(mimeType).type;
this.element.classList.add("console-prompt", WI.SyntaxHighlightedStyleClassName);
this.element.appendChild(WI.ImageUtilities.useSVGSymbol("Images/UserInputPrompt.svg", "glyph"));
this._delegate = delegate || null;
this._codeMirror = WI.CodeMirrorEditor.create(this.element, {
lineWrapping: true,
mode: {name: mimeType, globalVars: true},
indentWithTabs: true,
indentUnit: 4,
matchBrackets: true
});
var keyMap = {
"Up": this._handlePreviousKey.bind(this),
"Down": this._handleNextKey.bind(this),
"Ctrl-P": this._handlePreviousKey.bind(this),
"Ctrl-N": this._handleNextKey.bind(this),
"Enter": this._handleEnterKey.bind(this),
"Cmd-Enter": this._handleCommandEnterKey.bind(this),
"Tab": this._handleTabKey.bind(this),
"Esc": this._handleEscapeKey.bind(this)
};
this._codeMirror.addKeyMap(keyMap);
this._completionController = new WI.CodeMirrorCompletionController(this._codeMirror, this);
this._completionController.addExtendedCompletionProvider("javascript", WI.javaScriptRuntimeCompletionProvider);
this._history = [{}];
this._historyIndex = 0;
}
// Public
get delegate()
{
return this._delegate;
}
set delegate(delegate)
{
this._delegate = delegate || null;
}
set escapeKeyHandlerWhenEmpty(handler)
{
this._escapeKeyHandlerWhenEmpty = handler;
}
get text()
{
return this._codeMirror.getValue();
}
set text(text)
{
this._codeMirror.setValue(text || "");
this._codeMirror.clearHistory();
this._codeMirror.markClean();
}
get history()
{
this._history[this._historyIndex] = this._historyEntryForCurrentText();
return this._history;
}
set history(history)
{
this._history = history instanceof Array ? history.slice(0, WI.ConsolePrompt.MaximumHistorySize) : [{}];
this._historyIndex = 0;
this._restoreHistoryEntry(0);
}
get focused()
{
return this._codeMirror.getWrapperElement().classList.contains("CodeMirror-focused");
}
focus()
{
this._codeMirror.focus();
}
updateCompletions(completions, implicitSuffix)
{
this._completionController.updateCompletions(completions, implicitSuffix);
}
pushHistoryItem(text)
{
this._commitHistoryEntry({text});
}
// Protected
completionControllerCompletionsNeeded(completionController, prefix, defaultCompletions, base, suffix, forced)
{
if (this.delegate && typeof this.delegate.consolePromptCompletionsNeeded === "function")
this.delegate.consolePromptCompletionsNeeded(this, defaultCompletions, base, prefix, suffix, forced);
else
this._completionController.updateCompletions(defaultCompletions);
}
completionControllerShouldAllowEscapeCompletion(completionController)
{
// Only allow escape to complete if there is text in the prompt. Otherwise allow it to pass through
// so escape to toggle the quick console still works.
return !!this.text;
}
sizeDidChange()
{
super.sizeDidChange();
if (this.text)
this._codeMirror.refresh();
}
// Private
_handleTabKey(codeMirror)
{
var cursor = codeMirror.getCursor();
var line = codeMirror.getLine(cursor.line);
if (!line.trim().length)
return CodeMirror.Pass;
var firstNonSpace = line.search(/[^\s]/);
if (cursor.ch <= firstNonSpace)
return CodeMirror.Pass;
this._completionController.completeAtCurrentPositionIfNeeded().then(function(result) {
if (result === WI.CodeMirrorCompletionController.UpdatePromise.NoCompletionsFound)
InspectorFrontendHost.beep();
});
}
_handleEscapeKey(codeMirror)
{
if (this.text)
return CodeMirror.Pass;
if (!this._escapeKeyHandlerWhenEmpty)
return CodeMirror.Pass;
this._escapeKeyHandlerWhenEmpty();
}
_handlePreviousKey(codeMirror)
{
if (this._codeMirror.somethingSelected())
return CodeMirror.Pass;
// Pass unless we are on the first line.
if (this._codeMirror.getCursor().line)
return CodeMirror.Pass;
var historyEntry = this._history[this._historyIndex + 1];
if (!historyEntry)
return CodeMirror.Pass;
this._rememberCurrentTextInHistory();
++this._historyIndex;
this._restoreHistoryEntry(this._historyIndex);
}
_handleNextKey(codeMirror)
{
if (this._codeMirror.somethingSelected())
return CodeMirror.Pass;
// Pass unless we are on the last line.
if (this._codeMirror.getCursor().line !== this._codeMirror.lastLine())
return CodeMirror.Pass;
var historyEntry = this._history[this._historyIndex - 1];
if (!historyEntry)
return CodeMirror.Pass;
this._rememberCurrentTextInHistory();
--this._historyIndex;
this._restoreHistoryEntry(this._historyIndex);
}
_handleEnterKey(codeMirror, forceCommit, keepCurrentText)
{
var currentText = this.text;
// Always do nothing when there is just whitespace.
if (!currentText.trim())
return;
var cursor = this._codeMirror.getCursor();
var lastLine = this._codeMirror.lastLine();
var lastLineLength = this._codeMirror.getLine(lastLine).length;
var cursorIsAtLastPosition = positionsEqual(cursor, {line: lastLine, ch: lastLineLength});
function positionsEqual(a, b)
{
console.assert(a);
console.assert(b);
return a.line === b.line && a.ch === b.ch;
}
function commitTextOrInsertNewLine(commit)
{
if (!commit) {
// Only insert a new line if the previous cursor and the current cursor are in the same position.
if (positionsEqual(cursor, this._codeMirror.getCursor()))
CodeMirror.commands.newlineAndIndent(this._codeMirror);
return;
}
this._commitHistoryEntry(this._historyEntryForCurrentText());
if (!keepCurrentText) {
this._codeMirror.setValue("");
this._codeMirror.clearHistory();
}
if (this.delegate && typeof this.delegate.consolePromptHistoryDidChange === "function")
this.delegate.consolePromptHistoryDidChange(this);
if (this.delegate && typeof this.delegate.consolePromptTextCommitted === "function")
this.delegate.consolePromptTextCommitted(this, currentText);
}
if (!forceCommit && this.delegate && typeof this.delegate.consolePromptShouldCommitText === "function") {
this.delegate.consolePromptShouldCommitText(this, currentText, cursorIsAtLastPosition, commitTextOrInsertNewLine.bind(this));
return;
}
commitTextOrInsertNewLine.call(this, true);
}
_commitHistoryEntry(historyEntry)
{
// Replace the previous entry if it does not have text or if the text is the same.
if (this._history[1] && (!this._history[1].text || this._history[1].text === historyEntry.text)) {
this._history[1] = historyEntry;
this._history[0] = {};
} else {
// Replace the first history entry and push a new empty one.
this._history[0] = historyEntry;
this._history.unshift({});
// Trim the history length if needed.
if (this._history.length > WI.ConsolePrompt.MaximumHistorySize)
this._history = this._history.slice(0, WI.ConsolePrompt.MaximumHistorySize);
}
this._historyIndex = 0;
}
_handleCommandEnterKey(codeMirror)
{
this._handleEnterKey(codeMirror, true, true);
}
_restoreHistoryEntry(index)
{
var historyEntry = this._history[index];
this._codeMirror.setValue(historyEntry.text || "");
if (historyEntry.undoHistory)
this._codeMirror.setHistory(historyEntry.undoHistory);
else
this._codeMirror.clearHistory();
this._codeMirror.setCursor(historyEntry.cursor || {line: 0});
}
_historyEntryForCurrentText()
{
return {text: this.text, undoHistory: this._codeMirror.getHistory(), cursor: this._codeMirror.getCursor()};
}
_rememberCurrentTextInHistory()
{
this._history[this._historyIndex] = this._historyEntryForCurrentText();
if (this.delegate && typeof this.delegate.consolePromptHistoryDidChange === "function")
this.delegate.consolePromptHistoryDidChange(this);
}
};
WI.ConsolePrompt.MaximumHistorySize = 30;