diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 4316309de31..40acfc5762f 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -395,6 +395,7 @@ export interface ISuggestion { label: string; codeSnippet: string; type: string; + textEdit?: EditorCommon.ISingleEditOperation; highlights?: IHighlight[]; typeLabel?: string; documentationLabel?: string; diff --git a/src/vs/editor/contrib/suggest/common/suggest.ts b/src/vs/editor/contrib/suggest/common/suggest.ts index 482faaa32c7..c5423f9a3ab 100644 --- a/src/vs/editor/contrib/suggest/common/suggest.ts +++ b/src/vs/editor/contrib/suggest/common/suggest.ts @@ -5,7 +5,7 @@ 'use strict'; import {sequence} from 'vs/base/common/async'; -import {IModel, IPosition} from 'vs/editor/common/editorCommon'; +import {IModel, IPosition, IRange} from 'vs/editor/common/editorCommon'; import {TPromise} from 'vs/base/common/winjs.base'; import {mixin} from 'vs/base/common/objects'; import {onUnexpectedError, illegalArgument} from 'vs/base/common/errors'; @@ -42,29 +42,52 @@ export function suggest(model: IModel, position: IPosition, triggerCharacter: st // for each support in the group ask for suggestions let promises = supports.map(support => { - return support.suggest(resource, position, triggerCharacter).then(value => { + return support.suggest(resource, position, triggerCharacter).then(values => { let result: ISuggestions2[] = []; - for (let suggestions of value) { + for (let suggestResult of values) { - if (!suggestions - || !Array.isArray(suggestions.suggestions) - || suggestions.suggestions.length === 0) { + if (!suggestResult + || !Array.isArray(suggestResult.suggestions) + || suggestResult.suggestions.length === 0) { continue; } const suggestions2: ISuggestions2 = { support, - currentWord: suggestions.currentWord, - incomplete: suggestions.incomplete, - overwriteAfter: suggestions.overwriteAfter, - overwriteBefore: suggestions.overwriteBefore, - suggestions: suggestions.suggestions + currentWord: suggestResult.currentWord, + incomplete: suggestResult.incomplete, + overwriteAfter: suggestResult.overwriteAfter, + overwriteBefore: suggestResult.overwriteBefore, + suggestions: suggestResult.suggestions + } + + const defaultRange: IRange = { + startLineNumber: position.lineNumber, + startColumn: position.column, + endLineNumber: position.lineNumber, + endColumn: position.column + }; + + if (typeof suggestResult.overwriteBefore === 'number' && suggestResult.overwriteBefore > 0) { + defaultRange.startColumn -= suggestResult.overwriteBefore; + } + if (typeof suggestResult.overwriteAfter === 'number' && suggestResult.overwriteAfter > 0) { + defaultRange.endColumn += suggestResult.overwriteAfter + } + + for (let suggestion of suggestResult.suggestions) { + if (!suggestion.textEdit) { + suggestion.textEdit = { + text: suggestion.codeSnippet, + range: defaultRange + }; + } } // add additional properties - mixin(suggestions2, suggestions, false); + mixin(suggestions2, suggestResult, false); result.push(suggestions2); } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatureCommands.ts b/src/vs/workbench/api/common/extHostLanguageFeatureCommands.ts index 6274110b2c4..9c8b5e54693 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatureCommands.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatureCommands.ts @@ -168,7 +168,7 @@ export class ExtHostLanguageFeatureCommands { for (let group of value) { for (let suggestions of group) { for (let suggestion of suggestions.suggestions) { - const item = typeConverters.Suggest.to(suggestion, suggestions); + const item = typeConverters.Suggest.to(suggestion); items.push(item); } } diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 7c08bc24de9..af695bd229a 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -337,7 +337,7 @@ class DocumentFormattingAdapter implements modes.IFormattingSupport { return asWinJsPromise(token => this._provider.provideDocumentFormattingEdits(doc, options, token)).then(value => { if (Array.isArray(value)) { - return value.map(TypeConverters.fromTextEdit); + return value.map(TypeConverters.TextEdit.from); } }); } @@ -360,7 +360,7 @@ class RangeFormattingAdapter implements modes.IFormattingSupport { return asWinJsPromise(token => this._provider.provideDocumentRangeFormattingEdits(doc, ran, options, token)).then(value => { if (Array.isArray(value)) { - return value.map(TypeConverters.fromTextEdit); + return value.map(TypeConverters.TextEdit.from); } }); } @@ -385,7 +385,7 @@ class OnTypeFormattingAdapter implements modes.IFormattingSupport { return asWinJsPromise(token => this._provider.provideOnTypeFormattingEdits(doc, pos, ch, options, token)).then(value => { if (Array.isArray(value)) { - return value.map(TypeConverters.fromTextEdit); + return value.map(TypeConverters.TextEdit.from); } }); } @@ -489,7 +489,7 @@ class SuggestAdapter implements modes.ISuggestSupport { for (let i = 0; i < value.length; i++) { const item = value[i]; - const [suggestion] = TypeConverters.Suggest.from(item, defaultSuggestions); SuggestAdapter._convertCompletionItem(item); + const suggestion = TypeConverters.Suggest.from(item); if (item.textEdit) { @@ -540,22 +540,10 @@ class SuggestAdapter implements modes.ISuggestSupport { return TPromise.as(suggestion); } return asWinJsPromise(token => this._provider.resolveCompletionItem(item, token)).then(resolvedItem => { - return SuggestAdapter._convertCompletionItem(resolvedItem || item); + return TypeConverters.Suggest.from(resolvedItem || item); }); } - private static _convertCompletionItem(item: vscode.CompletionItem): modes.ISuggestion { - return { - label: item.label, - codeSnippet: item.insertText || item.label, - type: CompletionItemKind[item.kind || CompletionItemKind.Text].toString().toLowerCase(), - typeLabel: item.detail, - documentationLabel: item.documentation, - sortText: item.sortText, - filterText: item.filterText - }; - } - getFilter(): any{ throw new Error('illegal state'); } diff --git a/src/vs/workbench/api/common/pluginHostTypeConverters.ts b/src/vs/workbench/api/common/pluginHostTypeConverters.ts index 2a4770bd585..9410fc10a9c 100644 --- a/src/vs/workbench/api/common/pluginHostTypeConverters.ts +++ b/src/vs/workbench/api/common/pluginHostTypeConverters.ts @@ -168,10 +168,15 @@ export function fromRangeOrRangeWithMessage(ranges:vscode.Range[]|vscode.Decorat } } -export function fromTextEdit(edit: vscode.TextEdit) { - return { - text: edit.newText, - range: fromRange(edit.range) +export const TextEdit = { + from(edit: vscode.TextEdit): ISingleEditOperation{ + return { + text: edit.newText, + range: fromRange(edit.range) + } + }, + to(edit: ISingleEditOperation): vscode.TextEdit { + return new types.TextEdit(toRange(edit.range), edit.text); } } @@ -315,25 +320,21 @@ export function toDocumentHighlight(occurrence: modes.IOccurence): types.Documen export const Suggest = { - from(item: vscode.CompletionItem, defaultContainer: modes.ISuggestResult): [modes.ISuggestion, modes.ISuggestResult] { + from(item: vscode.CompletionItem): modes.ISuggestion { const suggestion: modes.ISuggestion = { label: item.label, codeSnippet: item.insertText || item.label, type: types.CompletionItemKind[item.kind || types.CompletionItemKind.Text].toString().toLowerCase(), typeLabel: item.detail, + textEdit: item.textEdit && TextEdit.from(item.textEdit), documentationLabel: item.documentation, sortText: item.sortText, filterText: item.filterText }; - - if (item.textEdit) { - // TODO@joh - } - - return [suggestion, defaultContainer]; + return suggestion; }, - to(suggestion: modes.ISuggestion, container: modes.ISuggestResult): types.CompletionItem { + to(suggestion: modes.ISuggestion): types.CompletionItem { const result = new types.CompletionItem(suggestion.label); result.insertText = suggestion.codeSnippet; result.kind = types.CompletionItemKind[suggestion.type.charAt(0).toUpperCase() + suggestion.type.substr(1)]; @@ -341,7 +342,7 @@ export const Suggest = { result.documentation = suggestion.documentationLabel; result.sortText = suggestion.sortText; result.filterText = suggestion.filterText; - // todo@joh edit range! + result.textEdit = suggestion.textEdit && TextEdit.to(suggestion.textEdit); return result; } } diff --git a/src/vs/workbench/test/common/api/extHostLanguageFeatureCommands.test.ts b/src/vs/workbench/test/common/api/extHostLanguageFeatureCommands.test.ts index 2a3aa398dd7..3d5b78dfb75 100644 --- a/src/vs/workbench/test/common/api/extHostLanguageFeatureCommands.test.ts +++ b/src/vs/workbench/test/common/api/extHostLanguageFeatureCommands.test.ts @@ -236,11 +236,11 @@ suite('ExtHostLanguageFeatureCommands', function() { test('Suggest, back and forth', function(done) { disposables.push(extHost.registerCompletionItemProvider(defaultSelector, { - provideCompletionItems(): any { - return [ - new types.CompletionItem('item1'), - new types.CompletionItem('item2') - ]; + provideCompletionItems(doc, pos): any { + let a = new types.CompletionItem('item1'); + let b = new types.CompletionItem('item2'); + b.textEdit = types.TextEdit.replace(new types.Range(0, 0, 0, 4), 'foo'); + return [a, b]; } }, [])); @@ -249,7 +249,18 @@ suite('ExtHostLanguageFeatureCommands', function() { assert.equal(values.length, 2); let [first, second] = values; assert.equal(first.label, 'item1'); + assert.equal(first.textEdit.newText, 'item1'); + assert.equal(first.textEdit.range.start.line, 0); + assert.equal(first.textEdit.range.start.character, 0); + assert.equal(first.textEdit.range.end.line, 0); + assert.equal(first.textEdit.range.end.character, 0); + assert.equal(second.label, 'item2'); + assert.equal(second.textEdit.newText, 'foo'); + assert.equal(second.textEdit.range.start.line, 0); + assert.equal(second.textEdit.range.start.character, 0); + assert.equal(second.textEdit.range.end.line, 0); + assert.equal(second.textEdit.range.end.character, 4); done(); }); });