From 7113718ce3de5648d9d20e5f3f19bd50496b2b70 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 8 May 2017 12:39:56 +0200 Subject: [PATCH] make text edit, set selections --- .../contrib/snippet/browser/editorSnippets.ts | 119 +++++++++++++++--- .../contrib/snippet/common/snippetParser.ts | 8 ++ .../test/browser/editorSnippets.test.ts | 19 ++- 3 files changed, 124 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/contrib/snippet/browser/editorSnippets.ts b/src/vs/editor/contrib/snippet/browser/editorSnippets.ts index d1501376c33..1f6729cc562 100644 --- a/src/vs/editor/contrib/snippet/browser/editorSnippets.ts +++ b/src/vs/editor/contrib/snippet/browser/editorSnippets.ts @@ -5,34 +5,115 @@ 'use strict'; -import { getLeadingWhitespace } from 'vs/base/common/strings'; -import { ICommonCodeEditor, IModel } from 'vs/editor/common/editorCommon'; -import { TextmateSnippet } from '../common/snippetParser'; +import { getLeadingWhitespace, compare } from 'vs/base/common/strings'; +import { ICommonCodeEditor, TrackedRangeStickiness } from 'vs/editor/common/editorCommon'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { TextmateSnippet, Placeholder } from '../common/snippetParser'; +import { Selection } from 'vs/editor/common/core/selection'; +import { Range } from 'vs/editor/common/core/range'; +class OneSnippet { + + private readonly _editor: ICommonCodeEditor; + private readonly _placeholderDecoration = new Map(); + private readonly _placeholderGroups: Placeholder[][]; + + private _placeholderGroupsIdx: number; + + constructor(editor: ICommonCodeEditor, selection: Selection, snippet: TextmateSnippet) { + + this._editor = editor; + + // for each selection get the leading 'reference'-whitespace and adjust the snippet accordingly. + const model = editor.getModel(); + const line = model.getLineContent(selection.startLineNumber); + const leadingWhitespace = getLeadingWhitespace(line, 0, selection.startColumn - 1); + snippet = snippet.withIndentation(whitespace => model.normalizeIndentation(leadingWhitespace + whitespace)); + + const offset = model.getOffsetAt(selection.getStartPosition()); + + this._editor.executeEdits('onesnieppt', [EditOperation.replaceMove(selection, snippet.value)]); + + // create a decoration (tracked range) for each placeholder + this._editor.changeDecorations(accessor => { + + let lastRange: Range; + + for (const placeholder of snippet.getPlaceholders()) { + const placeholderOffset = snippet.offset(placeholder); + const placeholderLen = snippet.len(placeholder); + const start = model.getPositionAt(offset + placeholderOffset); + const end = model.getPositionAt(offset + placeholderOffset + placeholderLen); + const range = new Range(start.lineNumber, start.column, end.lineNumber, end.column); + + let stickiness: TrackedRangeStickiness; + if (lastRange && lastRange.getEndPosition().equals(range.getStartPosition())) { + stickiness = TrackedRangeStickiness.GrowsOnlyWhenTypingAfter; + } else { + stickiness = TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges; + } + + const handle = accessor.addDecoration(range, { stickiness }); + this._placeholderDecoration.set(placeholder, handle); + + lastRange = range; + } + }); + + this._placeholderGroupsIdx = -1; + this._placeholderGroups = []; + let lastBucket: Placeholder[]; + snippet.getPlaceholders().sort((a, b) => compare(a.name, b.name)).reverse().forEach(a => { + if (!lastBucket || lastBucket[0].name !== a.name) { + lastBucket = [a]; + this._placeholderGroups.push(lastBucket); + } else { + lastBucket.push(a); + } + }); + } + + dispose(): void { + this._editor.changeDecorations(accessor => this._placeholderDecoration.forEach(handle => accessor.removeDecoration(handle))); + } + + next(): Selection[] { + this._placeholderGroupsIdx += 1; + if (this._placeholderGroupsIdx >= this._placeholderGroups.length) { + return undefined; + } + const ranges: Selection[] = []; + for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) { + const handle = this._placeholderDecoration.get(placeholder); + const range = this._editor.getModel().getDecorationRange(handle); + ranges.push(new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)); + } + return ranges; + } +} export class SnippetSession { private readonly _editor: ICommonCodeEditor; - private readonly _model: IModel; - private readonly _snippets: TextmateSnippet[] = []; + private readonly _snippets: OneSnippet[] = []; constructor(editor: ICommonCodeEditor, snippet: TextmateSnippet) { this._editor = editor; - this._model = editor.getModel(); - + this._editor.pushUndoStop(); for (const selection of editor.getSelections()) { - // for each selection get the leading 'reference' whitespace and - // adjust the snippet accordingly. this makes one snippet per selection/cursor - const line = this._model.getLineContent(selection.startLineNumber); - const leadingWhitespace = getLeadingWhitespace(line, 0, selection.startColumn - 1); - const newSnippet = snippet.withIndentation(whitespace => this._model.normalizeIndentation(leadingWhitespace + whitespace)); - this._snippets.push(newSnippet); - - // const offset = this._model.getOffsetAt(selection.getStartPosition()); - // for (const placeholder of snippet.getPlaceholders()) { - // const pos = this._model.getPositionAt(offset + snippet.offset(placeholder)); - // this._model.deltaDecorations - // } + const oneSnippet = new OneSnippet(editor, selection, snippet); + this._snippets.push(oneSnippet); + } + this._editor.pushUndoStop(); + this.next(); + } + + next(): void { + const selections: Selection[] = []; + for (const snippet of this._snippets) { + const sel = snippet.next(); + selections.push(...sel); } + this._editor.setSelections(selections); } } diff --git a/src/vs/editor/contrib/snippet/common/snippetParser.ts b/src/vs/editor/contrib/snippet/common/snippetParser.ts index 2fbe4c9e5f1..2b5c66816ce 100644 --- a/src/vs/editor/contrib/snippet/common/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/common/snippetParser.ts @@ -231,6 +231,14 @@ export class TextmateSnippet { return pos; } + len(marker: Marker): number { + let ret = 0; + walk([marker], marker => { + ret += marker.len(); + return true; + }); + return ret; + } getPlaceholders(): Placeholder[] { const ret: Placeholder[] = []; diff --git a/src/vs/editor/contrib/snippet/test/browser/editorSnippets.test.ts b/src/vs/editor/contrib/snippet/test/browser/editorSnippets.test.ts index 3a02a611d50..e1c48b7ae75 100644 --- a/src/vs/editor/contrib/snippet/test/browser/editorSnippets.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/editorSnippets.test.ts @@ -28,14 +28,27 @@ suite('Editor Contrib - Snippets', () => { editor.dispose(); }); - test('snippets', () => { + test('snippets, selections', () => { editor.setSelections([ new Selection(1, 1, 1, 1), new Selection(2, 2, 2, 2), ]); - new SnippetSession(editor, SnippetParser.parse('foo\n${1:bar}\nfoo')); - assert.ok(true); + const snippet = SnippetParser.parse('foo${1:bar}foo$0'); + const session = new SnippetSession(editor, snippet); + + assert.equal(editor.getModel().getLineContent(1), 'foobarfoofunction foo() {'); + assert.equal(editor.getModel().getLineContent(2), '\tfoobarfooconsole.log(a)'); + + assert.equal(editor.getSelections().length, 2); + assert.ok(editor.getSelections()[0].equalsSelection(new Selection(1, 4, 1, 7))); + assert.ok(editor.getSelections()[1].equalsSelection(new Selection(2, 5, 2, 8))); + + session.next(); + assert.equal(editor.getSelections().length, 2); + assert.ok(editor.getSelections()[0].equalsSelection(new Selection(1, 10, 1, 10))); + assert.ok(editor.getSelections()[1].equalsSelection(new Selection(2, 11, 2, 11))); + }); }); -- GitLab