Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new commands to allow finer-grain line navigation #9220

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/vs/editor/common/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,18 +205,38 @@ registerCoreCommand(H.CursorHome, {
primary: KeyCode.Home,
mac: { primary: KeyCode.Home, secondary: [KeyMod.CtrlCmd | KeyCode.LeftArrow, KeyMod.WinCtrl | KeyCode.KEY_A] }
});
registerCoreDispatchCommand2(H.CursorHomeVisualThenLogical);
registerCoreDispatchCommand2(H.CursorHomeLogicalThenVisual);
registerCoreDispatchCommand2(H.CursorHomeVisualOnly);
registerCoreDispatchCommand2(H.CursorHomeLogicalOnly);

registerCoreCommand(H.CursorHomeSelect, {
primary: KeyMod.Shift | KeyCode.Home,
mac: { primary: KeyMod.Shift | KeyCode.Home, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow] }
});
registerCoreDispatchCommand2(H.CursorHomeVisualThenLogicalSelect);
registerCoreDispatchCommand2(H.CursorHomeLogicalThenVisualSelect);
registerCoreDispatchCommand2(H.CursorHomeVisualOnlySelect);
registerCoreDispatchCommand2(H.CursorHomeLogicalOnlySelect);

registerCoreCommand(H.CursorEnd, {
primary: KeyCode.End,
mac: { primary: KeyCode.End, secondary: [KeyMod.CtrlCmd | KeyCode.RightArrow, KeyMod.WinCtrl | KeyCode.KEY_E] }
});
registerCoreDispatchCommand2(H.CursorEndVisualThenLogical);
registerCoreDispatchCommand2(H.CursorEndLogicalThenVisual);
registerCoreDispatchCommand2(H.CursorEndVisualOnly);
registerCoreDispatchCommand2(H.CursorEndLogicalOnly);

registerCoreCommand(H.CursorEndSelect, {
primary: KeyMod.Shift | KeyCode.End,
mac: { primary: KeyMod.Shift | KeyCode.End, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow] }
});
registerCoreDispatchCommand2(H.CursorEndVisualThenLogicalSelect);
registerCoreDispatchCommand2(H.CursorEndLogicalThenVisualSelect);
registerCoreDispatchCommand2(H.CursorEndVisualOnlySelect);
registerCoreDispatchCommand2(H.CursorEndLogicalOnlySelect);

registerCoreCommand(H.ExpandLineSelection, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_I
});
Expand Down
38 changes: 29 additions & 9 deletions src/vs/editor/common/controller/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {EventEmitter} from 'vs/base/common/eventEmitter';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {ReplaceCommand} from 'vs/editor/common/commands/replaceCommand';
import {CursorCollection, ICursorCollectionState} from 'vs/editor/common/controller/cursorCollection';
import {WordNavigationType, IOneCursorOperationContext, IPostOperationRunnable, IViewModelHelper, OneCursor, OneCursorOp} from 'vs/editor/common/controller/oneCursor';
import {WordNavigationType, LineNavigationType, IOneCursorOperationContext, IPostOperationRunnable, IViewModelHelper, OneCursor, OneCursorOp} from 'vs/editor/common/controller/oneCursor';
import {Position} from 'vs/editor/common/core/position';
import {Range} from 'vs/editor/common/core/range';
import {Selection, SelectionDirection} from 'vs/editor/common/core/selection';
Expand Down Expand Up @@ -985,11 +985,31 @@ export class Cursor extends EventEmitter {
this._handlers[H.CursorPageDown] = (ctx) => this._moveDown(false, true, ctx);
this._handlers[H.CursorPageDownSelect] = (ctx) => this._moveDown(true, true, ctx);

this._handlers[H.CursorHome] = (ctx) => this._moveToBeginningOfLine(false, ctx);
this._handlers[H.CursorHomeSelect] = (ctx) => this._moveToBeginningOfLine(true, ctx);
this._handlers[H.CursorHome] = (ctx) => this._moveToBeginningOfLine(false, LineNavigationType.VisualThenLogical, ctx);
this._handlers[H.CursorHomeSelect] = (ctx) => this._moveToBeginningOfLine(true, LineNavigationType.VisualThenLogical, ctx);

this._handlers[H.CursorEnd] = (ctx) => this._moveToEndOfLine(false, ctx);
this._handlers[H.CursorEndSelect] = (ctx) => this._moveToEndOfLine(true, ctx);
this._handlers[H.CursorHomeVisualThenLogical] = (ctx) => this._moveToBeginningOfLine(false, LineNavigationType.VisualThenLogical, ctx);
this._handlers[H.CursorHomeLogicalThenVisual] = (ctx) => this._moveToBeginningOfLine(false, LineNavigationType.LogicalThenVisual, ctx);
this._handlers[H.CursorHomeVisualOnly] = (ctx) => this._moveToBeginningOfLine(false, LineNavigationType.VisualOnly, ctx);
this._handlers[H.CursorHomeLogicalOnly] = (ctx) => this._moveToBeginningOfLine(false, LineNavigationType.LogicalOnly, ctx);

this._handlers[H.CursorHomeVisualThenLogicalSelect] = (ctx) => this._moveToBeginningOfLine(true, LineNavigationType.VisualThenLogical, ctx);
this._handlers[H.CursorHomeLogicalThenVisualSelect] = (ctx) => this._moveToBeginningOfLine(true, LineNavigationType.LogicalThenVisual, ctx);
this._handlers[H.CursorHomeVisualOnlySelect] = (ctx) => this._moveToBeginningOfLine(true, LineNavigationType.VisualOnly, ctx);
this._handlers[H.CursorHomeLogicalOnlySelect] = (ctx) => this._moveToBeginningOfLine(true, LineNavigationType.LogicalOnly, ctx);

this._handlers[H.CursorEnd] = (ctx) => this._moveToEndOfLine(false, LineNavigationType.VisualThenLogical, ctx);
this._handlers[H.CursorEndSelect] = (ctx) => this._moveToEndOfLine(true, LineNavigationType.VisualThenLogical, ctx);

this._handlers[H.CursorEndVisualThenLogical] = (ctx) => this._moveToEndOfLine(false, LineNavigationType.VisualThenLogical, ctx);
this._handlers[H.CursorEndLogicalThenVisual] = (ctx) => this._moveToEndOfLine(false, LineNavigationType.LogicalThenVisual, ctx);
this._handlers[H.CursorEndVisualOnly] = (ctx) => this._moveToEndOfLine(false, LineNavigationType.VisualOnly, ctx);
this._handlers[H.CursorEndLogicalOnly] = (ctx) => this._moveToEndOfLine(false, LineNavigationType.LogicalOnly, ctx);

this._handlers[H.CursorEndVisualThenLogicalSelect] = (ctx) => this._moveToEndOfLine(true, LineNavigationType.VisualThenLogical, ctx);
this._handlers[H.CursorEndLogicalThenVisualSelect] = (ctx) => this._moveToEndOfLine(true, LineNavigationType.LogicalThenVisual, ctx);
this._handlers[H.CursorEndVisualOnlySelect] = (ctx) => this._moveToEndOfLine(true, LineNavigationType.VisualOnly, ctx);
this._handlers[H.CursorEndLogicalOnlySelect] = (ctx) => this._moveToEndOfLine(true, LineNavigationType.LogicalOnly, ctx);

this._handlers[H.CursorTop] = (ctx) => this._moveToBeginningOfBuffer(false, ctx);
this._handlers[H.CursorTopSelect] = (ctx) => this._moveToBeginningOfBuffer(true, ctx);
Expand Down Expand Up @@ -1296,12 +1316,12 @@ export class Cursor extends EventEmitter {
return this._invokeForAll(ctx, (cursorIndex: number, oneCursor: OneCursor, oneCtx: IOneCursorOperationContext) => OneCursorOp.moveUp(oneCursor, inSelectionMode, isPaged, ctx.eventData && ctx.eventData.pageSize || 0, oneCtx));
}

private _moveToBeginningOfLine(inSelectionMode:boolean, ctx: IMultipleCursorOperationContext): boolean {
return this._invokeForAll(ctx, (cursorIndex: number, oneCursor: OneCursor, oneCtx: IOneCursorOperationContext) => OneCursorOp.moveToBeginningOfLine(oneCursor, inSelectionMode, oneCtx));
private _moveToBeginningOfLine(inSelectionMode:boolean, lineNavigationType: LineNavigationType, ctx: IMultipleCursorOperationContext): boolean {
return this._invokeForAll(ctx, (cursorIndex: number, oneCursor: OneCursor, oneCtx: IOneCursorOperationContext) => OneCursorOp.moveToBeginningOfLine(oneCursor, inSelectionMode, lineNavigationType, oneCtx));
}

private _moveToEndOfLine(inSelectionMode:boolean, ctx: IMultipleCursorOperationContext): boolean {
return this._invokeForAll(ctx, (cursorIndex: number, oneCursor: OneCursor, oneCtx: IOneCursorOperationContext) => OneCursorOp.moveToEndOfLine(oneCursor, inSelectionMode, oneCtx));
private _moveToEndOfLine(inSelectionMode:boolean, lineNavigationType: LineNavigationType, ctx: IMultipleCursorOperationContext): boolean {
return this._invokeForAll(ctx, (cursorIndex: number, oneCursor: OneCursor, oneCtx: IOneCursorOperationContext) => OneCursorOp.moveToEndOfLine(oneCursor, inSelectionMode, lineNavigationType, oneCtx));
}

private _moveToBeginningOfBuffer(inSelectionMode:boolean, ctx: IMultipleCursorOperationContext): boolean {
Expand Down
17 changes: 17 additions & 0 deletions src/vs/editor/common/controller/cursorMoveHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ export class CursorMoveHelper {
};
}

public getColumnAtBeginningOfVisualLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return model.getLineFirstNonWhitespaceColumn(lineNumber) || 1;
}

public getColumnAtBeginningOfLogicalLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return model.getLineMinColumn(lineNumber);
}

public getColumnAtBeginningOfLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
var firstNonBlankColumn = model.getLineFirstNonWhitespaceColumn(lineNumber) || 1;
var minColumn = model.getLineMinColumn(lineNumber);
Expand All @@ -206,6 +214,15 @@ export class CursorMoveHelper {
return column;
}

public getColumnAtEndOfVisualLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
var maxColumn = model.getLineMaxColumn(lineNumber);
return model.getLineLastNonWhitespaceColumn(lineNumber) || maxColumn;
}

public getColumnAtEndOfLogicalLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return model.getLineMaxColumn(lineNumber);
}

public getColumnAtEndOfLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
var maxColumn = model.getLineMaxColumn(lineNumber);
var lastNonBlankColumn = model.getLineLastNonWhitespaceColumn(lineNumber) || maxColumn;
Expand Down
121 changes: 117 additions & 4 deletions src/vs/editor/common/controller/oneCursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,38 @@ export enum WordNavigationType {
WordEnd = 1
}

/**
* Navigation mode when jumping to the beginning or at the end of a line.
* Remark: This is usually what happens when you press the 'Home' or the 'End' key.
* Terminology:
* - logical beginning/end of line: boundaries of a line, including the white-spaces.
* - visual beginning/end of line: boundaries of a line, excluding the white-spaces.
* Example:
* " alice and bob alice and bob "
* | | | |
* ^-------|-- logical boundaries ---|------^
* | |
* ^--- visual boundaries ---^
*/
export enum LineNavigationType {
/**
* First jumps to the visual boundary, then to the logical one, and so forth.
*/
VisualThenLogical = 0,
/**
* First jumps to the logical boundary, then to the visual one, and so forth.
*/
LogicalThenVisual = 1,
/**
* Always jumps to the visual boundary.
*/
VisualOnly = 2,
/**
* Always jumps to the logical boundary.
*/
LogicalOnly = 3
}

const CH_REGULAR = CharacterClass.Regular;
const CH_WHITESPACE = CharacterClass.Whitespace;
const CH_WORD_SEPARATOR = CharacterClass.WordSeparator;
Expand Down Expand Up @@ -530,9 +562,21 @@ export class OneCursor {
public getPositionDown(lineNumber:number, column:number, leftoverVisibleColumns:number, count:number, allowMoveOnLastLine:boolean): IMoveResult {
return this.helper.getPositionDown(this.model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnLastLine);
}
public getColumnAtBeginningOfVisualLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtBeginningOfVisualLine(this.model, lineNumber, column);
}
public getColumnAtBeginningOfLogicalLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtBeginningOfLogicalLine(this.model, lineNumber, column);
}
public getColumnAtBeginningOfLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtBeginningOfLine(this.model, lineNumber, column);
}
public getColumnAtEndOfVisualLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtEndOfVisualLine(this.model, lineNumber, column);
}
public getColumnAtEndOfLogicalLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtEndOfLogicalLine(this.model, lineNumber, column);
}
public getColumnAtEndOfLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtEndOfLine(this.model, lineNumber, column);
}
Expand Down Expand Up @@ -565,9 +609,21 @@ export class OneCursor {
public getViewPositionDown(lineNumber:number, column:number, leftoverVisibleColumns:number, count:number, allowMoveOnLastLine:boolean): IMoveResult {
return this.helper.getPositionDown(this.viewModelHelper.viewModel, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnLastLine);
}
public getColumnAtBeginningOfVisualViewLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtBeginningOfVisualLine(this.viewModelHelper.viewModel, lineNumber, column);
}
public getColumnAtBeginningOfLogicalViewLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtBeginningOfLogicalLine(this.viewModelHelper.viewModel, lineNumber, column);
}
public getColumnAtBeginningOfViewLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtBeginningOfLine(this.viewModelHelper.viewModel, lineNumber, column);
}
public getColumnAtEndOfVisualViewLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtEndOfVisualLine(this.viewModelHelper.viewModel, lineNumber, column);
}
public getColumnAtEndOfLogicalViewLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtEndOfLogicalLine(this.viewModelHelper.viewModel, lineNumber, column);
}
public getColumnAtEndOfViewLine(lineNumber:number, column:number): number {
return this.helper.getColumnAtEndOfLine(this.viewModelHelper.viewModel, lineNumber, column);
}
Expand Down Expand Up @@ -900,23 +956,64 @@ export class OneCursorOp {
return true;
}

public static moveToBeginningOfLine(cursor:OneCursor, inSelectionMode: boolean, ctx: IOneCursorOperationContext): boolean {
public static moveToBeginningOfLine(cursor:OneCursor, inSelectionMode: boolean, lineNavigationType: LineNavigationType, ctx: IOneCursorOperationContext): boolean {
let validatedViewPosition = cursor.getValidViewPosition();
let viewLineNumber = validatedViewPosition.lineNumber;
let viewColumn = validatedViewPosition.column;

viewColumn = cursor.getColumnAtBeginningOfViewLine(viewLineNumber, viewColumn);
var firstNonBlankColumn = cursor.getColumnAtBeginningOfVisualViewLine(viewLineNumber, viewColumn);
var minColumn = cursor.getColumnAtBeginningOfLogicalViewLine(viewLineNumber, viewColumn);

if (lineNavigationType === LineNavigationType.VisualThenLogical) {
if (viewColumn !== minColumn && viewColumn <= firstNonBlankColumn) {
viewColumn = minColumn;
} else {
viewColumn = firstNonBlankColumn;
}
} else if (lineNavigationType === LineNavigationType.LogicalThenVisual) {
if (viewColumn !== minColumn) {
viewColumn = minColumn;
} else {
viewColumn = firstNonBlankColumn;
}
} else if (lineNavigationType === LineNavigationType.LogicalOnly) {
viewColumn = minColumn;
} else if (lineNavigationType === LineNavigationType.VisualOnly) {
viewColumn = firstNonBlankColumn;
}

ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit;
cursor.moveViewPosition(inSelectionMode, viewLineNumber, viewColumn, 0, true);
return true;
}

public static moveToEndOfLine(cursor:OneCursor, inSelectionMode: boolean, ctx: IOneCursorOperationContext): boolean {
public static moveToEndOfLine(cursor:OneCursor, inSelectionMode: boolean, lineNavigationType: LineNavigationType, ctx: IOneCursorOperationContext): boolean {
let validatedViewPosition = cursor.getValidViewPosition();
let viewLineNumber = validatedViewPosition.lineNumber;
let viewColumn = validatedViewPosition.column;

viewColumn = cursor.getColumnAtEndOfViewLine(viewLineNumber, viewColumn);
let maxColumn = cursor.getColumnAtEndOfLogicalViewLine(viewLineNumber, viewColumn);
let lastNonBlankColumn = cursor.getColumnAtEndOfVisualViewLine(viewLineNumber, viewColumn);

if (lineNavigationType === LineNavigationType.VisualThenLogical) {

if (viewColumn !== maxColumn && viewColumn >= lastNonBlankColumn) {
viewColumn = maxColumn;
} else {
viewColumn = lastNonBlankColumn;
}
} else if (lineNavigationType === LineNavigationType.LogicalThenVisual) {
if (viewColumn !== maxColumn) {
viewColumn = maxColumn;
} else {
viewColumn = lastNonBlankColumn;
}
} else if (lineNavigationType === LineNavigationType.LogicalOnly) {
viewColumn = maxColumn;
} else if (lineNavigationType === LineNavigationType.VisualOnly) {
viewColumn = lastNonBlankColumn;
}

ctx.cursorPositionChangeReason = editorCommon.CursorChangeReason.Explicit;
cursor.moveViewPosition(inSelectionMode, viewLineNumber, viewColumn, 0, true);
return true;
Expand Down Expand Up @@ -2023,10 +2120,26 @@ class CursorHelper {
return this.moveHelper.getPositionDown(model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnLastLine);
}

public getColumnAtBeginningOfVisualLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return this.moveHelper.getColumnAtBeginningOfVisualLine(model, lineNumber, column);
}

public getColumnAtBeginningOfLogicalLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return this.moveHelper.getColumnAtBeginningOfLogicalLine(model, lineNumber, column);
}

public getColumnAtBeginningOfLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return this.moveHelper.getColumnAtBeginningOfLine(model, lineNumber, column);
}

public getColumnAtEndOfVisualLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return this.moveHelper.getColumnAtEndOfVisualLine(model, lineNumber, column);
}

public getColumnAtEndOfLogicalLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return this.moveHelper.getColumnAtEndOfLogicalLine(model, lineNumber, column);
}

public getColumnAtEndOfLine(model:ICursorMoveHelperModel, lineNumber:number, column:number): number {
return this.moveHelper.getColumnAtEndOfLine(model, lineNumber, column);
}
Expand Down
24 changes: 22 additions & 2 deletions src/vs/editor/common/editorCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3827,12 +3827,32 @@ export var Handler = {
CursorPageDown: 'cursorPageDown',
CursorPageDownSelect: 'cursorPageDownSelect',

CursorHome: 'cursorHome',
CursorHomeSelect: 'cursorHomeSelect',
CursorHome: 'cursorHome',
CursorHomeSelect: 'cursorHomeSelect',

CursorHomeVisualThenLogical: 'cursorHomeVisualThenLogical',
CursorHomeLogicalThenVisual: 'cursorHomeLogicalThenVisual',
CursorHomeVisualOnly: 'cursorHomeVisualOnly',
CursorHomeLogicalOnly: 'cursorHomeLogicalOnly',

CursorHomeVisualThenLogicalSelect: 'cursorHomeVisualThenLogicalSelect',
CursorHomeLogicalThenVisualSelect: 'cursorHomeLogicalThenVisualSelect',
CursorHomeVisualOnlySelect: 'cursorHomeVisualOnlySelect',
CursorHomeLogicalOnlySelect: 'cursorHomeLogicalOnlySelect',

CursorEnd: 'cursorEnd',
CursorEndSelect: 'cursorEndSelect',

CursorEndVisualThenLogical: 'cursorEndVisualThenLogical',
CursorEndLogicalThenVisual: 'cursorEndLogicalThenVisual',
CursorEndVisualOnly: 'cursorEndVisualOnly',
CursorEndLogicalOnly: 'cursorEndLogicalOnly',

CursorEndVisualThenLogicalSelect: 'cursorEndVisualThenLogicalSelect',
CursorEndLogicalThenVisualSelect: 'cursorEndLogicalThenVisualSelect',
CursorEndVisualOnlySelect: 'cursorEndVisualOnlySelect',
CursorEndLogicalOnlySelect: 'cursorEndLogicalOnlySelect',

ExpandLineSelection: 'expandLineSelection',

CursorTop: 'cursorTop',
Expand Down
Loading