Merge pull request #124707 from microsoft/alex/ghost-text

ghost text updates
This commit is contained in:
Alexandru Dima
2021-05-26 23:14:24 +02:00
committed by GitHub
11 changed files with 289 additions and 84 deletions
+1 -1
View File
@@ -3315,7 +3315,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
showIcons: true,
showStatusBar: false,
showSuggestionPreview: false,
suggestionPreviewExpanded: false,
suggestionPreviewExpanded: true,
showInlineDetails: true,
showMethods: true,
showFunctions: true,
+10 -5
View File
@@ -718,27 +718,32 @@ export interface InlineCompletion {
* If the text contains a line break, the range must end at the end of a line.
* If existing text should be replaced, the existing text must be a prefix of the text to insert.
*/
text: string;
readonly text: string;
/**
* The range to replace.
* Must begin and end on the same line.
*/
range?: IRange;
readonly range?: IRange;
readonly command?: Command;
}
/**
* @internal
*/
export interface InlineCompletions<TItem extends InlineCompletion = InlineCompletion> {
items: TItem[];
readonly items: readonly TItem[];
}
/**
* @internal
*/
export interface InlineCompletionsProvider {
provideInlineCompletions(model: model.ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult<InlineCompletions>;
export interface InlineCompletionsProvider<T extends InlineCompletions = InlineCompletions> {
provideInlineCompletions(model: model.ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult<T>;
handleItemDidShow?(completions: T, item: T['items'][number]): void;
freeInlineCompletions(completions: T): void;
}
export interface CodeAction {
@@ -13,6 +13,7 @@ import { GhostTextWidget } from 'vs/editor/contrib/inlineCompletions/ghostTextWi
import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsModel';
import { SuggestWidgetAdapterModel } from 'vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel';
import * as nls from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -31,7 +32,7 @@ class GhostTextController extends Disposable {
constructor(
private readonly editor: ICodeEditor,
@IInstantiationService instantiationService: IInstantiationService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
super();
@@ -55,7 +56,7 @@ class GhostTextController extends Disposable {
this.activeController.value = undefined;
this.activeController.value = this.editor.hasModel() && suggestOptions.showSuggestionPreview
? new ActiveGhostTextController(this.editor, this.widget, this.contextKeys)
? this.instantiationService.createInstance(ActiveGhostTextController, this.editor, this.widget, this.contextKeys)
: undefined;
}
@@ -92,12 +93,13 @@ class GhostTextContextKeys {
*/
export class ActiveGhostTextController extends Disposable {
private readonly suggestWidgetAdapterModel = new SuggestWidgetAdapterModel(this.editor);
private readonly inlineCompletionsModel = new InlineCompletionsModel(this.editor);
private readonly inlineCompletionsModel = new InlineCompletionsModel(this.editor, this.commandService);
constructor(
private readonly editor: IActiveCodeEditor,
private readonly widget: GhostTextWidget,
private readonly contextKeys: GhostTextContextKeys,
@ICommandService private readonly commandService: ICommandService,
) {
super();
@@ -226,7 +228,7 @@ export class ShowPreviousInlineCompletionAction extends EditorAction {
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise<void> {
const controller = GhostTextController.get(editor);
if (controller) {
controller.showNextInlineCompletion();
controller.showPreviousInlineCompletion();
}
}
}
@@ -81,7 +81,6 @@ export class GhostTextWidget extends Disposable {
private decorationIds: string[] = [];
private viewZoneId: string | null = null;
private viewMoreContentWidget: ViewMoreLinesContentWidget | null = null;
private viewMoreContentWidget2: ViewMoreLinesContentWidget | null = null;
constructor(
private readonly editor: ICodeEditor,
@@ -191,11 +190,6 @@ export class GhostTextWidget extends Disposable {
this.viewMoreContentWidget = null;
}
if (this.viewMoreContentWidget2) {
this.viewMoreContentWidget2.dispose();
this.viewMoreContentWidget2 = null;
}
this.editor.changeViewZones((changeAccessor) => {
if (this.viewZoneId) {
changeAccessor.removeZone(this.viewZoneId);
@@ -348,9 +342,20 @@ class ViewMoreLinesContentWidget extends Disposable implements IContentWidget {
}
registerThemingParticipant((theme, collector) => {
const suggestPreviewForeground = theme.getColor(editorSuggestPreviewOpacity);
if (suggestPreviewForeground) {
collector.addRule(`.monaco-editor .suggest-preview-text { opacity: ${suggestPreviewForeground.rgba.a}; }`);
function opaque(color: Color): Color {
const { r, b, g } = color.rgba;
return new Color(new RGBA(r, g, b, 255));
}
const opacity = String(suggestPreviewForeground.rgba.a);
const color = Color.Format.CSS.format(opaque(suggestPreviewForeground))!;
// We need to override the only used token type .mtk1
collector.addRule(`.monaco-editor .suggest-preview-text .mtk1 { opacity: ${opacity}; color: ${color}; }`);
}
const suggestPreviewBorder = theme.getColor(editorSuggestPreviewBorder);
@@ -5,7 +5,7 @@
import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import * as strings from 'vs/base/common/strings';
@@ -13,9 +13,10 @@ import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProviderRegistry, InlineCompletionTriggerKind } from 'vs/editor/common/modes';
import { InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, InlineCompletionsProviderRegistry, InlineCompletionTriggerKind } from 'vs/editor/common/modes';
import { BaseGhostTextWidgetModel, GhostText, GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostTextWidget';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { ICommandService } from 'vs/platform/commands/common/commands';
export class InlineCompletionsModel extends Disposable implements GhostTextWidgetModel {
protected readonly onDidChangeEmitter = new Emitter<void>();
@@ -25,7 +26,10 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge
private active: boolean = false;
constructor(private readonly editor: IActiveCodeEditor) {
constructor(
private readonly editor: IActiveCodeEditor,
private readonly commandService: ICommandService
) {
super();
this._register(this.editor.onDidChangeModelContent((e) => {
@@ -83,7 +87,7 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge
if (this.completionSession.value) {
return;
}
this.completionSession.value = new InlineCompletionsSession(this.editor, this.editor.getPosition(), () => this.active);
this.completionSession.value = new InlineCompletionsSession(this.editor, this.editor.getPosition(), () => this.active, this.commandService);
this.completionSession.value.takeOwnership(
this.completionSession.value.onDidChange(() => {
this.onDidChangeEmitter.fire();
@@ -120,7 +124,7 @@ class CachedInlineCompletion {
public lastRange: Range;
constructor(
public readonly inlineCompletion: NormalizedInlineCompletion,
public readonly inlineCompletion: LiveInlineCompletion,
public readonly decorationId: string,
) {
this.lastRange = inlineCompletion.range;
@@ -130,19 +134,38 @@ class CachedInlineCompletion {
class InlineCompletionsSession extends BaseGhostTextWidgetModel {
public readonly minReservedLineCount = 0;
private updatePromise: CancelablePromise<NormalizedInlineCompletions> | undefined = undefined;
private updatePromise: CancelablePromise<LiveInlineCompletions> | undefined = undefined;
private cachedCompletions: CachedInlineCompletion[] | undefined = undefined;
private cachedCompletionsSource: LiveInlineCompletions | undefined = undefined;
private updateSoon = this._register(new RunOnceScheduler(() => this.update(), 50));
private readonly textModel = this.editor.getModel();
constructor(editor: IActiveCodeEditor, private readonly triggerPosition: Position, private readonly shouldUpdate: () => boolean) {
constructor(
editor: IActiveCodeEditor,
private readonly triggerPosition: Position,
private readonly shouldUpdate: () => boolean,
private readonly commandService: ICommandService,
) {
super(editor);
this._register(toDisposable(() => {
this.clearGhostTextPromise();
this.clearCache();
}));
let lastCompletionItem: InlineCompletion | undefined = undefined;
this._register(this.onDidChange(() => {
const currentCompletion = this.currentCompletion;
if (currentCompletion && currentCompletion.sourceInlineCompletion !== lastCompletionItem) {
lastCompletionItem = currentCompletion.sourceInlineCompletion;
const provider = currentCompletion.sourceProvider;
if (provider.handleItemDidShow) {
provider.handleItemDidShow(currentCompletion.sourceInlineCompletions, lastCompletionItem);
}
}
}));
this._register(this.editor.onDidChangeModelDecorations(e => {
if (!this.cachedCompletions) {
return;
@@ -219,14 +242,18 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel {
return currentCompletion ? inlineCompletionToGhostText(currentCompletion, this.editor.getModel()) : undefined;
}
get currentCompletion(): NormalizedInlineCompletion | undefined {
get currentCompletion(): LiveInlineCompletion | undefined {
const completion = this.currentCachedCompletion;
if (!completion) {
return undefined;
}
return {
text: completion.inlineCompletion.text,
range: completion.lastRange
range: completion.lastRange,
command: completion.inlineCompletion.command,
sourceProvider: completion.inlineCompletion.sourceProvider,
sourceInlineCompletions: completion.inlineCompletion.sourceInlineCompletions,
sourceInlineCompletion: completion.inlineCompletion.sourceInlineCompletion,
};
}
@@ -262,15 +289,24 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel {
}))
);
this.cachedCompletionsSource?.dispose();
this.cachedCompletionsSource = result;
this.cachedCompletions = result.items.map((item, idx) => new CachedInlineCompletion(item, decorationIds[idx]));
this.onDidChangeEmitter.fire();
}, onUnexpectedError);
}
private clearCache(): void {
if (this.cachedCompletions) {
this.editor.deltaDecorations(this.cachedCompletions.map(c => c.decorationId), []);
const completions = this.cachedCompletions;
if (completions) {
this.cachedCompletions = undefined;
this.editor.deltaDecorations(completions.map(c => c.decorationId), []);
if (!this.cachedCompletionsSource) {
throw new Error('Unexpected state');
}
this.cachedCompletionsSource.dispose();
this.cachedCompletionsSource = undefined;
}
}
@@ -292,7 +328,7 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel {
}
}
public commit(completion: NormalizedInlineCompletion): void {
public commit(completion: LiveInlineCompletion): void {
this.clearCache();
this.editor.executeEdits(
'inlineCompletions.accept',
@@ -300,6 +336,10 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel {
EditOperation.replaceMove(completion.range, completion.text)
]
);
if (completion.command) {
this.commandService.executeCommand(completion.command.id, ...(completion.command.arguments || [])).then(undefined, onUnexpectedExternalError);
}
this.onDidChangeEmitter.fire();
}
}
@@ -308,12 +348,6 @@ export interface NormalizedInlineCompletion extends InlineCompletion {
range: Range;
}
/**
* Contains no duplicated items.
*/
export interface NormalizedInlineCompletions extends InlineCompletions<NormalizedInlineCompletion> {
}
export function inlineCompletionToGhostText(inlineCompletion: NormalizedInlineCompletion, textModel: ITextModel): GhostText | undefined {
const valueToBeReplaced = textModel.getValueInRange(inlineCompletion.range);
if (!inlineCompletion.text.startsWith(valueToBeReplaced)) {
@@ -328,6 +362,20 @@ export function inlineCompletionToGhostText(inlineCompletion: NormalizedInlineCo
};
}
export interface LiveInlineCompletion extends InlineCompletion {
range: Range;
sourceProvider: InlineCompletionsProvider;
sourceInlineCompletion: InlineCompletion;
sourceInlineCompletions: InlineCompletions;
}
/**
* Contains no duplicated items.
*/
export interface LiveInlineCompletions extends InlineCompletions<LiveInlineCompletion> {
dispose(): void;
}
function getDefaultRange(position: Position, model: ITextModel): Range {
const word = model.getWordAtPosition(position);
const maxColumn = model.getLineMaxColumn(position.lineNumber);
@@ -343,25 +391,50 @@ async function provideInlineCompletions(
model: ITextModel,
context: InlineCompletionContext,
token: CancellationToken = CancellationToken.None
): Promise<NormalizedInlineCompletions> {
): Promise<LiveInlineCompletions> {
const defaultReplaceRange = getDefaultRange(position, model);
const providers = InlineCompletionsProviderRegistry.all(model);
const results = await Promise.all(
providers.map(provider => provider.provideInlineCompletions(model, position, context, token))
providers.map(
async provider => {
const completions = await provider.provideInlineCompletions(model, position, context, token);
return ({
completions,
provider,
dispose: () => {
if (completions) {
provider.freeInlineCompletions(completions);
}
}
});
}
)
);
const itemsByHash = new Map<string, NormalizedInlineCompletion>();
const itemsByHash = new Map<string, LiveInlineCompletion>();
for (const result of results) {
if (result) {
for (const item of result.items.map<NormalizedInlineCompletion>(item => ({
const completions = result.completions;
if (completions) {
for (const item of completions.items.map<LiveInlineCompletion>(item => ({
text: item.text,
range: item.range ? Range.lift(item.range) : defaultReplaceRange
range: item.range ? Range.lift(item.range) : defaultReplaceRange,
command: item.command,
sourceProvider: result.provider,
sourceInlineCompletions: completions,
sourceInlineCompletion: item
}))) {
itemsByHash.set(JSON.stringify({ text: item.text, range: item.range }), item);
}
}
}
return { items: [...itemsByHash.values()] };
return {
items: [...itemsByHash.values()],
dispose: () => {
for (const result of results) {
result.dispose();
}
},
};
}
+54 -25
View File
@@ -3065,27 +3065,19 @@ declare module 'vscode' {
//#region https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima
export class InlineCompletionItem {
/**
* The text to insert.
* If the text contains a line break, the range must end at the end of a line.
* If existing text should be replaced, the existing text must be a prefix of the text to insert.
*/
text: string;
/**
* The range to replace.
* Must begin and end on the same line.
*/
range?: Range;
constructor(text: string);
export namespace languages {
export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable;
}
export class InlineCompletionList {
items: InlineCompletionItem[];
export interface InlineCompletionItemProvider<T extends InlineCompletionItem = InlineCompletionItem> {
provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult<InlineCompletionList<T>>;
}
constructor(items: InlineCompletionItem[]);
export interface InlineCompletionContext {
/**
* How the completion was triggered.
*/
readonly triggerKind: InlineCompletionTriggerKind;
}
/**
@@ -3104,19 +3096,56 @@ declare module 'vscode' {
*/
Explicit = 1,
}
export interface InlineCompletionContext {
export class InlineCompletionList<T extends InlineCompletionItem = InlineCompletionItem> {
items: T[];
constructor(items: T[]);
}
export class InlineCompletionItem {
/**
* How the completion was triggered.
* The text to insert.
* If the text contains a line break, the range must end at the end of a line.
* If existing text should be replaced, the existing text must be a prefix of the text to insert.
*/
text: string;
/**
* The range to replace.
* Must begin and end on the same line.
*/
range?: Range;
/**
* An optional {@link Command} that is executed *after* inserting this completion.
*/
readonly triggerKind: InlineCompletionTriggerKind;
command?: Command;
constructor(text: string, range?: Range, command?: Command);
}
export interface InlineCompletionItemProvider {
provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult<InlineCompletionList>;
/**
* Be aware that this API will not ever be finalized.
*/
export namespace window {
export function getInlineCompletionItemController<T extends InlineCompletionItem>(provider: InlineCompletionItemProvider<T>): InlineCompletionController<T>;
}
export namespace languages {
export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable;
/**
* Be aware that this API will not ever be finalized.
*/
export interface InlineCompletionController<T extends InlineCompletionItem> {
// eslint-disable-next-line vscode-dts-event-naming
readonly onDidShowCompletionItem: Event<InlineCompletionItemDidShowEvent<T>>;
}
/**
* Be aware that this API will not ever be finalized.
*/
export interface InlineCompletionItemDidShowEvent<T extends InlineCompletionItem> {
completionItem: T;
}
//#endregion
@@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { Range as EditorRange, IRange } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto } from '../common/extHost.protocol';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion } from '../common/extHost.protocol';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
import { IModeService } from 'vs/editor/common/services/modeService';
@@ -500,9 +500,15 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}
$registerInlineCompletionsSupport(handle: number, selector: IDocumentFilterDto[]): void {
const provider: modes.InlineCompletionsProvider = {
provideInlineCompletions: async (model: ITextModel, position: EditorPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise<modes.InlineCompletions | undefined> => {
const provider: modes.InlineCompletionsProvider<IdentifiableInlineCompletions> = {
provideInlineCompletions: async (model: ITextModel, position: EditorPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined> => {
return this._proxy.$provideInlineCompletions(handle, model.uri, position, context, token);
},
handleItemDidShow: async (completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion): Promise<void> => {
return this._proxy.$handleInlineCompletionDidShow(handle, completions.pid, item.idx);
},
freeInlineCompletions: (completions: IdentifiableInlineCompletions): void => {
this._proxy.$freeInlineCompletionsList(handle, completions.pid);
}
};
this._registrations.set(handle, modes.InlineCompletionsProviderRegistry.register(selector, provider));
@@ -28,7 +28,7 @@ import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocu
import { Extension, IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem';
import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService';
import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { ExtHostLanguageFeatures, InlineCompletionController } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages';
import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService';
import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput';
@@ -734,6 +734,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
get onDidChangeOpenEditors() {
checkProposedApiEnabled(extension);
return extHostEditorTabs.onDidChangeTabs;
},
getInlineCompletionItemController<T extends vscode.InlineCompletionItem>(provider: vscode.InlineCompletionItemProvider<T>): vscode.InlineCompletionController<T> {
checkProposedApiEnabled(extension);
return InlineCompletionController.get(provider);
}
};
@@ -367,6 +367,14 @@ export interface ISignatureHelpProviderMetadataDto {
readonly retriggerCharacters: readonly string[];
}
export interface IdentifiableInlineCompletions extends modes.InlineCompletions<IdentifiableInlineCompletion> {
pid: number;
}
export interface IdentifiableInlineCompletion extends modes.InlineCompletion {
idx: number;
}
export interface MainThreadLanguageFeaturesShape extends IDisposable {
$unregister(handle: number): void;
$registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], label: string): void;
@@ -1640,7 +1648,9 @@ export interface ExtHostLanguageFeaturesShape {
$provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise<ISuggestResultDto | undefined>;
$resolveCompletionItem(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<ISuggestDataDto | undefined>;
$releaseCompletionItems(handle: number, id: number): void;
$provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise<modes.InlineCompletions | undefined>;
$provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined>;
$handleInlineCompletionDidShow(handle: number, pid: number, idx: number): void;
$freeInlineCompletionsList(handle: number, pid: number): void;
$provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise<ISignatureHelpDto | undefined>;
$releaseSignatureHelp(handle: number, id: number): void;
$provideInlayHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<IInlayHintsDto | undefined>
@@ -33,6 +33,7 @@ import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostAp
import { Cache } from './cache';
import { StopWatch } from 'vs/base/common/stopwatch';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
// --- adapter
@@ -1038,15 +1039,17 @@ class SuggestAdapter {
}
}
class InlineCompletionsAdapter {
class InlineCompletionAdapter {
private readonly _cache = new Cache<vscode.InlineCompletionItem>('InlineCompletionItem');
private readonly _disposables = new Map<number, DisposableStore>();
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.InlineCompletionItemProvider,
private readonly _commands: CommandsConverter,
) { }
async provideInlineCompletions(resource: URI, position: IPosition, context: vscode.InlineCompletionContext, token: CancellationToken): Promise<modes.InlineCompletions | undefined> {
public async provideInlineCompletions(resource: URI, position: IPosition, context: vscode.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
const doc = this._documents.getDocument(resource);
const pos = typeConvert.Position.to(position);
@@ -1063,13 +1066,69 @@ class InlineCompletionsAdapter {
return undefined;
}
const pid = this._cache.add(result.items);
let disposableStore: DisposableStore | undefined = undefined;
return {
items: result.items.map(item => ({
text: item.text,
range: item.range ? typeConvert.Range.from(item.range) : undefined,
})),
pid,
items: result.items.map<extHostProtocol.IdentifiableInlineCompletion>((item, idx) => {
let command: modes.Command | undefined = undefined;
if (item.command) {
if (!disposableStore) {
disposableStore = new DisposableStore();
this._disposables.set(pid, disposableStore);
}
command = this._commands.toInternal(item.command, disposableStore);
}
return ({
text: item.text,
range: item.range ? typeConvert.Range.from(item.range) : undefined,
command,
idx: idx,
});
}),
};
}
public disposeCompletions(pid: number) {
this._cache.delete(pid);
const d = this._disposables.get(pid);
if (d) {
d.clear();
}
this._disposables.delete(pid);
}
public handleDidShowCompletionItem(pid: number, idx: number): void {
const completionItem = this._cache.get(pid, idx);
if (completionItem) {
InlineCompletionController.get(this._provider).fireOnDidShowCompletionItem({
completionItem
});
}
}
}
export class InlineCompletionController<T extends vscode.InlineCompletionItem> implements vscode.InlineCompletionController<T> {
private static readonly map = new WeakMap<vscode.InlineCompletionItemProvider<any>, InlineCompletionController<any>>();
public static get<T extends vscode.InlineCompletionItem>(provider: vscode.InlineCompletionItemProvider<T>): InlineCompletionController<T> {
let existing = InlineCompletionController.map.get(provider);
if (!existing) {
existing = new InlineCompletionController();
InlineCompletionController.map.set(provider, existing);
}
return existing;
}
private readonly _onDidShowCompletionItemEmitter = new Emitter<vscode.InlineCompletionItemDidShowEvent<T>>();
public readonly onDidShowCompletionItem: vscode.Event<vscode.InlineCompletionItemDidShowEvent<T>> = this._onDidShowCompletionItemEmitter.event;
public fireOnDidShowCompletionItem(event: vscode.InlineCompletionItemDidShowEvent<T>): void {
this._onDidShowCompletionItemEmitter.fire(event);
}
}
class SignatureHelpAdapter {
@@ -1389,7 +1448,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter
| EvaluatableExpressionAdapter | InlineValuesAdapter
| LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionsAdapter;
| LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter;
class AdapterData {
constructor(
@@ -1846,15 +1905,24 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
// --- ghost test
registerInlineCompletionsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProvider): vscode.Disposable {
const handle = this._addNewAdapter(new InlineCompletionsAdapter(this._documents, provider), extension);
const handle = this._addNewAdapter(new InlineCompletionAdapter(this._documents, provider, this._commands.converter), extension);
this._proxy.$registerInlineCompletionsSupport(handle, this._transformDocumentSelector(selector));
return this._createDisposable(handle);
}
$provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise<modes.InlineCompletions | undefined> {
return this._withAdapter(handle, InlineCompletionsAdapter, adapter => adapter.provideInlineCompletions(URI.revive(resource), position, context, token), undefined);
$provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
return this._withAdapter(handle, InlineCompletionAdapter, adapter => adapter.provideInlineCompletions(URI.revive(resource), position, context, token), undefined);
}
$handleInlineCompletionDidShow(handle: number, pid: number, idx: number): void {
this._withAdapter(handle, InlineCompletionAdapter, async adapter => {
adapter.handleDidShowCompletionItem(pid, idx);
}, undefined);
}
$freeInlineCompletionsList(handle: number, pid: number): void {
this._withAdapter(handle, InlineCompletionAdapter, async adapter => { adapter.disposeCompletions(pid); }, undefined);
}
// --- parameter hints
+4 -1
View File
@@ -1552,9 +1552,12 @@ export class InlineSuggestion implements vscode.InlineCompletionItem {
text: string;
range?: Range;
command?: vscode.Command;
constructor(text: string) {
constructor(text: string, range?: Range, command?: vscode.Command) {
this.text = text;
this.range = range;
this.command = command;
}
}