diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index ca28d204f2db9cf6bcc9073bb4fff24082e6ea25..41097c8025a5c88885153d5bfd30d9ebd773d755 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -202,28 +202,28 @@ export class ViewController { private wordSelect(source: string, viewPosition: Position): void { viewPosition = this._validateViewColumn(viewPosition); - this.triggerCursorHandler(source, editorCommon.Handler.WordSelect, { + this.triggerCursorHandler(source, CoreCommands.WordSelect.id, { position: this.convertViewToModelPosition(viewPosition) }); } private wordSelectDrag(source: string, viewPosition: Position): void { viewPosition = this._validateViewColumn(viewPosition); - this.triggerCursorHandler(source, editorCommon.Handler.WordSelectDrag, { + this.triggerCursorHandler(source, CoreCommands.WordSelectDrag.id, { position: this.convertViewToModelPosition(viewPosition) }); } private lastCursorWordSelect(source: string, viewPosition: Position): void { viewPosition = this._validateViewColumn(viewPosition); - this.triggerCursorHandler(source, editorCommon.Handler.LastCursorWordSelect, { + this.triggerCursorHandler(source, CoreCommands.LastCursorWordSelect.id, { position: this.convertViewToModelPosition(viewPosition) }); } private lineSelect(source: string, viewPosition: Position): void { viewPosition = this._validateViewColumn(viewPosition); - this.triggerCursorHandler(source, editorCommon.Handler.LineSelect, { + this.triggerCursorHandler(source, CoreCommands.LineSelect.id, { position: this.convertViewToModelPosition(viewPosition), viewPosition: viewPosition }); @@ -231,7 +231,7 @@ export class ViewController { private lineSelectDrag(source: string, viewPosition: Position): void { viewPosition = this._validateViewColumn(viewPosition); - this.triggerCursorHandler(source, editorCommon.Handler.LineSelectDrag, { + this.triggerCursorHandler(source, CoreCommands.LineSelectDrag.id, { position: this.convertViewToModelPosition(viewPosition), viewPosition: viewPosition }); @@ -239,7 +239,7 @@ export class ViewController { private lastCursorLineSelect(source: string, viewPosition: Position): void { viewPosition = this._validateViewColumn(viewPosition); - this.triggerCursorHandler(source, editorCommon.Handler.LastCursorLineSelect, { + this.triggerCursorHandler(source, CoreCommands.LastCursorLineSelect.id, { position: this.convertViewToModelPosition(viewPosition), viewPosition: viewPosition }); @@ -247,7 +247,7 @@ export class ViewController { private lastCursorLineSelectDrag(source: string, viewPosition: Position): void { viewPosition = this._validateViewColumn(viewPosition); - this.triggerCursorHandler(source, editorCommon.Handler.LastCursorLineSelectDrag, { + this.triggerCursorHandler(source, CoreCommands.LastCursorLineSelectDrag.id, { position: this.convertViewToModelPosition(viewPosition), viewPosition: viewPosition }); diff --git a/src/vs/editor/common/config/config.ts b/src/vs/editor/common/config/config.ts index 3bb6e872073a7cece2050506a4432c5fd727a746..bb08c60a805309b2867e920d2f032a5b57ff9207 100644 --- a/src/vs/editor/common/config/config.ts +++ b/src/vs/editor/common/config/config.ts @@ -19,146 +19,6 @@ import H = editorCommon.Handler; const CORE_WEIGHT = KeybindingsRegistry.WEIGHT.editorCore(); -export namespace EditorScroll { - - const isEditorScrollArgs = function (arg): boolean { - if (!types.isObject(arg)) { - return false; - } - - let scrollArg: RawArguments = arg; - - if (!types.isString(scrollArg.to)) { - return false; - } - - if (!types.isUndefined(scrollArg.by) && !types.isString(scrollArg.by)) { - return false; - } - - if (!types.isUndefined(scrollArg.value) && !types.isNumber(scrollArg.value)) { - return false; - } - - if (!types.isUndefined(scrollArg.revealCursor) && !types.isBoolean(scrollArg.revealCursor)) { - return false; - } - - return true; - }; - - export const description = { - description: 'Scroll editor in the given direction', - args: [ - { - name: 'Editor scroll argument object', - description: `Property-value pairs that can be passed through this argument: - * 'to': A mandatory direction value. - \`\`\` - 'up', 'down' - \`\`\` - * 'by': Unit to move. Default is computed based on 'to' value. - \`\`\` - 'line', 'wrappedLine', 'page', 'halfPage' - \`\`\` - * 'value': Number of units to move. Default is '1'. - * 'revealCursor': If 'true' reveals the cursor if it is outside view port. - `, - constraint: isEditorScrollArgs - } - ] - }; - - /** - * Directions in the view for editor scroll command. - */ - export const RawDirection = { - Up: 'up', - Down: 'down', - }; - - /** - * Units for editor scroll 'by' argument - */ - export const RawUnit = { - Line: 'line', - WrappedLine: 'wrappedLine', - Page: 'page', - HalfPage: 'halfPage' - }; - - /** - * Arguments for editor scroll command - */ - export interface RawArguments { - to: string; - by?: string; - value?: number; - revealCursor?: boolean; - }; - - export function parse(args: RawArguments): ParsedArguments { - let direction: Direction; - switch (args.to) { - case RawDirection.Up: - direction = Direction.Up; - break; - case RawDirection.Down: - direction = Direction.Down; - break; - default: - // Illegal arguments - return null; - } - - let unit: Unit; - switch (args.by) { - case RawUnit.Line: - unit = Unit.Line; - break; - case RawUnit.WrappedLine: - unit = Unit.WrappedLine; - break; - case RawUnit.Page: - unit = Unit.Page; - break; - case RawUnit.HalfPage: - unit = Unit.HalfPage; - break; - default: - unit = Unit.WrappedLine; - } - - const value = Math.floor(args.value || 1); - const revealCursor = !!args.revealCursor; - - return { - direction: direction, - unit: unit, - value: value, - revealCursor: revealCursor - }; - } - - export interface ParsedArguments { - direction: Direction; - unit: Unit; - value: number; - revealCursor: boolean; - } - - export const enum Direction { - Up = 1, - Down = 2 - } - - export const enum Unit { - Line = 1, - WrappedLine = 2, - Page = 3, - HalfPage = 4 - } -} export namespace RevealLine { @@ -254,10 +114,12 @@ export abstract class Command { } } + const weight = (typeof kbOpts.weight === 'number' ? kbOpts.weight : defaultWeight); + return { id: this.id, handler: (accessor, args) => this.runCommand(accessor, args), - weight: kbOpts.weight || defaultWeight, + weight: weight, when: kbWhen, primary: kbOpts.primary, secondary: kbOpts.secondary, @@ -362,15 +224,6 @@ class CoreCommand extends Command { } } -class UnboundCoreCommand extends CoreCommand { - constructor(handlerId: string, precondition: ContextKeyExpr = null) { - super({ - id: handlerId, - precondition: precondition - }); - } -} - function registerCommand(command: Command) { KeybindingsRegistry.registerCommandAndKeybindingRule(command.toCommandAndKeybindingRule(CORE_WEIGHT)); } @@ -397,185 +250,10 @@ registerCoreDispatchCommand(H.CompositionEnd); registerCoreDispatchCommand(H.Paste); registerCoreDispatchCommand(H.Cut); - -// https://support.apple.com/en-gb/HT201236 -// [ADDED] Control-H Delete the character to the left of the insertion point. Or use Delete. -// [ADDED] Control-D Delete the character to the right of the insertion point. Or use Fn-Delete. -// [ADDED] Control-K Delete the text between the insertion point and the end of the line or paragraph. -// [ADDED] Command–Up Arrow Move the insertion point to the beginning of the document. -// [ADDED] Command–Down Arrow Move the insertion point to the end of the document. -// [ADDED] Command–Left Arrow Move the insertion point to the beginning of the current line. -// [ADDED] Command–Right Arrow Move the insertion point to the end of the current line. -// [ADDED] Option–Left Arrow Move the insertion point to the beginning of the previous word. -// [ADDED] Option–Right Arrow Move the insertion point to the end of the next word. -// [ADDED] Command–Shift–Up Arrow Select the text between the insertion point and the beginning of the document. -// [ADDED] Command–Shift–Down Arrow Select the text between the insertion point and the end of the document. -// [ADDED] Command–Shift–Left Arrow Select the text between the insertion point and the beginning of the current line. -// [ADDED] Command–Shift–Right Arrow Select the text between the insertion point and the end of the current line. -// [USED BY DUPLICATE LINES] Shift–Option–Up Arrow Extend text selection to the beginning of the current paragraph, then to the beginning of the following paragraph if pressed again. -// [USED BY DUPLICATE LINES] Shift–Option–Down Arrow Extend text selection to the end of the current paragraph, then to the end of the following paragraph if pressed again. -// [ADDED] Shift–Option–Left Arrow Extend text selection to the beginning of the current word, then to the beginning of the following word if pressed again. -// [ADDED] Shift–Option–Right Arrow Extend text selection to the end of the current word, then to the end of the following word if pressed again. -// [ADDED] Control-A Move to the beginning of the line or paragraph. -// [ADDED] Control-E Move to the end of a line or paragraph. -// [ADDED] Control-F Move one character forward. -// [ADDED] Control-B Move one character backward. -//Control-L Center the cursor or selection in the visible area. -// [ADDED] Control-P Move up one line. -// [ADDED] Control-N Move down one line. -// [ADDED] Control-O Insert a new line after the insertion point. -//Control-T Swap the character behind the insertion point with the character in front of the insertion point. -// Unconfirmed???? -// Config.addKeyBinding(editorCommon.Handler.CursorPageDown, KeyMod.WinCtrl | KeyCode.KEY_V); - -// OS X built in commands -// Control+y => yank -// [ADDED] Command+backspace => Delete to Hard BOL -// [ADDED] Command+delete => Delete to Hard EOL -// [ADDED] Control+k => Delete to Hard EOL -// Control+l => show_at_center -// Control+Command+d => noop -// Control+Command+shift+d => noop - // Register cursor commands -registerCommand(new CoreCommand({ - id: H.ExpandLineSelection, - precondition: null, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_I - } -})); - -registerCoreAPICommand(H.EditorScroll, EditorScroll.description); - -registerCommand(new CoreCommand({ - id: H.ScrollLineUp, - precondition: null, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - mac: { primary: KeyMod.WinCtrl | KeyCode.PageUp } - } -})); -registerCommand(new CoreCommand({ - id: H.ScrollLineDown, - precondition: null, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - mac: { primary: KeyMod.WinCtrl | KeyCode.PageDown } - } -})); - -registerCommand(new CoreCommand({ - id: H.ScrollPageUp, - precondition: null, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: KeyMod.CtrlCmd | KeyCode.PageUp, - win: { primary: KeyMod.Alt | KeyCode.PageUp }, - linux: { primary: KeyMod.Alt | KeyCode.PageUp } - } -})); -registerCommand(new CoreCommand({ - id: H.ScrollPageDown, - precondition: null, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: KeyMod.CtrlCmd | KeyCode.PageDown, - win: { primary: KeyMod.Alt | KeyCode.PageDown }, - linux: { primary: KeyMod.Alt | KeyCode.PageDown } - } -})); - registerCoreAPICommand(H.RevealLine, RevealLine.description); -registerCommand(new CoreCommand({ - id: H.Tab, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: ContextKeyExpr.and( - EditorContextKeys.textFocus, - EditorContextKeys.tabDoesNotMoveFocus - ), - primary: KeyCode.Tab - } -})); -registerCommand(new CoreCommand({ - id: H.Outdent, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: ContextKeyExpr.and( - EditorContextKeys.textFocus, - EditorContextKeys.tabDoesNotMoveFocus - ), - primary: KeyMod.Shift | KeyCode.Tab - } -})); - -registerCommand(new CoreCommand({ - id: H.DeleteLeft, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: KeyCode.Backspace, - secondary: [KeyMod.Shift | KeyCode.Backspace], - mac: { primary: KeyCode.Backspace, secondary: [KeyMod.Shift | KeyCode.Backspace, KeyMod.WinCtrl | KeyCode.KEY_H, KeyMod.WinCtrl | KeyCode.Backspace] } - } -})); -registerCommand(new CoreCommand({ - id: H.DeleteRight, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: KeyCode.Delete, - mac: { primary: KeyCode.Delete, secondary: [KeyMod.WinCtrl | KeyCode.KEY_D, KeyMod.WinCtrl | KeyCode.Delete] } - } -})); - -registerCommand(new CoreCommand({ - id: H.CancelSelection, - precondition: EditorContextKeys.hasNonEmptySelection, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape] - } -})); -registerCommand(new CoreCommand({ - id: H.RemoveSecondaryCursors, - precondition: EditorContextKeys.hasMultipleSelections, - kbOpts: { - weight: CORE_WEIGHT + 1, - kbExpr: EditorContextKeys.textFocus, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape] - } -})); - -registerCommand(new CoreCommand({ - id: H.LineBreakInsert, - precondition: EditorContextKeys.writable, - kbOpts: { - weight: CORE_WEIGHT, - kbExpr: EditorContextKeys.textFocus, - primary: null, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_O } - } -})); - abstract class BaseTextInputAwareCommand extends Command { public runCommand(accessor: ServicesAccessor, args: any): void { diff --git a/src/vs/editor/common/controller/coreCommands.ts b/src/vs/editor/common/controller/coreCommands.ts index 51a639f76571dbe11363865bf57f2124d73ae572..db1d2bf716f55bd5677efa7f1d1c7d10f024681a 100644 --- a/src/vs/editor/common/controller/coreCommands.ts +++ b/src/vs/editor/common/controller/coreCommands.ts @@ -10,13 +10,18 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { CursorState, ICursors, RevealTarget, IColumnSelectData, CursorContext } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from "vs/editor/common/controller/cursorEvents"; import { CursorMoveCommands, CursorMove as CursorMove_ } from "vs/editor/common/controller/cursorMoveCommands"; -import { EditorCommand, ICommandOptions } from "vs/editor/common/config/config"; +import { EditorCommand, ICommandOptions, Command } from "vs/editor/common/config/config"; import { ServicesAccessor } from "vs/platform/instantiation/common/instantiation"; import { registerEditorCommand } from "vs/editor/common/editorCommonExtensions"; import { IColumnSelectResult, ColumnSelection } from "vs/editor/common/controller/cursorColumnSelection"; import { EditorContextKeys } from "vs/editor/common/editorContextKeys"; import { KeyMod, KeyCode } from "vs/base/common/keyCodes"; import { KeybindingsRegistry } from "vs/platform/keybinding/common/keybindingsRegistry"; +import H = editorCommon.Handler; +import { ICodeEditorService } from "vs/editor/common/services/codeEditorService"; +import { ContextKeyExpr } from "vs/platform/contextkey/common/contextkey"; +import * as types from 'vs/base/common/types'; +import { ICommandHandlerDescription } from "vs/platform/commands/common/commands"; const CORE_WEIGHT = KeybindingsRegistry.WEIGHT.editorCore(); @@ -28,6 +33,147 @@ export abstract class CoreEditorCommand extends EditorCommand { public abstract runCoreEditorCommand(cursors: ICursors, args: any): void; } +export namespace EditorScroll_ { + + const isEditorScrollArgs = function (arg): boolean { + if (!types.isObject(arg)) { + return false; + } + + let scrollArg: RawArguments = arg; + + if (!types.isString(scrollArg.to)) { + return false; + } + + if (!types.isUndefined(scrollArg.by) && !types.isString(scrollArg.by)) { + return false; + } + + if (!types.isUndefined(scrollArg.value) && !types.isNumber(scrollArg.value)) { + return false; + } + + if (!types.isUndefined(scrollArg.revealCursor) && !types.isBoolean(scrollArg.revealCursor)) { + return false; + } + + return true; + }; + + export const description = { + description: 'Scroll editor in the given direction', + args: [ + { + name: 'Editor scroll argument object', + description: `Property-value pairs that can be passed through this argument: + * 'to': A mandatory direction value. + \`\`\` + 'up', 'down' + \`\`\` + * 'by': Unit to move. Default is computed based on 'to' value. + \`\`\` + 'line', 'wrappedLine', 'page', 'halfPage' + \`\`\` + * 'value': Number of units to move. Default is '1'. + * 'revealCursor': If 'true' reveals the cursor if it is outside view port. + `, + constraint: isEditorScrollArgs + } + ] + }; + + /** + * Directions in the view for editor scroll command. + */ + export const RawDirection = { + Up: 'up', + Down: 'down', + }; + + /** + * Units for editor scroll 'by' argument + */ + export const RawUnit = { + Line: 'line', + WrappedLine: 'wrappedLine', + Page: 'page', + HalfPage: 'halfPage' + }; + + /** + * Arguments for editor scroll command + */ + export interface RawArguments { + to: string; + by?: string; + value?: number; + revealCursor?: boolean; + }; + + export function parse(args: RawArguments): ParsedArguments { + let direction: Direction; + switch (args.to) { + case RawDirection.Up: + direction = Direction.Up; + break; + case RawDirection.Down: + direction = Direction.Down; + break; + default: + // Illegal arguments + return null; + } + + let unit: Unit; + switch (args.by) { + case RawUnit.Line: + unit = Unit.Line; + break; + case RawUnit.WrappedLine: + unit = Unit.WrappedLine; + break; + case RawUnit.Page: + unit = Unit.Page; + break; + case RawUnit.HalfPage: + unit = Unit.HalfPage; + break; + default: + unit = Unit.WrappedLine; + } + + const value = Math.floor(args.value || 1); + const revealCursor = !!args.revealCursor; + + return { + direction: direction, + unit: unit, + value: value, + revealCursor: revealCursor + }; + } + + export interface ParsedArguments { + direction: Direction; + unit: Unit; + value: number; + revealCursor: boolean; + } + + export const enum Direction { + Up = 1, + Down = 2 + } + + export const enum Unit { + Line = 1, + WrappedLine = 2, + Page = 3, + HalfPage = 4 + } +} + export namespace CoreCommands { class BaseMoveToCommand extends CoreEditorCommand { @@ -53,13 +199,13 @@ export namespace CoreCommands { } export const MoveTo: CoreEditorCommand = registerEditorCommand(new BaseMoveToCommand({ - id: 'moveTo', + id: '_moveTo', inSelectionMode: false, precondition: null })); export const MoveToSelect: CoreEditorCommand = registerEditorCommand(new BaseMoveToCommand({ - id: 'moveToSelect', + id: '_moveToSelect', inSelectionMode: true, precondition: null })); @@ -175,7 +321,7 @@ export namespace CoreCommands { kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textFocus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.UpArrow, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.PageUp, linux: { primary: 0 } } })); @@ -519,7 +665,7 @@ export namespace CoreCommands { export const LastCursorMoveToSelect: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { constructor() { super({ - id: 'lastCursorMoveToSelect', + id: '_lastCursorMoveToSelect', precondition: null }); } @@ -732,4 +878,487 @@ export namespace CoreCommands { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow } } })); + + export class EditorScrollImpl extends CoreEditorCommand { + constructor() { + super({ + id: 'editorScroll', + precondition: null, + description: EditorScroll_.description + }); + } + + public runCoreEditorCommand(cursors: ICursors, args: any): void { + const parsed = EditorScroll_.parse(args); + if (!parsed) { + // illegal arguments + return; + } + this._runEditorScroll(cursors, args.source, parsed); + } + + _runEditorScroll(cursors: ICursors, source: string, args: EditorScroll_.ParsedArguments): void { + + const desiredScrollTop = this._computeDesiredScrollTop(cursors.context, args); + + if (args.revealCursor) { + // must ensure cursor is in new visible range + const desiredVisibleViewRange = cursors.context.getCompletelyVisibleViewRangeAtScrollTop(desiredScrollTop); + cursors.setStates( + source, + CursorChangeReason.Explicit, + [ + CursorMoveCommands.findPositionInViewportIfOutside(cursors.context, cursors.getPrimaryCursor(), desiredVisibleViewRange, false) + ] + ); + } + + cursors.scrollTo(desiredScrollTop); + } + + private _computeDesiredScrollTop(context: CursorContext, args: EditorScroll_.ParsedArguments): number { + + if (args.unit === EditorScroll_.Unit.Line) { + // scrolling by model lines + const visibleModelRange = context.getCompletelyVisibleModelRange(); + + let desiredTopModelLineNumber: number; + if (args.direction === EditorScroll_.Direction.Up) { + // must go x model lines up + desiredTopModelLineNumber = Math.max(1, visibleModelRange.startLineNumber - args.value); + } else { + // must go x model lines down + desiredTopModelLineNumber = Math.min(context.model.getLineCount(), visibleModelRange.startLineNumber + args.value); + } + + const desiredTopViewPosition = context.convertModelPositionToViewPosition(new Position(desiredTopModelLineNumber, 1)); + return context.getVerticalOffsetForViewLine(desiredTopViewPosition.lineNumber); + } + + let noOfLines: number; + if (args.unit === EditorScroll_.Unit.Page) { + noOfLines = context.config.pageSize * args.value; + } else if (args.unit === EditorScroll_.Unit.HalfPage) { + noOfLines = Math.round(context.config.pageSize / 2) * args.value; + } else { + noOfLines = args.value; + } + const deltaLines = (args.direction === EditorScroll_.Direction.Up ? -1 : 1) * noOfLines; + return context.getScrollTop() + deltaLines * context.config.lineHeight; + } + } + + export const EditorScroll: EditorScrollImpl = registerEditorCommand(new EditorScrollImpl()); + + export const ScrollLineUp: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'scrollLineUp', + precondition: null, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + mac: { primary: KeyMod.WinCtrl | KeyCode.PageUp } + } + }); + } + + runCoreEditorCommand(cursors: ICursors, args: any): void { + EditorScroll._runEditorScroll(cursors, args.source, { + direction: EditorScroll_.Direction.Up, + unit: EditorScroll_.Unit.WrappedLine, + value: 1, + revealCursor: false + }); + } + }); + + export const ScrollPageUp: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'scrollPageUp', + precondition: null, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: KeyMod.CtrlCmd | KeyCode.PageUp, + win: { primary: KeyMod.Alt | KeyCode.PageUp }, + linux: { primary: KeyMod.Alt | KeyCode.PageUp } + } + }); + } + + runCoreEditorCommand(cursors: ICursors, args: any): void { + EditorScroll._runEditorScroll(cursors, args.source, { + direction: EditorScroll_.Direction.Up, + unit: EditorScroll_.Unit.Page, + value: 1, + revealCursor: false + }); + } + }); + + export const ScrollLineDown: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'scrollLineDown', + precondition: null, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + mac: { primary: KeyMod.WinCtrl | KeyCode.PageDown } + } + }); + } + + runCoreEditorCommand(cursors: ICursors, args: any): void { + EditorScroll._runEditorScroll(cursors, args.source, { + direction: EditorScroll_.Direction.Down, + unit: EditorScroll_.Unit.WrappedLine, + value: 1, + revealCursor: false + }); + } + }); + + export const ScrollPageDown: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'scrollPageDown', + precondition: null, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: KeyMod.CtrlCmd | KeyCode.PageDown, + win: { primary: KeyMod.Alt | KeyCode.PageDown }, + linux: { primary: KeyMod.Alt | KeyCode.PageDown } + } + }); + } + + runCoreEditorCommand(cursors: ICursors, args: any): void { + EditorScroll._runEditorScroll(cursors, args.source, { + direction: EditorScroll_.Direction.Down, + unit: EditorScroll_.Unit.Page, + value: 1, + revealCursor: false + }); + } + }); + + class WordCommand extends CoreEditorCommand { + + private readonly _inSelectionMode: boolean; + + constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) { + super(opts); + this._inSelectionMode = opts.inSelectionMode; + } + + public runCoreEditorCommand(cursors: ICursors, args: any): void { + cursors.context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + [ + CursorMoveCommands.word(cursors.context, cursors.getPrimaryCursor(), this._inSelectionMode, args.position) + ] + ); + cursors.reveal(true, RevealTarget.Primary); + } + } + + export const WordSelect: CoreEditorCommand = registerEditorCommand(new WordCommand({ + inSelectionMode: false, + id: '_wordSelect', + precondition: null + })); + + export const WordSelectDrag: CoreEditorCommand = registerEditorCommand(new WordCommand({ + inSelectionMode: true, + id: '_wordSelectDrag', + precondition: null + })); + + export const LastCursorWordSelect: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'lastCursorWordSelect', + precondition: null + }); + } + + public runCoreEditorCommand(cursors: ICursors, args: any): void { + const context = cursors.context; + if (context.config.readOnly || context.model.hasEditableRange()) { + return; + } + + const lastAddedCursorIndex = cursors.getLastAddedCursorIndex(); + + let newStates = cursors.getAll().slice(0); + newStates[lastAddedCursorIndex] = CursorMoveCommands.word(context, newStates[lastAddedCursorIndex], true, args.position); + + context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + newStates + ); + } + }); + + class LineCommand extends CoreEditorCommand { + private readonly _inSelectionMode: boolean; + + constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) { + super(opts); + this._inSelectionMode = opts.inSelectionMode; + } + + public runCoreEditorCommand(cursors: ICursors, args: any): void { + cursors.context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + [ + CursorMoveCommands.line(cursors.context, cursors.getPrimaryCursor(), this._inSelectionMode, args.position, args.viewPosition) + ] + ); + cursors.reveal(false, RevealTarget.Primary); + } + } + + export const LineSelect: CoreEditorCommand = registerEditorCommand(new LineCommand({ + inSelectionMode: false, + id: '_lineSelect', + precondition: null + })); + + export const LineSelectDrag: CoreEditorCommand = registerEditorCommand(new LineCommand({ + inSelectionMode: true, + id: '_lineSelectDrag', + precondition: null + })); + + class LastCursorLineCommand extends CoreEditorCommand { + private readonly _inSelectionMode: boolean; + + constructor(opts: ICommandOptions & { inSelectionMode: boolean; }) { + super(opts); + this._inSelectionMode = opts.inSelectionMode; + } + + public runCoreEditorCommand(cursors: ICursors, args: any): void { + const context = cursors.context; + + if (context.config.readOnly || context.model.hasEditableRange()) { + return; + } + + const lastAddedCursorIndex = cursors.getLastAddedCursorIndex(); + + let newStates = cursors.getAll().slice(0); + newStates[lastAddedCursorIndex] = CursorMoveCommands.line(cursors.context, newStates[lastAddedCursorIndex], this._inSelectionMode, args.position, args.viewPosition); + + cursors.context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + newStates + ); + } + } + + export const LastCursorLineSelect: CoreEditorCommand = registerEditorCommand(new LastCursorLineCommand({ + inSelectionMode: false, + id: 'lastCursorLineSelect', + precondition: null + })); + + export const LastCursorLineSelectDrag: CoreEditorCommand = registerEditorCommand(new LastCursorLineCommand({ + inSelectionMode: true, + id: 'lastCursorLineSelectDrag', + precondition: null + })); + + export const ExpandLineSelection: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'expandLineSelection', + precondition: null, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: KeyMod.CtrlCmd | KeyCode.KEY_I + } + }); + } + + public runCoreEditorCommand(cursors: ICursors, args: any): void { + cursors.context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + CursorState.ensureInEditableRange( + cursors.context, + CursorMoveCommands.expandLineSelection(cursors.context, cursors.getAll()) + ) + ); + cursors.reveal(true, RevealTarget.Primary); + } + + }); + + export const CancelSelection: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'cancelSelection', + precondition: EditorContextKeys.hasNonEmptySelection, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape] + } + }); + } + + public runCoreEditorCommand(cursors: ICursors, args: any): void { + cursors.context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + [ + CursorMoveCommands.cancelSelection(cursors.context, cursors.getPrimaryCursor()) + ] + ); + cursors.reveal(true, RevealTarget.Primary); + } + }); + + export const RemoveSecondaryCursors: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'removeSecondaryCursors', + precondition: EditorContextKeys.hasMultipleSelections, + kbOpts: { + weight: CORE_WEIGHT + 1, + kbExpr: EditorContextKeys.textFocus, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape] + } + }); + } + + public runCoreEditorCommand(cursors: ICursors, args: any): void { + cursors.context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + [ + cursors.getPrimaryCursor() + ] + ); + cursors.reveal(true, RevealTarget.Primary); + } + }); }; + +namespace Config { + + function findFocusedEditor(commandId: string, accessor: ServicesAccessor, complain: boolean): editorCommon.ICommonCodeEditor { + let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (!editor) { + if (complain) { + console.warn('Cannot execute ' + commandId + ' because no code editor is focused.'); + } + return null; + } + return editor; + } + + function withCodeEditorFromCommandHandler(commandId: string, accessor: ServicesAccessor, callback: (editor: editorCommon.ICommonCodeEditor) => void): void { + let editor = findFocusedEditor(commandId, accessor, true); + if (editor) { + callback(editor); + } + } + + function triggerEditorHandler(handlerId: string, accessor: ServicesAccessor, args: any): void { + withCodeEditorFromCommandHandler(handlerId, accessor, (editor) => { + editor.trigger('keyboard', handlerId, args); + }); + } + + class CoreCommand extends Command { + public runCommand(accessor: ServicesAccessor, args: any): void { + triggerEditorHandler(this.id, accessor, args); + } + } + + function registerCommand(command: Command) { + KeybindingsRegistry.registerCommandAndKeybindingRule(command.toCommandAndKeybindingRule(CORE_WEIGHT)); + } + + + registerCommand(new CoreCommand({ + id: H.Tab, + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: ContextKeyExpr.and( + EditorContextKeys.textFocus, + EditorContextKeys.tabDoesNotMoveFocus + ), + primary: KeyCode.Tab + } + })); + registerCommand(new CoreCommand({ + id: H.Outdent, + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: ContextKeyExpr.and( + EditorContextKeys.textFocus, + EditorContextKeys.tabDoesNotMoveFocus + ), + primary: KeyMod.Shift | KeyCode.Tab + } + })); + + registerCommand(new CoreCommand({ + id: H.DeleteLeft, + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: KeyCode.Backspace, + secondary: [KeyMod.Shift | KeyCode.Backspace], + mac: { primary: KeyCode.Backspace, secondary: [KeyMod.Shift | KeyCode.Backspace, KeyMod.WinCtrl | KeyCode.KEY_H, KeyMod.WinCtrl | KeyCode.Backspace] } + } + })); + registerCommand(new CoreCommand({ + id: H.DeleteRight, + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: KeyCode.Delete, + mac: { primary: KeyCode.Delete, secondary: [KeyMod.WinCtrl | KeyCode.KEY_D, KeyMod.WinCtrl | KeyCode.Delete] } + } + })); + + registerCommand(new CoreCommand({ + id: H.LineBreakInsert, + precondition: EditorContextKeys.writable, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textFocus, + primary: null, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_O } + } + })); +} diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index e756b029220e12680b2584bcc8a291a146791ea8..b8ee8b3a487852866b5edce7b5232d3d713cf7b4 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -21,7 +21,7 @@ import { TypeOperations } from 'vs/editor/common/controller/cursorTypeOperations import { TextModelEventType, ModelRawContentChangedEvent, RawContentChangedType } from 'vs/editor/common/model/textModelEvents'; import { CursorEventType, CursorChangeReason, ICursorPositionChangedEvent, VerticalRevealType, ICursorSelectionChangedEvent, ICursorRevealRangeEvent, CursorScrollRequest } from "vs/editor/common/controller/cursorEvents"; import { CursorMoveCommands } from "vs/editor/common/controller/cursorMoveCommands"; -import { RevealLine, EditorScroll } from "vs/editor/common/config/config"; +import { RevealLine } from "vs/editor/common/config/config"; import { CommonEditorRegistry } from "vs/editor/common/editorCommonExtensions"; import { CoreEditorCommand } from 'vs/editor/common/controller/coreCommands'; @@ -234,6 +234,12 @@ export class Cursor extends Disposable implements ICursors { this.revealRange(target, VerticalRevealType.Simple, horizontal); } + public scrollTo(desiredScrollTop: number): void { + this._eventEmitter.emit(CursorEventType.CursorScrollRequest, new CursorScrollRequest( + desiredScrollTop + )); + } + public saveState(): editorCommon.ICursorState[] { var selections = this.cursors.getSelections(), @@ -866,26 +872,12 @@ export class Cursor extends Disposable implements ICursors { private _registerHandlers(): void { let H = editorCommon.Handler; - this._handlers[H.AddCursorUp] = (ctx) => this._addCursorUp(ctx); - this._handlers[H.AddCursorDown] = (ctx) => this._addCursorDown(ctx); - this._handlers[H.SelectAll] = (ctx) => this._selectAll(ctx); - this._handlers[H.LineSelect] = (ctx) => this._line(false, ctx); - this._handlers[H.LineSelectDrag] = (ctx) => this._line(true, ctx); - this._handlers[H.LastCursorLineSelect] = (ctx) => this._lastCursorLine(false, ctx); - this._handlers[H.LastCursorLineSelectDrag] = (ctx) => this._lastCursorLine(true, ctx); - this._handlers[H.LineInsertBefore] = (ctx) => this._lineInsertBefore(ctx); this._handlers[H.LineInsertAfter] = (ctx) => this._lineInsertAfter(ctx); this._handlers[H.LineBreakInsert] = (ctx) => this._lineBreakInsert(ctx); - this._handlers[H.WordSelect] = (ctx) => this._word(false, ctx); - this._handlers[H.WordSelectDrag] = (ctx) => this._word(true, ctx); - this._handlers[H.LastCursorWordSelect] = (ctx) => this._lastCursorWord(ctx); - this._handlers[H.CancelSelection] = (ctx) => this._cancelSelection(ctx); - this._handlers[H.RemoveSecondaryCursors] = (ctx) => this._removeSecondaryCursors(ctx); - this._handlers[H.Type] = (ctx) => this._type(ctx); this._handlers[H.ReplacePreviousChar] = (ctx) => this._replacePreviousChar(ctx); this._handlers[H.CompositionStart] = (ctx) => this._compositionStart(ctx); @@ -895,20 +887,11 @@ export class Cursor extends Disposable implements ICursors { this._handlers[H.Outdent] = (ctx) => this._outdent(ctx); this._handlers[H.Paste] = (ctx) => this._paste(ctx); - this._handlers[H.EditorScroll] = (ctx) => this._editorScroll(ctx); - - this._handlers[H.ScrollLineUp] = (ctx) => this._scrollUp(false, ctx); - this._handlers[H.ScrollLineDown] = (ctx) => this._scrollDown(false, ctx); - this._handlers[H.ScrollPageUp] = (ctx) => this._scrollUp(true, ctx); - this._handlers[H.ScrollPageDown] = (ctx) => this._scrollDown(true, ctx); - this._handlers[H.DeleteLeft] = (ctx) => this._deleteLeft(ctx); this._handlers[H.DeleteRight] = (ctx) => this._deleteRight(ctx); this._handlers[H.Cut] = (ctx) => this._cut(ctx); - this._handlers[H.ExpandLineSelection] = (ctx) => this._expandLineSelection(ctx); - this._handlers[H.Undo] = (ctx) => this._undo(ctx); this._handlers[H.Redo] = (ctx) => this._redo(ctx); @@ -930,30 +913,6 @@ export class Cursor extends Disposable implements ICursors { }; } - private _addCursorUp(ctx: IMultipleCursorOperationContext): void { - if (this.configuration.editor.readOnly) { - return; - } - ctx.cursorPositionChangeReason = CursorChangeReason.Explicit; - ctx.shouldRevealTarget = RevealTarget.TopMost; - ctx.shouldPushStackElementBefore = true; - ctx.shouldPushStackElementAfter = true; - - this.cursors.setStates(CursorMoveCommands.addCursorUp(this.context, this.getAll()), true); - } - - private _addCursorDown(ctx: IMultipleCursorOperationContext): void { - if (this.configuration.editor.readOnly) { - return; - } - ctx.cursorPositionChangeReason = CursorChangeReason.Explicit; - ctx.shouldRevealTarget = RevealTarget.BottomMost; - ctx.shouldPushStackElementBefore = true; - ctx.shouldPushStackElementAfter = true; - - this.cursors.setStates(CursorMoveCommands.addCursorDown(this.context, this.getAll()), true); - } - private _selectAll(ctx: IMultipleCursorOperationContext): void { ctx.shouldPushStackElementBefore = true; ctx.shouldPushStackElementAfter = true; @@ -962,74 +921,6 @@ export class Cursor extends Disposable implements ICursors { this.cursors.setStates([result], false); } - private _line(inSelectionMode: boolean, ctx: IMultipleCursorOperationContext): void { - ctx.shouldPushStackElementBefore = true; - ctx.shouldPushStackElementAfter = true; - ctx.cursorPositionChangeReason = CursorChangeReason.Explicit; - ctx.shouldRevealHorizontal = false; - - const r = CursorMoveCommands.line(this.context, this.getPrimaryCursor(), inSelectionMode, ctx.eventData.position, ctx.eventData.viewPosition); - this.cursors.setStates([r], false); - } - - private _lastCursorLine(inSelectionMode: boolean, ctx: IMultipleCursorOperationContext): void { - if (this.configuration.editor.readOnly || this.model.hasEditableRange()) { - return; - } - - ctx.shouldPushStackElementBefore = true; - ctx.shouldPushStackElementAfter = true; - ctx.cursorPositionChangeReason = CursorChangeReason.Explicit; - ctx.shouldReveal = false; - - const lastAddedCursor = this.cursors.getLastAddedCursor(); - const result = CursorMoveCommands.line(this.context, lastAddedCursor.asCursorState(), inSelectionMode, ctx.eventData.position, ctx.eventData.viewPosition); - lastAddedCursor.setState(this.context, result.modelState, result.viewState, false); - } - - private _expandLineSelection(ctx: IMultipleCursorOperationContext): void { - ctx.cursorPositionChangeReason = CursorChangeReason.Explicit; - ctx.shouldPushStackElementBefore = true; - ctx.shouldPushStackElementAfter = true; - this.cursors.setStates(CursorMoveCommands.expandLineSelection(this.context, this.getAll()), true); - } - - private _word(inSelectionMode: boolean, ctx: IMultipleCursorOperationContext): void { - ctx.cursorPositionChangeReason = CursorChangeReason.Explicit; - ctx.shouldPushStackElementBefore = true; - ctx.shouldPushStackElementAfter = true; - - const primaryCursor = this.getPrimaryCursor(); - const r = CursorMoveCommands.word(this.context, primaryCursor, inSelectionMode, ctx.eventData.position); - this.cursors.setStates([r], false); - } - - private _lastCursorWord(ctx: IMultipleCursorOperationContext): void { - if (this.configuration.editor.readOnly || this.model.hasEditableRange()) { - return; - } - - ctx.cursorPositionChangeReason = CursorChangeReason.Explicit; - ctx.shouldPushStackElementBefore = true; - ctx.shouldPushStackElementAfter = true; - ctx.shouldReveal = false; - - const lastAddedCursor = this.cursors.getLastAddedCursor(); - const r = CursorMoveCommands.word(this.context, lastAddedCursor.asCursorState(), true, ctx.eventData.position); - lastAddedCursor.setState(this.context, r.modelState, r.viewState, false); - } - - private _removeSecondaryCursors(ctx: IMultipleCursorOperationContext): void { - this.cursors.killSecondaryCursors(); - } - - private _cancelSelection(ctx: IMultipleCursorOperationContext): void { - ctx.shouldPushStackElementBefore = true; - ctx.shouldPushStackElementAfter = true; - const r = CursorMoveCommands.cancelSelection(this.context, this.getPrimaryCursor()); - this.cursors.setStates([r], false); - } - // -------------------- START editing operations private _applyEdits(ctx: IMultipleCursorOperationContext, edits: EditOperationResult): void { @@ -1220,82 +1111,6 @@ export class Cursor extends Disposable implements ICursors { this.emitCursorRevealRange(range, null, revealAt, false); } - private _scrollUp(isPaged: boolean, ctx: IMultipleCursorOperationContext): void { - this._doEditorScroll({ - direction: EditorScroll.Direction.Up, - unit: (isPaged ? EditorScroll.Unit.Page : EditorScroll.Unit.WrappedLine), - value: 1, - revealCursor: false - }, ctx); - } - - private _scrollDown(isPaged: boolean, ctx: IMultipleCursorOperationContext): void { - this._doEditorScroll({ - direction: EditorScroll.Direction.Down, - unit: (isPaged ? EditorScroll.Unit.Page : EditorScroll.Unit.WrappedLine), - value: 1, - revealCursor: false - }, ctx); - } - - private _editorScroll(ctx: IMultipleCursorOperationContext): void { - const args = EditorScroll.parse(ctx.eventData); - if (!args) { - // illegal arguments - return; - } - this._doEditorScroll(args, ctx); - } - - private _doEditorScroll(args: EditorScroll.ParsedArguments, ctx: IMultipleCursorOperationContext): void { - - const desiredScrollTop = this._computeDesiredScrollTop(args); - - if (args.revealCursor) { - // must ensure cursor is in new visible range - const desiredVisibleViewRange = this.context.getCompletelyVisibleViewRangeAtScrollTop(desiredScrollTop); - const r = CursorMoveCommands.findPositionInViewportIfOutside(this.context, this.getPrimaryCursor(), desiredVisibleViewRange, false); - this.cursors.setStates([r], false); - } - - this._eventEmitter.emit(CursorEventType.CursorScrollRequest, new CursorScrollRequest( - desiredScrollTop - )); - - ctx.shouldReveal = false; - } - - private _computeDesiredScrollTop(args: EditorScroll.ParsedArguments): number { - - if (args.unit === EditorScroll.Unit.Line) { - // scrolling by model lines - const visibleModelRange = this.context.getCompletelyVisibleModelRange(); - - let desiredTopModelLineNumber: number; - if (args.direction === EditorScroll.Direction.Up) { - // must go x model lines up - desiredTopModelLineNumber = Math.max(1, visibleModelRange.startLineNumber - args.value); - } else { - // must go x model lines down - desiredTopModelLineNumber = Math.min(this.context.model.getLineCount(), visibleModelRange.startLineNumber + args.value); - } - - const desiredTopViewPosition = this.context.convertModelPositionToViewPosition(new Position(desiredTopModelLineNumber, 1)); - return this.context.getVerticalOffsetForViewLine(desiredTopViewPosition.lineNumber); - } - - let noOfLines: number; - if (args.unit === EditorScroll.Unit.Page) { - noOfLines = this.context.config.pageSize * args.value; - } else if (args.unit === EditorScroll.Unit.HalfPage) { - noOfLines = Math.round(this.context.config.pageSize / 2) * args.value; - } else { - noOfLines = args.value; - } - const deltaLines = (args.direction === EditorScroll.Direction.Up ? -1 : 1) * noOfLines; - return this.context.getScrollTop() + deltaLines * this.context.config.lineHeight; - } - private _undo(ctx: IMultipleCursorOperationContext): void { ctx.cursorPositionChangeReason = CursorChangeReason.Undo; this._interpretCommandResult(this.model.undo()); diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 107dd5b458e3ee3090b11d701abbc513e8b5252a..1684c392155d345a0d238eebb360b73565bdee9d 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -41,6 +41,7 @@ export interface ICursors { setStates(source: string, reason: CursorChangeReason, states: CursorState[]): void; reveal(horizontal: boolean, target: RevealTarget): void; + scrollTo(desiredScrollTop: number): void; } export interface CharacterMap { diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 60325c1c136282c3da7c6f42130ec6fa5bff7990..7691b10ddd4a8b33b1a3bff3feb9cef4ba0bc450 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -2032,11 +2032,6 @@ export var Handler = { ExecuteCommand: 'executeCommand', ExecuteCommands: 'executeCommands', - ExpandLineSelection: 'expandLineSelection', - - AddCursorDown: 'addCursorDown', - AddCursorUp: 'addCursorUp', - Type: 'type', ReplacePreviousChar: 'replacePreviousChar', CompositionStart: 'compositionStart', @@ -2050,36 +2045,17 @@ export var Handler = { DeleteLeft: 'deleteLeft', DeleteRight: 'deleteRight', - RemoveSecondaryCursors: 'removeSecondaryCursors', - CancelSelection: 'cancelSelection', - Cut: 'cut', Undo: 'undo', Redo: 'redo', - WordSelect: 'wordSelect', - WordSelectDrag: 'wordSelectDrag', - LastCursorWordSelect: 'lastCursorWordSelect', - - LineSelect: 'lineSelect', - LineSelectDrag: 'lineSelectDrag', - LastCursorLineSelect: 'lastCursorLineSelect', - LastCursorLineSelectDrag: 'lastCursorLineSelectDrag', LineInsertBefore: 'lineInsertBefore', LineInsertAfter: 'lineInsertAfter', LineBreakInsert: 'lineBreakInsert', SelectAll: 'selectAll', - EditorScroll: 'editorScroll', - - ScrollLineUp: 'scrollLineUp', - ScrollLineDown: 'scrollLineDown', - - ScrollPageUp: 'scrollPageUp', - ScrollPageDown: 'scrollPageDown', - RevealLine: 'revealLine' }; diff --git a/src/vs/editor/contrib/multicursor/common/multicursor.ts b/src/vs/editor/contrib/multicursor/common/multicursor.ts index 20d1c2805404e9770f9d70cea8d554998f1b366f..1d29d1b9911f4ebb58f016067be5289804533f39 100644 --- a/src/vs/editor/contrib/multicursor/common/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/common/multicursor.ts @@ -6,20 +6,22 @@ import * as nls from 'vs/nls'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Handler, ICommonCodeEditor } from 'vs/editor/common/editorCommon'; +import { ICommonCodeEditor } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { editorAction, ServicesAccessor, EditorAction, HandlerEditorAction } from 'vs/editor/common/editorCommonExtensions'; +import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; import { Selection } from 'vs/editor/common/core/selection'; +import { CursorChangeReason } from "vs/editor/common/controller/cursorEvents"; +import { CursorMoveCommands } from "vs/editor/common/controller/cursorMoveCommands"; +import { CursorState, RevealTarget } from 'vs/editor/common/controller/cursorCommon'; @editorAction -class InsertCursorAbove extends HandlerEditorAction { +export class InsertCursorAbove extends EditorAction { constructor() { super({ id: 'editor.action.insertCursorAbove', label: nls.localize('mutlicursor.insertAbove', "Add Cursor Above"), alias: 'Add Cursor Above', precondition: null, - handlerId: Handler.AddCursorUp, kbOpts: { kbExpr: EditorContextKeys.textFocus, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.UpArrow, @@ -30,17 +32,36 @@ class InsertCursorAbove extends HandlerEditorAction { } }); } + + public run(accessor: ServicesAccessor, editor: ICommonCodeEditor, args: any): void { + const cursors = editor._getCursors(); + const context = cursors.context; + + if (context.config.readOnly) { + return; + } + + context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + CursorState.ensureInEditableRange( + context, + CursorMoveCommands.addCursorUp(context, cursors.getAll()) + ) + ); + cursors.reveal(true, RevealTarget.TopMost); + } } @editorAction -class InsertCursorBelow extends HandlerEditorAction { +export class InsertCursorBelow extends EditorAction { constructor() { super({ id: 'editor.action.insertCursorBelow', label: nls.localize('mutlicursor.insertBelow', "Add Cursor Below"), alias: 'Add Cursor Below', precondition: null, - handlerId: Handler.AddCursorDown, kbOpts: { kbExpr: EditorContextKeys.textFocus, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.DownArrow, @@ -51,6 +72,26 @@ class InsertCursorBelow extends HandlerEditorAction { } }); } + + public run(accessor: ServicesAccessor, editor: ICommonCodeEditor, args: any): void { + const cursors = editor._getCursors(); + const context = cursors.context; + + if (context.config.readOnly) { + return; + } + + context.model.pushStackElement(); + cursors.setStates( + args.source, + CursorChangeReason.Explicit, + CursorState.ensureInEditableRange( + context, + CursorMoveCommands.addCursorDown(context, cursors.getAll()) + ) + ); + cursors.reveal(true, RevealTarget.BottomMost); + } } @editorAction diff --git a/src/vs/editor/contrib/multicursor/test/common/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/common/multicursor.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ea7de76704a2f97b3037b547d56666ed737ab10 --- /dev/null +++ b/src/vs/editor/contrib/multicursor/test/common/multicursor.test.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import { withMockCodeEditor } from "vs/editor/test/common/mocks/mockCodeEditor"; +import { Selection } from "vs/editor/common/core/selection"; +import { InsertCursorAbove, InsertCursorBelow } from "vs/editor/contrib/multicursor/common/multicursor"; +import { Handler } from "vs/editor/common/editorCommon"; + + +suite('Multicursor', () => { + + test('issue #2205: Multi-cursor pastes in reverse order', () => { + withMockCodeEditor([ + 'abc', + 'def' + ], {}, (editor, cursor) => { + let addCursorUpAction = new InsertCursorAbove(); + + editor.setSelection(new Selection(2, 1, 2, 1)); + addCursorUpAction.run(null, editor, {}); + assert.equal(cursor.getSelections().length, 2); + + editor.trigger('test', Handler.Paste, { text: '1\n2' }); + // cursorCommand(cursor, H.Paste, { text: '1\n2' }); + assert.equal(editor.getModel().getLineContent(1), '1abc'); + assert.equal(editor.getModel().getLineContent(2), '2def'); + }); + }); + + test('issue #1336: Insert cursor below on last line adds a cursor to the end of the current line', () => { + withMockCodeEditor([ + 'abc' + ], {}, (editor, cursor) => { + let addCursorDownAction = new InsertCursorBelow(); + addCursorDownAction.run(null, editor, {}); + assert.equal(cursor.getSelections().length, 1); + }); + }); + +}); diff --git a/src/vs/editor/test/common/controller/cursor.test.ts b/src/vs/editor/test/common/controller/cursor.test.ts index 89520086bd2f5203dd3e9b74c3fc865b42528877..456a1d206adaeb11febbcb8eb325c57efacf7ae4 100644 --- a/src/vs/editor/test/common/controller/cursor.test.ts +++ b/src/vs/editor/test/common/controller/cursor.test.ts @@ -599,37 +599,37 @@ suite('Editor Controller - Cursor', () => { // 01234 56789012345678 0 // let LINE1 = ' \tMy First Line\t '; moveTo(thisCursor, 1, 1); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 2, 1)); moveTo(thisCursor, 1, 2); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 2, 1)); moveTo(thisCursor, 1, 5); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 2, 1)); moveTo(thisCursor, 1, 19); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 2, 1)); moveTo(thisCursor, 1, 20); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 2, 1)); moveTo(thisCursor, 1, 21); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 2, 1)); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 3, 1)); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 4, 1)); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 5, 1)); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 5, LINE5.length + 1)); - cursorCommand(thisCursor, H.ExpandLineSelection); + CoreCommands.ExpandLineSelection.runCoreEditorCommand(thisCursor, {}); assertCursor(thisCursor, new Selection(1, 1, 5, LINE5.length + 1)); }); @@ -1501,34 +1501,6 @@ suite('Editor Controller - Regression tests', () => { mode.dispose(); }); - test('issue #1336: Insert cursor below on last line adds a cursor to the end of the current line', () => { - usingCursor({ - text: [ - 'abc' - ], - }, (model, cursor) => { - cursorCommand(cursor, H.AddCursorDown); - assert.equal(cursor.getSelections().length, 1); - }); - }); - - test('issue #2205: Multi-cursor pastes in reverse order', () => { - usingCursor({ - text: [ - 'abc', - 'def' - ], - }, (model, cursor) => { - moveTo(cursor, 2, 1, false); - cursorCommand(cursor, H.AddCursorUp); - assert.equal(cursor.getSelections().length, 2); - - cursorCommand(cursor, H.Paste, { text: '1\n2' }); - assert.equal(model.getLineContent(1), '1abc'); - assert.equal(model.getLineContent(2), '2def'); - }); - }); - test('issue #10212: Pasting entire line does not replace selection', () => { usingCursor({ text: [ @@ -1653,13 +1625,17 @@ suite('Editor Controller - Regression tests', () => { moveTo(cursor, 1, 1, false); function assertWordRight(col, expectedCol) { - cursorCommand(cursor, col === 1 ? H.WordSelect : H.WordSelectDrag, { + let args = { position: { lineNumber: 1, column: col - }, - preference: 'right' - }); + } + }; + if (col === 1) { + CoreCommands.WordSelect.runCoreEditorCommand(cursor, args); + } else { + CoreCommands.WordSelectDrag.runCoreEditorCommand(cursor, args); + } assert.equal(cursor.getSelection().startColumn, 1, 'TEST FOR ' + col); assert.equal(cursor.getSelection().endColumn, expectedCol, 'TEST FOR ' + col);