DevTools: [CodeMirror] add support for camel case movements.

Camel-case movement allows movement between
subwords of camel-case-written words or phrases.

This patch adds support for camel-case movement to the DevTools editor.
The camel-case movement is done via Alt-Left/Alt-Right on Win and Linux,
and via Ctrl-Left/Ctrl-Right on Mac.

Review URL: https://codereview.chromium.org/357483007

git-svn-id: svn://svn.chromium.org/blink/trunk@177099 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent d23b0e7c
This test checks how text editor handles different ctrl-based movements, i.e. ctrl-left, ctrl-right, ctrl-shift-left, ctrl-backspace.
This test checks how text editor handles different movements: ctrl-left, ctrl-right, ctrl-shift-left, ctrl-backspace, alt-left, alt-right, alt-shift-left, alt-shift-right.
function testFunction(foo, bar)
{
......@@ -501,3 +501,404 @@ function <<
===============
<<
Running: testAltRight
====== CAMEL CASE MOVEMENTS ======
|function testMyCamelMove(foo, bar)
function| testMyCamelMove(foo, bar)
function test|MyCamelMove(foo, bar)
function testMy|CamelMove(foo, bar)
function testMyCamel|Move(foo, bar)
function testMyCamelMove|(foo, bar)
function testMyCamelMove(|foo, bar)
function testMyCamelMove(foo|, bar)
function testMyCamelMove(foo,| bar)
function testMyCamelMove(foo, bar|)
function testMyCamelMove(foo, bar)|
|{
{|
| /* HelloWorld.TestSTRIng */
/*| HelloWorld.TestSTRIng */
/* Hello|World.TestSTRIng */
/* HelloWorld|.TestSTRIng */
/* HelloWorld.|TestSTRIng */
/* HelloWorld.Test|STRIng */
/* HelloWorld.TestSTR|Ing */
/* HelloWorld.TestSTRIng| */
/* HelloWorld.TestSTRIng */|
|
|
| var a = e === 2;
var| a = e === 2;
var a| = e === 2;
var a =| e === 2;
var a = e| === 2;
var a = e ===| 2;
var a = e === 2|;
var a = e === 2;|
var a = e === 2; |
|{}
{}|
|}
}|
}|
Running: testAltLeft
}|
|}
{}|
|{}
var a = e === 2; |
var a = e === 2|;
var a = e === |2;
var a = e |=== 2;
var a = |e === 2;
var a |= e === 2;
var |a = e === 2;
|var a = e === 2;
| var a = e === 2;
|
|
/* HelloWorld.TestSTRIng */|
/* HelloWorld.TestSTRIng |*/
/* HelloWorld.TestSTR|Ing */
/* HelloWorld.Test|STRIng */
/* HelloWorld.|TestSTRIng */
/* HelloWorld|.TestSTRIng */
/* Hello|World.TestSTRIng */
/* |HelloWorld.TestSTRIng */
|/* HelloWorld.TestSTRIng */
| /* HelloWorld.TestSTRIng */
{|
|{
function testMyCamelMove(foo, bar)|
function testMyCamelMove(foo, bar|)
function testMyCamelMove(foo, |bar)
function testMyCamelMove(foo|, bar)
function testMyCamelMove(|foo, bar)
function testMyCamelMove|(foo, bar)
function testMyCamel|Move(foo, bar)
function testMy|CamelMove(foo, bar)
function test|MyCamelMove(foo, bar)
function |testMyCamelMove(foo, bar)
|function testMyCamelMove(foo, bar)
|function testMyCamelMove(foo, bar)
Running: testAltShiftRight
|function testMyCamelMove(foo, bar)
>>function<<
>>function test<<
>>function testMy<<
>>function testMyCamel<<
>>function testMyCamelMove<<
>>function testMyCamelMove(<<
>>function testMyCamelMove(foo<<
>>function testMyCamelMove(foo,<<
>>function testMyCamelMove(foo, bar<<
>>function testMyCamelMove(foo, bar)<<
>>function testMyCamelMove(foo, bar)
<<
>>function testMyCamelMove(foo, bar)
{<<
>>function testMyCamelMove(foo, bar)
{
<<
>>function testMyCamelMove(foo, bar)
{
/*<<
>>function testMyCamelMove(foo, bar)
{
/* Hello<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.Test<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTR<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a =<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e ===<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2; <<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
Running: testAltShiftLeft
}|
>>}<<
>>
}<<
>>{}
}<<
>>
{}
}<<
>>;
{}
}<<
>>2;
{}
}<<
>>=== 2;
{}
}<<
>>e === 2;
{}
}<<
>>= e === 2;
{}
}<<
>>a = e === 2;
{}
}<<
>>var a = e === 2;
{}
}<<
>> var a = e === 2;
{}
}<<
>>
var a = e === 2;
{}
}<<
>>
var a = e === 2;
{}
}<<
>>
var a = e === 2;
{}
}<<
>>*/
var a = e === 2;
{}
}<<
>>Ing */
var a = e === 2;
{}
}<<
>>STRIng */
var a = e === 2;
{}
}<<
>>TestSTRIng */
var a = e === 2;
{}
}<<
>>.TestSTRIng */
var a = e === 2;
{}
}<<
>>World.TestSTRIng */
var a = e === 2;
{}
}<<
>>HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>> /* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>Move(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>CamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>MyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
>>function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}<<
......@@ -12,6 +12,14 @@ function testFunction(foo, bar)
return a === 1 ? true : false;
}
function testMyCamelMove(foo, bar)
{
/* HelloWorld.TestSTRIng */
var a = e === 2;
{}
}
var textEditor = InspectorTest.createTestEditor();
textEditor.setMimeType("text/javascript");
textEditor.setReadOnly(false);
......@@ -20,6 +28,7 @@ function testFunction(foo, bar)
InspectorTest.addResult(textEditor.text());
const wordJumpModifier = WebInspector.isMac() ? "altKey" : "ctrlKey";
const camelJumpModifier = WebInspector.isMac() ? "ctrlKey" : "altKey";
function dumpEditorSelection()
{
......@@ -106,6 +115,36 @@ function testFunction(foo, bar)
next();
}
InspectorTest.fakeKeyEvent(textEditor, "\b", [wordJumpModifier], eventCallback);
},
function testAltRight(next)
{
InspectorTest.addResult("====== CAMEL CASE MOVEMENTS ======");
textEditor.setText(testMyCamelMove.toString());
setCursorAtBeginning();
dumpEditorSelection();
fireEventWhileSelectionChanges("rightArrow", [camelJumpModifier], next);
},
function testAltLeft(next)
{
setCursorAtEnd();
dumpEditorSelection();
fireEventWhileSelectionChanges("leftArrow", [camelJumpModifier], next);
},
function testAltShiftRight(next)
{
setCursorAtBeginning();
dumpEditorSelection();
fireEventWhileSelectionChanges("rightArrow", [camelJumpModifier, "shiftKey"], next);
},
function testAltShiftLeft(next)
{
setCursorAtEnd();
dumpEditorSelection();
fireEventWhileSelectionChanges("leftArrow", [camelJumpModifier, "shiftKey"], next);
}
]);
......@@ -116,7 +155,7 @@ function testFunction(foo, bar)
<body onload="runTest();">
<p>
This test checks how text editor handles different ctrl-based movements, i.e. ctrl-left, ctrl-right, ctrl-shift-left, ctrl-backspace.
This test checks how text editor handles different movements: ctrl-left, ctrl-right, ctrl-shift-left, ctrl-backspace, alt-left, alt-right, alt-shift-left, alt-shift-right.
</p>
</body>
......
......@@ -83,8 +83,10 @@ WebInspector.CodeMirrorTextEditor = function(url, delegate)
"Ctrl-Down": "goDocEnd",
"Ctrl-Left": "goGroupLeft",
"Ctrl-Right": "goGroupRight",
"Alt-Left": "goLineStart",
"Alt-Right": "goLineEnd",
"Alt-Left": "moveCamelLeft",
"Alt-Right": "moveCamelRight",
"Shift-Alt-Left": "selectCamelLeft",
"Shift-Alt-Right": "selectCamelRight",
"Ctrl-Backspace": "delGroupBefore",
"Ctrl-Delete": "delGroupAfter",
"Ctrl-/": "toggleComment",
......@@ -101,6 +103,10 @@ WebInspector.CodeMirrorTextEditor = function(url, delegate)
"Cmd-Down": "goDocEnd",
"Alt-Left": "goGroupLeft",
"Alt-Right": "goGroupRight",
"Ctrl-Left": "moveCamelLeft",
"Ctrl-Right": "moveCamelRight",
"Shift-Ctrl-Left": "selectCamelLeft",
"Shift-Ctrl-Right": "selectCamelRight",
"Cmd-Left": "goLineStartSmart",
"Cmd-Right": "goLineEnd",
"Alt-Backspace": "delGroupBefore",
......@@ -199,6 +205,28 @@ WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMir
}
CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand;
/**
* @param {boolean} shift
* @param {!CodeMirror} codeMirror
*/
WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand = function(shift, codeMirror)
{
codeMirror._codeMirrorTextEditor._doCamelCaseMovement(-1, shift);
}
CodeMirror.commands.moveCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, false);
CodeMirror.commands.selectCamelLeft = WebInspector.CodeMirrorTextEditor.moveCamelLeftCommand.bind(null, true);
/**
* @param {boolean} shift
* @param {!CodeMirror} codeMirror
*/
WebInspector.CodeMirrorTextEditor.moveCamelRightCommand = function(shift, codeMirror)
{
codeMirror._codeMirrorTextEditor._doCamelCaseMovement(1, shift);
}
CodeMirror.commands.moveCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, false);
CodeMirror.commands.selectCamelRight = WebInspector.CodeMirrorTextEditor.moveCamelRightCommand.bind(null, true);
/**
* @param {!CodeMirror} codeMirror
*/
......@@ -272,6 +300,154 @@ WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
WebInspector.CodeMirrorTextEditor.prototype = {
/**
* @param {number} lineNumber
* @param {number} lineLength
* @param {number} charNumber
* @return {{lineNumber: number, columnNumber: number}}
*/
_normalizePositionForOverlappingColumn: function(lineNumber, lineLength, charNumber)
{
var linesCount = this._codeMirror.lineCount();
var columnNumber = charNumber;
if (charNumber < 0 && lineNumber > 0) {
--lineNumber;
columnNumber = this.line(lineNumber).length;
} else if (charNumber >= lineLength && lineNumber < linesCount - 1) {
++lineNumber;
columnNumber = 0;
} else {
columnNumber = Number.constrain(charNumber, 0, lineLength);
}
return {
lineNumber: lineNumber,
columnNumber: columnNumber
};
},
/**
* @param {number} lineNumber
* @param {number} columnNumber
* @param {number} direction
* @return {{lineNumber: number, columnNumber: number}}
*/
_camelCaseMoveFromPosition: function(lineNumber, columnNumber, direction)
{
/**
* @param {number} charNumber
* @param {number} length
* @return {boolean}
*/
function valid(charNumber, length)
{
return charNumber >= 0 && charNumber < length;
}
/**
* @param {string} text
* @param {number} charNumber
* @return {boolean}
*/
function isWordStart(text, charNumber)
{
var position = charNumber;
var nextPosition = charNumber + 1;
return valid(position, text.length) && valid(nextPosition, text.length)
&& WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[nextPosition])
&& WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[nextPosition]);
}
/**
* @param {string} text
* @param {number} charNumber
* @return {boolean}
*/
function isWordEnd(text, charNumber)
{
var position = charNumber;
var prevPosition = charNumber - 1;
return valid(position, text.length) && valid(prevPosition, text.length)
&& WebInspector.TextUtils.isWordChar(text[position]) && WebInspector.TextUtils.isWordChar(text[prevPosition])
&& WebInspector.TextUtils.isUpperCase(text[position]) && WebInspector.TextUtils.isLowerCase(text[prevPosition]);
}
/**
* @param {number} lineNumber
* @param {number} lineLength
* @param {number} columnNumber
* @return {{lineNumber: number, columnNumber: number}}
*/
function constrainPosition(lineNumber, lineLength, columnNumber)
{
return {
lineNumber: lineNumber,
columnNumber: Number.constrain(columnNumber, 0, lineLength)
};
}
var text = this.line(lineNumber);
var length = text.length;
if ((columnNumber === length && direction === 1)
|| (columnNumber === 0 && direction === -1))
return this._normalizePositionForOverlappingColumn(lineNumber, length, columnNumber + direction);
var charNumber = direction === 1 ? columnNumber : columnNumber - 1;
// Move through initial spaces if any.
while (valid(charNumber, length) && WebInspector.TextUtils.isSpaceChar(text[charNumber]))
charNumber += direction;
if (!valid(charNumber, length))
return constrainPosition(lineNumber, length, charNumber);
if (WebInspector.TextUtils.isStopChar(text[charNumber])) {
while (valid(charNumber, length) && WebInspector.TextUtils.isStopChar(text[charNumber]))
charNumber += direction;
if (!valid(charNumber, length))
return constrainPosition(lineNumber, length, charNumber);
return {
lineNumber: lineNumber,
columnNumber: direction === -1 ? charNumber + 1 : charNumber
};
}
charNumber += direction;
while (valid(charNumber, length) && !isWordStart(text, charNumber) && !isWordEnd(text, charNumber) && WebInspector.TextUtils.isWordChar(text[charNumber]))
charNumber += direction;
if (!valid(charNumber, length))
return constrainPosition(lineNumber, length, charNumber);
if (isWordStart(text, charNumber) || isWordEnd(text, charNumber)) {
return {
lineNumber: lineNumber,
columnNumber: charNumber
};
}
return {
lineNumber: lineNumber,
columnNumber: direction === -1 ? charNumber + 1 : charNumber
};
},
/**
* @param {number} direction
* @param {boolean} shift
*/
_doCamelCaseMovement: function(direction, shift)
{
var selections = this.selections();
for (var i = 0; i < selections.length; ++i) {
var selection = selections[i];
var move = this._camelCaseMoveFromPosition(selection.endLine, selection.endColumn, direction);
selection.endLine = move.lineNumber;
selection.endColumn = move.columnNumber;
if (!shift)
selections[i] = selection.collapseToEnd();
}
this.setSelections(selections);
},
dispose: function()
{
WebInspector.settings.textEditorIndent.removeChangeListener(this._updateEditorIndentation, this);
......
......@@ -153,6 +153,24 @@ WebInspector.TextUtils = {
}
}
return -1;
},
/**
* @param {string} text
* @return {boolean}
*/
isUpperCase: function(text)
{
return text === text.toUpperCase();
},
/**
* @param {string} text
* @return {boolean}
*/
isLowerCase: function(text)
{
return text === text.toLowerCase();
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment