Bug 804845 - CTRL+P and CTRL+N should work the same as up arrow and down arrow respectively; r=msucan

This commit is contained in:
zmgmoz 2012-12-17 15:47:23 +02:00
parent 61a01d653b
commit a990b55ae7
3 changed files with 285 additions and 3 deletions

View File

@ -117,6 +117,12 @@ MOCHITEST_BROWSER_FILES = \
head.js \
$(NULL)
ifeq ($(OS_ARCH), Darwin)
MOCHITEST_BROWSER_FILES += \
browser_webconsole_bug_804845_ctrl_key_nav.js \
$(NULL)
endif
ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
MOCHITEST_BROWSER_FILES += \
browser_webconsole_bug_618311_private_browsing.js \

View File

@ -0,0 +1,214 @@
/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*
* Contributor(s):
* zmgmoz <zmgmoz@gmail.com>
*
* ***** END LICENSE BLOCK ***** */
// Test navigation of webconsole contents via ctrl-a, ctrl-e, ctrl-p, ctrl-n
// see https://bugzilla.mozilla.org/show_bug.cgi?id=804845
let jsterm, inputNode;
function test() {
addTab("data:text/html;charset=utf-8,Web Console test for bug 804845 and bug 619598");
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, doTests);
}, true);
}
function doTests(HUD) {
jsterm = HUD.jsterm;
inputNode = jsterm.inputNode;
ok(!jsterm.inputNode.value, "inputNode.value is empty");
is(jsterm.inputNode.selectionStart, 0);
is(jsterm.inputNode.selectionEnd, 0);
testSingleLineInputNavNoHistory();
testMultiLineInputNavNoHistory();
testNavWithHistory();
jsterm = inputNode = null;
executeSoon(finishTest);
}
function testSingleLineInputNavNoHistory() {
// Single char input
EventUtils.synthesizeKey("1", {});
is(inputNode.selectionStart, 1, "caret location after single char input");
// nav to start/end with ctrl-a and ctrl-e;
EventUtils.synthesizeKey("a", { ctrlKey: true });
is(inputNode.selectionStart, 0, "caret location after single char input and ctrl-a");
EventUtils.synthesizeKey("e", { ctrlKey: true });
is(inputNode.selectionStart, 1, "caret location after single char input and ctrl-e");
// Second char input
EventUtils.synthesizeKey("2", {});
// nav to start/end with up/down keys; verify behaviour using ctrl-p/ctrl-n
EventUtils.synthesizeKey("VK_UP", {});
is(inputNode.selectionStart, 0, "caret location after two char input and VK_UP");
EventUtils.synthesizeKey("VK_DOWN", {});
is(inputNode.selectionStart, 2, "caret location after two char input and VK_DOWN");
EventUtils.synthesizeKey("a", { ctrlKey: true });
is(inputNode.selectionStart, 0, "move caret to beginning of 2 char input with ctrl-a");
EventUtils.synthesizeKey("a", { ctrlKey: true });
is(inputNode.selectionStart, 0, "no change of caret location on repeat ctrl-a");
EventUtils.synthesizeKey("p", { ctrlKey: true });
is(inputNode.selectionStart, 0, "no change of caret location on ctrl-p from beginning of line");
EventUtils.synthesizeKey("e", { ctrlKey: true });
is(inputNode.selectionStart, 2, "move caret to end of 2 char input with ctrl-e");
EventUtils.synthesizeKey("e", { ctrlKey: true });
is(inputNode.selectionStart, 2, "no change of caret location on repeat ctrl-e");
EventUtils.synthesizeKey("n", { ctrlKey: true });
is(inputNode.selectionStart, 2, "no change of caret location on ctrl-n from end of line");
EventUtils.synthesizeKey("p", { ctrlKey: true });
is(inputNode.selectionStart, 0, "ctrl-p moves to start of line");
EventUtils.synthesizeKey("n", { ctrlKey: true });
is(inputNode.selectionStart, 2, "ctrl-n moves to end of line");
}
function testMultiLineInputNavNoHistory() {
let lineValues = ["one", "2", "something longer", "", "", "three!"];
jsterm.setInputValue("");
// simulate shift-return
for (let i = 0; i < lineValues.length; i++) {
jsterm.setInputValue(inputNode.value + lineValues[i]);
EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true });
}
let inputValue = inputNode.value;
is(inputNode.selectionStart, inputNode.selectionEnd);
is(inputNode.selectionStart, inputValue.length, "caret at end of multiline input");
// possibility newline is represented by one ('\r', '\n') or two ('\r\n') chars
let newlineString = inputValue.match(/(\r\n?|\n\r?)$/)[0];
// Ok, test navigating within the multi-line string!
EventUtils.synthesizeKey("VK_UP", {});
let expectedStringAfterCarat = lineValues[5]+newlineString;
is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
"up arrow from end of multiline");
EventUtils.synthesizeKey("VK_DOWN", {});
is(inputNode.value.slice(inputNode.selectionStart), "",
"down arrow from within multiline");
// navigate up through input lines
EventUtils.synthesizeKey("p", { ctrlKey: true });
is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
"ctrl-p from end of multiline");
for (let i = 4; i >= 0; i--) {
EventUtils.synthesizeKey("p", { ctrlKey: true });
expectedStringAfterCarat = lineValues[i] + newlineString + expectedStringAfterCarat;
is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
"ctrl-p from within line " + i + " of multiline input");
}
EventUtils.synthesizeKey("p", { ctrlKey: true });
is(inputNode.selectionStart, 0, "reached start of input");
is(inputNode.value, inputValue,
"no change to multiline input on ctrl-p from beginning of multiline");
// navigate to end of first line
EventUtils.synthesizeKey("e", { ctrlKey: true });
let caretPos = inputNode.selectionStart;
let expectedStringBeforeCarat = lineValues[0];
is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
"ctrl-e into multiline input");
EventUtils.synthesizeKey("e", { ctrlKey: true });
is(inputNode.selectionStart, caretPos,
"repeat ctrl-e doesn't change caret position in multiline input");
// navigate down one line; ctrl-a to the beginning; ctrl-e to end
for (let i = 1; i < lineValues.length; i++) {
EventUtils.synthesizeKey("n", { ctrlKey: true });
EventUtils.synthesizeKey("a", { ctrlKey: true });
caretPos = inputNode.selectionStart;
expectedStringBeforeCarat += newlineString;
is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
"ctrl-a to beginning of line " + (i+1) + " in multiline input");
EventUtils.synthesizeKey("e", { ctrlKey: true });
caretPos = inputNode.selectionStart;
expectedStringBeforeCarat += lineValues[i];
is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
"ctrl-e to end of line " + (i+1) + "in multiline input");
}
}
function testNavWithHistory() {
// NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
// caret placed _within_ single line input
let values = ['"single line input"',
'"a longer single-line input to check caret repositioning"',
['"multi-line"', '"input"', '"here!"'].join("\n"),
];
// submit to history
for (let i = 0; i < values.length; i++) {
jsterm.setInputValue(values[i]);
jsterm.execute();
}
is(inputNode.selectionStart, 0, "caret location at start of empty line");
EventUtils.synthesizeKey("p", { ctrlKey: true });
is(inputNode.selectionStart, values[values.length-1].length,
"caret location correct at end of last history input");
// Navigate backwards history with ctrl-p
for (let i = values.length-1; i > 0; i--) {
let match = values[i].match(/(\n)/g);
if (match) {
// multi-line inputs won't update from history unless caret at beginning
EventUtils.synthesizeKey("a", { ctrlKey: true });
for (let i = 0; i < match.length; i++) {
EventUtils.synthesizeKey("p", { ctrlKey: true });
}
EventUtils.synthesizeKey("p", { ctrlKey: true });
} else {
// single-line inputs will update from history from end of line
EventUtils.synthesizeKey("p", { ctrlKey: true });
}
is(inputNode.value, values[i-1],
"ctrl-p updates inputNode from backwards history values[" + i-1 + "]");
}
let inputValue = inputNode.value;
EventUtils.synthesizeKey("p", { ctrlKey: true });
is(inputNode.selectionStart, 0,
"ctrl-p at beginning of history moves caret location to beginning of line");
is(inputNode.value, inputValue,
"no change to input value on ctrl-p from beginning of line");
// Navigate forwards history with ctrl-n
for (let i = 1; i<values.length; i++) {
EventUtils.synthesizeKey("n", { ctrlKey: true });
is(inputNode.value, values[i],
"ctrl-n updates inputNode from forwards history values[" + i + "]");
is(inputNode.selectionStart, values[i].length,
"caret location correct at end of history input for values[" + i + "]");
}
EventUtils.synthesizeKey("n", { ctrlKey: true });
ok(!inputNode.value, "ctrl-n at end of history updates to empty input");
// Simulate editing multi-line
inputValue = "one\nlinebreak";
jsterm.setInputValue(inputValue);
// Attempt nav within input
EventUtils.synthesizeKey("p", { ctrlKey: true });
is(inputNode.value, inputValue,
"ctrl-p from end of multi-line does not trigger history");
EventUtils.synthesizeKey("a", { ctrlKey: true });
EventUtils.synthesizeKey("p", { ctrlKey: true });
is(inputNode.value, values[values.length-1],
"ctrl-p from start of multi-line triggers history");
}

View File

@ -3181,21 +3181,72 @@ JSTerm.prototype = {
keyPress: function JSTF_keyPress(aEvent)
{
if (aEvent.ctrlKey) {
let inputNode = this.inputNode;
let closePopup = false;
switch (aEvent.charCode) {
case 97:
// control-a
this.inputNode.setSelectionRange(0, 0);
let lineBeginPos = 0;
if (this.hasMultilineInput()) {
// find index of closest newline <= to cursor
for (let i = inputNode.selectionStart-1; i >= 0; i--) {
if (inputNode.value.charAt(i) == "\r" ||
inputNode.value.charAt(i) == "\n") {
lineBeginPos = i+1;
break;
}
}
}
inputNode.setSelectionRange(lineBeginPos, lineBeginPos);
aEvent.preventDefault();
closePopup = true;
break;
case 101:
// control-e
this.inputNode.setSelectionRange(this.inputNode.value.length,
this.inputNode.value.length);
let lineEndPos = inputNode.value.length;
if (this.hasMultilineInput()) {
// find index of closest newline >= cursor
for (let i = inputNode.selectionEnd; i<lineEndPos; i++) {
if (inputNode.value.charAt(i) == "\r" ||
inputNode.value.charAt(i) == "\n") {
lineEndPos = i;
break;
}
}
}
inputNode.setSelectionRange(lineEndPos, lineEndPos);
aEvent.preventDefault();
break;
case 110:
// Control-N differs from down arrow: it ignores autocomplete state.
// Note that we preserve the default 'down' navigation within
// multiline text.
if (Services.appinfo.OS == "Darwin" &&
this.canCaretGoNext() &&
this.historyPeruse(HISTORY_FORWARD)) {
aEvent.preventDefault();
}
closePopup = true;
break;
case 112:
// Control-P differs from up arrow: it ignores autocomplete state.
// Note that we preserve the default 'up' navigation within
// multiline text.
if (Services.appinfo.OS == "Darwin" &&
this.canCaretGoPrevious() &&
this.historyPeruse(HISTORY_BACK)) {
aEvent.preventDefault();
}
closePopup = true;
break;
default:
break;
}
if (closePopup) {
if (this.autocompletePopup.isOpen) {
this.clearCompletion();
}
}
return;
}
else if (aEvent.shiftKey &&
@ -3316,6 +3367,17 @@ JSTerm.prototype = {
return true;
},
/**
* Test for multiline input.
*
* @return boolean
* True if CR or LF found in node value; else false.
*/
hasMultilineInput: function JST_hasMultilineInput()
{
return /[\r\n]/.test(this.inputNode.value);
},
/**
* Check if the caret is at a location that allows selecting the previous item
* in history when the user presses the Up arrow key.