Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ dist
.github/prompts/*.local.prompt.md
.agents/skills/.local/
.github/skills/.local/
.antigravity/
3 changes: 2 additions & 1 deletion src/vs/editor/browser/coreCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ export namespace CoreNavigationCommands {
export interface MoveCommandOptions extends BaseCommandOptions {
position: IPosition;
viewPosition?: IPosition;
leftoverVisibleColumns?: number;
revealType: NavigationCommandRevealType;
}

Expand All @@ -395,7 +396,7 @@ export namespace CoreNavigationCommands {
args.source,
CursorChangeReason.Explicit,
[
CursorMoveCommands.moveTo(viewModel, viewModel.getPrimaryCursorState(), this._inSelectionMode, args.position, args.viewPosition)
CursorMoveCommands.moveTo(viewModel, viewModel.getPrimaryCursorState(), this._inSelectionMode, args.position, args.viewPosition, args.leftoverVisibleColumns)
]
);
if (cursorStateChanged && args.revealType !== NavigationCommandRevealType.None) {
Expand Down
58 changes: 48 additions & 10 deletions src/vs/editor/browser/view/viewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as platform from '../../../base/common/platform.js';
import { StandardTokenType } from '../../common/encodedTokenAttributes.js';
import { ITextModel } from '../../common/model.js';
import { containsRTL } from '../../../base/common/strings.js';
import { CursorColumns } from '../../common/core/cursorColumns.js';

export interface IMouseDispatchData {
position: Position;
Expand Down Expand Up @@ -286,7 +287,7 @@ export class ViewController {
} else {
// Do multi-cursor operations only when purely alt is pressed
if (data.inSelectionMode) {
this._lastCursorMoveToSelect(data.position, data.revealType);
this._lastCursorMoveToSelect(data.position, data.mouseColumn, data.revealType);
} else {
this._createCursor(data.position, false);
}
Expand All @@ -300,32 +301,63 @@ export class ViewController {
if (columnSelection) {
this._columnSelect(data.position, data.mouseColumn, true);
} else {
this._moveToSelect(data.position, data.revealType);
this._moveToSelect(data.position, data.mouseColumn, data.revealType);
}
}
} else {
this.moveTo(data.position, data.revealType);
this.moveTo(data.position, data.mouseColumn, data.revealType);
}
}
}
}

private _usualArgs(viewPosition: Position, revealType: NavigationCommandRevealType): CoreNavigationCommands.MoveCommandOptions {
private _usualArgs(viewPosition: Position, revealType: NavigationCommandRevealType): CoreNavigationCommands.MoveCommandOptions;
private _usualArgs(viewPosition: Position, mouseColumn: number, revealType: NavigationCommandRevealType): CoreNavigationCommands.MoveCommandOptions;
private _usualArgs(viewPosition: Position, arg2: number | NavigationCommandRevealType, arg3?: NavigationCommandRevealType): CoreNavigationCommands.MoveCommandOptions {
viewPosition = this._validateViewColumn(viewPosition);
let mouseColumn: number;
let revealType: NavigationCommandRevealType;
if (arg3 !== undefined) {
mouseColumn = arg2 as number;
revealType = arg3;
} else {
mouseColumn = CursorColumns.visibleColumnFromColumn(this.viewModel.getLineContent(viewPosition.lineNumber), viewPosition.column, this.viewModel.model.getOptions().tabSize) + 1;
revealType = arg2 as NavigationCommandRevealType;
}

let leftoverVisibleColumns = 0;
if (this.configuration.options.get(EditorOption.virtualSpace)) {
const maxColumn = this.viewModel.getLineMaxColumn(viewPosition.lineNumber);
const maxVisibleColumn = CursorColumns.visibleColumnFromColumn(this.viewModel.getLineContent(viewPosition.lineNumber), maxColumn, this.viewModel.model.getOptions().tabSize);
leftoverVisibleColumns = Math.max(0, (mouseColumn - 1) - maxVisibleColumn);
}
return {
source: 'mouse',
position: this._convertViewToModelPosition(viewPosition),
viewPosition,
leftoverVisibleColumns,
revealType
};
}

public moveTo(viewPosition: Position, revealType: NavigationCommandRevealType): void {
CoreNavigationCommands.MoveTo.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
public moveTo(viewPosition: Position, revealType: NavigationCommandRevealType): void;
public moveTo(viewPosition: Position, mouseColumn: number, revealType: NavigationCommandRevealType): void;
public moveTo(viewPosition: Position, arg2: number | NavigationCommandRevealType, arg3?: NavigationCommandRevealType): void {
if (arg3 !== undefined) {
CoreNavigationCommands.MoveTo.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, arg2 as number, arg3));
} else {
CoreNavigationCommands.MoveTo.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, arg2 as NavigationCommandRevealType));
}
}

private _moveToSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
CoreNavigationCommands.MoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
private _moveToSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void;
private _moveToSelect(viewPosition: Position, mouseColumn: number, revealType: NavigationCommandRevealType): void;
private _moveToSelect(viewPosition: Position, arg2: number | NavigationCommandRevealType, arg3?: NavigationCommandRevealType): void {
if (arg3 !== undefined) {
CoreNavigationCommands.MoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, arg2 as number, arg3));
} else {
CoreNavigationCommands.MoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, arg2 as NavigationCommandRevealType));
}
}

private _columnSelect(viewPosition: Position, mouseColumn: number, doColumnSelect: boolean): void {
Expand All @@ -349,8 +381,14 @@ export class ViewController {
});
}

private _lastCursorMoveToSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
CoreNavigationCommands.LastCursorMoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
private _lastCursorMoveToSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void;
private _lastCursorMoveToSelect(viewPosition: Position, mouseColumn: number, revealType: NavigationCommandRevealType): void;
private _lastCursorMoveToSelect(viewPosition: Position, arg2: number | NavigationCommandRevealType, arg3?: NavigationCommandRevealType): void {
if (arg3 !== undefined) {
CoreNavigationCommands.LastCursorMoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, arg2 as number, arg3));
} else {
CoreNavigationCommands.LastCursorMoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, arg2 as NavigationCommandRevealType));
}
}

private _wordSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
Expand Down
16 changes: 14 additions & 2 deletions src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Position } from '../../../common/core/position.js';
import { Range } from '../../../common/core/range.js';
import { RenderingContext, RestrictedRenderingContext } from '../../view/renderingContext.js';
import { ViewContext } from '../../../common/viewModel/viewContext.js';
import { CursorColumns } from '../../../common/core/cursorColumns.js';
import * as viewEvents from '../../../common/viewEvents.js';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../../base/browser/ui/mouseCursor/mouseCursor.js';

Expand Down Expand Up @@ -53,6 +54,7 @@ export class ViewCursor {
private _isVisible: boolean;

private _position: Position;
private _leftoverVisibleColumns: number;
private _pluralityClass: string;

private _lastRenderedContent: string;
Expand Down Expand Up @@ -80,6 +82,7 @@ export class ViewCursor {
this._domNode.setDisplay('none');

this._position = new Position(1, 1);
this._leftoverVisibleColumns = 0;
this._pluralityClass = '';
this.setPlurality(plurality);

Expand Down Expand Up @@ -139,13 +142,14 @@ export class ViewCursor {
return true;
}

public onCursorPositionChanged(position: Position, pauseAnimation: boolean): boolean {
public onCursorPositionChanged(position: Position, leftoverVisibleColumns: number, pauseAnimation: boolean): boolean {
if (pauseAnimation) {
this._domNode.domNode.style.transitionProperty = 'none';
} else {
this._domNode.domNode.style.transitionProperty = '';
}
this._position = position;
this._leftoverVisibleColumns = leftoverVisibleColumns;
return true;
}

Expand Down Expand Up @@ -192,6 +196,9 @@ export class ViewCursor {
}

let left = visibleRange.left;
if (this._leftoverVisibleColumns > 0 && this._leftoverVisibleColumns < CursorColumns.MAX_VIRTUAL_SPACE_COLUMNS && this._context.configuration.options.get(EditorOption.virtualSpace)) {
left += this._leftoverVisibleColumns * this._typicalHalfwidthCharacterWidth;
}
Comment thread
jamiefutch marked this conversation as resolved.
let paddingLeft = 0;
if (width >= 2 && left >= 1) {
// shift the cursor a bit between the characters
Expand Down Expand Up @@ -238,7 +245,12 @@ export class ViewCursor {
height = 2;
}

return new ViewCursorRenderData(top, range.left, 0, width, height, textContent, textContentClassName);
let left = range.left;
if (this._leftoverVisibleColumns > 0 && this._leftoverVisibleColumns < CursorColumns.MAX_VIRTUAL_SPACE_COLUMNS && this._context.configuration.options.get(EditorOption.virtualSpace)) {
left += this._leftoverVisibleColumns * this._typicalHalfwidthCharacterWidth;
}

return new ViewCursorRenderData(top, left, 0, width, height, textContent, textContentClassName);
}

private _getTokenClassName(position: Position): string {
Expand Down
8 changes: 4 additions & 4 deletions src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ export class ViewCursors extends ViewPart {
}
return true;
}
private _onCursorPositionChanged(position: Position, secondaryPositions: Position[], reason: CursorChangeReason): void {
private _onCursorPositionChanged(position: Position, leftoverVisibleColumns: number, secondaryPositions: Position[], secondaryLeftoverVisibleColumns: number[], reason: CursorChangeReason): void {
const pauseAnimation = (
this._secondaryCursors.length !== secondaryPositions.length
|| (this._cursorSmoothCaretAnimation === 'explicit' && reason !== CursorChangeReason.Explicit)
);
this._primaryCursor.setPlurality(secondaryPositions.length ? CursorPlurality.MultiPrimary : CursorPlurality.Single);
this._primaryCursor.onCursorPositionChanged(position, pauseAnimation);
this._primaryCursor.onCursorPositionChanged(position, leftoverVisibleColumns, pauseAnimation);
this._updateBlinking();

if (this._secondaryCursors.length < secondaryPositions.length) {
Expand All @@ -154,7 +154,7 @@ export class ViewCursors extends ViewPart {
}

for (let i = 0; i < secondaryPositions.length; i++) {
this._secondaryCursors[i].onCursorPositionChanged(secondaryPositions[i], pauseAnimation);
this._secondaryCursors[i].onCursorPositionChanged(secondaryPositions[i], secondaryLeftoverVisibleColumns[i], pauseAnimation);
}

}
Expand All @@ -163,7 +163,7 @@ export class ViewCursors extends ViewPart {
for (let i = 0, len = e.selections.length; i < len; i++) {
positions[i] = e.selections[i].getPosition();
}
this._onCursorPositionChanged(positions[0], positions.slice(1), e.reason);
this._onCursorPositionChanged(positions[0], e.leftoverVisibleColumns[0], positions.slice(1), e.leftoverVisibleColumns.slice(1), e.reason);

const selectionIsEmpty = e.selections[0].isEmpty();
if (this._selectionIsEmpty !== selectionIsEmpty) {
Expand Down
12 changes: 12 additions & 0 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,10 @@ export interface IEditorOptions {
* Controls whether suggestions allow matches in the middle of the word instead of only at the beginning
*/
matchOnWordStartOnly?: boolean;
/**
* Controls whether the cursor should be allowed to move into virtual space.
*/
virtualSpace?: boolean;
/**
* Control the behavior and rendering of the inline hints.
*/
Expand Down Expand Up @@ -5931,6 +5935,10 @@ export const enum EditorOption {
inertialScroll,
inlayHints,
wrapOnEscapedLineFeeds,
/**
* Controls whether the cursor should be allowed to move into virtual space.
*/
virtualSpace,
// Leave these at the end (because they have dependencies!)
effectiveCursorStyle,
editorClassName,
Expand Down Expand Up @@ -6742,6 +6750,10 @@ export const EditorOptions = {
useShadowDOM: register(new EditorBooleanOption(
EditorOption.useShadowDOM, 'useShadowDOM', true
)),
virtualSpace: register(new EditorBooleanOption(
EditorOption.virtualSpace, 'virtualSpace', false,
{ description: nls.localize('virtualSpace', "Controls whether the cursor should be allowed to move into virtual space.") }
)),
Comment thread
jamiefutch marked this conversation as resolved.
useTabStops: register(new EditorBooleanOption(
EditorOption.useTabStops, 'useTabStops', true,
{ description: nls.localize('useTabStops', "Spaces and tabs are inserted and deleted in alignment with tab stops.") }
Expand Down
6 changes: 6 additions & 0 deletions src/vs/editor/common/core/cursorColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import * as strings from '../../../base/common/strings.js';
* **NOTE**: These methods work and make sense both on the model and on the view model.
*/
export class CursorColumns {
/**
* Threshold used to distinguish between a "sticky" end-of-line position
* and actual virtual space columns.
*/
public static readonly MAX_VIRTUAL_SPACE_COLUMNS = 1000000;


private static _nextVisibleColumn(codePoint: number, visibleColumn: number, tabSize: number): number {
if (codePoint === CharCode.Tab) {
Expand Down
17 changes: 13 additions & 4 deletions src/vs/editor/common/cursor/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,13 @@ export class CursorsController extends Disposable {
return false;
}

const cursorStates = this._cursors.getAll();
const selections = this._cursors.getSelections();
const viewSelections = this._cursors.getViewSelections();
const leftoverVisibleColumns = cursorStates.map(s => s.viewState.leftoverVisibleColumns);

// Let the view get the event first.
eventsCollector.emitViewEvent(new ViewCursorStateChangedEvent(viewSelections, selections, reason));
eventsCollector.emitViewEvent(new ViewCursorStateChangedEvent(viewSelections, selections, leftoverVisibleColumns, reason));

// Only after the view has been notified, let the rest of the world know...
if (!oldState
Expand Down Expand Up @@ -566,14 +568,19 @@ export class CursorsController extends Disposable {
const charLength = strings.nextCharLength(text, offset);
const chr = text.substr(offset, charLength);

const cursorStates = this._cursors.getAll();
const leftoverVisibleColumns = cursorStates.map(s => s.viewState.leftoverVisibleColumns);

// Here we must interpret each typed character individually
this._executeEditOperation(TypeOperations.typeWithInterceptors(!!this._compositionState, this._prevEditOperationType, this.context.cursorConfig, this._model, this.getSelections(), this.getAutoClosedCharacters(), chr), reason);
this._executeEditOperation(TypeOperations.typeWithInterceptors(!!this._compositionState, this._prevEditOperationType, this.context.cursorConfig, this._model, this.getSelections(), leftoverVisibleColumns, this.getAutoClosedCharacters(), chr), reason);

offset += charLength;
}

} else {
this._executeEditOperation(TypeOperations.typeWithoutInterceptors(this._prevEditOperationType, this.context.cursorConfig, this._model, this.getSelections(), text), reason);
const cursorStates = this._cursors.getAll();
const leftoverVisibleColumns = cursorStates.map(s => s.viewState.leftoverVisibleColumns);
this._executeEditOperation(TypeOperations.typeWithoutInterceptors(this._prevEditOperationType, this.context.cursorConfig, this._model, this.getSelections(), leftoverVisibleColumns, text), reason);
}
}, eventsCollector, source);
}
Expand Down Expand Up @@ -601,8 +608,10 @@ export class CursorsController extends Disposable {
public paste(eventsCollector: ViewModelEventsCollector, text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void {
const reason = EditSources.cursor({ kind: 'paste', detailedSource: source });

const cursorStates = this._cursors.getAll();
const leftoverVisibleColumns = cursorStates.map(s => s.viewState.leftoverVisibleColumns);
this._executeEdit(() => {
this._executeEditOperation(TypeOperations.paste(this.context.cursorConfig, this._model, this.getSelections(), text, pasteOnNewLine, multicursorText || []), reason);
this._executeEditOperation(TypeOperations.paste(this.context.cursorConfig, this._model, this.getSelections(), leftoverVisibleColumns, text, pasteOnNewLine, multicursorText || []), reason);
}, eventsCollector, source, CursorChangeReason.Paste);
}

Expand Down
25 changes: 14 additions & 11 deletions src/vs/editor/common/cursor/cursorColumnSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ export class ColumnSelection {

const result: SingleCursorState[] = [];

// console.log(`fromVisibleColumn: ${fromVisibleColumn}, toVisibleColumn: ${toVisibleColumn}`);

for (let i = 0; i < lineCount; i++) {
const lineNumber = fromLineNumber + (reversed ? -i : i);

Expand All @@ -27,29 +25,30 @@ export class ColumnSelection {
const visibleStartColumn = config.visibleColumnFromColumn(model, new Position(lineNumber, startColumn));
const visibleEndColumn = config.visibleColumnFromColumn(model, new Position(lineNumber, endColumn));

// console.log(`lineNumber: ${lineNumber}: visibleStartColumn: ${visibleStartColumn}, visibleEndColumn: ${visibleEndColumn}`);
const leftoverStartColumn = Math.max(0, fromVisibleColumn - visibleStartColumn);
const leftoverEndColumn = Math.max(0, toVisibleColumn - visibleEndColumn);

if (isLTR) {
if (visibleStartColumn > toVisibleColumn) {
if (visibleStartColumn + leftoverStartColumn > toVisibleColumn) {
continue;
}
if (visibleEndColumn < fromVisibleColumn) {
if (visibleEndColumn + leftoverEndColumn < fromVisibleColumn) {
continue;
}
}

if (isRTL) {
if (visibleEndColumn > fromVisibleColumn) {
if (visibleEndColumn + leftoverEndColumn > fromVisibleColumn) {
continue;
}
if (visibleStartColumn < toVisibleColumn) {
if (visibleStartColumn + leftoverStartColumn < toVisibleColumn) {
continue;
}
}

result.push(new SingleCursorState(
new Range(lineNumber, startColumn, lineNumber, startColumn), SelectionStartKind.Simple, 0,
new Position(lineNumber, endColumn), 0
new Range(lineNumber, startColumn, lineNumber, startColumn), SelectionStartKind.Simple, leftoverStartColumn,
new Position(lineNumber, endColumn), leftoverEndColumn
));
}

Expand All @@ -58,10 +57,14 @@ export class ColumnSelection {
for (let i = 0; i < lineCount; i++) {
const lineNumber = fromLineNumber + (reversed ? -i : i);
const maxColumn = model.getLineMaxColumn(lineNumber);
const maxVisibleColumn = config.visibleColumnFromColumn(model, new Position(lineNumber, maxColumn));

const leftoverStartColumn = Math.max(0, fromVisibleColumn - maxVisibleColumn);
const leftoverEndColumn = Math.max(0, toVisibleColumn - maxVisibleColumn);

result.push(new SingleCursorState(
new Range(lineNumber, maxColumn, lineNumber, maxColumn), SelectionStartKind.Simple, 0,
new Position(lineNumber, maxColumn), 0
new Range(lineNumber, maxColumn, lineNumber, maxColumn), SelectionStartKind.Simple, leftoverStartColumn,
new Position(lineNumber, maxColumn), leftoverEndColumn
));
}
}
Expand Down
Loading