mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-25 17:27:26 +01:00
Merge pull request #124707 from microsoft/alex/ghost-text
ghost text updates
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Vendored
+54
-25
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user