Bug 725618 - Source Editor: keyboard shortcut for moving lines up/down; r=msucan

This commit is contained in:
Allen Eubank 2012-03-06 17:53:59 +02:00
parent 6bbad53cec
commit a8eb63c0a8
4 changed files with 214 additions and 1 deletions

View File

@ -23,6 +23,7 @@
* Mihai Sucan <mihai.sucan@gmail.com> (original author) * Mihai Sucan <mihai.sucan@gmail.com> (original author)
* Kenny Heaton <kennyheaton@gmail.com> * Kenny Heaton <kennyheaton@gmail.com>
* Spyros Livathinos <livathinos.spyros@gmail.com> * Spyros Livathinos <livathinos.spyros@gmail.com>
* Allen Eubank <adeubank@gmail.com>
* *
* Alternatively, the contents of this file may be used under the terms of * Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or * either the GNU General Public License Version 2 or later (the "GPL"), or
@ -124,6 +125,18 @@ const DEFAULT_KEYBINDINGS = [
code: Ci.nsIDOMKeyEvent.DOM_VK_TAB, code: Ci.nsIDOMKeyEvent.DOM_VK_TAB,
shift: true, shift: true,
}, },
{
action: "Move Lines Up",
code: Ci.nsIDOMKeyEvent.DOM_VK_UP,
ctrl: Services.appinfo.OS == "Darwin",
alt: true,
},
{
action: "Move Lines Down",
code: Ci.nsIDOMKeyEvent.DOM_VK_DOWN,
ctrl: Services.appinfo.OS == "Darwin",
alt: true,
},
]; ];
var EXPORTED_SYMBOLS = ["SourceEditor"]; var EXPORTED_SYMBOLS = ["SourceEditor"];
@ -367,6 +380,7 @@ SourceEditor.prototype = {
"Find Next Occurrence": [this.ui.findNext, this.ui], "Find Next Occurrence": [this.ui.findNext, this.ui],
"Find Previous Occurrence": [this.ui.findPrevious, this.ui], "Find Previous Occurrence": [this.ui.findPrevious, this.ui],
"Goto Line...": [this.ui.gotoLine, this.ui], "Goto Line...": [this.ui.gotoLine, this.ui],
"Move Lines Down": [this._moveLines, this],
}; };
for (let name in actions) { for (let name in actions) {
@ -374,9 +388,17 @@ SourceEditor.prototype = {
this._view.setAction(name, action[0].bind(action[1])); this._view.setAction(name, action[0].bind(action[1]));
} }
this._view.setAction("Move Lines Up", this._moveLines.bind(this, true));
let keys = (config.keys || []).concat(DEFAULT_KEYBINDINGS); let keys = (config.keys || []).concat(DEFAULT_KEYBINDINGS);
keys.forEach(function(aKey) { keys.forEach(function(aKey) {
let binding = new KeyBinding(aKey.code, aKey.accel, aKey.shift, aKey.alt); // In Orion mod1 refers to Cmd on Macs and Ctrl on Windows and Linux.
// So, if ctrl is in aKey we use it on Windows and Linux, otherwise
// we use aKey.accel for mod1.
let mod1 = Services.appinfo.OS != "Darwin" &&
"ctrl" in aKey ? aKey.ctrl : aKey.accel;
let binding = new KeyBinding(aKey.code, mod1, aKey.shift, aKey.alt,
aKey.ctrl);
this._view.setKeyBinding(binding, aKey.action); this._view.setKeyBinding(binding, aKey.action);
if (aKey.callback) { if (aKey.callback) {
@ -578,6 +600,78 @@ SourceEditor.prototype = {
return true; return true;
}, },
/**
* Move lines upwards or downwards, relative to the current caret location.
*
* @private
* @param boolean aLineAbove
* True if moving lines up, false to move lines down.
*/
_moveLines: function SE__moveLines(aLineAbove)
{
if (this.readOnly) {
return false;
}
let model = this._model;
let selection = this.getSelection();
let firstLine = model.getLineAtOffset(selection.start);
if (firstLine == 0 && aLineAbove) {
return true;
}
let lastLine = model.getLineAtOffset(selection.end);
let firstLineStart = model.getLineStart(firstLine);
let lastLineStart = model.getLineStart(lastLine);
if (selection.start != selection.end && lastLineStart == selection.end) {
lastLine--;
}
if (!aLineAbove && (lastLine + 1) == this.getLineCount()) {
return true;
}
let lastLineEnd = model.getLineEnd(lastLine, true);
let text = this.getText(firstLineStart, lastLineEnd);
if (aLineAbove) {
let aboveLine = firstLine - 1;
let aboveLineStart = model.getLineStart(aboveLine);
this.startCompoundChange();
if (lastLine == (this.getLineCount() - 1)) {
let delimiterStart = model.getLineEnd(aboveLine);
let delimiterEnd = model.getLineEnd(aboveLine, true);
let lineDelimiter = this.getText(delimiterStart, delimiterEnd);
text += lineDelimiter;
this.setText("", firstLineStart - lineDelimiter.length, lastLineEnd);
} else {
this.setText("", firstLineStart, lastLineEnd);
}
this.setText(text, aboveLineStart, aboveLineStart);
this.endCompoundChange();
this.setSelection(aboveLineStart, aboveLineStart + text.length);
} else {
let belowLine = lastLine + 1;
let belowLineEnd = model.getLineEnd(belowLine, true);
let insertAt = belowLineEnd - lastLineEnd + firstLineStart;
let lineDelimiter = "";
if (belowLine == this.getLineCount() - 1) {
let delimiterStart = model.getLineEnd(lastLine);
lineDelimiter = this.getText(delimiterStart, lastLineEnd);
text = lineDelimiter + text.substr(0, text.length -
lineDelimiter.length);
}
this.startCompoundChange();
this.setText("", firstLineStart, lastLineEnd);
this.setText(text, insertAt, insertAt);
this.endCompoundChange();
this.setSelection(insertAt + lineDelimiter.length,
insertAt + text.length);
}
return true;
},
/** /**
* The Orion Selection event handler. The current caret line is * The Orion Selection event handler. The current caret line is
* highlighted and for Linux users the selected text is copied into the X11 * highlighted and for Linux users the selected text is copied into the X11

View File

@ -192,6 +192,7 @@ SourceEditor.DEFAULTS = {
* - action - name of the editor action to invoke. * - action - name of the editor action to invoke.
* - code - keyCode for the shortcut. * - code - keyCode for the shortcut.
* - accel - boolean for the Accel key (Cmd on Macs, Ctrl on Linux/Windows). * - accel - boolean for the Accel key (Cmd on Macs, Ctrl on Linux/Windows).
* - ctrl - boolean for the Control key
* - shift - boolean for the Shift key. * - shift - boolean for the Shift key.
* - alt - boolean for the Alt key. * - alt - boolean for the Alt key.
* - callback - optional function to invoke, if the action is not predefined * - callback - optional function to invoke, if the action is not predefined

View File

@ -58,6 +58,7 @@ _BROWSER_TEST_FILES = \
browser_bug725388_mouse_events.js \ browser_bug725388_mouse_events.js \
browser_bug707987_debugger_breakpoints.js \ browser_bug707987_debugger_breakpoints.js \
browser_bug712982_line_ruler_click.js \ browser_bug712982_line_ruler_click.js \
browser_bug725618_moveLines_shortcut.js \
browser_bug700893_dirty_state.js \ browser_bug700893_dirty_state.js \
head.js \ head.js \

View File

@ -0,0 +1,117 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let tempScope = {};
Cu.import("resource:///modules/source-editor.jsm", tempScope);
let SourceEditor = tempScope.SourceEditor;
let editor;
let testWin;
function test()
{
waitForExplicitFinish();
const windowUrl = "data:application/vnd.mozilla.xul+xml,<?xml version='1.0'?>" +
"<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
" title='test for bug 725618 - moveLines shortcut' width='300' height='500'>" +
"<box flex='1'/></window>";
const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
testWin.addEventListener("load", function onWindowLoad() {
testWin.removeEventListener("load", onWindowLoad, false);
waitForFocus(initEditor, testWin);
}, false);
}
function initEditor()
{
let box = testWin.document.querySelector("box");
let text = "target\nfoo\nbar"
let config = {
initialText: text,
};
editor = new SourceEditor();
editor.init(box, config, editorLoaded);
}
function editorLoaded()
{
editor.focus();
editor.setCaretOffset(0);
let modifiers = {altKey: true, ctrlKey: Services.appinfo.OS == "Darwin"};
EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
is(editor.getText(), "foo\ntarget\nbar", "Move lines down works");
is(editor.getSelectedText(), "target\n", "selection is correct");
EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
is(editor.getText(), "foo\nbar\ntarget", "Move lines down works");
is(editor.getSelectedText(), "target", "selection is correct");
EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
is(editor.getText(), "foo\nbar\ntarget", "Check for bottom of editor works");
is(editor.getSelectedText(), "target", "selection is correct");
EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
is(editor.getText(), "foo\ntarget\nbar", "Move lines up works");
is(editor.getSelectedText(), "target\n", "selection is correct");
EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
is(editor.getText(), "target\nfoo\nbar", "Move lines up works");
is(editor.getSelectedText(), "target\n", "selection is correct");
EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
is(editor.getText(), "target\nfoo\nbar", "Check for top of editor works");
is(editor.getSelectedText(), "target\n", "selection is correct");
editor.setSelection(0, 10);
info("text within selection =" + editor.getSelectedText());
EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
is(editor.getText(), "bar\ntarget\nfoo", "Multiple line move down works");
is(editor.getSelectedText(), "target\nfoo", "selection is correct");
EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
is(editor.getText(), "bar\ntarget\nfoo",
"Check for bottom of editor works with multiple line selection");
is(editor.getSelectedText(), "target\nfoo", "selection is correct");
EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
is(editor.getText(), "target\nfoo\nbar", "Multiple line move up works");
is(editor.getSelectedText(), "target\nfoo\n", "selection is correct");
EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
is(editor.getText(), "target\nfoo\nbar",
"Check for top of editor works with multiple line selection");
is(editor.getSelectedText(), "target\nfoo\n", "selection is correct");
editor.readOnly = true;
editor.setCaretOffset(0);
EventUtils.synthesizeKey("VK_UP", modifiers, testWin);
is(editor.getText(), "target\nfoo\nbar",
"Check for readOnly mode works with move lines up");
EventUtils.synthesizeKey("VK_DOWN", modifiers, testWin);
is(editor.getText(), "target\nfoo\nbar",
"Check for readOnly mode works with move lines down");
finish();
}
registerCleanupFunction(function()
{
editor.destroy();
testWin.close();
testWin = editor = null;
});