diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index 1939996a24279..300174e613aa8 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -39,7 +39,8 @@ const enum WordType { export const enum WordNavigationType { WordStart = 0, WordStartFast = 1, - WordEnd = 2 + WordEnd = 2, + WordAccessibility = 3 // Respect chrome defintion of a word } export class WordOperations { @@ -202,6 +203,18 @@ export class WordOperations { return new Position(lineNumber, prevWordOnLine ? prevWordOnLine.start + 1 : 1); } + if (wordNavigationType === WordNavigationType.WordAccessibility) { + while ( + prevWordOnLine + && prevWordOnLine.wordType === WordType.Separator + ) { + // Skip over words made up of only separators + prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, new Position(lineNumber, prevWordOnLine.start + 1)); + } + + return new Position(lineNumber, prevWordOnLine ? prevWordOnLine.start + 1 : 1); + } + // We are stopping at the ending of words if (prevWordOnLine && column <= prevWordOnLine.end + 1) { @@ -270,6 +283,21 @@ export class WordOperations { nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1)); } } + if (nextWordOnLine) { + column = nextWordOnLine.end + 1; + } else { + column = model.getLineMaxColumn(lineNumber); + } + } else if (wordNavigationType === WordNavigationType.WordAccessibility) { + + while ( + nextWordOnLine + && nextWordOnLine.wordType === WordType.Separator + ) { + // Skip over a word made up of one single separator + nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1)); + } + if (nextWordOnLine) { column = nextWordOnLine.end + 1; } else { @@ -617,4 +645,4 @@ export class WordPartOperations extends WordOperations { function enforceDefined(arr: Array): T[] { return arr.filter(el => Boolean(el)); -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index 98c22c721efa6..baacaf2b44588 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -9,7 +9,7 @@ import { EditorCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { deserializePipePositions, serializePipePositions, testRepeatedActionAndExtractPositions } from 'vs/editor/contrib/wordOperations/test/wordTestUtils'; -import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorWordEndRightSelect, CursorWordLeft, CursorWordLeftSelect, CursorWordRight, CursorWordRightSelect, CursorWordStartLeft, CursorWordStartLeftSelect, CursorWordStartRight, CursorWordStartRightSelect, DeleteWordEndLeft, DeleteWordEndRight, DeleteWordLeft, DeleteWordRight, DeleteWordStartLeft, DeleteWordStartRight } from 'vs/editor/contrib/wordOperations/wordOperations'; +import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorWordEndRightSelect, CursorWordLeft, CursorWordLeftSelect, CursorWordRight, CursorWordRightSelect, CursorWordStartLeft, CursorWordStartLeftSelect, CursorWordStartRight, CursorWordStartRightSelect, DeleteWordEndLeft, DeleteWordEndRight, DeleteWordLeft, DeleteWordRight, DeleteWordStartLeft, DeleteWordStartRight, CursorWordAccessibilityLeft, CursorWordAccessibilityLeftSelect, CursorWordAccessibilityRight, CursorWordAccessibilityRightSelect } from 'vs/editor/contrib/wordOperations/wordOperations'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; suite('WordOperations', () => { @@ -26,6 +26,10 @@ suite('WordOperations', () => { const _cursorWordStartRightSelect = new CursorWordStartRightSelect(); const _cursorWordEndRightSelect = new CursorWordEndRightSelect(); const _cursorWordRightSelect = new CursorWordRightSelect(); + const _cursorWordAccessibilityLeft = new CursorWordAccessibilityLeft(); + const _cursorWordAccessibilityLeftSelect = new CursorWordAccessibilityLeftSelect(); + const _cursorWordAccessibilityRight = new CursorWordAccessibilityRight(); + const _cursorWordAccessibilityRightSelect = new CursorWordAccessibilityRightSelect(); const _deleteWordLeft = new DeleteWordLeft(); const _deleteWordStartLeft = new DeleteWordStartLeft(); const _deleteWordEndLeft = new DeleteWordEndLeft(); @@ -39,6 +43,12 @@ suite('WordOperations', () => { function cursorWordLeft(editor: ICodeEditor, inSelectionMode: boolean = false): void { runEditorCommand(editor, inSelectionMode ? _cursorWordLeftSelect : _cursorWordLeft); } + function cursorWordAccessibilityLeft(editor: ICodeEditor, inSelectionMode: boolean = false): void { + runEditorCommand(editor, inSelectionMode ? _cursorWordAccessibilityLeft : _cursorWordAccessibilityLeftSelect); + } + function cursorWordAccessibilityRight(editor: ICodeEditor, inSelectionMode: boolean = false): void { + runEditorCommand(editor, inSelectionMode ? _cursorWordAccessibilityRightSelect : _cursorWordAccessibilityRight); + } function cursorWordStartLeft(editor: ICodeEditor, inSelectionMode: boolean = false): void { runEditorCommand(editor, inSelectionMode ? _cursorWordStartLeftSelect : _cursorWordStartLeft); } @@ -326,6 +336,34 @@ suite('WordOperations', () => { assert.deepEqual(actual, EXPECTED); }); + test('cursorWordAccessibilityLeft', () => { + const EXPECTED = ['| /* |Just |some |more |text |a+= |3 +|5-|3 + |7 */ '].join('\n'); + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1000, 1000), + ed => cursorWordAccessibilityLeft(ed), + ed => ed.getPosition()!, + ed => ed.getPosition()!.equals(new Position(1, 1)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + + test('cursorWordAccessibilityRight', () => { + const EXPECTED = [' /* Just| some| more| text| a|+= 3| +5|-3| + 7| */ |'].join('\n'); + const [text,] = deserializePipePositions(EXPECTED); + const actualStops = testRepeatedActionAndExtractPositions( + text, + new Position(1, 1), + ed => cursorWordAccessibilityRight(ed), + ed => ed.getPosition()!, + ed => ed.getPosition()!.equals(new Position(1, 50)) + ); + const actual = serializePipePositions(text, actualStops); + assert.deepEqual(actual, EXPECTED); + }); + test('deleteWordLeft for non-empty selection', () => { withTestCodeEditor([ ' \tMy First Line\t ', diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index dc4e111f96261..e104f52a534d5 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -18,6 +18,9 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions'; export interface MoveWordOptions extends ICommandOptions { inSelectionMode: boolean; @@ -170,6 +173,48 @@ export class CursorWordLeftSelect extends WordLeftCommand { } } +export class CursorWordAccessibilityLeft extends WordLeftCommand { + constructor() { + super({ + inSelectionMode: false, + wordNavigationType: WordNavigationType.WordAccessibility, + id: 'cursorWordAccessibilityLeft', + precondition: undefined, + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.textInputFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + primary: KeyMod.CtrlCmd | KeyCode.LeftArrow, + mac: { primary: KeyMod.Alt | KeyCode.LeftArrow }, + weight: KeybindingWeight.EditorContrib + 1 + } + }); + } + + protected _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + return super._move(getMapForWordSeparators(EDITOR_DEFAULTS.wordSeparators), model, position, wordNavigationType); + } +} + +export class CursorWordAccessibilityLeftSelect extends WordLeftCommand { + constructor() { + super({ + inSelectionMode: true, + wordNavigationType: WordNavigationType.WordAccessibility, + id: 'cursorWordAccessibilitLeftSelecty', + precondition: undefined, + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.textInputFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow, + mac: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow }, + weight: KeybindingWeight.EditorContrib + 1 + } + }); + } + + protected _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + return super._move(getMapForWordSeparators(EDITOR_DEFAULTS.wordSeparators), model, position, wordNavigationType); + } +} + export class CursorWordStartRight extends WordRightCommand { constructor() { super({ @@ -248,6 +293,48 @@ export class CursorWordRightSelect extends WordRightCommand { } } +export class CursorWordAccessibilityRight extends WordRightCommand { + constructor() { + super({ + inSelectionMode: false, + wordNavigationType: WordNavigationType.WordAccessibility, + id: 'cursorWordAccessibilityRight', + precondition: undefined, + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.textInputFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + primary: KeyMod.CtrlCmd | KeyCode.RightArrow, + mac: { primary: KeyMod.Alt | KeyCode.RightArrow }, + weight: KeybindingWeight.EditorContrib + 1 + } + }); + } + + protected _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + return super._move(getMapForWordSeparators(EDITOR_DEFAULTS.wordSeparators), model, position, wordNavigationType); + } +} + +export class CursorWordAccessibilityRightSelect extends WordRightCommand { + constructor() { + super({ + inSelectionMode: true, + wordNavigationType: WordNavigationType.WordAccessibility, + id: 'cursorWordAccessibilityRightSelect', + precondition: undefined, + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.textInputFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow, + mac: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow }, + weight: KeybindingWeight.EditorContrib + 1 + } + }); + } + + protected _move(_: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { + return super._move(getMapForWordSeparators(EDITOR_DEFAULTS.wordSeparators), model, position, wordNavigationType); + } +} + export interface DeleteWordOptions extends ICommandOptions { whitespaceHeuristics: boolean; wordNavigationType: WordNavigationType; @@ -397,6 +484,10 @@ registerEditorCommand(new CursorWordRight()); registerEditorCommand(new CursorWordStartRightSelect()); registerEditorCommand(new CursorWordEndRightSelect()); registerEditorCommand(new CursorWordRightSelect()); +registerEditorCommand(new CursorWordAccessibilityLeft()); +registerEditorCommand(new CursorWordAccessibilityLeftSelect()); +registerEditorCommand(new CursorWordAccessibilityRight()); +registerEditorCommand(new CursorWordAccessibilityRightSelect()); registerEditorCommand(new DeleteWordStartLeft()); registerEditorCommand(new DeleteWordEndLeft()); registerEditorCommand(new DeleteWordLeft());