Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Issue 55: Auto-completion for Command Editor

  • Loading branch information...
commit fd8957fbdba57b3d1931c6e1adc286337af996c1 1 parent db8b2ca
@simonlindholm authored
View
168 extension/content/firebug/console/autoCompleter.js
@@ -127,40 +127,32 @@ Firebug.JSAutoCompleter = function(textBox, completionBox, options)
this.complete = function(context)
{
this.revertValue = null;
- this.createCandidates(context);
- this.showCompletions(false);
+ var offset = this.textBox.selectionStart;
+ if (this.createCandidates(context, this.textBox.value, offset))
+ this.showCompletions(false);
+ else
+ this.hide();
};
/**
* Update the completion base and create completion candidates for the
* current value of the text box.
+ * Return true iff new candidates were constructed.
*/
- this.createCandidates = function(context)
+ this.createCandidates = function(context, value, offset)
{
- var offset = this.textBox.selectionStart;
- if (offset !== this.textBox.value.length)
- {
- this.hide();
- return;
- }
-
- var value = this.textBox.value;
+ if (offset !== value.length)
+ return false;
// Create a simplified expression by redacting contents/normalizing
// delimiters of strings and regexes, to make parsing easier.
// Give up if the syntax is too weird.
var svalue = simplifyExpr(value);
if (svalue === null)
- {
- this.hide();
- return;
- }
+ return false;
if (killCompletions(svalue, value))
- {
- this.hide();
- return;
- }
+ return false;
// Find the expression to be completed.
var parseStart = getExpressionOffset(svalue);
@@ -198,6 +190,7 @@ Firebug.JSAutoCompleter = function(textBox, completionBox, options)
}
this.createCompletions(prop, prevCompletions);
+ return true;
};
/**
@@ -861,22 +854,129 @@ Firebug.JSAutoCompleter = function(textBox, completionBox, options)
// ********************************************************************************************* //
-/**
- * A dummy auto-completer, set as current by CommandLine.setAutoCompleter when
- * no completion is supposed to be done (such as in the large command line,
- * currently, or when there is no context).
- */
-Firebug.EmptyJSAutoCompleter = function()
+Firebug.LargeJSAutoCompleter = function(editor)
{
- this.empty = true;
- this.shutdown = function() {};
- this.hide = function() {};
- this.complete = function() {};
- this.acceptReturn = function() { return true; };
- this.revert = function() { return false; };
- this.handleKeyDown = function() {};
- this.handleKeyPress = function() {};
+ var options = {includeCurrentScope: true};
+ Firebug.JSAutoCompleter.call(this, null, null, options);
+
+ this.editor = editor;
+ this.previous = null;
+
+ /**
+ * Handle a press of the tab key.
+ * Returns 2 if completion succeeded, 1 if it didn't but the caret was still
+ * in a completion area (so the tab shouldn't give rise to an actual tab), or
+ * 0 if it was not intended to complete (e.g., a tab at the start of a line,
+ * or indenting a selection).
+ */
+ this.onTab = function(context, reverse)
+ {
+ var editor = this.editor;
+
+ // If there is a selection, tab either indents or gives an actual tab.
+ if (editor.getSelectedText())
+ return 0;
+
+ var caret = editor.getCaretPosition();
+ var line = caret.line, col = caret.col;
+ var lineStart = editor.getLineStart(line);
+ var lineEnd = editor.getLineEnd(line);
+ var value = editor.getText(lineStart, lineEnd);
+
+ // An actual tab if the previous thing wasn't a JS char.
+ var prevChar = value.charAt(col-1);
+ if (!reJSChar.test(prevChar) && prevChar !== ".")
+ return 0;
+
+ // If the next thing is a JS char, we can't complete (e.g. 'doc|ument'
+ // should not complete to 'document|cument').
+ if (reJSChar.test(value.charAt(col)))
+ return 1;
+
+ var completer = this.completer;
+ var prev = this.previous, compl;
+ if (prev && prev.col === col && prev.line === line && prev.value === value)
+ {
+ // Cycle completion. Restore the input to the original, then set
+ // the completion to the next one in turn.
+ compl = prev.completions;
+ if (reverse)
+ compl.index += compl.list.length-1;
+ else
+ ++compl.index;
+ compl.index %= compl.list.length;
+ value = value.substr(0, prev.baseOffset) + compl.prefix + value.substr(col);
+ col = prev.offset;
+ }
+ else
+ {
+ // Perform a new completion.
+ // For now, only look at the current line.
+ this.hide();
+ var worked = this.createCandidates(context, value.substr(0, col), col);
+ if (!worked || !this.completions)
+ return 1;
+ compl = this.completions;
+ }
+
+ var theCompletion = this.getCurrentCompletion();
+ var baseCol = col - compl.prefix.length;
+ var newCol = baseCol + theCompletion.length;
+ var newValue = value.substr(0, baseCol) + theCompletion + value.substr(col);
+ try
+ {
+ Firebug.CommandEditor.ignoreChanges = true;
+ editor.setText(newValue, lineStart, lineEnd);
+ editor.setCaretOffset(lineStart + baseCol + theCompletion.length);
+ }
+ finally
+ {
+ Firebug.CommandEditor.ignoreChanges = false;
+ }
+
+ this.previous = {
+ col: newCol,
+ line: line,
+ value: newValue,
+ offset: col,
+ baseOffset: baseCol,
+ completions: compl
+ };
+
+ return 2;
+ };
+
+ this.revert = function()
+ {
+ var prev = this.previous;
+ if (!prev)
+ return;
+ var editor = this.editor;
+ var caret = editor.getCaretPosition();
+ var line = caret.line, col = caret.col;
+ var lineStart = editor.getLineStart(line);
+ var lineEnd = editor.getLineEnd(line);
+ var value = editor.getText(lineStart, lineEnd);
+ if (prev.col === col && prev.line === line && prev.value === value)
+ {
+ var newValue = value.substr(0, prev.baseOffset) + prev.completions.prefix + value.substr(col);
+ editor.setText(newValue, lineStart, lineEnd);
+ editor.setCaretOffset(prev.offset);
+ return true;
+ }
+ return false;
+ };
+
+ this.showCompletions = function() {};
+
+ var oldHide = this.hide;
+ this.hide = function()
+ {
+ oldHide.apply(this, arguments);
+ this.previous = null;
+ };
};
+Firebug.LargeJSAutoCompleter.prototype = Object.create(Firebug.JSAutoCompleter.prototype);
// ********************************************************************************************* //
@@ -1806,7 +1906,7 @@ function evalPropChainStep(step, tempExpr, evalChain, out, context)
tempExpr.thisCommand = "window";
tempExpr.command += "(" + link.origCont + ")";
}
- else if (link.name === "")
+ else if (!link.name)
{
// We cannot know about functions without name; try the
// heuristic directly.
View
100 extension/content/firebug/console/commandEditor.js
@@ -9,8 +9,9 @@ define([
"firebug/lib/locale",
"firebug/lib/css",
"firebug/lib/options",
+ "firebug/console/autoCompleter",
],
-function(Obj, Firebug, Events, Menu, Dom, Locale, Css, Options) {
+function(Obj, Firebug, Events, Menu, Dom, Locale, Css, Options, AutoCompleter) {
// ********************************************************************************************* //
// Constants
@@ -75,17 +76,22 @@ Firebug.CommandEditor = Obj.extend(Firebug.Module,
action: "firebug-cmdEditor-execute",
code: KeyEvent.DOM_VK_RETURN,
accel: true,
- callback: this.onExecute.bind(this),
+ callback: this.onExecute.bind(this)
},{
action: "firebug-cmdEditor-escape",
code: KeyEvent.DOM_VK_ESCAPE,
- callback: this.onEscape.bind(this),
+ callback: this.onEscape.bind(this)
}];
+ this.completer = new Firebug.LargeJSAutoCompleter(this.editor);
+
// Initialize Orion editor.
this.parent = document.getElementById("fbCommandEditor");
this.editor.init(this.parent, config, this.onEditorLoad.bind(this));
+ // Re-fetch the parent, it may have been replaced.
+ this.parent = document.getElementById("fbCommandEditor");
+
if (FBTrace.DBG_COMMANDEDITOR)
FBTrace.sysout("commandEditor: SourceEditor initialized");
},
@@ -97,6 +103,7 @@ Firebug.CommandEditor = Obj.extend(Firebug.Module,
this.editor.removeEventListener(CONTEXT_MENU, this.onContextMenu);
this.editor.removeEventListener(TEXT_CHANGED, this.onTextChanged);
+ this.parent.removeEventListener("keydown", this.onKeyDown, true);
this.editor.destroy();
this.editor = null;
@@ -109,9 +116,13 @@ Firebug.CommandEditor = Obj.extend(Firebug.Module,
onEditorLoad: function()
{
// xxxHonza: Context menu support is going to change in SourceEditor
+ this.onTextChanged = this.onTextChanged.bind(this);
this.editor.addEventListener(CONTEXT_MENU, this.onContextMenu);
this.editor.addEventListener(TEXT_CHANGED, this.onTextChanged);
+ this.onKeyDown = this.onKeyDown.bind(this);
+ this.parent.addEventListener("keydown", this.onKeyDown, true);
+
this.editor.setCaretOffset(this.editor.getCharCount());
Firebug.chrome.applyTextSize(Firebug.textSize);
@@ -134,8 +145,11 @@ Firebug.CommandEditor = Obj.extend(Firebug.Module,
onEscape: function()
{
var context = Firebug.currentContext;
- Firebug.CommandLine.update(context);
- Firebug.CommandLine.cancel(context);
+ if (this.completer.revert())
+ Firebug.CommandLine.update(context);
+ else
+ Firebug.CommandLine.cancel(context);
+ this.completer.hide();
return true;
},
@@ -148,7 +162,27 @@ Firebug.CommandEditor = Obj.extend(Firebug.Module,
if (Firebug.CommandEditor.ignoreChanges)
return;
- Firebug.CommandLine.onCommandLineInput(event);
+ var context = Firebug.currentContext;
+ this.completer.hide();
+ Firebug.CommandLine.update(context);
+ },
+
+ onKeyDown: function(event)
+ {
+ if (event.keyCode !== KeyEvent.DOM_VK_TAB)
+ return;
+ if (event.ctrlKey || event.metaKey || event.altKey)
+ return;
+ var reverse = event.shiftKey;
+ var context = Firebug.currentContext;
+ var ret = this.completer.onTab(context, reverse);
+ if (ret)
+ {
+ // Tab was captured. Save if anything changed.
+ if (ret === 2)
+ Firebug.CommandLine.update(context);
+ Events.cancelEvent(event);
+ }
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -344,23 +378,65 @@ TextEditor.prototype =
Events.removeEventListener(this.textBox, type, callback, true);
},
+ getCaretOffset: function()
+ {
+ return this.textBox.selectionEnd;
+ },
+
+ getCaretPosition: function()
+ {
+ var offset = this.getCaretOffset();
+ var text = this.getText();
+ var pre = text.substr(0, offset);
+ var lines = pre.split("\n");
+ return {
+ line: lines.length-1,
+ col: lines[lines.length-1].length
+ };
+ },
+
+ getLineStart: function(line)
+ {
+ var lines = this.getText().split("\n").slice(0, line);
+ return lines.join("").length + line;
+ },
+
+ getLineEnd: function(line, includeDelimiter)
+ {
+ var lines = this.getText().split("\n").slice(0, line+1);
+ return lines.join("\n").length + (includeDelimiter ? 1 : 0);
+ },
+
setCaretOffset: function(offset)
{
+ this.textBox.setSelectionRange(offset, offset);
},
getCharCount: function()
{
- return this.textBox.value ? this.textBox.value.length : 0;
+ return this.textBox.value.length;
},
- setText: function(text)
+ setText: function(text, start, end)
{
- this.textBox.value = text;
+ if (typeof start === "undefined")
+ {
+ this.textBox.value = text;
+ }
+ else
+ {
+ var value = this.textBox.value;
+ this.textBox.value = value.substr(0, start) + text +
+ (typeof end !== "undefined" ? value.substr(end) : "");
+ }
},
- getText: function()
+ getText: function(start, end)
{
- return this.textBox.value;
+ var text = this.textBox.value;
+ if (typeof start !== "undefined")
+ text = text.substring(start, end);
+ return text;
},
setSelection: function(start, end)
@@ -393,7 +469,7 @@ TextEditor.prototype =
return this.textBox.value.substring(start, end);
}
-}
+};
// ********************************************************************************************* //
// Registration
View
96 extension/content/firebug/console/commandLine.js
@@ -522,7 +522,7 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
if (commandLine.value)
{
commandLine.value = "";
- this.autoCompleter.hide();
+ this.smallAutoCompleter.hide();
this.update(context);
return true;
}
@@ -584,8 +584,6 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
commandEditor.value = Str.cleanIndentation(text);
else
commandLine.value = Str.stripNewLines(text);
-
- this.setAutoCompleter();
}
// else we may be hiding a panel while turning Firebug off
},
@@ -628,7 +626,7 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
{
Firebug.Module.initialize.apply(this, arguments);
- this.autoCompleter = new Firebug.EmptyJSAutoCompleter();
+ this.setAutoCompleter();
this.commandHistory = new Firebug.CommandHistory();
if (Firebug.commandEditor)
@@ -637,36 +635,22 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
setAutoCompleter: function()
{
- var context = Firebug.currentContext;
-
- // xxxHonza: see http://code.google.com/p/fbug/issues/detail?id=4901#c21
- if (this.autoCompleter)
- this.autoCompleter.shutdown();
+ if (this.smallAutoCompleter)
+ this.smallAutoCompleter.shutdown();
- // Set the auto-completer even if the command-editor is currently displayed
- // in the Console panel. This is to make the auto-completion work even in
- // the small-command-line available on other panels (see issue 5006).
-
- if (!context/* || Firebug.commandEditor*/)
- {
- this.autoCompleter = new Firebug.EmptyJSAutoCompleter();
- }
- else
- {
- // Always create the auto-completer for the single command line.
- var commandLine = this.getSingleRowCommandLine();
- var completionBox = this.getCompletionBox();
+ // Always create the auto-completer for the single command line.
+ var commandLine = this.getSingleRowCommandLine();
+ var completionBox = this.getCompletionBox();
- var options = {
- showCompletionPopup: Firebug.Options.get("commandLineShowCompleterPopup"),
- completionPopup: Firebug.chrome.$("fbCommandLineCompletionList"),
- tabWarnings: true,
- includeCurrentScope: true
- };
+ var options = {
+ showCompletionPopup: Firebug.Options.get("commandLineShowCompleterPopup"),
+ completionPopup: Firebug.chrome.$("fbCommandLineCompletionList"),
+ tabWarnings: true,
+ includeCurrentScope: true
+ };
- this.autoCompleter = new Firebug.JSAutoCompleter(commandLine,
- completionBox, options);
- }
+ this.smallAutoCompleter = new Firebug.JSAutoCompleter(commandLine,
+ completionBox, options);
},
initializeUI: function()
@@ -692,17 +676,14 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
Events.addEventListener(commandLine, "keydown", this.onCommandLineKeyDown, true);
Events.addEventListener(commandLine, "keypress", this.onCommandLineKeyPress, true);
Events.addEventListener(commandLine, "blur", this.onCommandLineBlur, true);
-
- Firebug.Console.addListener(this); // to get onConsoleInjected
},
shutdown: function()
{
var commandLine = this.getSingleRowCommandLine();
- // Make sure all listeners registered by the auto completer are removed.
- if (this.autoCompleter)
- this.autoCompleter.shutdown();
+ if (this.smallAutoCompleter)
+ this.smallAutoCompleter.shutdown();
if (this.commandHistory)
this.commandHistory.detachListeners();
@@ -724,7 +705,7 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
var commandLine = this.getCommandLine(context);
commandLine.value = "";
- this.autoCompleter.hide();
+ this.smallAutoCompleter.hide();
Persist.persistObjects(this, panelState);
// more of our work is done in the Console
@@ -757,7 +738,7 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
delete panelState.commandLineText;
}
- this.autoCompleter.hide();
+ this.smallAutoCompleter.hide();
},
updateOption: function(name, value)
@@ -817,12 +798,12 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
{
var context = Firebug.currentContext;
- this.autoCompleter.handleKeyDown(event, context);
+ this.smallAutoCompleter.handleKeyDown(event, context);
if (event.keyCode === KeyEvent.DOM_VK_H && Events.isControl(event))
{
event.preventDefault();
- this.autoCompleter.hide();
+ this.smallAutoCompleter.hide();
this.commandHistory.show(Firebug.chrome.$("fbCommandLineHistoryButton"));
return true;
}
@@ -834,7 +815,7 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
{
var context = Firebug.currentContext;
- if (!this.autoCompleter.handleKeyPress(event, context))
+ if (!this.smallAutoCompleter.handleKeyPress(event, context))
{
this.handleKeyPress(event);
}
@@ -890,13 +871,7 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
{
var context = Firebug.currentContext;
- var commandEditorOpen = (Firebug.commandEditor && context.panelName == "console");
- if (!this.commandHistory.isShown() && !commandEditorOpen)
- {
- this.autoCompleter.complete(context);
- }
-
- // Always update the buffer in context, even if command line is empty.
+ this.smallAutoCompleter.complete(context);
this.update(context);
},
@@ -906,15 +881,6 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
onCommandLineFocus: function(event)
{
- if (FBTrace.DBG_COMMANDLINE)
- {
- var context = Firebug.currentContext;
- FBTrace.sysout("commandLine.onCommandLineFocus; for: " +
- (context ? context.getName() : "no context"));
- }
-
- if (this.autoCompleter.empty)
- this.setAutoCompleter();
},
isAttached: function(context, win)
@@ -944,20 +910,6 @@ Firebug.CommandLine = Obj.extend(Firebug.Module,
Dom.collapse(Firebug.chrome.$("fbSidePanelDeck"), true);
},
- // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
- // Firebug.Console listener
-
- onConsoleInjected: function(context, win)
- {
- // for some reason the console has been injected. If the user had focus in the command
- // line they want it added in the page also. If the user has the cursor in the command
- // line and reloads, the focus will already be there. issue 1339
- var isFocused = Firebug.CommandEditor.hasFocus();
- isFocused = isFocused || (this.getSingleRowCommandLine().getAttribute("focused") == "true");
- if (isFocused)
- setTimeout(this.onCommandLineFocus);
- },
-
getCommandLine: function(context)
{
return (!this.isInOtherPanel(context) && Firebug.commandEditor) ?
@@ -1461,7 +1413,7 @@ function CommandLineHandler(context, win)
// Appends variables into the api.
var htmlPanel = context.getPanel("html", true);
- var vars = htmlPanel ? htmlPanel.getInspectorVars():null;
+ var vars = htmlPanel ? htmlPanel.getInspectorVars() : null;
for (var prop in vars)
{
View
11 extension/content/firebug/firefox/bindings.xml
@@ -1875,9 +1875,12 @@
]]></handler>
<handler event="keypress" keycode="VK_TAB"><![CDATA[
- var input = document.getAnonymousElementByAttribute(this, "anonid", "input");
- FBL.insertTextIntoElement(input, Firebug.Editor.tabCharacter);
- event.preventDefault();
+ if (!event.defaultPrevented)
+ {
+ var input = document.getAnonymousElementByAttribute(this, "anonid", "input");
+ FBL.insertTextIntoElement(input, Firebug.Editor.tabCharacter);
+ event.preventDefault();
+ }
]]></handler>
<handler event="keypress" keycode="VK_RETURN" modifiers="meta" preventdefault="true"><![CDATA[
@@ -1889,7 +1892,7 @@
]]></handler>
<handler event="keypress" keycode="VK_ESCAPE" preventdefault="true"><![CDATA[
- Firebug.CommandLine.cancel(Firebug.currentContext);
+ Firebug.CommandEditor.onEscape();
]]></handler>
</handlers>
</binding>
Please sign in to comment.
Something went wrong with that request. Please try again.