diff --git a/src/vs/editor/contrib/snippet/common/snippet.ts b/src/vs/editor/contrib/snippet/common/snippet.ts deleted file mode 100644 index 8efd9e2b8877c44b6d9e436ddd77ff59b7f748a4..0000000000000000000000000000000000000000 --- a/src/vs/editor/contrib/snippet/common/snippet.ts +++ /dev/null @@ -1,405 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as strings from 'vs/base/common/strings'; -import { Range } from 'vs/editor/common/core/range'; -import { Marker, Variable, Placeholder, Text, SnippetParser, walk } from 'vs/editor/contrib/snippet/common/snippetParser'; - -export interface IIndentationNormalizer { - normalizeIndentation(str: string): string; -} - -export interface IPlaceHolder { - id: string; - value: string; - occurences: Range[]; -} - -export interface ICodeSnippet { - lines: string[]; - placeHolders: IPlaceHolder[]; - finishPlaceHolderIndex: number; -} - -export interface ISnippetVariableResolver { - resolve(name: string): string; -} - -export class CodeSnippet implements ICodeSnippet { - - static fromTextmate(template: string, variableResolver?: ISnippetVariableResolver): CodeSnippet { - const marker = new SnippetParser(true, false).parse(template); - const snippet = new CodeSnippet(); - _resolveSnippetVariables(marker, variableResolver); - _fillCodeSnippetFromMarker(snippet, marker); - return snippet; - } - - static fromInternal(template: string): CodeSnippet { - const marker = new SnippetParser(false, true).parse(template); - const snippet = new CodeSnippet(); - _fillCodeSnippetFromMarker(snippet, marker); - return snippet; - } - - static none(template: string): CodeSnippet { - const snippet = new CodeSnippet(); - snippet.lines = template.split(/\r\n|\n|\r/); - return snippet; - } - - static fixEmmetFinalTabstop(template: string): string { - - let matchFinalStops = template.match(/\$\{0\}|\$0/g); - if (!matchFinalStops || matchFinalStops.length === 1) { - return template; - } - - // string to string conversion that tries to fix the - // snippet in-place - - let marker = new SnippetParser(true, false).parse(template); - let maxIndex = -Number.MIN_VALUE; - - // find highest placeholder index - walk(marker, candidate => { - if (candidate instanceof Placeholder) { - let index = Number(candidate.index); - if (index > maxIndex) { - maxIndex = index; - } - } - return true; - }); - - // rewrite final tabstops - walk(marker, candidate => { - if (candidate instanceof Placeholder) { - if (candidate.isFinalTabstop) { - candidate.index = String(++maxIndex); - } - } - return true; - }); - - // write back as string - function toSnippetString(marker: Marker): string { - if (marker instanceof Text) { - return marker.string; - } else if (marker instanceof Placeholder) { - if (marker.defaultValue.length > 0) { - return `\${${marker.index}:${marker.defaultValue.map(toSnippetString).join('')}}`; - } else { - return `\$${marker.index}`; - } - } else if (marker instanceof Variable) { - if (marker.defaultValue.length > 0) { - return `\${${marker.name}:${marker.defaultValue.map(toSnippetString).join('')}}`; - } else { - return `\$${marker.name}`; - } - } else { - throw new Error('unexpected marker: ' + marker); - } - } - return marker.map(toSnippetString).join(''); - } - - static fromEmmet(template: string): CodeSnippet { - - CodeSnippet.fixEmmetFinalTabstop(template); - - let matchFinalStops = template.match(/\$\{0\}|\$0/g); - if (!matchFinalStops || matchFinalStops.length === 1) { - return CodeSnippet.fromTextmate(template); - } - - // Emmet sometimes returns snippets with multiple ${0} - // In such cases, replace ${0} with incremental tab stops - - const snippetMarkers: Marker[] = new SnippetParser(true, false).parse(template) || []; - let getMaxTabStop = (markers: Marker[]): number => { - let currentMaxTabStop = -1; - markers.forEach(marker => { - if (marker instanceof Placeholder && /^\d+$/.test(marker['index'])) { - let currentTabStop = Number(marker['index']); - let nestedMaxTabStop = getMaxTabStop(marker['defaultValue'] || []); - currentMaxTabStop = Math.max(currentMaxTabStop, currentTabStop, nestedMaxTabStop); - } - }); - return currentMaxTabStop; - }; - - let maxTabStop = getMaxTabStop(snippetMarkers); - - let setNextTabStop = (markers: Marker[]) => { - markers.forEach(marker => { - if (marker instanceof Placeholder) { - if (marker['index'] === '0') { - marker['index'] = ++maxTabStop + ''; - } - setNextTabStop(marker['defaultValue'] || []); - } - }); - }; - - setNextTabStop(snippetMarkers); - - const snippet = new CodeSnippet(); - _fillCodeSnippetFromMarker(snippet, snippetMarkers); - return snippet; - } - - public lines: string[] = []; - public placeHolders: IPlaceHolder[] = []; - public finishPlaceHolderIndex: number = -1; - - get isInsertOnly(): boolean { - return this.placeHolders.length === 0; - } - - get isSingleTabstopOnly(): boolean { - if (this.placeHolders.length !== 1) { - return false; - } - - const [placeHolder] = this.placeHolders; - if (placeHolder.value !== '' || placeHolder.occurences.length !== 1) { - return false; - } - - const [placeHolderRange] = placeHolder.occurences; - if (!Range.isEmpty(placeHolderRange)) { - return false; - } - return true; - } - - private extractLineIndentation(str: string, maxColumn: number = Number.MAX_VALUE): string { - var fullIndentation = strings.getLeadingWhitespace(str); - - if (fullIndentation.length > maxColumn - 1) { - return fullIndentation.substring(0, maxColumn - 1); - } - - return fullIndentation; - } - - public bind(referenceLine: string, deltaLine: number, firstLineDeltaColumn: number, config: IIndentationNormalizer): ICodeSnippet { - const resultLines: string[] = []; - const resultPlaceHolders: IPlaceHolder[] = []; - - const referenceIndentation = this.extractLineIndentation(referenceLine, firstLineDeltaColumn + 1); - - // Compute resultLines & keep deltaColumns as a reference for adjusting placeholders - const deltaColumns: number[] = []; - - for (let i = 0, len = this.lines.length; i < len; i++) { - let originalLine = this.lines[i]; - if (i === 0) { - deltaColumns[i + 1] = firstLineDeltaColumn; - resultLines[i] = originalLine; - } else { - let originalLineIndentation = this.extractLineIndentation(originalLine); - let remainingLine = originalLine.substr(originalLineIndentation.length); - let indentation = config.normalizeIndentation(referenceIndentation + originalLineIndentation); - deltaColumns[i + 1] = indentation.length - originalLineIndentation.length; - resultLines[i] = indentation + remainingLine; - } - } - - // Compute resultPlaceHolders - for (const originalPlaceHolder of this.placeHolders) { - let resultOccurences: Range[] = []; - - for (let { startLineNumber, startColumn, endLineNumber, endColumn } of originalPlaceHolder.occurences) { - - if (startColumn > 1 || startLineNumber === 1) { - // placeholders that aren't at the beginning of new snippet lines - // will be moved by how many characters the indentation has been - // adjusted - startColumn = startColumn + deltaColumns[startLineNumber]; - endColumn = endColumn + deltaColumns[endLineNumber]; - - } else { - // placeholders at the beginning of new snippet lines - // will be indented by the reference indentation - startColumn += referenceIndentation.length; - endColumn += referenceIndentation.length; - } - - resultOccurences.push(new Range( - startLineNumber + deltaLine, - startColumn, - endLineNumber + deltaLine, - endColumn, - )); - } - - resultPlaceHolders.push({ - id: originalPlaceHolder.id, - value: originalPlaceHolder.value, - occurences: resultOccurences - }); - } - - return { - lines: resultLines, - placeHolders: resultPlaceHolders, - finishPlaceHolderIndex: this.finishPlaceHolderIndex - }; - } -} - - -// --- parsing - - -interface ISnippetParser { - parse(input: string): CodeSnippet; -} - -interface IParsedLinePlaceHolderInfo { - id: string; - value: string; - startColumn: number; - endColumn: number; -} - -interface IParsedLine { - line: string; - placeHolders: IParsedLinePlaceHolderInfo[]; -} - -function _resolveSnippetVariables(marker: Marker[], resolver: ISnippetVariableResolver) { - if (resolver) { - const stack = [...marker]; - - while (stack.length > 0) { - const marker = stack.shift(); - if (marker instanceof Variable) { - - try { - marker.resolvedValue = resolver.resolve(marker.name); - } catch (e) { - // - } - if (marker.isDefined) { - continue; - } - } - - if (marker instanceof Variable || marker instanceof Placeholder) { - // 'recurse' - stack.unshift(...marker.defaultValue); - } - } - } -} - -function _isFinishPlaceHolder(v: IPlaceHolder) { - return (v.id === '' && v.value === '') || v.id === '0'; -} - -function _fillCodeSnippetFromMarker(snippet: CodeSnippet, marker: Marker[]) { - - let placeHolders: { [id: string]: IPlaceHolder } = Object.create(null); - let hasFinishPlaceHolder = false; - - const stack = [...marker]; - snippet.lines = ['']; - while (stack.length > 0) { - const marker = stack.shift(); - if (marker instanceof Text) { - // simple text - let lines = marker.string.split(/\r\n|\n|\r/); - snippet.lines[snippet.lines.length - 1] += lines.shift(); - snippet.lines.push(...lines); - - } else if (marker instanceof Placeholder) { - - let placeHolder = placeHolders[marker.index]; - if (!placeHolder) { - placeHolders[marker.index] = placeHolder = { - id: marker.index, - value: Marker.toString(marker.defaultValue), - occurences: [] - }; - snippet.placeHolders.push(placeHolder); - } - hasFinishPlaceHolder = hasFinishPlaceHolder || _isFinishPlaceHolder(placeHolder); - - const line = snippet.lines.length; - const column = snippet.lines[line - 1].length + 1; - - placeHolder.occurences.push(new Range( - line, - column, - line, - column + Marker.toString(marker.defaultValue).length // TODO multiline placeholders! - )); - - stack.unshift(...marker.defaultValue); - - } else if (marker instanceof Variable) { - - if (!marker.isDefined) { - // contine as placeholder - // THIS is because of us having falsy - // advertised ${foo} as placeholder syntax - stack.unshift(new Placeholder(marker.name, marker.defaultValue.length === 0 - ? [new Text(marker.name)] - : marker.defaultValue)); - - } else if (marker.resolvedValue) { - // contine with the value - stack.unshift(new Text(marker.resolvedValue)); - - } else { - // continue with default values - stack.unshift(...marker.defaultValue); - } - } - - if (stack.length === 0 && !hasFinishPlaceHolder) { - stack.push(new Placeholder('0', [])); - } - } - - // Named variables (e.g. {greeting} and {greeting:Hello}) are sorted first, followed by - // tab-stops and numeric variables (e.g. $1, $2, ${3:foo}) which are sorted in ascending order - snippet.placeHolders.sort((a, b) => { - let nonIntegerId = (v: IPlaceHolder) => !(/^\d+$/).test(v.id); - - // Sort finish placeholder last - if (_isFinishPlaceHolder(a)) { - return 1; - } else if (_isFinishPlaceHolder(b)) { - return -1; - } - - // Sort named placeholders first - if (nonIntegerId(a) && nonIntegerId(b)) { - return 0; - } else if (nonIntegerId(a)) { - return -1; - } else if (nonIntegerId(b)) { - return 1; - } - - if (a.id === b.id) { - return 0; - } - - return Number(a.id) < Number(b.id) ? -1 : 1; - }); - - if (snippet.placeHolders.length > 0) { - snippet.finishPlaceHolderIndex = snippet.placeHolders.length - 1; - snippet.placeHolders[snippet.finishPlaceHolderIndex].id = ''; - } -} diff --git a/src/vs/editor/contrib/snippet/common/snippetController.ts b/src/vs/editor/contrib/snippet/common/snippetController.ts deleted file mode 100644 index 3a6dc9730f1ce0844922e0c48e3369805dd1f4a1..0000000000000000000000000000000000000000 --- a/src/vs/editor/contrib/snippet/common/snippetController.ts +++ /dev/null @@ -1,762 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { binarySearch } from 'vs/base/common/arrays'; -import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { CommonEditorRegistry, commonEditorContribution, EditorCommand } from 'vs/editor/common/editorCommonExtensions'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ISnippetVariableResolver, ICodeSnippet, CodeSnippet } from './snippet'; -import { SnippetVariablesResolver } from './snippetVariables'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { Position } from 'vs/editor/common/core/position'; -import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; - -export class InsertSnippetController { - - private editor: editorCommon.ICommonCodeEditor; - private model: editorCommon.IModel; - private finishPlaceHolderIndex: number; - - private listenersToRemove: IDisposable[]; - private trackedPlaceHolders: ITrackedPlaceHolder[]; - private placeHolderDecorations: string[]; - private currentPlaceHolderIndex: number; - private highlightDecorationId: string; - private isFinished: boolean; - - private _onStop: () => void; - private _initialAlternativeVersionId: number; - - constructor(editor: editorCommon.ICommonCodeEditor, adaptedSnippet: ICodeSnippet, startLineNumber: number, initialAlternativeVersionId: number, onStop: () => void) { - this.editor = editor; - this._onStop = onStop; - this.model = editor.getModel(); - this.finishPlaceHolderIndex = adaptedSnippet.finishPlaceHolderIndex; - - this.trackedPlaceHolders = []; - this.placeHolderDecorations = []; - this.currentPlaceHolderIndex = 0; - this.highlightDecorationId = null; - this.isFinished = false; - - this._initialAlternativeVersionId = initialAlternativeVersionId; - - this.initialize(adaptedSnippet, startLineNumber); - } - - public dispose(): void { - this.stopAll(); - } - - private initialize(adaptedSnippet: ICodeSnippet, startLineNumber: number): void { - - // sorted list of all placeholder occurences for subsequent lockups - const sortedOccurrences: Range[] = []; - for (const { occurences } of adaptedSnippet.placeHolders) { - for (const range of occurences) { - sortedOccurrences.push(range); - } - } - sortedOccurrences.sort(Range.compareRangesUsingStarts); - - // track each occurence - this.model.changeDecorations((changeAccessor) => { - - for (let i = 0; i < adaptedSnippet.placeHolders.length; i++) { - const { occurences } = adaptedSnippet.placeHolders[i]; - const trackedRanges: string[] = []; - - for (const range of occurences) { - let stickiness = editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges; - - if (i === adaptedSnippet.finishPlaceHolderIndex) { - // final tab stop decoration never grows - stickiness = editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; - - } else { - // Check if the previous range ends exactly where this range starts - // and iff so change the stickiness to avoid conflicts - let idx = binarySearch(sortedOccurrences, range, Range.compareRangesUsingStarts); - if (idx > 0 - && sortedOccurrences[idx - 1].endLineNumber === range.startLineNumber - && sortedOccurrences[idx - 1].endColumn === range.startColumn) { - - stickiness = editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingAfter; - } - } - - trackedRanges.push(changeAccessor.addDecoration(range, { - stickiness: stickiness - })); - } - - this.trackedPlaceHolders.push({ - ranges: trackedRanges - }); - } - }); - - this.editor.changeDecorations((changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => { - let newDecorations: editorCommon.IModelDeltaDecoration[] = []; - - let endLineNumber = startLineNumber + adaptedSnippet.lines.length - 1; - let endLineNumberMaxColumn = this.model.getLineMaxColumn(endLineNumber); - newDecorations.push({ - range: new Range(startLineNumber, 1, endLineNumber, endLineNumberMaxColumn), - options: { - className: 'new-snippet', - isWholeLine: true - } - }); - - for (let i = 0, len = this.trackedPlaceHolders.length; i < len; i++) { - let className = (i === this.finishPlaceHolderIndex) ? 'finish-snippet-placeholder' : 'snippet-placeholder'; - newDecorations.push({ - range: this.model.getDecorationRange(this.trackedPlaceHolders[i].ranges[0]), - options: { - stickiness: this.model.getDecorationOptions(this.trackedPlaceHolders[i].ranges[0]).stickiness, - className: className - } - }); - } - - let decorations = changeAccessor.deltaDecorations([], newDecorations); - this.highlightDecorationId = decorations[0]; - this.placeHolderDecorations = decorations.slice(1); - }); - - // let print = () => { - // console.log('trackedPlaceHolders: ' + this.trackedPlaceHolders.map((placeholder, index) => 'placeHolder index ' + index + ': ' + placeholder.ranges.map(id => id + '(' + this.model.getDecorationRange(id) + ')').join(', ')).join('\n')); - // console.log('highlightDecoration: ' + this.highlightDecorationId + '(' + this.model.getDecorationRange(this.highlightDecorationId) + ')'); - // console.log('placeHolderDecorations: ' + this.placeHolderDecorations.map(id => id + '(' + this.model.getDecorationRange(id) + ')').join(', ')); - // }; - // print(); - - let _highlightRange = this.model.getDecorationRange(this.highlightDecorationId); - - this.listenersToRemove = []; - this.listenersToRemove.push(this.editor.onDidChangeModelContent((e) => { - // console.log('-------MODEL CHANGED'); - // print(); - if (this.isFinished) { - return; - } - - if (e.isFlush) { - // a model.setValue() was called - this.stopAll(); - return; - } - - const newAlternateVersionId = this.editor.getModel().getAlternativeVersionId(); - if (this._initialAlternativeVersionId === newAlternateVersionId) { - // We executed undo until we reached the same version we started with - this.stopAll(); - return; - } - - for (let i = 0, len = e.changes.length; i < len; i++) { - const change = e.changes[i]; - const intersection = _highlightRange.intersectRanges(change.range); - if (intersection === null) { - // Did an edit outside of the snippet - this.stopAll(); - return; - } - } - - // Keep the highlightRange for the next round of model change events - _highlightRange = this.model.getDecorationRange(this.highlightDecorationId); - })); - - this.listenersToRemove.push(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { - if (this.isFinished) { - return; - } - var highlightRange = this.model.getDecorationRange(this.highlightDecorationId); - if (!highlightRange) { - this.stopAll(); - return; - } - var lineNumber = e.position.lineNumber; - if (lineNumber < highlightRange.startLineNumber || lineNumber > highlightRange.endLineNumber) { - this.stopAll(); - } - })); - - this.listenersToRemove.push(this.editor.onDidChangeModel(() => { - this.stopAll(); - })); - - var blurTimeout = -1; - this.listenersToRemove.push(this.editor.onDidBlurEditor(() => { - // Blur if within 100ms we do not focus back - blurTimeout = setTimeout(() => { - this.stopAll(); - }, 100); - })); - - this.listenersToRemove.push(this.editor.onDidFocusEditor(() => { - // Cancel the blur timeout (if any) - if (blurTimeout !== -1) { - clearTimeout(blurTimeout); - blurTimeout = -1; - } - })); - - this.listenersToRemove.push(this.model.onDidChangeDecorations((e) => { - if (this.isFinished) { - return; - } - - var modelEditableRange = this.model.getEditableRange(), - previousRange: Range = null, - allCollapsed = true, - allEqualToEditableRange = true; - - for (var i = 0; (allCollapsed || allEqualToEditableRange) && i < this.trackedPlaceHolders.length; i++) { - var ranges = this.trackedPlaceHolders[i].ranges; - - for (var j = 0; (allCollapsed || allEqualToEditableRange) && j < ranges.length; j++) { - var range = this.model.getDecorationRange(ranges[j]); - - if (allCollapsed) { - if (!range.isEmpty()) { - allCollapsed = false; - } else if (previousRange === null) { - previousRange = range; - } else if (!previousRange.equalsRange(range)) { - allCollapsed = false; - } - } - - if (allEqualToEditableRange && !modelEditableRange.equalsRange(range)) { - allEqualToEditableRange = false; - } - } - } - - - if (allCollapsed || allEqualToEditableRange) { - this.stopAll(); - } else { - if (this.finishPlaceHolderIndex !== -1) { - var finishPlaceHolderDecorationId = this.placeHolderDecorations[this.finishPlaceHolderIndex]; - var finishPlaceHolderRange = this.model.getDecorationRange(finishPlaceHolderDecorationId); - var finishPlaceHolderOptions = this.model.getDecorationOptions(finishPlaceHolderDecorationId); - - var finishPlaceHolderRangeIsEmpty = finishPlaceHolderRange.isEmpty(); - var finishPlaceHolderClassNameIsForEmpty = (finishPlaceHolderOptions.className === 'finish-snippet-placeholder'); - - // Remember xor? :) - var needsChanging = Number(finishPlaceHolderRangeIsEmpty) ^ Number(finishPlaceHolderClassNameIsForEmpty); - - if (needsChanging) { - this.editor.changeDecorations((changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => { - var className = finishPlaceHolderRangeIsEmpty ? 'finish-snippet-placeholder' : 'snippet-placeholder'; - changeAccessor.changeDecorationOptions(finishPlaceHolderDecorationId, { - className: className - }); - }); - } - } - } - })); - - this.doLinkEditing(); - } - - public onNextPlaceHolder(): boolean { - return this.changePlaceHolder(true); - } - - public onPrevPlaceHolder(): boolean { - return this.changePlaceHolder(false); - } - - private changePlaceHolder(goToNext: boolean): boolean { - if (this.isFinished) { - return false; - } - - var oldPlaceHolderIndex = this.currentPlaceHolderIndex; - var oldRange = this.model.getDecorationRange(this.trackedPlaceHolders[oldPlaceHolderIndex].ranges[0]); - var sameRange = true; - do { - if (goToNext) { - this.currentPlaceHolderIndex = (this.currentPlaceHolderIndex + 1) % this.trackedPlaceHolders.length; - } else { - this.currentPlaceHolderIndex = (this.trackedPlaceHolders.length + this.currentPlaceHolderIndex - 1) % this.trackedPlaceHolders.length; - } - - var newRange = this.model.getDecorationRange(this.trackedPlaceHolders[this.currentPlaceHolderIndex].ranges[0]); - - sameRange = oldRange.equalsRange(newRange); - - } while (this.currentPlaceHolderIndex !== oldPlaceHolderIndex && sameRange); - - this.doLinkEditing(); - return true; - } - - public onAccept(): boolean { - if (this.isFinished) { - return false; - } - if (this.finishPlaceHolderIndex !== -1) { - var finishRange = this.model.getDecorationRange(this.trackedPlaceHolders[this.finishPlaceHolderIndex].ranges[0]); - // Let's just position cursor at the end of the finish range - this.editor.setPosition({ - lineNumber: finishRange.endLineNumber, - column: finishRange.endColumn - }); - } - this.stopAll(); - return true; - } - - public onEscape(): boolean { - if (this.isFinished) { - return false; - } - this.stopAll(); - // Cancel multi-cursor - this.editor.setSelections([this.editor.getSelections()[0]]); - return true; - } - - private doLinkEditing(): void { - const selections: Selection[] = []; - for (let i = 0, len = this.trackedPlaceHolders[this.currentPlaceHolderIndex].ranges.length; i < len; i++) { - const range = this.model.getDecorationRange(this.trackedPlaceHolders[this.currentPlaceHolderIndex].ranges[i]); - selections.push(new Selection( - range.startLineNumber, - range.startColumn, - range.endLineNumber, - range.endColumn - )); - } - this.editor.setSelections(selections); - this.editor.revealRangeInCenterIfOutsideViewport(this.editor.getSelection()); - } - - private stopAll(): void { - if (this.isFinished) { - return; - } - this.isFinished = true; - - this._onStop(); - - this.listenersToRemove = dispose(this.listenersToRemove); - - this.model.changeDecorations((changeAccessor) => { - for (var i = 0; i < this.trackedPlaceHolders.length; i++) { - var ranges = this.trackedPlaceHolders[i].ranges; - for (var j = 0; j < ranges.length; j++) { - changeAccessor.removeDecoration(ranges[j]); - } - } - }); - this.trackedPlaceHolders = []; - - this.editor.changeDecorations((changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => { - let toRemove: string[] = []; - toRemove.push(this.highlightDecorationId); - for (let i = 0; i < this.placeHolderDecorations.length; i++) { - toRemove.push(this.placeHolderDecorations[i]); - } - changeAccessor.deltaDecorations(toRemove, []); - this.placeHolderDecorations = []; - this.highlightDecorationId = null; - }); - } -} - -export interface ITrackedPlaceHolder { - ranges: string[]; -} - -interface IPreparedSnippet { - typeRange: Range; - adaptedSnippet: ICodeSnippet; -} - -class BeforeAfterData { - - static create(model: editorCommon.IModel, selection: Selection, overwriteBefore: number, overwriteAfter: number) { - - let contentBefore = ''; - if (overwriteBefore > 0) { - contentBefore = model.getLineContent(selection.startLineNumber).substring(selection.startColumn - 1 - overwriteBefore, selection.startColumn - 1); - } - - let contentAfter = ''; - if (overwriteAfter > 0) { - contentAfter = model.getLineContent(selection.endLineNumber).substring(selection.endColumn - 1, selection.endColumn - 1 + overwriteAfter); - } - - return new BeforeAfterData(model, contentBefore, contentAfter, overwriteBefore, overwriteAfter); - } - - constructor(private readonly _model: editorCommon.IModel, - private readonly _contentBefore: string, - private readonly _contentAfter: string, - public readonly overwriteBefore: number, - public readonly overwriteAfter: number - ) { - // - } - - next(selection: Selection) { - const data = BeforeAfterData.create(this._model, selection, this.overwriteBefore, this.overwriteAfter); - let { overwriteBefore, overwriteAfter } = data; - if (data._contentBefore !== this._contentBefore) { - overwriteBefore = 0; - } - if (data._contentAfter !== this._contentAfter) { - overwriteAfter = 0; - } - return new BeforeAfterData(this._model, null, null, overwriteBefore, overwriteAfter); - } -} - -@commonEditorContribution -export class SnippetController { - - private static ID = 'editor.contrib.snippetController'; - - public static get(editor: editorCommon.ICommonCodeEditor): SnippetController { - return editor.getContribution(SnippetController.ID); - } - - private _editor: editorCommon.ICommonCodeEditor; - private _variableResolver: ISnippetVariableResolver; - protected _currentController: InsertSnippetController; - private _inSnippetMode: IContextKey; - - constructor(editor: editorCommon.ICommonCodeEditor, @IContextKeyService contextKeyService: IContextKeyService) { - this._editor = editor; - this._variableResolver = new SnippetVariablesResolver(editor); - this._currentController = null; - this._inSnippetMode = CONTEXT_SNIPPET_MODE.bindTo(contextKeyService); - } - - public dispose(): void { - if (this._currentController) { - this._currentController.dispose(); - this._currentController = null; - } - } - - public getId(): string { - return SnippetController.ID; - } - - public insertSnippet(template: string, overwriteBefore: number, overwriteAfter: number): void { - const snippet = CodeSnippet.fromTextmate(template, this._variableResolver); - this.run(snippet, overwriteBefore, overwriteAfter); - } - - public run(snippet: CodeSnippet, overwriteBefore: number, overwriteAfter: number): void { - this._runAndRestoreController(() => { - if (snippet.isInsertOnly || snippet.isSingleTabstopOnly) { - // Only inserts text, not placeholders, tabstops etc - // Only cursor endposition - this._runForAllSelections(snippet, overwriteBefore, overwriteAfter); - - } else { - let prepared = SnippetController._prepareSnippet(this._editor, this._editor.getSelection(), snippet, overwriteBefore, overwriteAfter); - this._runPreparedSnippetForPrimarySelection(prepared, true); - } - }); - } - - /** - * Inserts once `snippet` at the start of `replaceRange`, after deleting `replaceRange`. - */ - public runWithReplaceRange(snippet: CodeSnippet, replaceRange: Range): void { - this._runAndRestoreController(() => { - this._runPreparedSnippetForPrimarySelection({ - typeRange: replaceRange, - adaptedSnippet: SnippetController._getAdaptedSnippet(this._editor.getModel(), snippet, replaceRange) - }, false); - }); - } - - private _runAndRestoreController(callback: () => void): void { - let prevController = this._currentController; - this._currentController = null; - - callback(); - - if (!this._currentController) { - // we didn't end up in snippet mode again => restore previous controller - this._currentController = prevController; - } else { - // we ended up in snippet mode => dispose previous controller if necessary - if (prevController) { - prevController.dispose(); - } - } - } - - private static _addCommandForSnippet(model: editorCommon.ITextModel, adaptedSnippet: ICodeSnippet, typeRange: Range, out: editorCommon.IIdentifiedSingleEditOperation[]): void { - let insertText = adaptedSnippet.lines.join('\n'); - let currentText = model.getValueInRange(typeRange, editorCommon.EndOfLinePreference.LF); - if (insertText !== currentText) { - out.push(EditOperation.replaceMove(typeRange, insertText)); - } - } - - private _runPreparedSnippetForPrimarySelection(prepared: IPreparedSnippet, undoStops: boolean): void { - let initialAlternativeVersionId = this._editor.getModel().getAlternativeVersionId(); - - let edits: editorCommon.IIdentifiedSingleEditOperation[] = []; - - SnippetController._addCommandForSnippet(this._editor.getModel(), prepared.adaptedSnippet, prepared.typeRange, edits); - - if (edits.length > 0) { - if (undoStops) { - this._editor.pushUndoStop(); - } - this._editor.executeEdits('editor.contrib.insertSnippetHelper', edits); - if (undoStops) { - this._editor.pushUndoStop(); - } - } - - let cursorOnly = SnippetController._getSnippetCursorOnly(prepared.adaptedSnippet); - if (cursorOnly) { - this._editor.setSelection(new Selection(cursorOnly.lineNumber, cursorOnly.column, cursorOnly.lineNumber, cursorOnly.column)); - } else if (prepared.adaptedSnippet.placeHolders.length > 0) { - this._inSnippetMode.set(true); - this._currentController = new InsertSnippetController(this._editor, prepared.adaptedSnippet, prepared.typeRange.startLineNumber, initialAlternativeVersionId, () => { - this._inSnippetMode.reset(); - if (this._currentController) { - this._currentController.dispose(); - this._currentController = null; - } - }); - } - } - - private _runForAllSelections(snippet: CodeSnippet, overwriteBefore: number, overwriteAfter: number): void { - - const edits: editorCommon.IIdentifiedSingleEditOperation[] = []; - const selections = this._editor.getSelections(); - const model = this._editor.getModel(); - const primaryBeforeAfter = BeforeAfterData.create(model, selections[0], overwriteBefore, overwriteAfter); - - let totalDelta = 0; - const newSelections: { offset: number; i: number }[] = []; - - // sort selections by start position but remember where - // each selection came from - const selectionEntries = selections - .map((selection, i) => ({ selection, i })) - .sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection)); - - for (const { selection, i } of selectionEntries) { - - // only use overwrite[Before|After] for secondary cursors - // when the same text as with the primary cursor is selected - const beforeAfter = i !== 0 ? primaryBeforeAfter.next(selection) : primaryBeforeAfter; - - let { adaptedSnippet, typeRange } = SnippetController._prepareSnippet( - this._editor, - selection, - snippet, - beforeAfter.overwriteBefore, - beforeAfter.overwriteAfter - ); - - SnippetController._addCommandForSnippet(this._editor.getModel(), adaptedSnippet, typeRange, edits); - - // compute new selection offset - // * get current offset - // * get length of snippet that we insert - // * get final cursor position of snippet that we insert (might not exist) - // * NEW selection offset is current + final cursor pos + inserts_until_here - - let offset = model.getOffsetAt(typeRange.getStartPosition()); - - // inserts until here - offset += totalDelta; - - // each snippet has a different length (because of whitespace changes) - let snippetLength = (adaptedSnippet.lines.length - 1) * model.getEOL().length; - for (const line of adaptedSnippet.lines) { - snippetLength += line.length; - } - - // each snippet has a different cursor offset - const finalCursorPos = SnippetController._getSnippetCursorOnly(adaptedSnippet); - if (finalCursorPos) { - let finalCursorOffset: number; - if (finalCursorPos.lineNumber === typeRange.startLineNumber) { - finalCursorOffset = finalCursorPos.column - typeRange.startColumn; - } else { - finalCursorOffset = finalCursorPos.column - 1; - for (let i = 0, lineNumber = typeRange.startLineNumber; lineNumber < finalCursorPos.lineNumber; i++ , lineNumber++) { - finalCursorOffset += adaptedSnippet.lines[i].length + model.getEOL().length; - } - } - offset += finalCursorOffset; - - } else { - offset += snippetLength; - } - - newSelections.push({ offset, i }); - totalDelta += (snippetLength - model.getValueLengthInRange(typeRange)); - } - - if (edits.length === 0) { - return; - } - - const cursorStateComputer: editorCommon.ICursorStateComputer = function () { - // create new selections from the new selection offsets - // and restore the order we had at the beginning - const result: Selection[] = []; - for (const { offset, i } of newSelections) { - const pos = model.getPositionAt(offset); - result[i] = new Selection(pos.lineNumber, pos.column, pos.lineNumber, pos.column); - } - return result; - }; - - model.pushStackElement(); - this._editor.setSelections(model.pushEditOperations(selections, edits, cursorStateComputer)); - model.pushStackElement(); - } - - private static _prepareSnippet(editor: editorCommon.ICommonCodeEditor, selection: Selection, snippet: CodeSnippet, overwriteBefore: number, overwriteAfter: number): { typeRange: Range; adaptedSnippet: ICodeSnippet; } { - const model = editor.getModel(); - const typeRange = SnippetController._getTypeRangeForSelection(model, selection, overwriteBefore, overwriteAfter); - const adaptedSnippet = SnippetController._getAdaptedSnippet(model, snippet, typeRange); - - return { typeRange, adaptedSnippet }; - } - - private static _getTypeRangeForSelection(model: editorCommon.IModel, selection: Selection, overwriteBefore: number, overwriteAfter: number): Range { - var typeRange: Range; - if (overwriteBefore || overwriteAfter) { - typeRange = model.validateRange(Range.plusRange(selection, { - startLineNumber: selection.positionLineNumber, - startColumn: selection.positionColumn - overwriteBefore, - endLineNumber: selection.positionLineNumber, - endColumn: selection.positionColumn + overwriteAfter - })); - } else { - typeRange = selection; - } - return typeRange; - } - - private static _getAdaptedSnippet(model: editorCommon.IModel, snippet: CodeSnippet, typeRange: Range): ICodeSnippet { - return snippet.bind(model.getLineContent(typeRange.startLineNumber), typeRange.startLineNumber - 1, typeRange.startColumn - 1, model); - } - - private static _getSnippetCursorOnly(snippet: ICodeSnippet): Position { - - if (snippet.placeHolders.length !== 1) { - return null; - } - - var placeHolder = snippet.placeHolders[0]; - if (placeHolder.value !== '' || placeHolder.occurences.length !== 1) { - return null; - } - - var placeHolderRange = placeHolder.occurences[0]; - if (!Range.isEmpty(placeHolderRange)) { - return null; - } - - return new Position( - placeHolderRange.startLineNumber, - placeHolderRange.startColumn - ); - } - - public jumpToNextPlaceholder(): void { - if (this._currentController) { - this._currentController.onNextPlaceHolder(); - } - } - - public jumpToPrevPlaceholder(): void { - if (this._currentController) { - this._currentController.onPrevPlaceHolder(); - } - } - - public acceptSnippet(): void { - if (this._currentController) { - this._currentController.onAccept(); - } - } - - public leaveSnippet(): void { - if (this._currentController) { - this._currentController.onEscape(); - } - } -} - -export var CONTEXT_SNIPPET_MODE = new RawContextKey('old.inSnippetMode', false); - -const SnippetCommand = EditorCommand.bindToContribution(SnippetController.get); - -CommonEditorRegistry.registerEditorCommand(new SnippetCommand({ - id: 'old.jumpToNextSnippetPlaceholder', - precondition: CONTEXT_SNIPPET_MODE, - handler: x => x.jumpToNextPlaceholder(), - kbOpts: { - weight: CommonEditorRegistry.commandWeight(30), - kbExpr: EditorContextKeys.textFocus, - primary: KeyCode.Tab - } -})); -CommonEditorRegistry.registerEditorCommand(new SnippetCommand({ - id: 'old.jumpToPrevSnippetPlaceholder', - precondition: CONTEXT_SNIPPET_MODE, - handler: x => x.jumpToPrevPlaceholder(), - kbOpts: { - weight: CommonEditorRegistry.commandWeight(30), - kbExpr: EditorContextKeys.textFocus, - primary: KeyMod.Shift | KeyCode.Tab - } -})); -CommonEditorRegistry.registerEditorCommand(new SnippetCommand({ - id: 'old.acceptSnippet', - precondition: CONTEXT_SNIPPET_MODE, - handler: x => x.acceptSnippet(), - kbOpts: { - weight: CommonEditorRegistry.commandWeight(30), - kbExpr: EditorContextKeys.textFocus, - primary: KeyCode.Enter - } -})); -CommonEditorRegistry.registerEditorCommand(new SnippetCommand({ - id: 'old.leaveSnippet', - precondition: CONTEXT_SNIPPET_MODE, - handler: x => x.leaveSnippet(), - kbOpts: { - weight: CommonEditorRegistry.commandWeight(30), - kbExpr: EditorContextKeys.textFocus, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape] - } -})); diff --git a/src/vs/editor/contrib/snippet/common/snippetVariables.ts b/src/vs/editor/contrib/snippet/common/snippetVariables.ts index ee9aadcea505274a26a8c769f6ddff8cb4526f53..ab345893eacb26ba9c8f63e3de38fe3ff8494bcd 100644 --- a/src/vs/editor/contrib/snippet/common/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/common/snippetVariables.ts @@ -5,11 +5,9 @@ 'use strict'; -import { basename, dirname, normalize } from 'vs/base/common/paths'; -import { ICommonCodeEditor, IModel } from 'vs/editor/common/editorCommon'; +import { basename, dirname } from 'vs/base/common/paths'; +import { IModel } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; -import { ISnippetVariableResolver } from './snippet'; - export class EditorSnippetVariableResolver { @@ -67,66 +65,3 @@ export class EditorSnippetVariableResolver { } } } - -export class SnippetVariablesResolver implements ISnippetVariableResolver { - - private readonly _editor: ICommonCodeEditor; - - constructor(editor: ICommonCodeEditor) { - this._editor = editor; - } - - resolve(name: string): string { - const model = this._editor.getModel(); - if (!model) { - throw new Error(); - } - switch (name) { - case 'SELECTION': - case 'TM_SELECTED_TEXT': return this._tmSelectedText(); - case 'TM_CURRENT_LINE': return this._tmCurrentLine(); - case 'TM_CURRENT_WORD': return this._tmCurrentWord(); - case 'TM_LINE_INDEX': return this._tmLineIndex(); - case 'TM_LINE_NUMBER': return this._tmLineNumber(); - case 'TM_FILENAME': return this._tmFilename(); - case 'TM_DIRECTORY': return this._tmDirectory(); - case 'TM_FILEPATH': return this._tmFilepath(); - } - return undefined; - } - - private _tmCurrentLine(): string { - const { positionLineNumber } = this._editor.getSelection(); - return this._editor.getModel().getValueInRange({ startLineNumber: positionLineNumber, startColumn: 1, endLineNumber: positionLineNumber, endColumn: Number.MAX_VALUE }); - } - - private _tmCurrentWord(): string { - const word = this._editor.getModel().getWordAtPosition(this._editor.getPosition()); - return word ? word.word : ''; - } - - private _tmFilename(): string { - return basename(this._editor.getModel().uri.fsPath); - } - - private _tmDirectory(): string { - const dir = dirname(normalize(this._editor.getModel().uri.fsPath)); - return dir !== '.' ? dir : ''; - } - - private _tmFilepath(): string { - return this._editor.getModel().uri.fsPath; - } - - private _tmLineIndex(): string { - return String(this._editor.getSelection().positionLineNumber - 1); - } - - private _tmLineNumber(): string { - return String(this._editor.getSelection().positionLineNumber); - } - - private _tmSelectedText(): string { - return this._editor.getModel().getValueInRange(this._editor.getSelection()); - } -} diff --git a/src/vs/editor/contrib/snippet/test/common/snippet.test.ts b/src/vs/editor/contrib/snippet/test/common/snippet.test.ts deleted file mode 100644 index 256d778fdaaab062a1b057f28cd869c151e084e5..0000000000000000000000000000000000000000 --- a/src/vs/editor/contrib/snippet/test/common/snippet.test.ts +++ /dev/null @@ -1,351 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as assert from 'assert'; -import { Range } from 'vs/editor/common/core/range'; -import { CodeSnippet, ICodeSnippet, ISnippetVariableResolver } from 'vs/editor/contrib/snippet/common/snippet'; - -suite('Editor Contrib - Snippets', () => { - - function assertInternalAndTextmate(internal: string, textmate: string, callback: (snippet: ICodeSnippet) => any) { - callback(CodeSnippet.fromInternal(internal)); - callback(CodeSnippet.fromTextmate(textmate)); - } - - test('Support tab stop order', () => { - - assertInternalAndTextmate( - 'finished:{{}}, second:{{2:name}}, first:{{1:}}, third:{{3:}}', - 'finished:$0, second:${2:name}, first:$1, third:$3', - snippet => { - assert.deepEqual(snippet.lines, ['finished:, second:name, first:, third:']); - assert.equal(snippet.placeHolders.length, 4); - assert.equal(snippet.placeHolders[0].id, '1'); - assert.equal(snippet.placeHolders[0].value, ''); - assert.equal(snippet.placeHolders[1].id, '2'); - assert.equal(snippet.placeHolders[1].value, 'name'); - assert.equal(snippet.placeHolders[2].id, '3'); - assert.equal(snippet.placeHolders[2].value, ''); - assert.equal(snippet.placeHolders[3].id, ''); - assert.equal(snippet.placeHolders[3].value, ''); - assert.equal(snippet.finishPlaceHolderIndex, 3); - }); - }); - - test('Support tab stop order with implicit finish', () => { - - assertInternalAndTextmate( - 't2:{{2:}}, t1:{{1:}}', - 't2:$2, t1:$1', - snippet => { - assert.deepEqual(snippet.lines, ['t2:, t1:']); - assert.equal(snippet.placeHolders.length, 3); - assert.equal(snippet.placeHolders[0].id, '1'); - assert.equal(snippet.placeHolders[0].value, ''); - assert.equal(snippet.placeHolders[1].id, '2'); - assert.equal(snippet.placeHolders[1].value, ''); - assert.equal(snippet.finishPlaceHolderIndex, 2); - }); - }); - - test('Support tab stop order with no finish', () => { - - assertInternalAndTextmate( - 't2:{{2:second}}, t3:{{3:last}}, t1:{{1:first}}', - 't2:${2:second}, t3:${3:last}, t1:${1:first}', - snippet => { - assert.deepEqual(snippet.lines, ['t2:second, t3:last, t1:first']); - assert.equal(snippet.placeHolders.length, 4); - assert.equal(snippet.placeHolders[0].id, '1'); - assert.equal(snippet.placeHolders[0].value, 'first'); - assert.equal(snippet.placeHolders[1].id, '2'); - assert.equal(snippet.placeHolders[1].value, 'second'); - assert.equal(snippet.placeHolders[2].id, '3'); - assert.equal(snippet.placeHolders[2].value, 'last'); - assert.equal(snippet.finishPlaceHolderIndex, 3); - }); - }); - - test('Support tab stop order which does not affect named variable id\'s', () => { - - assertInternalAndTextmate( - '{{first}}-{{2:}}-{{second}}-{{1:}}', - '${first}-${2}-${second}-${1}', - snippet => { - assert.deepEqual(snippet.lines, ['first--second-']); - assert.equal(snippet.placeHolders.length, 5); - assert.equal(snippet.placeHolders[0].id, 'first'); - assert.equal(snippet.placeHolders[1].id, 'second'); - assert.equal(snippet.placeHolders[2].id, '1'); - assert.equal(snippet.placeHolders[3].id, '2'); - } - ); - }); - - test('nested placeholder', () => { - let snippet = CodeSnippet.fromTextmate([ - '', - '\t$0', - '' - ].join('\n')); - - assert.equal(snippet.placeHolders.length, 3); - assert.equal(snippet.finishPlaceHolderIndex, 2); - let [first, second, third] = snippet.placeHolders; - - assert.equal(third.id, 0); - assert.equal(third.occurences.length, 1); - assert.deepEqual(third.occurences[0], new Range(2, 2, 2, 2)); - - assert.equal(second.id, 2); - assert.equal(second.occurences.length, 1); - assert.deepEqual(second.occurences[0], new Range(1, 10, 1, 17)); - - assert.equal(first.id, '1'); - assert.equal(first.occurences.length, 1); - assert.deepEqual(first.occurences[0], new Range(1, 5, 1, 18)); - }); - - test('bug #17541:[snippets] Support default text in mirrors', () => { - - var external = [ - 'begin{${1:enumerate}}', - '\t$0', - 'end{$1}' - ].join('\n'); - - var internal = [ - 'begin\\{{{1:enumerate}}\\}', - '\t{{}}', - 'end\\{{{1:}}\\}' - ].join('\n'); - - assertInternalAndTextmate(internal, external, snippet => { - assert.deepEqual(snippet.lines, [ - 'begin{enumerate}', - '\t', - 'end{enumerate}' - ]); - assert.equal(snippet.placeHolders.length, 2); - assert.equal(snippet.placeHolders[0].id, '1'); - assert.equal(snippet.placeHolders[0].occurences.length, 2); - assert.deepEqual(snippet.placeHolders[0].occurences[0], new Range(1, 7, 1, 16)); - assert.deepEqual(snippet.placeHolders[0].occurences[1], new Range(3, 5, 3, 14)); - assert.equal(snippet.placeHolders[1].id, ''); - assert.equal(snippet.placeHolders[1].occurences.length, 1); - assert.deepEqual(snippet.placeHolders[1].occurences[0], new Range(2, 2, 2, 2)); - }); - }); - - test('bug #7093: Snippet default value is only populated for first variable reference', () => { - var internal = 'logger.error({ logContext: lc, errorContext: `{{1:err}}`, error: {{1:}} });'; - var external = 'logger.error({ logContext: lc, errorContext: `${1:err}`, error: $1 });'; - - assertInternalAndTextmate(internal, external, snippet => { - assert.equal(snippet.lines.length, 1); - assert.equal(snippet.lines[0], 'logger.error({ logContext: lc, errorContext: `err`, error: err });'); - }); - }); - - test('bug #17487:[snippets] four backslashes are required to get one backslash in the inserted text', () => { - - var external = [ - '\\begin{${1:enumerate}}', - '\t$0', - '\\end{$1}' - ].join('\n'); - - var internal = [ - '\\begin\\{{{1:enumerate}}\\}', - '\t{{}}', - '\\end\\{{{1:}}\\}' - ].join('\n'); - - assertInternalAndTextmate(internal, external, snippet => { - assert.deepEqual(snippet.lines, [ - '\\begin{enumerate}', - '\t', - '\\end{enumerate}' - ]); - assert.equal(snippet.placeHolders.length, 2); - assert.equal(snippet.placeHolders[0].id, '1'); - assert.equal(snippet.placeHolders[0].occurences.length, 2); - assert.deepEqual(snippet.placeHolders[0].occurences[0], new Range(1, 8, 1, 17)); - assert.deepEqual(snippet.placeHolders[0].occurences[1], new Range(3, 6, 3, 15)); - assert.equal(snippet.placeHolders[1].id, ''); - assert.equal(snippet.placeHolders[1].occurences.length, 1); - assert.deepEqual(snippet.placeHolders[1].occurences[0], new Range(2, 2, 2, 2)); - }); - }); - - test('issue #3552: Snippet Converted Not Working for literal Dollar Sign', () => { - - let external = '\n\\$scope.\\$broadcast(\'scroll.infiniteScrollComplete\');\n'; - let snippet = CodeSnippet.fromTextmate(external); - assert.equal(snippet.placeHolders.length, 1); - assert.equal(snippet.finishPlaceHolderIndex, 0); - assert.deepEqual(snippet.lines, ['', '$scope.$broadcast(\'scroll.infiniteScrollComplete\');', '']); - }); - - test('bind, adjust indentation', () => { - - // don't move placeholder at the beginning of the line - let snippet = CodeSnippet.fromTextmate([ - 'afterEach((done) => {', - '\t${1}test${2}', - '})' - ].join('\n')); - - // replace tab-stop with two spaces - let boundSnippet = snippet.bind('', 0, 0, { - normalizeIndentation(str: string): string { - return str.replace(/\t/g, ' '); - } - }); - let [first, second] = boundSnippet.placeHolders; - assert.equal(first.occurences.length, 1); - assert.equal(first.occurences[0].startColumn, 3); - assert.equal(second.occurences.length, 1); - assert.equal(second.occurences[0].startColumn, 7); - - // keep tab-stop, identity - boundSnippet = snippet.bind('', 0, 0, { - normalizeIndentation(str: string): string { - return str; - } - }); - [first, second] = boundSnippet.placeHolders; - assert.equal(first.occurences.length, 1); - assert.equal(first.occurences[0].startColumn, 2); - assert.equal(second.occurences.length, 1); - assert.equal(second.occurences[0].startColumn, 6); - }); - - - test('issue #11890: Bad cursor position 1/2', () => { - - let snippet = CodeSnippet.fromTextmate([ - 'afterEach((done) => {', - '${1}\ttest${2}', - '})' - ].join('\n')); - - let boundSnippet = snippet.bind('', 0, 0, { - normalizeIndentation(str: string): string { - return str.replace(/\t/g, ' '); - } - }); - - assert.equal(boundSnippet.lines[1], ' test'); - assert.equal(boundSnippet.placeHolders.length, 3); - assert.equal(boundSnippet.finishPlaceHolderIndex, 2); - - let [first, second] = boundSnippet.placeHolders; - assert.equal(first.occurences.length, 1); - assert.equal(first.occurences[0].startColumn, 1); - assert.equal(second.occurences.length, 1); - assert.equal(second.occurences[0].startColumn, 7); - }); - - test('issue #11890: Bad cursor position 2/2', () => { - - let snippet = CodeSnippet.fromTextmate('${1}\ttest'); - - let boundSnippet = snippet.bind('abc abc abc prefix3', 0, 12, { - normalizeIndentation(str: string): string { - return str.replace(/\t/g, ' '); - } - }); - - assert.equal(boundSnippet.lines[0], '\ttest'); - assert.equal(boundSnippet.placeHolders.length, 2); - assert.equal(boundSnippet.finishPlaceHolderIndex, 1); - - let [first, second] = boundSnippet.placeHolders; - assert.equal(first.occurences.length, 1); - assert.equal(first.occurences[0].startColumn, 13); - assert.equal(second.occurences.length, 1); - assert.equal(second.occurences[0].startColumn, 18); - }); - - test('issue #17989: Bad selection', () => { - - let snippet = CodeSnippet.fromTextmate('${1:HoldMeTight}'); - - let boundSnippet = snippet.bind('abc abc abc prefix3', 0, 12, { - normalizeIndentation(str: string): string { - return str.replace(/\t/g, ' '); - } - }); - - assert.equal(boundSnippet.lines[0], 'HoldMeTight'); - assert.equal(boundSnippet.placeHolders.length, 2); - assert.equal(boundSnippet.finishPlaceHolderIndex, 1); - let [first, second] = boundSnippet.placeHolders; - assert.equal(first.occurences.length, 1); - assert.equal(first.occurences[0].startColumn, 13); - - assert.equal(second.occurences.length, 1); - assert.equal(second.occurences[0].startColumn, 24); - - }); - - test('variables, simple', () => { - - const resolver: ISnippetVariableResolver = { - resolve(name) { - return name.split('').reverse().join(''); - } - }; - - // simple - let snippet = CodeSnippet.fromTextmate('$FOO', resolver); - assert.equal(snippet.lines[0], 'OOF'); - assert.equal(snippet.placeHolders.length, 1); - assert.equal(snippet.placeHolders[0].occurences[0].endColumn, 4); - - snippet = CodeSnippet.fromTextmate('${FOO:BAR}', resolver); - assert.equal(snippet.lines[0], 'OOF'); - assert.equal(snippet.placeHolders.length, 1); - assert.equal(snippet.placeHolders[0].occurences[0].endColumn, 4); - - // placeholder - snippet = CodeSnippet.fromTextmate('${1:$FOO}bar$1', resolver); - assert.equal(snippet.lines[0], 'OOFbarOOF'); - assert.equal(snippet.placeHolders.length, 2); - assert.equal(snippet.placeHolders[0].occurences.length, 2); - assert.equal(snippet.placeHolders[0].occurences[0].startColumn, 1); - assert.equal(snippet.placeHolders[0].occurences[0].endColumn, 4); - assert.equal(snippet.placeHolders[0].occurences[1].startColumn, 7); - assert.equal(snippet.placeHolders[0].occurences[1].endColumn, 10); - assert.equal(snippet.placeHolders[1].occurences.length, 1); - - snippet = CodeSnippet.fromTextmate('${1:${FOO:abc}}bar$1', resolver); - assert.equal(snippet.lines[0], 'OOFbarOOF'); - }); - - test('variables, evil resolver', () => { - - let snippet = CodeSnippet.fromTextmate('$FOO', { resolve(): string { throw new Error(); } }); - assert.equal(snippet.lines[0], 'FOO'); - }); - - test('variables, default', () => { - - let snippet = CodeSnippet.fromTextmate('$FOO', { resolve(): string { return undefined; } }); - assert.equal(snippet.lines[0], 'FOO'); - - snippet = CodeSnippet.fromTextmate('$FOO', { resolve(): string { return ''; } }); - assert.equal(snippet.lines[0], ''); - - snippet = CodeSnippet.fromTextmate('${FOO:BAR}', { resolve(): string { return undefined; } }); - assert.equal(snippet.lines[0], 'BAR'); - - snippet = CodeSnippet.fromTextmate('${FOO:BAR}', { resolve(): string { return ''; } }); - assert.equal(snippet.lines[0], 'BAR'); - }); -}); - diff --git a/src/vs/editor/contrib/snippet/test/common/snippetController.test.ts b/src/vs/editor/contrib/snippet/test/common/snippetController.test.ts deleted file mode 100644 index 75ddf1777cffcff36f87493b2bd0b99e95a667eb..0000000000000000000000000000000000000000 --- a/src/vs/editor/contrib/snippet/test/common/snippetController.test.ts +++ /dev/null @@ -1,553 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as assert from 'assert'; -import { Range } from 'vs/editor/common/core/range'; -import { Position } from 'vs/editor/common/core/position'; -import { Selection } from 'vs/editor/common/core/selection'; -import { CodeSnippet } from 'vs/editor/contrib/snippet/common/snippet'; -import { SnippetController } from 'vs/editor/contrib/snippet/common/snippetController'; -import { MockCodeEditor, withMockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor'; -import { Cursor } from 'vs/editor/common/controller/cursor'; - -class TestSnippetController extends SnippetController { - - isInSnippetMode(): boolean { - return !!this._currentController; - } - -} - -suite('SnippetController', () => { - - function snippetTest(cb: (editor: MockCodeEditor, cursor: Cursor, codeSnippet: CodeSnippet, snippetController: TestSnippetController) => void, lines?: string[]): void { - - if (!lines) { - lines = [ - 'function test() {', - '\tvar x = 3;', - '\tvar arr = [];', - '\t', - '}' - ]; - }; - - withMockCodeEditor(lines, {}, (editor, cursor) => { - editor.getModel().updateOptions({ - insertSpaces: false - }); - let snippetController = editor.registerAndInstantiateContribution(TestSnippetController); - let codeSnippet = CodeSnippet.fromInternal([ - 'for (var {{index}}; {{index}} < {{array}}.length; {{index}}++) {', - '\tvar element = {{array}}[{{index}}];', - '\t{{}}', - '}' - ].join('\n')); - - cb(editor, cursor, codeSnippet, snippetController); - - snippetController.dispose(); - }); - } - - test('Simple accepted', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - - snippetController.run(codeSnippet, 0, 0); - assert.equal(editor.getModel().getLineContent(4), '\tfor (var index; index < array.length; index++) {'); - assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[index];'); - assert.equal(editor.getModel().getLineContent(6), '\t\t'); - assert.equal(editor.getModel().getLineContent(7), '\t}'); - - editor.trigger('test', 'type', { text: 'i' }); - assert.equal(editor.getModel().getLineContent(4), '\tfor (var i; i < array.length; i++) {'); - assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[i];'); - assert.equal(editor.getModel().getLineContent(6), '\t\t'); - assert.equal(editor.getModel().getLineContent(7), '\t}'); - - snippetController.jumpToNextPlaceholder(); - editor.trigger('test', 'type', { text: 'arr' }); - assert.equal(editor.getModel().getLineContent(4), '\tfor (var i; i < arr.length; i++) {'); - assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = arr[i];'); - assert.equal(editor.getModel().getLineContent(6), '\t\t'); - assert.equal(editor.getModel().getLineContent(7), '\t}'); - - snippetController.jumpToPrevPlaceholder(); - editor.trigger('test', 'type', { text: 'j' }); - assert.equal(editor.getModel().getLineContent(4), '\tfor (var j; j < arr.length; j++) {'); - assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = arr[j];'); - assert.equal(editor.getModel().getLineContent(6), '\t\t'); - assert.equal(editor.getModel().getLineContent(7), '\t}'); - - snippetController.acceptSnippet(); - assert.deepEqual(editor.getPosition(), new Position(6, 3)); - }); - }); - - test('Simple canceled', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - - snippetController.run(codeSnippet, 0, 0); - assert.equal(editor.getModel().getLineContent(4), '\tfor (var index; index < array.length; index++) {'); - assert.equal(editor.getModel().getLineContent(5), '\t\tvar element = array[index];'); - assert.equal(editor.getModel().getLineContent(6), '\t\t'); - assert.equal(editor.getModel().getLineContent(7), '\t}'); - - snippetController.leaveSnippet(); - assert.deepEqual(editor.getPosition(), new Position(4, 16)); - }); - }); - - test('Stops when deleting lines above', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - editor.getModel().applyEdits([{ - forceMoveMarkers: false, - identifier: null, - isAutoWhitespaceEdit: false, - range: new Range(1, 1, 3, 1), - text: null - }]); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Stops when deleting lines below', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - editor.getModel().applyEdits([{ - forceMoveMarkers: false, - identifier: null, - isAutoWhitespaceEdit: false, - range: new Range(8, 1, 8, 100), - text: null - }]); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Stops when inserting lines above', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - editor.getModel().applyEdits([{ - forceMoveMarkers: false, - identifier: null, - isAutoWhitespaceEdit: false, - range: new Range(1, 100, 1, 100), - text: '\nHello' - }]); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Stops when inserting lines below', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - editor.getModel().applyEdits([{ - forceMoveMarkers: false, - identifier: null, - isAutoWhitespaceEdit: false, - range: new Range(8, 100, 8, 100), - text: '\nHello' - }]); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Stops when calling model.setValue()', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - editor.getModel().setValue('goodbye'); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Stops when undoing', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - editor.getModel().undo(); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Stops when moving cursor outside', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - editor.setPosition({ lineNumber: 1, column: 1 }); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Stops when disconnecting editor model', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - editor.setModel(null); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Stops when disposing editor', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setPosition({ lineNumber: 4, column: 2 }); - snippetController.run(codeSnippet, 0, 0); - - snippetController.dispose(); - - assert.equal(snippetController.isInSnippetMode(), false); - }); - }); - - test('Final tabstop with multiple selections', () => { - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setSelections([ - new Selection(1, 1, 1, 1), - new Selection(2, 1, 2, 1), - ]); - - codeSnippet = CodeSnippet.fromInternal('foo{{}}'); - snippetController.run(codeSnippet, 0, 0); - - assert.equal(editor.getSelections().length, 2); - const [first, second] = editor.getSelections(); - assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString()); - assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString()); - }); - - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setSelections([ - new Selection(1, 1, 1, 1), - new Selection(2, 1, 2, 1), - ]); - - codeSnippet = CodeSnippet.fromInternal('foo{{}}bar'); - snippetController.run(codeSnippet, 0, 0); - - assert.equal(editor.getSelections().length, 2); - const [first, second] = editor.getSelections(); - assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString()); - assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString()); - }); - - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setSelections([ - new Selection(1, 1, 1, 1), - new Selection(1, 5, 1, 5), - ]); - - codeSnippet = CodeSnippet.fromInternal('foo{{}}bar'); - snippetController.run(codeSnippet, 0, 0); - - assert.equal(editor.getSelections().length, 2); - const [first, second] = editor.getSelections(); - assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString()); - assert.ok(second.equalsRange({ startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 14 }), second.toString()); - }); - - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setSelections([ - new Selection(1, 1, 1, 1), - new Selection(1, 5, 1, 5), - ]); - - codeSnippet = CodeSnippet.fromInternal('foo\n{{}}\nbar'); - snippetController.run(codeSnippet, 0, 0); - - assert.equal(editor.getSelections().length, 2); - const [first, second] = editor.getSelections(); - assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString()); - assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString()); - }); - - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setSelections([ - new Selection(1, 1, 1, 1), - new Selection(1, 5, 1, 5), - ]); - - codeSnippet = CodeSnippet.fromInternal('foo\n{{}}\nbar'); - snippetController.run(codeSnippet, 0, 0); - - assert.equal(editor.getSelections().length, 2); - const [first, second] = editor.getSelections(); - assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString()); - assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString()); - }); - - snippetTest((editor, cursor, codeSnippet, snippetController) => { - editor.setSelections([ - new Selection(2, 7, 2, 7), - ]); - - codeSnippet = CodeSnippet.fromInternal('xo{{}}r'); - snippetController.run(codeSnippet, 1, 0); - - assert.equal(editor.getSelections().length, 1); - assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 8, endColumn: 8, endLineNumber: 2 })); - }); - }); - - test('Final tabstop, #11742 simple', () => { - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelection(new Selection(1, 19, 1, 19)); - - codeSnippet = CodeSnippet.fromTextmate('{{% url_**$1** %}}'); - controller.run(codeSnippet, 2, 0); - - assert.equal(editor.getSelections().length, 1); - assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 27, endLineNumber: 1, endColumn: 27 })); - assert.equal(editor.getModel().getValue(), 'example example {{% url_**** %}}'); - - }, ['example example sc']); - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelection(new Selection(1, 3, 1, 3)); - - codeSnippet = CodeSnippet.fromTextmate([ - 'afterEach((done) => {', - '\t${1}test', - '});' - ].join('\n')); - - controller.run(codeSnippet, 2, 0); - - assert.equal(editor.getSelections().length, 1); - assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), editor.getSelection().toString()); - assert.equal(editor.getModel().getValue(), 'afterEach((done) => {\n\ttest\n});'); - - }, ['af']); - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelection(new Selection(1, 3, 1, 3)); - - codeSnippet = CodeSnippet.fromTextmate([ - 'afterEach((done) => {', - '${1}\ttest', - '});' - ].join('\n')); - - controller.run(codeSnippet, 2, 0); - - assert.equal(editor.getSelections().length, 1); - assert.ok(editor.getSelection().equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), editor.getSelection().toString()); - assert.equal(editor.getModel().getValue(), 'afterEach((done) => {\n\ttest\n});'); - - }, ['af']); - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelection(new Selection(1, 9, 1, 9)); - - codeSnippet = CodeSnippet.fromTextmate([ - 'aft${1}er' - ].join('\n')); - - controller.run(codeSnippet, 8, 0); - - assert.equal(editor.getModel().getValue(), 'after'); - assert.equal(editor.getSelections().length, 1); - assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), editor.getSelection().toString()); - - }, ['afterone']); - }); - - test('Final tabstop, #11742 different indents', () => { - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(2, 4, 2, 4), - new Selection(1, 3, 1, 3) - ]); - - codeSnippet = CodeSnippet.fromTextmate([ - 'afterEach((done) => {', - '\t${0}test', - '});' - ].join('\n')); - - controller.run(codeSnippet, 2, 0); - - assert.equal(editor.getSelections().length, 2); - const [first, second] = editor.getSelections(); - - assert.ok(first.equalsRange({ startLineNumber: 5, startColumn: 3, endLineNumber: 5, endColumn: 3 }), first.toString()); - assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), second.toString()); - - }, ['af', '\taf']); - }); - - test('Final tabstop, #11890 stay at the beginning', () => { - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(1, 5, 1, 5) - ]); - - codeSnippet = CodeSnippet.fromTextmate([ - 'afterEach((done) => {', - '${1}\ttest', - '});' - ].join('\n')); - - controller.run(codeSnippet, 2, 0); - - assert.equal(editor.getSelections().length, 1); - const [first] = editor.getSelections(); - - assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 3, endLineNumber: 2, endColumn: 3 }), first.toString()); - - }, [' af']); - }); - - test('Final tabstop, no tabstop', () => { - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(1, 3, 1, 3) - ]); - - codeSnippet = CodeSnippet.fromTextmate('afterEach'); - - controller.run(codeSnippet, 2, 0); - - assert.ok(editor.getSelection().equalsRange({ startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 10 })); - - }, ['af', '\taf']); - }); - - test('Multiple cursor and overwriteBefore/After, issue #11060', () => { - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(1, 7, 1, 7), - new Selection(2, 4, 2, 4) - ]); - - codeSnippet = CodeSnippet.fromTextmate('_foo'); - controller.run(codeSnippet, 1, 0); - assert.equal(editor.getModel().getValue(), 'this._foo\nabc_foo'); - - }, ['this._', 'abc']); - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(1, 7, 1, 7), - new Selection(2, 4, 2, 4) - ]); - - codeSnippet = CodeSnippet.fromTextmate('XX'); - controller.run(codeSnippet, 1, 0); - assert.equal(editor.getModel().getValue(), 'this.XX\nabcXX'); - - }, ['this._', 'abc']); - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(1, 7, 1, 7), - new Selection(2, 4, 2, 4), - new Selection(3, 5, 3, 5) - ]); - - codeSnippet = CodeSnippet.fromTextmate('_foo'); - controller.run(codeSnippet, 1, 0); - assert.equal(editor.getModel().getValue(), 'this._foo\nabc_foo\ndef_foo'); - - }, ['this._', 'abc', 'def_']); - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(1, 7, 1, 7), - new Selection(2, 4, 2, 4), - new Selection(3, 6, 3, 6) - ]); - - codeSnippet = CodeSnippet.fromTextmate('._foo'); - controller.run(codeSnippet, 2, 0); - assert.equal(editor.getModel().getValue(), 'this._foo\nabc._foo\ndef._foo'); - - }, ['this._', 'abc', 'def._']); - - }); - - test('Multiple cursor and overwriteBefore/After, #16277', () => { - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(1, 5, 1, 5), - new Selection(2, 5, 2, 5), - ]); - - codeSnippet = CodeSnippet.fromTextmate('document'); - controller.run(codeSnippet, 3, 0); - assert.equal(editor.getModel().getValue(), '{document}\n{document && true}'); - - }, ['{foo}', '{foo && true}']); - }); - - test('Insert snippet twice, #19449', () => { - - snippetTest((editor, cursor, codeSnippet, controller) => { - - editor.setSelections([ - new Selection(1, 1, 1, 1) - ]); - - codeSnippet = CodeSnippet.fromTextmate('for (var ${1:i}=0; ${1:i} { - - editor.setSelections([ - new Selection(1, 1, 1, 1) - ]); - - codeSnippet = CodeSnippet.fromTextmate('for (let ${1:i}=0; ${1:i} { + if (candidate instanceof Placeholder) { + let index = Number(candidate.index); + if (index > maxIndex) { + maxIndex = index; + } + } + return true; + }); + + // rewrite final tabstops + walk(marker, candidate => { + if (candidate instanceof Placeholder) { + if (candidate.isFinalTabstop) { + candidate.index = String(++maxIndex); + } + } + return true; + }); + + // write back as string + function toSnippetString(marker: Marker): string { + if (marker instanceof Text) { + return SnippetParser.escape(marker.string); + + } else if (marker instanceof Placeholder) { + if (marker.defaultValue.length > 0) { + return `\${${marker.index}:${marker.defaultValue.map(toSnippetString).join('')}}`; + } else { + return `\$${marker.index}`; + } + } else if (marker instanceof Variable) { + if (marker.defaultValue.length > 0) { + return `\${${marker.name}:${marker.defaultValue.map(toSnippetString).join('')}}`; + } else { + return `\$${marker.name}`; + } + } else { + throw new Error('unexpected marker: ' + marker); + } + } + return marker.map(toSnippetString).join(''); + } + public getRangeToReplace(value: string, start: number, end: number): Range { //console.log('value', value); let startPosition = this.getPositionFromOffset(start);