blob: d497b1cd438b5d460697487e187cd5935c2e6cdb [file] [log] [blame]
/*
* Copyright (C) 2013 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.
*/
WebInspector.ConsolePrompt = function(delegate, mimeType, element)
{
// FIXME: Convert this to a WebInspector.Object subclass, and call super().
// WebInspector.Object.call(this);
mimeType = parseMIMEType(mimeType).type;
this._element = element || document.createElement("div");
this._element.classList.add(WebInspector.ConsolePrompt.StyleClassName);
this._element.classList.add(WebInspector.SyntaxHighlightedStyleClassName);
this._delegate = delegate || null;
this._codeMirror = CodeMirror(this.element, {
lineWrapping: true,
mode: mimeType,
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),
"Esc": this._handleEscapeKey.bind(this)
};
this._codeMirror.addKeyMap(keyMap);
this._completionController = new WebInspector.CodeMirrorCompletionController(this._codeMirror, this);
this._completionController.addExtendedCompletionProvider("javascript", WebInspector.javaScriptRuntimeCompletionProvider);
this._history = [{}];
this._historyIndex = 0;
};
WebInspector.ConsolePrompt.StyleClassName = "console-prompt";
WebInspector.ConsolePrompt.MaximumHistorySize = 30;
WebInspector.ConsolePrompt.prototype = {
constructor: WebInspector.ConsolePrompt,
// Public
get element()
{
return this._element;
},
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, WebInspector.ConsolePrompt.MaximumHistorySize) : [{}];
this._historyIndex = 0;
this._restoreHistoryEntry(0);
},
get focused()
{
return this._codeMirror.getWrapperElement().classList.contains("CodeMirror-focused");
},
focus: function()
{
this._codeMirror.focus();
},
shown: function()
{
this._codeMirror.refresh();
},
updateLayout: function()
{
this._codeMirror.refresh();
},
updateCompletions: function(completions, implicitSuffix)
{
this._completionController.updateCompletions(completions, implicitSuffix);
},
pushHistoryItem: function(text)
{
this._commitHistoryEntry({text});
},
// Protected
completionControllerCompletionsNeeded: function(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: function(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;
},
// Private
_handleEscapeKey: function(codeMirror)
{
if (this.text)
return CodeMirror.Pass;
if (!this._escapeKeyHandlerWhenEmpty)
return CodeMirror.Pass;
this._escapeKeyHandlerWhenEmpty();
},
_handlePreviousKey: function(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: function(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: function(codeMirror, forceCommit)
{
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());
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: function(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 > WebInspector.ConsolePrompt.MaximumHistorySize)
this._history = this._history.slice(0, WebInspector.ConsolePrompt.MaximumHistorySize);
}
this._historyIndex = 0;
},
_handleCommandEnterKey: function(codeMirror)
{
this._handleEnterKey(codeMirror, true);
},
_restoreHistoryEntry: function(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: function()
{
return {text: this.text, undoHistory: this._codeMirror.getHistory(), cursor: this._codeMirror.getCursor()};
},
_rememberCurrentTextInHistory: function()
{
this._history[this._historyIndex] = this._historyEntryForCurrentText();
if (this.delegate && typeof this.delegate.consolePromptHistoryDidChange === "function")
this.delegate.consolePromptHistoryDidChange(this);
}
};
WebInspector.ConsolePrompt.prototype.__proto__ = WebInspector.Object.prototype;