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

Adding overtype mode #233188

Merged
merged 45 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
69e6bff
adding code in order to enable overtype
aiday-mar Nov 6, 2024
5b951f7
adding a setting to determine the cursor style in overtype mode
aiday-mar Nov 6, 2024
b17acbe
adding action to toggle between overtype and insert mode
aiday-mar Nov 6, 2024
1fd450b
adding a contribution which adds a statu bar icon
aiday-mar Nov 6, 2024
c3b3496
adding same as tab focus mode
aiday-mar Nov 11, 2024
f15cac5
polishing
aiday-mar Nov 11, 2024
e8fd730
polish
aiday-mar Nov 12, 2024
0d07ee0
updating by clicking on the status bar
aiday-mar Nov 12, 2024
01de87e
polish
aiday-mar Nov 12, 2024
5048d07
changing the name of the status bar
aiday-mar Nov 12, 2024
4a2b843
add some keybindings and make status bar less prominent
aiday-mar Nov 12, 2024
779035d
polishing the replacement of text
aiday-mar Nov 12, 2024
900af97
adding overtype on paste
aiday-mar Nov 12, 2024
0949847
adding pasting support
aiday-mar Nov 12, 2024
0968f6d
using paste on distributed cursors
aiday-mar Nov 12, 2024
fb0434c
fix ReplaceOvertypeCommandWithOffsetCursorState
aiday-mar Nov 12, 2024
5706284
add code
aiday-mar Nov 13, 2024
44c77db
adding support for composition
aiday-mar Nov 13, 2024
5f03a29
undoing edit context changes
aiday-mar Nov 13, 2024
669ecc1
changing the status bar entry so it disappears when moving to insert …
aiday-mar Nov 13, 2024
d848314
rerender the cursors
aiday-mar Nov 13, 2024
164c67d
allowing composition over several lines
aiday-mar Nov 13, 2024
6a6e9ae
removing log
aiday-mar Nov 13, 2024
24f1dab
polishing the code
aiday-mar Nov 13, 2024
6a5daee
adding tests for overtype mode
aiday-mar Nov 13, 2024
d78453c
reset back the input mode to insert from ovetype on suite teardown
aiday-mar Nov 14, 2024
539e18b
moving inputMode class to the editor folder from the base folder
aiday-mar Nov 19, 2024
47e2c50
remove input mode default value
aiday-mar Nov 19, 2024
09e30fb
removing undefined check on input mode
aiday-mar Nov 19, 2024
473deea
changing the keybindings for `alt+cmd+o`
aiday-mar Nov 19, 2024
619926f
removing the composition payload
aiday-mar Nov 20, 2024
e0276c4
surfacing input mode through cursor configuration
aiday-mar Nov 20, 2024
79744cf
removing disposableness on viewcursor
aiday-mar Nov 20, 2024
7b6a59f
adding effective cursor type
aiday-mar Nov 20, 2024
a539207
removing the usage of the getOffsetAt
aiday-mar Nov 20, 2024
6f706c0
polishing
aiday-mar Nov 21, 2024
2ecbd60
polishing some more
aiday-mar Nov 21, 2024
74aaf07
polishing the code
aiday-mar Nov 21, 2024
83226c4
polishing
aiday-mar Nov 21, 2024
368ae9c
polishing
aiday-mar Nov 21, 2024
acbabe3
Merge branch 'main' into nuclear-swallow
alexdima Nov 26, 2024
3cf0822
removing reading of config service
aiday-mar Nov 27, 2024
43a4c8e
changing the keybiding so it is only on max
aiday-mar Nov 27, 2024
24f6b15
removing reading of config service
aiday-mar Nov 27, 2024
70cc13f
polishing
aiday-mar Nov 27, 2024
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
27 changes: 27 additions & 0 deletions src/vs/base/common/inputMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Emitter, Event } from './event.js';

class InputModeImpl {

private _inputMode: 'overtype' | 'insert' = 'insert';
private readonly _onDidChangeInputMode = new Emitter<'overtype' | 'insert'>();
public readonly onDidChangeInputMode: Event<'overtype' | 'insert'> = this._onDidChangeInputMode.event;

public getInputMode(): 'overtype' | 'insert' {
return this._inputMode;
}

public setInputMode(inputMode: 'overtype' | 'insert'): void {
this._inputMode = inputMode;
this._onDidChangeInputMode.fire(this._inputMode);
}
}

/**
* Controls the type mode, whether insert or overtype
*/
export const InputMode = new InputModeImpl();
aiday-mar marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 10 additions & 2 deletions src/vs/editor/browser/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { OverviewRuler } from './viewParts/overviewRuler/overviewRuler.js';
import { Rulers } from './viewParts/rulers/rulers.js';
import { ScrollDecorationViewPart } from './viewParts/scrollDecoration/scrollDecoration.js';
import { SelectionsOverlay } from './viewParts/selections/selections.js';
import { ViewCursors } from './viewParts/viewCursors/viewCursors.js';
import { IViewCursorsHelper, ViewCursors } from './viewParts/viewCursors/viewCursors.js';
import { ViewZones } from './viewParts/viewZones/viewZones.js';
import { WhitespaceOverlay } from './viewParts/whitespace/whitespace.js';
import { IEditorConfiguration } from '../common/config/editorConfiguration.js';
Expand Down Expand Up @@ -209,7 +209,7 @@ export class View extends ViewEventHandler {
this._contentWidgets = new ViewContentWidgets(this._context, this.domNode);
this._viewParts.push(this._contentWidgets);

this._viewCursors = new ViewCursors(this._context);
this._viewCursors = new ViewCursors(this._context, this._createViewCursorsHelper());
this._viewParts.push(this._viewCursors);

// Overlay widgets
Expand Down Expand Up @@ -369,6 +369,14 @@ export class View extends ViewEventHandler {
};
}

private _createViewCursorsHelper(): IViewCursorsHelper {
return {
renderNow: (): void => {
this.render(true, false);
}
};
}

private _createTextAreaHandlerHelper(): IVisibleRangeProvider {
return {
visibleRangeForPosition: (position: Position) => {
Expand Down
27 changes: 22 additions & 5 deletions src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import * as dom from '../../../../base/browser/dom.js';
import { FastDomNode, createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import * as strings from '../../../../base/common/strings.js';
import { applyFontInfo } from '../../config/domFontInfo.js';
import { TextEditorCursorStyle, EditorOption } from '../../../common/config/editorOptions.js';
import { TextEditorCursorStyle, EditorOption, IComputedEditorOptions } from '../../../common/config/editorOptions.js';
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 * as viewEvents from '../../../common/viewEvents.js';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../../base/browser/ui/mouseCursor/mouseCursor.js';
import { InputMode } from '../../../../base/common/inputMode.js';
import { Disposable } from '../../../../base/common/lifecycle.js';

export interface IViewCursorRenderData {
domNode: HTMLElement;
Expand Down Expand Up @@ -41,11 +43,11 @@ export enum CursorPlurality {
MultiSecondary,
}

export class ViewCursor {
export class ViewCursor extends Disposable {
private readonly _context: ViewContext;
private readonly _domNode: FastDomNode<HTMLElement>;

private _cursorStyle: TextEditorCursorStyle;
private _cursorStyle!: TextEditorCursorStyle;
private _lineCursorWidth: number;
private _lineHeight: number;
private _typicalHalfwidthCharacterWidth: number;
Expand All @@ -59,11 +61,11 @@ export class ViewCursor {
private _renderData: ViewCursorRenderData | null;

constructor(context: ViewContext, plurality: CursorPlurality) {
super();
this._context = context;
const options = this._context.configuration.options;
const fontInfo = options.get(EditorOption.fontInfo);

this._cursorStyle = options.get(EditorOption.cursorStyle);
this._lineHeight = options.get(EditorOption.lineHeight);
this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this._lineCursorWidth = Math.min(options.get(EditorOption.cursorWidth), this._typicalHalfwidthCharacterWidth);
Expand All @@ -85,6 +87,11 @@ export class ViewCursor {

this._lastRenderedContent = '';
this._renderData = null;

this._updateCursorStyle();
this._register(InputMode.onDidChangeInputMode(() => {
this._updateCursorStyle();
}));
aiday-mar marked this conversation as resolved.
Show resolved Hide resolved
}

public getDomNode(): FastDomNode<HTMLElement> {
Expand Down Expand Up @@ -130,7 +137,7 @@ export class ViewCursor {
const options = this._context.configuration.options;
const fontInfo = options.get(EditorOption.fontInfo);

this._cursorStyle = options.get(EditorOption.cursorStyle);
this._updateCursorStyle();
this._lineHeight = options.get(EditorOption.lineHeight);
this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
this._lineCursorWidth = Math.min(options.get(EditorOption.cursorWidth), this._typicalHalfwidthCharacterWidth);
Expand Down Expand Up @@ -234,6 +241,10 @@ export class ViewCursor {
return new ViewCursorRenderData(top, range.left, 0, width, height, textContent, textContentClassName);
}

private _updateCursorStyle(): void {
this._cursorStyle = getCursorStyle(this._context.configuration.options);
}

private _getTokenClassName(position: Position): string {
const lineData = this._context.viewModel.getViewLineData(position.lineNumber);
const tokenIndex = lineData.tokens.findTokenIndexAtOffset(position.column - 1);
Expand Down Expand Up @@ -274,3 +285,9 @@ export class ViewCursor {
};
}
}

export function getCursorStyle(options: IComputedEditorOptions) {
return InputMode.getInputMode() === 'overtype' ?
options.get(EditorOption.overtypeCursorStyle) :
options.get(EditorOption.cursorStyle);
}
32 changes: 25 additions & 7 deletions src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import './viewCursors.css';
import { FastDomNode, createFastDomNode } from '../../../../base/browser/fastDomNode.js';
import { IntervalTimer, TimeoutTimer } from '../../../../base/common/async.js';
import { ViewPart } from '../../view/viewPart.js';
import { IViewCursorRenderData, ViewCursor, CursorPlurality } from './viewCursor.js';
import { IViewCursorRenderData, ViewCursor, CursorPlurality, getCursorStyle } from './viewCursor.js';
import { TextEditorCursorBlinkingStyle, TextEditorCursorStyle, EditorOption } from '../../../common/config/editorOptions.js';
import { Position } from '../../../common/core/position.js';
import {
Expand All @@ -22,6 +22,11 @@ import { registerThemingParticipant } from '../../../../platform/theme/common/th
import { isHighContrast } from '../../../../platform/theme/common/theme.js';
import { CursorChangeReason } from '../../../common/cursorEvents.js';
import { WindowIntervalTimer, getWindow } from '../../../../base/browser/dom.js';
import { InputMode } from '../../../../base/common/inputMode.js';

export interface IViewCursorsHelper {
renderNow(): void;
}

/**
* View cursors is a view part responsible for rendering the primary cursor and
Expand All @@ -33,7 +38,7 @@ export class ViewCursors extends ViewPart {

private _readOnly: boolean;
private _cursorBlinking: TextEditorCursorBlinkingStyle;
private _cursorStyle: TextEditorCursorStyle;
private _cursorStyle!: TextEditorCursorStyle;
private _cursorSmoothCaretAnimation: 'off' | 'explicit' | 'on';
private _experimentalEditContextEnabled: boolean;
private _selectionIsEmpty: boolean;
Expand All @@ -52,22 +57,23 @@ export class ViewCursors extends ViewPart {
private readonly _primaryCursor: ViewCursor;
private readonly _secondaryCursors: ViewCursor[];
private _renderData: IViewCursorRenderData[];
private _viewHelper: IViewCursorsHelper;

constructor(context: ViewContext) {
constructor(context: ViewContext, viewHelper: IViewCursorsHelper) {
super(context);

const options = this._context.configuration.options;
this._viewHelper = viewHelper;
this._readOnly = options.get(EditorOption.readOnly);
this._cursorBlinking = options.get(EditorOption.cursorBlinking);
this._cursorStyle = options.get(EditorOption.cursorStyle);
this._cursorSmoothCaretAnimation = options.get(EditorOption.cursorSmoothCaretAnimation);
this._experimentalEditContextEnabled = options.get(EditorOption.experimentalEditContextEnabled);
this._selectionIsEmpty = true;
this._isComposingInput = false;

this._isVisible = false;

this._primaryCursor = new ViewCursor(this._context, CursorPlurality.Single);
this._primaryCursor = this._register(new ViewCursor(this._context, CursorPlurality.Single));
this._secondaryCursors = [];
this._renderData = [];

Expand All @@ -85,12 +91,17 @@ export class ViewCursors extends ViewPart {

this._editorHasFocus = false;
this._updateBlinking();
this._updateCursorStyle();
this._register(InputMode.onDidChangeInputMode(() => {
this._updateCursorStyle();
}));
}

public override dispose(): void {
super.dispose();
this._startCursorBlinkAnimation.dispose();
this._cursorFlatBlinkInterval.dispose();
this._secondaryCursors.forEach((cursor) => cursor.dispose());
}

public getDomNode(): FastDomNode<HTMLElement> {
Expand All @@ -114,12 +125,12 @@ export class ViewCursors extends ViewPart {

this._readOnly = options.get(EditorOption.readOnly);
this._cursorBlinking = options.get(EditorOption.cursorBlinking);
this._cursorStyle = options.get(EditorOption.cursorStyle);
this._cursorSmoothCaretAnimation = options.get(EditorOption.cursorSmoothCaretAnimation);
this._experimentalEditContextEnabled = options.get(EditorOption.experimentalEditContextEnabled);

this._updateBlinking();
this._updateDomClassName();
this._updateCursorStyle();

this._primaryCursor.onConfigurationChanged(e);
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
Expand Down Expand Up @@ -149,7 +160,8 @@ export class ViewCursors extends ViewPart {
const removeCnt = this._secondaryCursors.length - secondaryPositions.length;
for (let i = 0; i < removeCnt; i++) {
this._domNode.removeChild(this._secondaryCursors[0].getDomNode());
this._secondaryCursors.splice(0, 1);
const cursors = this._secondaryCursors.splice(0, 1);
cursors.forEach((cursor) => cursor.dispose());
}
}

Expand Down Expand Up @@ -339,6 +351,12 @@ export class ViewCursors extends ViewPart {
return result;
}

private _updateCursorStyle(): void {
this._cursorStyle = getCursorStyle(this._context.configuration.options);
this.forceShouldRender();
this._viewHelper.renderNow();
}

private _show(): void {
this._primaryCursor.show();
for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
Expand Down
97 changes: 96 additions & 1 deletion src/vs/editor/common/commands/replaceCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Position } from '../core/position.js';
import { Range } from '../core/range.js';
import { Selection, SelectionDirection } from '../core/selection.js';
import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from '../editorCommon.js';
import { ITextModel } from '../model.js';
import { EndOfLineSequence, ITextModel } from '../model.js';

export class ReplaceCommand implements ICommand {

Expand All @@ -31,6 +32,73 @@ export class ReplaceCommand implements ICommand {
}
}

export class ReplaceOvertypeCommand implements ICommand {

private readonly _range: Range;
private readonly _text: string;
public readonly insertsAutoWhitespace: boolean;

constructor(range: Range, text: string, insertsAutoWhitespace: boolean = false) {
this._range = range;
this._text = text;
this.insertsAutoWhitespace = insertsAutoWhitespace;
}

public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
const startPosition = this._range.getStartPosition();
const endPosition = this._range.getEndPosition();
const rangeEndOffset = model.getOffsetAt(endPosition);
const endOffset = rangeEndOffset + this._text.length + (this._range.isEmpty() ? 0 : - 1);
const endOfLine = model.getEndOfLineSequence() === EndOfLineSequence.CRLF ? '\r\n' : '\n';
const lastCharacterRange = Range.fromPositions(model.getPositionAt(endOffset - 1), model.getPositionAt(endOffset));
const lastCharacter = model.getValueInRange(lastCharacterRange);
const newEndOffset = lastCharacter === endOfLine ? endOffset - 1 : endOffset;
const replaceRange = Range.fromPositions(startPosition, model.getPositionAt(newEndOffset));
builder.addTrackedEditOperation(replaceRange, this._text);
}
aiday-mar marked this conversation as resolved.
Show resolved Hide resolved

public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
const inverseEditOperations = helper.getInverseEditOperations();
const srcRange = inverseEditOperations[0].range;
return Selection.fromPositions(srcRange.getEndPosition());
}
}

export class OvertypePasteCommand implements ICommand {

private readonly _range: Range;
private readonly _text: string;
public readonly insertsAutoWhitespace: boolean;

constructor(range: Range, text: string, insertsAutoWhitespace: boolean = false) {
this._range = range;
this._text = text;
this.insertsAutoWhitespace = insertsAutoWhitespace;
}

public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
const startPosition = this._range.getStartPosition();
const endPosition = this._range.getEndPosition();
const endLineNumber = endPosition.lineNumber;
const potentialEndOffset = model.getOffsetAt(endPosition) + this._text.length + (this._range.isEmpty() ? 0 : - 1);
const potentialEndPosition = model.getPositionAt(potentialEndOffset);
let newEndPosition: Position;
if (potentialEndPosition.lineNumber > endLineNumber) {
newEndPosition = new Position(endLineNumber, model.getLineMaxColumn(endLineNumber));
} else {
newEndPosition = potentialEndPosition;
}
const replaceRange = Range.fromPositions(startPosition, newEndPosition);
builder.addTrackedEditOperation(replaceRange, this._text);
}

public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
const inverseEditOperations = helper.getInverseEditOperations();
const srcRange = inverseEditOperations[0].range;
return Selection.fromPositions(srcRange.getEndPosition());
}
}

export class ReplaceCommandThatSelectsText implements ICommand {

private readonly _range: Range;
Expand Down Expand Up @@ -102,6 +170,33 @@ export class ReplaceCommandWithOffsetCursorState implements ICommand {
}
}

export class ReplaceOvertypeCommandInComposition implements ICommand {

private readonly _range: Range;

constructor(range: Range) {
this._range = range;
}

public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
const text = model.getValueInRange(this._range);
const startPosition = this._range.getStartPosition();
const endOffset = model.getOffsetAt(startPosition) + 2 * text.length;
let endPosition = model.getPositionAt(endOffset);
if (endPosition.lineNumber > this._range.endLineNumber) {
endPosition = new Position(this._range.endLineNumber, model.getLineMaxColumn(this._range.endLineNumber));
}
const replaceRange = Range.fromPositions(startPosition, endPosition);
builder.addTrackedEditOperation(replaceRange, text);
}

public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
const inverseEditOperations = helper.getInverseEditOperations();
const srcRange = inverseEditOperations[0].range;
return Selection.fromPositions(srcRange.getEndPosition());
}
}

export class ReplaceCommandThatPreservesSelection implements ICommand {

private readonly _range: Range;
Expand Down
Loading
Loading