diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index d65d85cfe25f6..701c940c7367c 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -22,6 +22,7 @@ import { AccessibilitySupport, IAccessibilityService } from '../../../platform/a import { getWindow, getWindowById } from '../../../base/browser/dom.js'; import { PixelRatio } from '../../../base/browser/pixelRatio.js'; import { MenuId } from '../../../platform/actions/common/actions.js'; +import { InputMode } from '../../common/inputMode.js'; export interface IEditorConstructionOptions extends IEditorOptions { /** @@ -95,6 +96,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat this._register(FontMeasurements.onDidChange(() => this._recomputeOptions())); this._register(PixelRatio.getInstance(getWindow(container)).onDidChange(() => this._recomputeOptions())); this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions())); + this._register(InputMode.onDidChangeInputMode(() => this._recomputeOptions())); } private _recomputeOptions(): void { @@ -126,6 +128,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat emptySelectionClipboard: partialEnv.emptySelectionClipboard, pixelRatio: partialEnv.pixelRatio, tabFocusMode: TabFocus.getTabFocusMode(), + inputMode: InputMode.getInputMode(), accessibilitySupport: partialEnv.accessibilitySupport, glyphMarginDecorationLaneCount: this._glyphMarginDecorationLaneCount }; diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index b69180772cc8c..b4380373c526a 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -63,7 +63,7 @@ export class ViewCursor { const options = this._context.configuration.options; const fontInfo = options.get(EditorOption.fontInfo); - this._cursorStyle = options.get(EditorOption.cursorStyle); + this._cursorStyle = options.get(EditorOption.effectiveCursorStyle); this._lineHeight = options.get(EditorOption.lineHeight); this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this._lineCursorWidth = Math.min(options.get(EditorOption.cursorWidth), this._typicalHalfwidthCharacterWidth); @@ -130,7 +130,7 @@ export class ViewCursor { const options = this._context.configuration.options; const fontInfo = options.get(EditorOption.fontInfo); - this._cursorStyle = options.get(EditorOption.cursorStyle); + this._cursorStyle = options.get(EditorOption.effectiveCursorStyle); this._lineHeight = options.get(EditorOption.lineHeight); this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this._lineCursorWidth = Math.min(options.get(EditorOption.cursorWidth), this._typicalHalfwidthCharacterWidth); diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index fc8695d3e1f01..823a8e5c0a559 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -59,7 +59,7 @@ export class ViewCursors extends ViewPart { const options = this._context.configuration.options; this._readOnly = options.get(EditorOption.readOnly); this._cursorBlinking = options.get(EditorOption.cursorBlinking); - this._cursorStyle = options.get(EditorOption.cursorStyle); + this._cursorStyle = options.get(EditorOption.effectiveCursorStyle); this._cursorSmoothCaretAnimation = options.get(EditorOption.cursorSmoothCaretAnimation); this._experimentalEditContextEnabled = options.get(EditorOption.experimentalEditContextEnabled); this._selectionIsEmpty = true; @@ -114,7 +114,7 @@ export class ViewCursors extends ViewPart { this._readOnly = options.get(EditorOption.readOnly); this._cursorBlinking = options.get(EditorOption.cursorBlinking); - this._cursorStyle = options.get(EditorOption.cursorStyle); + this._cursorStyle = options.get(EditorOption.effectiveCursorStyle); this._cursorSmoothCaretAnimation = options.get(EditorOption.cursorSmoothCaretAnimation); this._experimentalEditContextEnabled = options.get(EditorOption.experimentalEditContextEnabled); diff --git a/src/vs/editor/common/commands/replaceCommand.ts b/src/vs/editor/common/commands/replaceCommand.ts index f4beabe78a68b..779dfd9a7b928 100644 --- a/src/vs/editor/common/commands/replaceCommand.ts +++ b/src/vs/editor/common/commands/replaceCommand.ts @@ -3,6 +3,7 @@ * 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'; @@ -31,6 +32,38 @@ 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 intialStartPosition = this._range.getStartPosition(); + const initialEndPosition = this._range.getEndPosition(); + const initialEndLineNumber = initialEndPosition.lineNumber; + const offsetDelta = this._text.length + (this._range.isEmpty() ? 0 : -1); + let endPosition = addPositiveOffsetToModelPosition(model, initialEndPosition, offsetDelta); + if (endPosition.lineNumber > initialEndLineNumber) { + endPosition = new Position(initialEndLineNumber, model.getLineMaxColumn(initialEndLineNumber)); + } + const replaceRange = Range.fromPositions(intialStartPosition, endPosition); + 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; @@ -102,6 +135,33 @@ export class ReplaceCommandWithOffsetCursorState implements ICommand { } } +export class ReplaceOvertypeCommandOnCompositionEnd 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 initialEndPosition = this._range.getEndPosition(); + const initialEndLineNumber = initialEndPosition.lineNumber; + let endPosition = addPositiveOffsetToModelPosition(model, initialEndPosition, text.length); + if (endPosition.lineNumber > initialEndLineNumber) { + endPosition = new Position(initialEndLineNumber, model.getLineMaxColumn(initialEndLineNumber)); + } + const replaceRange = Range.fromPositions(initialEndPosition, endPosition); + builder.addTrackedEditOperation(replaceRange, ''); + } + + 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; @@ -127,3 +187,29 @@ export class ReplaceCommandThatPreservesSelection implements ICommand { return helper.getTrackedSelection(this._selectionId!); } } + +function addPositiveOffsetToModelPosition(model: ITextModel, position: Position, offset: number): Position { + if (offset < 0) { + throw new Error('Unexpected negative delta'); + } + const lineCount = model.getLineCount(); + let endPosition = new Position(lineCount, model.getLineMaxColumn(lineCount)); + for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { + if (lineNumber === position.lineNumber) { + const futureOffset = offset - model.getLineMaxColumn(position.lineNumber) + position.column; + if (futureOffset <= 0) { + endPosition = new Position(position.lineNumber, position.column + offset); + break; + } + offset = futureOffset; + } else { + const futureOffset = offset - model.getLineMaxColumn(lineNumber); + if (futureOffset <= 0) { + endPosition = new Position(lineNumber, offset); + break; + } + offset = futureOffset; + } + } + return endPosition; +} diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 17e0805cbdb5f..ae45246578bb2 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -239,10 +239,19 @@ export interface IEditorOptions { */ cursorSmoothCaretAnimation?: 'off' | 'explicit' | 'on'; /** - * Control the cursor style, either 'block' or 'line'. + * Control the cursor style in insert mode. * Defaults to 'line'. */ cursorStyle?: 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin'; + /** + * Control the cursor style in overtype mode. + * Defaults to 'block'. + */ + overtypeCursorStyle?: 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin'; + /** + * Controls whether paste in overtype mode should overwrite or insert. + */ + overtypeOnPaste?: boolean; /** * Control the width of the cursor when cursorStyle is set to 'line' */ @@ -974,6 +983,7 @@ export interface IEnvironmentalOptions { readonly emptySelectionClipboard: boolean; readonly pixelRatio: number; readonly tabFocusMode: boolean; + readonly inputMode: 'insert' | 'overtype'; readonly accessibilitySupport: AccessibilitySupport; readonly glyphMarginDecorationLaneCount: number; } @@ -1889,6 +1899,23 @@ class EditorFontInfo extends ComputedEditorOption { + + constructor() { + super(EditorOption.effectiveCursorStyle); + } + + public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: TextEditorCursorStyle): TextEditorCursorStyle { + return env.inputMode === 'overtype' ? + options.get(EditorOption.overtypeCursorStyle) : + options.get(EditorOption.cursorStyle); + } +} + +//#endregion + //#region fontSize class EditorFontSize extends SimpleEditorOption { @@ -5424,6 +5451,8 @@ export const enum EditorOption { multiCursorLimit, occurrencesHighlight, occurrencesHighlightDelay, + overtypeCursorStyle, + overtypeOnPaste, overviewRulerBorder, overviewRulerLanes, padding, @@ -5486,6 +5515,7 @@ export const enum EditorOption { showDeprecated, inlayHints, // Leave these at the end (because they have dependencies!) + effectiveCursorStyle, editorClassName, pixelRatio, tabFocusMode, @@ -5710,7 +5740,14 @@ export const EditorOptions = { TextEditorCursorStyle.Line, 'line', ['line', 'block', 'underline', 'line-thin', 'block-outline', 'underline-thin'], cursorStyleFromString, - { description: nls.localize('cursorStyle', "Controls the cursor style.") } + { description: nls.localize('cursorStyle', "Controls the cursor style in insert input mode.") } + )), + overtypeCursorStyle: register(new EditorEnumOption( + EditorOption.overtypeCursorStyle, 'overtypeCursorStyle', + TextEditorCursorStyle.Block, 'block', + ['line', 'block', 'underline', 'line-thin', 'block-outline', 'underline-thin'], + cursorStyleFromString, + { description: nls.localize('overtypeCursorStyle', "Controls the cursor style in overtype input mode.") } )), cursorSurroundingLines: register(new EditorIntOption( EditorOption.cursorSurroundingLines, 'cursorSurroundingLines', @@ -5967,6 +6004,10 @@ export const EditorOptions = { tags: ['preview'] } )), + overtypeOnPaste: register(new EditorBooleanOption( + EditorOption.overtypeOnPaste, 'overtypeOnPaste', true, + { description: nls.localize('overtypeOnPaste', "Controls whether pasting should overtype.") } + )), overviewRulerBorder: register(new EditorBooleanOption( EditorOption.overviewRulerBorder, 'overviewRulerBorder', true, { description: nls.localize('overviewRulerBorder', "Controls whether a border should be drawn around the overview ruler.") } @@ -6291,6 +6332,7 @@ export const EditorOptions = { )), // Leave these at the end (because they have dependencies!) + effectiveCursorStyle: register(new EffectiveCursorStyle()), editorClassName: register(new EditorClassName()), defaultColorDecorators: register(new EditorBooleanOption( EditorOption.defaultColorDecorators, 'defaultColorDecorators', true, diff --git a/src/vs/editor/common/cursor/cursor.ts b/src/vs/editor/common/cursor/cursor.ts index d68f60c56ed18..a276dfb354806 100644 --- a/src/vs/editor/common/cursor/cursor.ts +++ b/src/vs/editor/common/cursor/cursor.ts @@ -1020,8 +1020,9 @@ export class CommandExecutor { class CompositionLineState { constructor( public readonly text: string, - public readonly startSelection: number, - public readonly endSelection: number + public readonly lineNumber: number, + public readonly startSelectionOffset: number, + public readonly endSelectionOffset: number ) { } } @@ -1035,8 +1036,10 @@ class CompositionState { if (selection.startLineNumber !== selection.endLineNumber) { return null; } + const lineNumber = selection.startLineNumber; result.push(new CompositionLineState( - textModel.getLineContent(selection.startLineNumber), + textModel.getLineContent(lineNumber), + lineNumber, selection.startColumn - 1, selection.endColumn - 1 )); @@ -1072,24 +1075,28 @@ class CompositionState { private static _deduceOutcome(original: CompositionLineState, current: CompositionLineState): CompositionOutcome { const commonPrefix = Math.min( - original.startSelection, - current.startSelection, + original.startSelectionOffset, + current.startSelectionOffset, strings.commonPrefixLength(original.text, current.text) ); const commonSuffix = Math.min( - original.text.length - original.endSelection, - current.text.length - current.endSelection, + original.text.length - original.endSelectionOffset, + current.text.length - current.endSelectionOffset, strings.commonSuffixLength(original.text, current.text) ); const deletedText = original.text.substring(commonPrefix, original.text.length - commonSuffix); - const insertedText = current.text.substring(commonPrefix, current.text.length - commonSuffix); + const insertedTextStartOffset = commonPrefix; + const insertedTextEndOffset = current.text.length - commonSuffix; + const insertedText = current.text.substring(insertedTextStartOffset, insertedTextEndOffset); + const insertedTextRange = new Range(current.lineNumber, insertedTextStartOffset + 1, current.lineNumber, insertedTextEndOffset + 1); return new CompositionOutcome( deletedText, - original.startSelection - commonPrefix, - original.endSelection - commonPrefix, + original.startSelectionOffset - commonPrefix, + original.endSelectionOffset - commonPrefix, insertedText, - current.startSelection - commonPrefix, - current.endSelection - commonPrefix + current.startSelectionOffset - commonPrefix, + current.endSelectionOffset - commonPrefix, + insertedTextRange ); } } diff --git a/src/vs/editor/common/cursor/cursorTypeEditOperations.ts b/src/vs/editor/common/cursor/cursorTypeEditOperations.ts index 7b30480af2b55..e250bb744314f 100644 --- a/src/vs/editor/common/cursor/cursorTypeEditOperations.ts +++ b/src/vs/editor/common/cursor/cursorTypeEditOperations.ts @@ -6,7 +6,7 @@ import { CharCode } from '../../../base/common/charCode.js'; import { onUnexpectedError } from '../../../base/common/errors.js'; import * as strings from '../../../base/common/strings.js'; -import { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition, ReplaceCommandThatPreservesSelection } from '../commands/replaceCommand.js'; +import { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition, ReplaceCommandThatPreservesSelection, ReplaceOvertypeCommand, ReplaceOvertypeCommandOnCompositionEnd } from '../commands/replaceCommand.js'; import { ShiftCommand } from '../commands/shiftCommand.js'; import { SurroundSelectionCommand } from '../commands/surroundSelectionCommand.js'; import { CursorConfiguration, EditOperationResult, EditOperationType, ICursorSimpleModel, isQuote } from '../cursorCommon.js'; @@ -23,6 +23,7 @@ import { EditorAutoClosingStrategy, EditorAutoIndentStrategy } from '../config/e import { createScopedLineTokens } from '../languages/supports.js'; import { getIndentActionForType, getIndentForEnter, getInheritIndentForLine } from '../languages/autoIndent.js'; import { getEnterAction } from '../languages/enterAction.js'; +import { CompositionOutcome } from './cursorTypeOperations.js'; export class AutoIndentOperation { @@ -354,6 +355,21 @@ export class AutoClosingOpenCharTypeOperation { } } +export class CompositionEndOvertypeOperation { + + public static getEdits(config: CursorConfiguration, compositions: CompositionOutcome[]): EditOperationResult | null { + const isOvertypeMode = config.inputMode === 'overtype'; + if (!isOvertypeMode) { + return null; + } + const commands = compositions.map(composition => new ReplaceOvertypeCommandOnCompositionEnd(composition.insertedTextRange)); + return new EditOperationResult(EditOperationType.TypingOther, commands, { + shouldPushStackElementBefore: true, + shouldPushStackElementAfter: false + }); + } +} + export class SurroundSelectionOperation { public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, isDoingComposition: boolean): EditOperationResult | undefined { @@ -483,11 +499,12 @@ export class InterceptorElectricCharOperation { export class SimpleCharacterTypeOperation { - public static getEdits(prevEditOperationType: EditOperationType, selections: Selection[], ch: string): EditOperationResult { + public static getEdits(config: CursorConfiguration, prevEditOperationType: EditOperationType, selections: Selection[], ch: string, isDoingComposition: boolean): EditOperationResult { // A simple character type const commands: ICommand[] = []; for (let i = 0, len = selections.length; i < len; i++) { - commands[i] = new ReplaceCommand(selections[i], ch); + const ChosenReplaceCommand = config.inputMode === 'overtype' && !isDoingComposition ? ReplaceOvertypeCommand : ReplaceCommand; + commands[i] = new ChosenReplaceCommand(selections[i], ch); } const opType = getTypingOperation(ch, prevEditOperationType); @@ -677,7 +694,9 @@ export class PasteOperation { private static _distributedPaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string[]): EditOperationResult { const commands: ICommand[] = []; for (let i = 0, len = selections.length; i < len; i++) { - commands[i] = new ReplaceCommand(selections[i], text[i]); + const shouldOvertypeOnPaste = config.overtypeOnPaste && config.inputMode === 'overtype'; + const ChosenReplaceCommand = shouldOvertypeOnPaste ? ReplaceOvertypeCommand : ReplaceCommand; + commands[i] = new ChosenReplaceCommand(selections[i], text[i]); } return new EditOperationResult(EditOperationType.Other, commands, { shouldPushStackElementBefore: true, @@ -701,7 +720,9 @@ export class PasteOperation { const typeSelection = new Range(position.lineNumber, 1, position.lineNumber, 1); commands[i] = new ReplaceCommandThatPreservesSelection(typeSelection, text, selection, true); } else { - commands[i] = new ReplaceCommand(selection, text); + const shouldOvertypeOnPaste = config.overtypeOnPaste && config.inputMode === 'overtype'; + const ChosenReplaceCommand = shouldOvertypeOnPaste ? ReplaceOvertypeCommand : ReplaceCommand; + commands[i] = new ChosenReplaceCommand(selection, text); } } return new EditOperationResult(EditOperationType.Other, commands, { diff --git a/src/vs/editor/common/cursor/cursorTypeOperations.ts b/src/vs/editor/common/cursor/cursorTypeOperations.ts index c518f3f97c44a..b6d1a559cd087 100644 --- a/src/vs/editor/common/cursor/cursorTypeOperations.ts +++ b/src/vs/editor/common/cursor/cursorTypeOperations.ts @@ -11,7 +11,7 @@ import { Selection } from '../core/selection.js'; import { Position } from '../core/position.js'; import { ICommand } from '../editorCommon.js'; import { ITextModel } from '../model.js'; -import { AutoClosingOpenCharTypeOperation, AutoClosingOvertypeOperation, AutoClosingOvertypeWithInterceptorsOperation, AutoIndentOperation, CompositionOperation, EnterOperation, InterceptorElectricCharOperation, PasteOperation, shiftIndent, shouldSurroundChar, SimpleCharacterTypeOperation, SurroundSelectionOperation, TabOperation, TypeWithoutInterceptorsOperation, unshiftIndent } from './cursorTypeEditOperations.js'; +import { AutoClosingOpenCharTypeOperation, AutoClosingOvertypeOperation, AutoClosingOvertypeWithInterceptorsOperation, AutoIndentOperation, CompositionOperation, CompositionEndOvertypeOperation, EnterOperation, InterceptorElectricCharOperation, PasteOperation, shiftIndent, shouldSurroundChar, SimpleCharacterTypeOperation, SurroundSelectionOperation, TabOperation, TypeWithoutInterceptorsOperation, unshiftIndent } from './cursorTypeEditOperations.js'; export class TypeOperations { @@ -90,7 +90,7 @@ export class TypeOperations { if (!insertedText || insertedText.length !== 1) { // we're only interested in the case where a single character was inserted - return null; + return CompositionEndOvertypeOperation.getEdits(config, compositions); } const ch = insertedText; @@ -159,7 +159,7 @@ export class TypeOperations { return autoClosingOpenCharEdits; } - return null; + return CompositionEndOvertypeOperation.getEdits(config, compositions); } public static typeWithInterceptors(isDoingComposition: boolean, prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult { @@ -194,7 +194,7 @@ export class TypeOperations { return interceptorElectricCharOperation; } - return SimpleCharacterTypeOperation.getEdits(prevEditOperationType, selections, ch); + return SimpleCharacterTypeOperation.getEdits(config, prevEditOperationType, selections, ch, isDoingComposition); } public static typeWithoutInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], str: string): EditOperationResult { @@ -210,5 +210,6 @@ export class CompositionOutcome { public readonly insertedText: string, public readonly insertedSelectionStart: number, public readonly insertedSelectionEnd: number, + public readonly insertedTextRange: Range, ) { } } diff --git a/src/vs/editor/common/cursorCommon.ts b/src/vs/editor/common/cursorCommon.ts index 52f8673a32758..2e1c78484fcb3 100644 --- a/src/vs/editor/common/cursorCommon.ts +++ b/src/vs/editor/common/cursorCommon.ts @@ -17,6 +17,7 @@ import { createScopedLineTokens } from './languages/supports.js'; import { IElectricAction } from './languages/supports/electricCharacter.js'; import { CursorColumns } from './core/cursorColumns.js'; import { normalizeIndentation } from './core/indentation.js'; +import { InputMode } from './inputMode.js'; export interface IColumnSelectData { isReal: boolean; @@ -77,6 +78,7 @@ export class CursorConfiguration { public readonly blockCommentStartToken: string | null; public readonly shouldAutoCloseBefore: { quote: (ch: string) => boolean; bracket: (ch: string) => boolean; comment: (ch: string) => boolean }; public readonly wordSegmenterLocales: string[]; + public readonly overtypeOnPaste: boolean; private readonly _languageId: string; private _electricChars: { [key: string]: boolean } | null; @@ -99,6 +101,7 @@ export class CursorConfiguration { || e.hasChanged(EditorOption.fontInfo) || e.hasChanged(EditorOption.readOnly) || e.hasChanged(EditorOption.wordSegmenterLocales) + || e.hasChanged(EditorOption.overtypeOnPaste) ); } @@ -137,6 +140,7 @@ export class CursorConfiguration { this.autoSurround = options.get(EditorOption.autoSurround); this.autoIndent = options.get(EditorOption.autoIndent); this.wordSegmenterLocales = options.get(EditorOption.wordSegmenterLocales); + this.overtypeOnPaste = options.get(EditorOption.overtypeOnPaste); this.surroundingPairs = {}; this._electricChars = null; @@ -173,6 +177,10 @@ export class CursorConfiguration { return this._electricChars; } + public get inputMode(): 'insert' | 'overtype' { + return InputMode.getInputMode(); + } + /** * Should return opening bracket type to match indentation with */ diff --git a/src/vs/editor/common/inputMode.ts b/src/vs/editor/common/inputMode.ts new file mode 100644 index 0000000000000..304ac1f140e9b --- /dev/null +++ b/src/vs/editor/common/inputMode.ts @@ -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 '../../base/common/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(); diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 01607d8310bb2..db9a28f4fedd0 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -257,75 +257,78 @@ export enum EditorOption { multiCursorLimit = 81, occurrencesHighlight = 82, occurrencesHighlightDelay = 83, - overviewRulerBorder = 84, - overviewRulerLanes = 85, - padding = 86, - pasteAs = 87, - parameterHints = 88, - peekWidgetDefaultFocus = 89, - placeholder = 90, - definitionLinkOpensInPeek = 91, - quickSuggestions = 92, - quickSuggestionsDelay = 93, - readOnly = 94, - readOnlyMessage = 95, - renameOnType = 96, - renderControlCharacters = 97, - renderFinalNewline = 98, - renderLineHighlight = 99, - renderLineHighlightOnlyWhenFocus = 100, - renderValidationDecorations = 101, - renderWhitespace = 102, - revealHorizontalRightPadding = 103, - roundedSelection = 104, - rulers = 105, - scrollbar = 106, - scrollBeyondLastColumn = 107, - scrollBeyondLastLine = 108, - scrollPredominantAxis = 109, - selectionClipboard = 110, - selectionHighlight = 111, - selectOnLineNumbers = 112, - showFoldingControls = 113, - showUnused = 114, - snippetSuggestions = 115, - smartSelect = 116, - smoothScrolling = 117, - stickyScroll = 118, - stickyTabStops = 119, - stopRenderingLineAfter = 120, - suggest = 121, - suggestFontSize = 122, - suggestLineHeight = 123, - suggestOnTriggerCharacters = 124, - suggestSelection = 125, - tabCompletion = 126, - tabIndex = 127, - unicodeHighlighting = 128, - unusualLineTerminators = 129, - useShadowDOM = 130, - useTabStops = 131, - wordBreak = 132, - wordSegmenterLocales = 133, - wordSeparators = 134, - wordWrap = 135, - wordWrapBreakAfterCharacters = 136, - wordWrapBreakBeforeCharacters = 137, - wordWrapColumn = 138, - wordWrapOverride1 = 139, - wordWrapOverride2 = 140, - wrappingIndent = 141, - wrappingStrategy = 142, - showDeprecated = 143, - inlayHints = 144, - editorClassName = 145, - pixelRatio = 146, - tabFocusMode = 147, - layoutInfo = 148, - wrappingInfo = 149, - defaultColorDecorators = 150, - colorDecoratorsActivatedOn = 151, - inlineCompletionsAccessibilityVerbose = 152 + overtypeCursorStyle = 84, + overtypeOnPaste = 85, + overviewRulerBorder = 86, + overviewRulerLanes = 87, + padding = 88, + pasteAs = 89, + parameterHints = 90, + peekWidgetDefaultFocus = 91, + placeholder = 92, + definitionLinkOpensInPeek = 93, + quickSuggestions = 94, + quickSuggestionsDelay = 95, + readOnly = 96, + readOnlyMessage = 97, + renameOnType = 98, + renderControlCharacters = 99, + renderFinalNewline = 100, + renderLineHighlight = 101, + renderLineHighlightOnlyWhenFocus = 102, + renderValidationDecorations = 103, + renderWhitespace = 104, + revealHorizontalRightPadding = 105, + roundedSelection = 106, + rulers = 107, + scrollbar = 108, + scrollBeyondLastColumn = 109, + scrollBeyondLastLine = 110, + scrollPredominantAxis = 111, + selectionClipboard = 112, + selectionHighlight = 113, + selectOnLineNumbers = 114, + showFoldingControls = 115, + showUnused = 116, + snippetSuggestions = 117, + smartSelect = 118, + smoothScrolling = 119, + stickyScroll = 120, + stickyTabStops = 121, + stopRenderingLineAfter = 122, + suggest = 123, + suggestFontSize = 124, + suggestLineHeight = 125, + suggestOnTriggerCharacters = 126, + suggestSelection = 127, + tabCompletion = 128, + tabIndex = 129, + unicodeHighlighting = 130, + unusualLineTerminators = 131, + useShadowDOM = 132, + useTabStops = 133, + wordBreak = 134, + wordSegmenterLocales = 135, + wordSeparators = 136, + wordWrap = 137, + wordWrapBreakAfterCharacters = 138, + wordWrapBreakBeforeCharacters = 139, + wordWrapColumn = 140, + wordWrapOverride1 = 141, + wordWrapOverride2 = 142, + wrappingIndent = 143, + wrappingStrategy = 144, + showDeprecated = 145, + inlayHints = 146, + effectiveCursorStyle = 147, + editorClassName = 148, + pixelRatio = 149, + tabFocusMode = 150, + layoutInfo = 151, + wrappingInfo = 152, + defaultColorDecorators = 153, + colorDecoratorsActivatedOn = 154, + inlineCompletionsAccessibilityVerbose = 155 } /** diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 2eb1303c28cc5..fe7c5e59c46ec 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -28,6 +28,7 @@ import { OutgoingViewModelEventKind } from '../../../common/viewModelEventDispat import { ITestCodeEditor, TestCodeEditorInstantiationOptions, createCodeEditorServices, instantiateTestCodeEditor, withTestCodeEditor } from '../testCodeEditor.js'; import { IRelaxedTextModelCreationOptions, createTextModel, instantiateTextModel } from '../../common/testTextModel.js'; import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { InputMode } from '../../../common/inputMode.js'; // --------- utils @@ -6451,3 +6452,183 @@ suite('Undo stops', () => { model.dispose(); }); }); + +suite('Overtype Mode', () => { + + setup(() => { + InputMode.setInputMode('overtype'); + }); + + teardown(() => { + InputMode.setInputMode('insert'); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + test('simple type', () => { + const model = createTextModel( + [ + '123456789', + '123456789', + ].join('\n'), + undefined, + { + insertSpaces: false, + } + ); + + withTestCodeEditor(model, {}, (editor, viewModel) => { + viewModel.setSelections('test', [new Selection(1, 3, 1, 3)]); + viewModel.type('a', 'keyboard'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '12a456789', + '123456789', + ].join('\n'), 'assert1'); + + viewModel.setSelections('test', [new Selection(1, 9, 1, 9)]); + viewModel.type('bbb', 'keyboard'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '12a45678bbb', + '123456789', + ].join('\n'), 'assert2'); + }); + + model.dispose(); + }); + + test('multi-line selection type', () => { + const model = createTextModel( + [ + '123456789', + '123456789', + ].join('\n'), + undefined, + { + insertSpaces: false, + } + ); + + withTestCodeEditor(model, {}, (editor, viewModel) => { + viewModel.setSelections('test', [new Selection(1, 5, 2, 3)]); + viewModel.type('cc', 'keyboard'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '1234cc456789', + ].join('\n'), 'assert1'); + }); + + model.dispose(); + }); + + test('simple paste', () => { + const model = createTextModel( + [ + '123456789', + '123456789', + ].join('\n'), + undefined, + { + insertSpaces: false, + } + ); + + withTestCodeEditor(model, {}, (editor, viewModel) => { + viewModel.setSelections('test', [new Selection(1, 5, 1, 5)]); + viewModel.paste('cc', false); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '1234cc789', + '123456789', + ].join('\n'), 'assert1'); + + viewModel.setSelections('test', [new Selection(1, 5, 1, 5)]); + viewModel.paste('dddddddd', false); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '1234dddddddd', + '123456789', + ].join('\n'), 'assert2'); + }); + + model.dispose(); + }); + + test('multi-line selection paste', () => { + const model = createTextModel( + [ + '123456789', + '123456789', + ].join('\n'), + undefined, + { + insertSpaces: false, + } + ); + + withTestCodeEditor(model, {}, (editor, viewModel) => { + viewModel.setSelections('test', [new Selection(1, 5, 2, 3)]); + viewModel.paste('cc', false); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '1234cc456789', + ].join('\n'), 'assert1'); + }); + + model.dispose(); + }); + + test('paste multi-line text', () => { + const model = createTextModel( + [ + '123456789', + '123456789', + ].join('\n'), + undefined, + { + insertSpaces: false, + } + ); + + withTestCodeEditor(model, {}, (editor, viewModel) => { + viewModel.setSelections('test', [new Selection(1, 5, 1, 5)]); + viewModel.paste([ + 'aaaaaaa', + 'bbbbbbb' + ].join('\n'), false); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '1234aaaaaaa', + 'bbbbbbb', + '123456789', + ].join('\n'), 'assert1'); + }); + + model.dispose(); + }); + + test('composition type', () => { + const model = createTextModel( + [ + '123456789', + '123456789', + ].join('\n'), + undefined, + { + insertSpaces: false, + } + ); + + withTestCodeEditor(model, {}, (editor, viewModel) => { + viewModel.setSelections('test', [new Selection(1, 5, 1, 5)]); + viewModel.startComposition(); + viewModel.compositionType('セ', 0, 0, 0, 'keyboard'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '1234セ56789', + '123456789', + ].join('\n'), 'assert1'); + + viewModel.endComposition('keyboard'); + assert.strictEqual(model.getValue(EndOfLinePreference.LF), [ + '1234セ6789', + '123456789', + ].join('\n'), 'assert1'); + }); + + model.dispose(); + }); +}); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 11b8f7415dcc2..dd8d6289c3423 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3284,10 +3284,19 @@ declare namespace monaco.editor { */ cursorSmoothCaretAnimation?: 'off' | 'explicit' | 'on'; /** - * Control the cursor style, either 'block' or 'line'. + * Control the cursor style in insert mode. * Defaults to 'line'. */ cursorStyle?: 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin'; + /** + * Control the cursor style in overtype mode. + * Defaults to 'block'. + */ + overtypeCursorStyle?: 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin'; + /** + * Controls whether paste in overtype mode should overwrite or insert. + */ + overtypeOnPaste?: boolean; /** * Control the width of the cursor when cursorStyle is set to 'line' */ @@ -4963,75 +4972,78 @@ declare namespace monaco.editor { multiCursorLimit = 81, occurrencesHighlight = 82, occurrencesHighlightDelay = 83, - overviewRulerBorder = 84, - overviewRulerLanes = 85, - padding = 86, - pasteAs = 87, - parameterHints = 88, - peekWidgetDefaultFocus = 89, - placeholder = 90, - definitionLinkOpensInPeek = 91, - quickSuggestions = 92, - quickSuggestionsDelay = 93, - readOnly = 94, - readOnlyMessage = 95, - renameOnType = 96, - renderControlCharacters = 97, - renderFinalNewline = 98, - renderLineHighlight = 99, - renderLineHighlightOnlyWhenFocus = 100, - renderValidationDecorations = 101, - renderWhitespace = 102, - revealHorizontalRightPadding = 103, - roundedSelection = 104, - rulers = 105, - scrollbar = 106, - scrollBeyondLastColumn = 107, - scrollBeyondLastLine = 108, - scrollPredominantAxis = 109, - selectionClipboard = 110, - selectionHighlight = 111, - selectOnLineNumbers = 112, - showFoldingControls = 113, - showUnused = 114, - snippetSuggestions = 115, - smartSelect = 116, - smoothScrolling = 117, - stickyScroll = 118, - stickyTabStops = 119, - stopRenderingLineAfter = 120, - suggest = 121, - suggestFontSize = 122, - suggestLineHeight = 123, - suggestOnTriggerCharacters = 124, - suggestSelection = 125, - tabCompletion = 126, - tabIndex = 127, - unicodeHighlighting = 128, - unusualLineTerminators = 129, - useShadowDOM = 130, - useTabStops = 131, - wordBreak = 132, - wordSegmenterLocales = 133, - wordSeparators = 134, - wordWrap = 135, - wordWrapBreakAfterCharacters = 136, - wordWrapBreakBeforeCharacters = 137, - wordWrapColumn = 138, - wordWrapOverride1 = 139, - wordWrapOverride2 = 140, - wrappingIndent = 141, - wrappingStrategy = 142, - showDeprecated = 143, - inlayHints = 144, - editorClassName = 145, - pixelRatio = 146, - tabFocusMode = 147, - layoutInfo = 148, - wrappingInfo = 149, - defaultColorDecorators = 150, - colorDecoratorsActivatedOn = 151, - inlineCompletionsAccessibilityVerbose = 152 + overtypeCursorStyle = 84, + overtypeOnPaste = 85, + overviewRulerBorder = 86, + overviewRulerLanes = 87, + padding = 88, + pasteAs = 89, + parameterHints = 90, + peekWidgetDefaultFocus = 91, + placeholder = 92, + definitionLinkOpensInPeek = 93, + quickSuggestions = 94, + quickSuggestionsDelay = 95, + readOnly = 96, + readOnlyMessage = 97, + renameOnType = 98, + renderControlCharacters = 99, + renderFinalNewline = 100, + renderLineHighlight = 101, + renderLineHighlightOnlyWhenFocus = 102, + renderValidationDecorations = 103, + renderWhitespace = 104, + revealHorizontalRightPadding = 105, + roundedSelection = 106, + rulers = 107, + scrollbar = 108, + scrollBeyondLastColumn = 109, + scrollBeyondLastLine = 110, + scrollPredominantAxis = 111, + selectionClipboard = 112, + selectionHighlight = 113, + selectOnLineNumbers = 114, + showFoldingControls = 115, + showUnused = 116, + snippetSuggestions = 117, + smartSelect = 118, + smoothScrolling = 119, + stickyScroll = 120, + stickyTabStops = 121, + stopRenderingLineAfter = 122, + suggest = 123, + suggestFontSize = 124, + suggestLineHeight = 125, + suggestOnTriggerCharacters = 126, + suggestSelection = 127, + tabCompletion = 128, + tabIndex = 129, + unicodeHighlighting = 130, + unusualLineTerminators = 131, + useShadowDOM = 132, + useTabStops = 133, + wordBreak = 134, + wordSegmenterLocales = 135, + wordSeparators = 136, + wordWrap = 137, + wordWrapBreakAfterCharacters = 138, + wordWrapBreakBeforeCharacters = 139, + wordWrapColumn = 140, + wordWrapOverride1 = 141, + wordWrapOverride2 = 142, + wrappingIndent = 143, + wrappingStrategy = 144, + showDeprecated = 145, + inlayHints = 146, + effectiveCursorStyle = 147, + editorClassName = 148, + pixelRatio = 149, + tabFocusMode = 150, + layoutInfo = 151, + wrappingInfo = 152, + defaultColorDecorators = 153, + colorDecoratorsActivatedOn = 154, + inlineCompletionsAccessibilityVerbose = 155 } export const EditorOptions: { @@ -5066,6 +5078,7 @@ declare namespace monaco.editor { cursorBlinking: IEditorOption; cursorSmoothCaretAnimation: IEditorOption; cursorStyle: IEditorOption; + overtypeCursorStyle: IEditorOption; cursorSurroundingLines: IEditorOption; cursorSurroundingLinesStyle: IEditorOption; cursorWidth: IEditorOption; @@ -5121,6 +5134,7 @@ declare namespace monaco.editor { multiCursorLimit: IEditorOption; occurrencesHighlight: IEditorOption; occurrencesHighlightDelay: IEditorOption; + overtypeOnPaste: IEditorOption; overviewRulerBorder: IEditorOption; overviewRulerLanes: IEditorOption; padding: IEditorOption>>; @@ -5180,6 +5194,7 @@ declare namespace monaco.editor { wordWrapColumn: IEditorOption; wordWrapOverride1: IEditorOption; wordWrapOverride2: IEditorOption; + effectiveCursorStyle: IEditorOption; editorClassName: IEditorOption; defaultColorDecorators: IEditorOption; pixelRatio: IEditorOption; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 1c777faacaf8a..8c75858d98106 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -43,7 +43,8 @@ import { SplitEditorToFirstGroupAction, SplitEditorToLastGroupAction, SplitEditorToLeftGroupAction, SplitEditorToNextGroupAction, SplitEditorToPreviousGroupAction, SplitEditorToRightGroupAction, NavigateForwardInEditsAction, NavigateBackwardsInEditsAction, NavigateForwardInNavigationsAction, NavigateBackwardsInNavigationsAction, NavigatePreviousInNavigationsAction, NavigatePreviousInEditsAction, NavigateToLastNavigationLocationAction, MaximizeGroupHideSidebarAction, MoveEditorToNewWindowAction, CopyEditorToNewindowAction, RestoreEditorsToMainWindowAction, ToggleMaximizeEditorGroupAction, MinimizeOtherGroupsHideSidebarAction, CopyEditorGroupToNewWindowAction, - MoveEditorGroupToNewWindowAction, NewEmptyEditorWindowAction + MoveEditorGroupToNewWindowAction, NewEmptyEditorWindowAction, + ToggleOvertypeInsertMode } from './editorActions.js'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, @@ -171,6 +172,8 @@ quickAccessRegistry.registerQuickAccessProvider({ //#region Actions & Commands +registerAction2(ToggleOvertypeInsertMode); + registerAction2(ChangeLanguageAction); registerAction2(ChangeEOLAction); registerAction2(ChangeEncodingAction); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 2deaa49417b4c..be79b3f0af2f8 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -38,6 +38,7 @@ import { ICommandActionTitle } from '../../../../platform/action/common/action.j import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { resolveCommandsContext } from './editorCommandsContext.js'; import { IListService } from '../../../../platform/list/browser/listService.js'; +import { InputMode } from '../../../../editor/common/inputMode.js'; class ExecuteCommandAction extends Action2 { @@ -2696,3 +2697,32 @@ export class NewEmptyEditorWindowAction extends Action2 { auxiliaryEditorPart.activeGroup.focus(); } } + +export class ToggleOvertypeInsertMode extends Action2 { + + constructor() { + super({ + id: 'editor.action.toggleOvertypeInsertMode', + title: { + ...localize2('toggleOvertypeInsertMode', "Toggle Overtype/Insert Mode"), + mnemonicTitle: localize({ key: 'mitoggleOvertypeInsertMode', comment: ['&& denotes a mnemonic'] }, "&&Toggle Overtype/Insert Mode"), + }, + metadata: { + description: localize2('toggleOvertypeMode.description', "Toggle between overtype and insert mode"), + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.Insert, + mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.KeyO }, + }, + f1: true, + category: Categories.View + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const oldInputMode = InputMode.getInputMode(); + const newInputMode = oldInputMode === 'insert' ? 'overtype' : 'insert'; + InputMode.setInputMode(newInputMode); + } +} diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index acc013522c51f..a1b9bd5215c2c 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -56,6 +56,7 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { TabFocus } from '../../../../editor/browser/config/tabFocus.js'; import { IEditorGroupsService, IEditorPart } from '../../../services/editor/common/editorGroupsService.js'; +import { InputMode } from '../../../../editor/common/inputMode.js'; class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private primary: IEncodingSupport, private secondary: IEncodingSupport) { } @@ -149,6 +150,7 @@ class StateChange { encoding: boolean = false; EOL: boolean = false; tabFocusMode: boolean = false; + inputMode: boolean = false; columnSelectionMode: boolean = false; metadata: boolean = false; @@ -160,6 +162,7 @@ class StateChange { this.encoding = this.encoding || other.encoding; this.EOL = this.EOL || other.EOL; this.tabFocusMode = this.tabFocusMode || other.tabFocusMode; + this.inputMode = this.inputMode || other.inputMode; this.columnSelectionMode = this.columnSelectionMode || other.columnSelectionMode; this.metadata = this.metadata || other.metadata; } @@ -172,6 +175,7 @@ class StateChange { || this.encoding || this.EOL || this.tabFocusMode + || this.inputMode || this.columnSelectionMode || this.metadata; } @@ -186,6 +190,7 @@ type StateDelta = ( | { type: 'tabFocusMode'; tabFocusMode: boolean } | { type: 'columnSelectionMode'; columnSelectionMode: boolean } | { type: 'metadata'; metadata: string | undefined } + | { type: 'inputMode'; inputMode: 'overtype' | 'insert' } ); class State { @@ -208,6 +213,9 @@ class State { private _tabFocusMode: boolean | undefined; get tabFocusMode(): boolean | undefined { return this._tabFocusMode; } + private _inputMode: 'overtype' | 'insert' | undefined; + get inputMode(): 'overtype' | 'insert' | undefined { return this._inputMode; } + private _columnSelectionMode: boolean | undefined; get columnSelectionMode(): boolean | undefined { return this._columnSelectionMode; } @@ -260,6 +268,13 @@ class State { } break; + case 'inputMode': + if (this._inputMode !== update.inputMode) { + this._inputMode = update.inputMode; + change.inputMode = true; + } + break; + case 'columnSelectionMode': if (this._columnSelectionMode !== update.columnSelectionMode) { this._columnSelectionMode = update.columnSelectionMode; @@ -307,6 +322,18 @@ class TabFocusMode extends Disposable { } } +class StatusInputMode extends Disposable { + + private readonly _onDidChange = this._register(new Emitter<'overtype' | 'insert'>()); + public readonly onDidChange = this._onDidChange.event; + + constructor() { + super(); + InputMode.setInputMode('insert'); + this._register(InputMode.onDidChangeInputMode(inputMode => this._onDidChange.fire(inputMode))); + } +} + const nlsSingleSelectionRange = localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)"); const nlsSingleSelection = localize('singleSelection', "Ln {0}, Col {1}"); const nlsMultiSelectionRange = localize('multiSelectionRange', "{0} selections ({1} characters selected)"); @@ -317,6 +344,7 @@ const nlsEOLCRLF = localize('endOfLineCarriageReturnLineFeed', "CRLF"); class EditorStatus extends Disposable { private readonly tabFocusModeElement = this._register(new MutableDisposable()); + private readonly inputModeElement = this._register(new MutableDisposable()); private readonly columnSelectionModeElement = this._register(new MutableDisposable()); private readonly indentationElement = this._register(new MutableDisposable()); private readonly selectionElement = this._register(new MutableDisposable()); @@ -327,6 +355,7 @@ class EditorStatus extends Disposable { private readonly currentMarkerStatus = this._register(this.instantiationService.createInstance(ShowCurrentMarkerInStatusbarContribution)); private readonly tabFocusMode = this._register(this.instantiationService.createInstance(TabFocusMode)); + private readonly inputMode = this._register(this.instantiationService.createInstance(StatusInputMode)); private readonly state = new State(); private toRender: StateChange | undefined = undefined; @@ -361,6 +390,7 @@ class EditorStatus extends Disposable { this.onTabFocusModeChange(this.configurationService.getValue('editor.tabFocusMode')); } })); + this._register(Event.runAndSubscribe(this.inputMode.onDidChange, (inputMode) => this.onInputModeChange(inputMode ?? 'insert'))); } private registerCommands(): void { @@ -422,6 +452,25 @@ class EditorStatus extends Disposable { } } + private updateInputModeElement(inputMode: 'overtype' | 'insert' | undefined): void { + if (inputMode === 'overtype') { + if (!this.inputModeElement.value) { + const text = localize('inputModeOvertype', 'OVR'); + const name = localize('status.editor.enableInsertMode', "Enable Insert Mode"); + this.inputModeElement.value = this.statusbarService.addEntry({ + name, + text, + ariaLabel: text, + tooltip: name, + command: 'editor.action.toggleOvertypeInsertMode', + kind: 'prominent' + }, 'status.editor.inputMode', StatusbarAlignment.RIGHT, 100.6); + } + } else { + this.inputModeElement.clear(); + } + } + private updateColumnSelectionModeElement(visible: boolean): void { if (visible) { if (!this.columnSelectionModeElement.value) { @@ -586,6 +635,7 @@ class EditorStatus extends Disposable { private doRenderNow(): void { this.updateTabFocusModeElement(!!this.state.tabFocusMode); + this.updateInputModeElement(this.state.inputMode); this.updateColumnSelectionModeElement(!!this.state.columnSelectionMode); this.updateIndentationElement(this.state.indentation); this.updateSelectionElement(this.state.selectionStatus); @@ -872,6 +922,11 @@ class EditorStatus extends Disposable { this.updateState(info); } + private onInputModeChange(inputMode: 'insert' | 'overtype'): void { + const info: StateDelta = { type: 'inputMode', inputMode }; + this.updateState(info); + } + private isActiveEditor(control: IEditorPane): boolean { const activeEditorPane = this.editorService.activeEditorPane;