From 3c817c7fe2b994a80882f6af43a062e22d75ea28 Mon Sep 17 00:00:00 2001 From: Joel Day Date: Tue, 17 Jan 2017 16:09:15 -0800 Subject: [PATCH] Insert snippet API changes. --- .../vscode-api-tests/src/editor.test.ts | 11 ++++-- .../snippet/common/snippetController.ts | 5 --- src/vs/vscode.d.ts | 12 +++---- src/vs/vscode.proposed.d.ts | 12 +++---- src/vs/workbench/api/node/extHost.protocol.ts | 4 +-- src/vs/workbench/api/node/extHostEditors.ts | 35 +++++++------------ src/vs/workbench/api/node/extHostTypes.ts | 10 ++++++ .../workbench/api/node/mainThreadEditors.ts | 8 ++--- .../api/node/mainThreadEditorsTracker.ts | 28 ++++++++------- 9 files changed, 63 insertions(+), 62 deletions(-) diff --git a/extensions/vscode-api-tests/src/editor.test.ts b/extensions/vscode-api-tests/src/editor.test.ts index fb6b37c8f19..2874606d7dc 100644 --- a/extensions/vscode-api-tests/src/editor.test.ts +++ b/extensions/vscode-api-tests/src/editor.test.ts @@ -6,7 +6,7 @@ 'use strict'; import * as assert from 'assert'; -import { workspace, window, Position, Range, commands, TextEditor, TextDocument, TextEditorCursorStyle, TextEditorLineNumbersStyle, SnippetString } from 'vscode'; +import { workspace, window, Position, Range, commands, TextEditor, TextDocument, TextEditorCursorStyle, TextEditorLineNumbersStyle, SnippetString, Selection } from 'vscode'; import { createRandomFile, deleteFile, cleanUp } from './utils'; suite('editor tests', () => { @@ -41,7 +41,7 @@ suite('editor tests', () => { .appendText(' snippet'); return withRandomFileEditor('', (editor, doc) => { - return editor.insertSnippet(snippetString.value, new Position(0, 0)).then(inserted => { + return editor.edit(snippetString).then(inserted => { assert.ok(inserted); assert.equal(doc.getText(), 'This is a placeholder snippet'); assert.ok(doc.isDirty); @@ -54,7 +54,12 @@ suite('editor tests', () => { .appendText('has been'); return withRandomFileEditor('This will be replaced', (editor, doc) => { - return editor.insertSnippet(snippetString.value, new Range(0, 5, 0, 12)).then(inserted => { + editor.selection = new Selection( + new Position(0, 4), + new Position(0, 12) + ); + + return editor.edit(snippetString).then(inserted => { assert.ok(inserted); assert.equal(doc.getText(), 'This has been replaced'); assert.ok(doc.isDirty); diff --git a/src/vs/editor/contrib/snippet/common/snippetController.ts b/src/vs/editor/contrib/snippet/common/snippetController.ts index 3329f969b00..2251065a43d 100644 --- a/src/vs/editor/contrib/snippet/common/snippetController.ts +++ b/src/vs/editor/contrib/snippet/common/snippetController.ts @@ -476,11 +476,6 @@ export class SnippetController { this.run(snippet, overwriteBefore, overwriteAfter); } - public insertSnippetWithReplaceRange(template: string, replaceRange: Range): void { - const snippet = CodeSnippet.fromTextmate(template, this._variableResolver); - this.runWithReplaceRange(snippet, replaceRange); - } - public run(snippet: CodeSnippet, overwriteBefore: number, overwriteAfter: number, stripPrefix?: boolean): void { this._runAndRestoreController(() => { if (snippet.isInsertOnly || snippet.isSingleTabstopOnly) { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index f60be6f005f..ebaf887395c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -937,15 +937,13 @@ declare module 'vscode' { edit(callback: (editBuilder: TextEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; /** - * Inserts the given snippet template and enters snippet mode. + * Enters snippet mode in the editor with the specified snippet. * - * If the editor is already in snippet mode, insertion fails and the returned promise resolves to false. - * - * @param template The snippet template to insert - * @param posOrRange The position or replacement range representing the location of the insertion. - * @return A promise that resolves with a value indicating if the snippet could be inserted. + * @param snippet The snippet to insert + * @param options The undo/redo behaviour around this edit. By default, undo stops will be created before and after this edit. + * @return A promise that resolves with a value indicating if the editor entered snippet mode. */ - insertSnippet(template: string, posOrRange: Position | Range): Thenable; + edit(snippet: SnippetString, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; /** * Adds a set of decorations to the text editor. If a set of decorations already exists with diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d660967414f..9eb68707f5f 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -110,15 +110,13 @@ declare module 'vscode' { export interface TextEditor { /** - * Inserts the given snippet template and enters snippet mode. + * Enters snippet mode in the editor with the specified snippet. * - * If the editor is already in snippet mode, insertion fails and the returned promise resolves to false. - * - * @param template The snippet template to insert - * @param posOrRange The position or replacement range representing the location of the insertion. - * @return A promise that resolves with a value indicating if the snippet could be inserted. + * @param snippet The snippet to insert + * @param options The undo/redo behaviour around this edit. By default, undo stops will be created before and after this edit. + * @return A promise that resolves with a value indicating if the editor entered snippet mode. */ - insertSnippet(template: string, posOrRange: Position | Range): Thenable; + edit(snippet: SnippetString, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; } export interface SCMResourceThemableDecorations { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 763a9063c57..72e0002891f 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -34,7 +34,7 @@ import { IWorkspaceConfigurationValues } from 'vs/workbench/services/configurati import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkspaceSymbol } from 'vs/workbench/parts/search/common/search'; -import { IApplyEditsOptions, TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent } from './mainThreadEditorsTracker'; +import { IApplyEditsOptions, IInsertSnippetOptions, TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent } from './mainThreadEditorsTracker'; import { InternalTreeExplorerNodeContent } from 'vs/workbench/parts/explorers/common/treeExplorerViewModel'; @@ -137,7 +137,7 @@ export abstract class MainThreadEditorsShape { $tryRevealRange(id: string, range: editorCommon.IRange, revealType: TextEditorRevealType): TPromise { throw ni(); } $trySetSelections(id: string, selections: editorCommon.ISelection[]): TPromise { throw ni(); } $tryApplyEdits(id: string, modelVersionId: number, edits: editorCommon.ISingleEditOperation[], opts: IApplyEditsOptions): TPromise { throw ni(); } - $tryInsertSnippet(id: string, template: string, posOrRange: editorCommon.IPosition | editorCommon.IRange): TPromise { throw ni(); } + $tryInsertSnippet(id: string, template: string, opts: IInsertSnippetOptions): TPromise { throw ni(); } } export abstract class MainThreadTreeExplorersShape { diff --git a/src/vs/workbench/api/node/extHostEditors.ts b/src/vs/workbench/api/node/extHostEditors.ts index d1a5f9187a2..3a65e8da293 100644 --- a/src/vs/workbench/api/node/extHostEditors.ts +++ b/src/vs/workbench/api/node/extHostEditors.ts @@ -12,8 +12,8 @@ import Event, { Emitter } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import { ExtHostDocuments, ExtHostDocumentData } from 'vs/workbench/api/node/extHostDocuments'; -import { Selection, Range, Position, EndOfLine, TextEditorRevealType, TextEditorSelectionChangeKind, TextEditorLineNumbersStyle } from './extHostTypes'; -import { ISingleEditOperation, TextEditorCursorStyle, IPosition, IRange } from 'vs/editor/common/editorCommon'; +import { Selection, Range, Position, EndOfLine, TextEditorRevealType, TextEditorSelectionChangeKind, TextEditorLineNumbersStyle, SnippetString } from './extHostTypes'; +import { ISingleEditOperation, TextEditorCursorStyle } from 'vs/editor/common/editorCommon'; import { IResolvedTextEditorConfiguration, ISelectionChangeEvent, ITextEditorConfigurationUpdate } from 'vs/workbench/api/node/mainThreadEditorsTracker'; import * as TypeConverters from './extHostTypeConverters'; import { MainContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextEditorAddData, ITextEditorPositionData } from './extHost.protocol'; @@ -595,10 +595,17 @@ class ExtHostTextEditor implements vscode.TextEditor { // ---- editing - edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Thenable { - let edit = new TextEditorEdit(this._documentData.document, options); - callback(edit); - return this._applyEdit(edit); + edit(callback: (edit: TextEditorEdit) => void, options: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; + edit(snippet: SnippetString, options: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; + + edit(callbackOrSnippet: ((edit: TextEditorEdit) => void) | SnippetString, options: { undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Thenable { + if (SnippetString.isSnippetString(callbackOrSnippet)) { + return this._proxy.$tryInsertSnippet(this._id, callbackOrSnippet.value, options); + } else { + let edit = new TextEditorEdit(this._documentData.document, options); + callbackOrSnippet(edit); + return this._applyEdit(edit); + } } _applyEdit(editBuilder: TextEditorEdit): TPromise { @@ -620,22 +627,6 @@ class ExtHostTextEditor implements vscode.TextEditor { }); } - insertSnippet(template: string, posOrRange: Position | Range) { - let convertedPosOrRange: IPosition | IRange; - - if (Position.isPosition(posOrRange)) { - convertedPosOrRange = TypeConverters.fromPosition(posOrRange); - } - else if (Range.isRange(posOrRange)) { - convertedPosOrRange = TypeConverters.fromRange(posOrRange); - } - else { - return TPromise.wrapError(new Error('Unrecognized value for posOrRange')); - } - - return this._proxy.$tryInsertSnippet(this._id, template, convertedPosOrRange); - } - // ---- util private _runOnProxy(callback: () => TPromise, silent: boolean): TPromise { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index e7b26461e60..b74137ff36e 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -519,6 +519,16 @@ export class WorkspaceEdit { export class SnippetString { + static isSnippetString(thing: any): thing is SnippetString { + if (thing instanceof SnippetString) { + return true; + } + if (!thing) { + return false; + } + return typeof (thing).value === 'string'; + } + private static _escape(value: string): string { return value.replace(/\$|}|\\/g, '\\$&'); } diff --git a/src/vs/workbench/api/node/mainThreadEditors.ts b/src/vs/workbench/api/node/mainThreadEditors.ts index 61b796f4af3..13b1016a707 100644 --- a/src/vs/workbench/api/node/mainThreadEditors.ts +++ b/src/vs/workbench/api/node/mainThreadEditors.ts @@ -8,13 +8,13 @@ import URI from 'vs/base/common/uri'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { ISingleEditOperation, ISelection, IPosition, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IDecorationOptions } from 'vs/editor/common/editorCommon'; +import { ISingleEditOperation, ISelection, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IDecorationOptions } from 'vs/editor/common/editorCommon'; import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { Position as EditorPosition } from 'vs/platform/editor/common/editor'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, IApplyEditsOptions, ITextEditorConfigurationUpdate } from 'vs/workbench/api/node/mainThreadEditorsTracker'; +import { MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, IApplyEditsOptions, IInsertSnippetOptions, ITextEditorConfigurationUpdate } from 'vs/workbench/api/node/mainThreadEditorsTracker'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { equals as arrayEquals } from 'vs/base/common/arrays'; import { equals as objectEquals } from 'vs/base/common/objects'; @@ -293,11 +293,11 @@ export class MainThreadEditors extends MainThreadEditorsShape { return TPromise.as(this._textEditorsMap[id].applyEdits(modelVersionId, edits, opts)); } - $tryInsertSnippet(id: string, template: string, posOrRange: IPosition | IRange): TPromise { + $tryInsertSnippet(id: string, template: string, opts: IInsertSnippetOptions): TPromise { if (!this._textEditorsMap[id]) { return TPromise.wrapError('TextEditor disposed'); } - return TPromise.as(this._textEditorsMap[id].insertSnippet(template, posOrRange)); + return TPromise.as(this._textEditorsMap[id].insertSnippet(template, opts)); } $registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void { diff --git a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts index 99bbe71a30b..a9e8c13538f 100644 --- a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts +++ b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts @@ -12,7 +12,6 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IdGenerator } from 'vs/base/common/idGenerator'; -import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { SnippetController } from 'vs/editor/contrib/snippet/common/snippetController'; @@ -60,12 +59,19 @@ export enum TextEditorRevealType { InCenterIfOutsideViewport = 2 } -export interface IApplyEditsOptions { +export interface IUndoStopOptions { undoStopBefore: boolean; undoStopAfter: boolean; +} + +export interface IApplyEditsOptions extends IUndoStopOptions { setEndOfLine: EndOfLine; } +export interface IInsertSnippetOptions extends IUndoStopOptions { + +} + /** * Text Editor that is permanently bound to the same model. * It can be bound or not to a CodeEditor. @@ -386,24 +392,22 @@ export class MainThreadTextEditor { return false; } - insertSnippet(template: string, posOrRange: EditorCommon.IPosition | EditorCommon.IRange) { + insertSnippet(template: string, opts: IInsertSnippetOptions) { const snippetController = SnippetController.get(this._codeEditor); if (snippetController.inSnippetMode) { return false; } - const range = Range.isIRange(posOrRange) ? Range.lift(posOrRange) : null; - const position = Position.isIPosition(posOrRange) ? Position.lift(posOrRange) : range.getStartPosition(); - - this._codeEditor.setPosition(position); - this._codeEditor.revealLine(position.lineNumber); this._codeEditor.focus(); - if (range) { - snippetController.insertSnippetWithReplaceRange(template, range); + if (opts.undoStopBefore) { + this._codeEditor.pushUndoStop(); } - else { - snippetController.insertSnippet(template, 0, 0); + + snippetController.insertSnippet(template, 0, 0); + + if (opts.undoStopAfter) { + this._codeEditor.pushUndoStop(); } return true;