diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 07e9ad9b0dfb7..c837b9fd9915a 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1241,6 +1241,10 @@ export interface ApplyEditsOptions extends UndoStopOptions { setEndOfLine: EndOfLine | undefined; } +export interface SnippetEditOptions extends UndoStopOptions { + keepWhitespace?: boolean; +} + export interface ThemeColor { id: string; } @@ -1345,7 +1349,7 @@ export interface TextEditorsMain { $trySetSelections(id: string, selections: Selection[]): Promise; $tryApplyEdits(id: string, modelVersionId: number, edits: SingleEditOperation[], opts: ApplyEditsOptions): Promise; $tryApplyWorkspaceEdit(workspaceEditDto: WorkspaceEditDto, metadata?: WorkspaceEditMetadataDto): Promise; - $tryInsertSnippet(id: string, template: string, selections: Range[], opts: UndoStopOptions): Promise; + $tryInsertSnippet(id: string, template: string, selections: Range[], opts: SnippetEditOptions): Promise; $save(uri: UriComponents): PromiseLike; $saveAs(uri: UriComponents): PromiseLike; $saveAll(includeUntitled?: boolean): Promise; @@ -1545,7 +1549,7 @@ export interface WorkspaceFileEditDto { export interface WorkspaceTextEditDto { resource: UriComponents; modelVersionId?: number; - textEdit: TextEdit & { insertAsSnippet?: boolean }; + textEdit: TextEdit & { insertAsSnippet?: boolean, keepWhitespace?: boolean }; metadata?: WorkspaceEditEntryMetadataDto; } export namespace WorkspaceTextEditDto { diff --git a/packages/plugin-ext/src/main/browser/text-editor-main.ts b/packages/plugin-ext/src/main/browser/text-editor-main.ts index 0ce4f478ed8a7..67ac7ccaa3907 100644 --- a/packages/plugin-ext/src/main/browser/text-editor-main.ts +++ b/packages/plugin-ext/src/main/browser/text-editor-main.ts @@ -26,7 +26,7 @@ import { TextEditorRevealType, SingleEditOperation, ApplyEditsOptions, - UndoStopOptions, + SnippetEditOptions, DecorationOptions } from '../../common/plugin-api-rpc'; import { Range } from '../../common/plugin-api-rpc-model'; @@ -281,7 +281,7 @@ export class TextEditorMain implements Disposable { return true; } - insertSnippet(template: string, ranges: Range[], opts: UndoStopOptions): boolean { + insertSnippet(template: string, ranges: Range[], opts: SnippetEditOptions): boolean { const snippetController: SnippetController2 | null | undefined = this.editor?.getControl().getContribution('snippetController2'); if (!snippetController || !this.editor) { return false; } @@ -290,7 +290,13 @@ export class TextEditorMain implements Disposable { this.editor.getControl().setSelections(selections); this.editor.focus(); - snippetController.insert(template, 0, 0, opts.undoStopBefore, opts.undoStopAfter); + snippetController.insert(template, { + undoStopBefore: opts.undoStopBefore, + undoStopAfter: opts.undoStopAfter, + adjustWhitespace: !opts.keepWhitespace, + overwriteBefore: 0, + overwriteAfter: 0 + }); return true; } @@ -324,11 +330,17 @@ export class TextEditorMain implements Disposable { } } +interface SnippetInsertOptions { + overwriteBefore: number, + overwriteAfter: number, + undoStopBefore: boolean, + undoStopAfter: boolean, + adjustWhitespace: boolean +} + // TODO move to monaco typings! interface SnippetController2 extends monaco.editor.IEditorContribution { - insert(template: string, - overwriteBefore: number, overwriteAfter: number, - undoStopBefore: boolean, undoStopAfter: boolean): void; + insert(template: string, options?: Partial): void; finish(): void; cancel(): void; dispose(): void; diff --git a/packages/plugin-ext/src/main/browser/text-editors-main.ts b/packages/plugin-ext/src/main/browser/text-editors-main.ts index 93c2d820c54ef..262a704a75048 100644 --- a/packages/plugin-ext/src/main/browser/text-editors-main.ts +++ b/packages/plugin-ext/src/main/browser/text-editors-main.ts @@ -23,7 +23,6 @@ import { TextEditorRevealType, SingleEditOperation, ApplyEditsOptions, - UndoStopOptions, DecorationRenderOptions, ThemeDecorationInstanceRenderOptions, DecorationOptions, @@ -31,6 +30,7 @@ import { WorkspaceNotebookCellEditDto, DocumentsMain, WorkspaceEditMetadataDto, + SnippetEditOptions, } from '../../common/plugin-api-rpc'; import { Range, TextDocumentShowOptions } from '../../common/plugin-api-rpc-model'; import { EditorsAndDocumentsMain } from './editors-and-documents-main'; @@ -157,7 +157,7 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable { } } - $tryInsertSnippet(id: string, template: string, ranges: Range[], opts: UndoStopOptions): Promise { + $tryInsertSnippet(id: string, template: string, ranges: Range[], opts: SnippetEditOptions): Promise { if (!this.editorsAndDocuments.getEditor(id)) { return Promise.reject(disposed(`TextEditor(${id})`)); } diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index b3be5296552cb..8f8124611909c 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -342,11 +342,12 @@ export function fromTextEdit(edit: theia.TextEdit): model.TextEdit { }; } -function fromSnippetTextEdit(edit: theia.SnippetTextEdit): model.TextEdit & { insertAsSnippet?: boolean } { +function fromSnippetTextEdit(edit: theia.SnippetTextEdit): model.TextEdit & { insertAsSnippet?: boolean, keepWhitespace?: boolean } { return { text: edit.snippet.value, range: fromRange(edit.range), - insertAsSnippet: true + insertAsSnippet: true, + keepWhitespace: edit.keepWhitespace }; } diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 881b9edc58cd1..4d6c42d228e6c 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -1341,6 +1341,7 @@ export class NotebookRange implements theia.NotebookRange { export class SnippetTextEdit implements theia.SnippetTextEdit { range: Range; snippet: SnippetString; + keepWhitespace?: boolean; static isSnippetTextEdit(thing: unknown): thing is SnippetTextEdit { return thing instanceof SnippetTextEdit || isObject(thing) diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index d2ca6164f250f..a8e3377f28f83 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -1146,7 +1146,20 @@ export module '@theia/plugin' { * @return A promise that resolves with a value indicating if the snippet could be inserted. Note that the promise does not signal * that the snippet is completely filled-in or accepted. */ - insertSnippet(snippet: SnippetString, location?: Position | Range | Position[] | Range[], options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; + insertSnippet(snippet: SnippetString, location?: Position | Range | Position[] | Range[], options?: { + /** + * Add undo stop before making the edits. + */ + readonly undoStopBefore: boolean; + /** + * Add undo stop after making the edits. + */ + readonly undoStopAfter: boolean; + /** + * Keep whitespace of the {@link SnippetString.value} as is. + */ + readonly keepWhitespace?: boolean; + }): Thenable; /** * Adds a set of decorations to the text editor. If a set of decorations already exists with @@ -16299,6 +16312,11 @@ export module '@theia/plugin' { */ snippet: SnippetString; + /** + * Whether the snippet edit should be applied with existing whitespace preserved. + */ + keepWhitespace?: boolean; + /** * Create a new snippet edit. *