mirror of
https://github.com/microsoft/vscode.git
synced 2026-06-05 15:16:06 +01:00
Merge pull request #141146 from microsoft/hediet/inline-completions-bracket-completion
inline completions bracket completion
This commit is contained in:
@@ -797,6 +797,12 @@ export interface InlineCompletion {
|
||||
readonly range?: IRange;
|
||||
|
||||
readonly command?: Command;
|
||||
|
||||
/**
|
||||
* If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed.
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
readonly completeBracketPairs?: boolean;
|
||||
}
|
||||
|
||||
export interface InlineCompletions<TItem extends InlineCompletion = InlineCompletion> {
|
||||
|
||||
@@ -938,6 +938,11 @@ export interface ITextModel {
|
||||
*/
|
||||
getTokenTypeIfInsertingCharacter(lineNumber: number, column: number, character: string): StandardTokenType;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
tokenizeLineWithEdit(position: IPosition, length: number, newText: string): LineTokens | null;
|
||||
|
||||
/**
|
||||
* Get the word under or besides `position`.
|
||||
* @param position The position to look for a word.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { CursorColumns } from 'vs/editor/common/core/cursorColumns';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { Length, lengthAdd, lengthGetLineCount, lengthHash, lengthToObj, lengthZero } from './length';
|
||||
import { Length, lengthAdd, lengthGetLineCount, lengthToObj, lengthZero } from './length';
|
||||
import { SmallImmutableSet } from './smallImmutableSet';
|
||||
import { OpeningBracketId } from './tokenizer';
|
||||
|
||||
@@ -624,17 +624,12 @@ export class TextAstNode extends ImmutableLeafAstNode {
|
||||
}
|
||||
|
||||
export class BracketAstNode extends ImmutableLeafAstNode {
|
||||
private static cacheByLength = new Map<number, BracketAstNode>();
|
||||
|
||||
public static create(length: Length): BracketAstNode {
|
||||
const lengthKey = lengthHash(length);
|
||||
const cached = BracketAstNode.cacheByLength.get(lengthKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const node = new BracketAstNode(length);
|
||||
BracketAstNode.cacheByLength.set(lengthKey, node);
|
||||
public static create(
|
||||
length: Length,
|
||||
languageId: string,
|
||||
bracketIds: SmallImmutableSet<OpeningBracketId>
|
||||
): BracketAstNode {
|
||||
const node = new BracketAstNode(length, languageId, bracketIds);
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -646,7 +641,15 @@ export class BracketAstNode extends ImmutableLeafAstNode {
|
||||
return SmallImmutableSet.getEmpty();
|
||||
}
|
||||
|
||||
private constructor(length: Length) {
|
||||
private constructor(
|
||||
length: Length,
|
||||
public readonly languageId: string,
|
||||
/**
|
||||
* In case of a opening bracket, this is the id of the opening bracket.
|
||||
* In case of a closing bracket, this contains the ids of all opening brackets it can close.
|
||||
*/
|
||||
public readonly bracketIds: SmallImmutableSet<OpeningBracketId>
|
||||
) {
|
||||
super(length);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,19 +41,20 @@ export class BracketTokens {
|
||||
TokenKind.ClosingBracket,
|
||||
info.first,
|
||||
info.openingBrackets,
|
||||
BracketAstNode.create(length)
|
||||
BracketAstNode.create(length, configuration.languageId, info.openingBrackets)
|
||||
));
|
||||
}
|
||||
|
||||
for (const openingText of openingBrackets) {
|
||||
const length = toLength(0, openingText.length);
|
||||
const openingTextId = getId(configuration.languageId, openingText);
|
||||
const bracketIds = SmallImmutableSet.getEmpty().add(openingTextId, identityKeyProvider);
|
||||
map.set(openingText, new Token(
|
||||
length,
|
||||
TokenKind.OpeningBracket,
|
||||
openingTextId,
|
||||
SmallImmutableSet.getEmpty().add(openingTextId, identityKeyProvider),
|
||||
BracketAstNode.create(length)
|
||||
bracketIds,
|
||||
BracketAstNode.create(length, configuration.languageId, bracketIds)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -94,6 +95,15 @@ export class BracketTokens {
|
||||
return this.map.get(value);
|
||||
}
|
||||
|
||||
findClosingTokenText(openingBracketIds: SmallImmutableSet<OpeningBracketId>): string | undefined {
|
||||
for (const [closingText, info] of this.map) {
|
||||
if (info.bracketIds.intersects(openingBracketIds)) {
|
||||
return closingText;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get isEmpty(): boolean {
|
||||
return this.map.size === 0;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NotSupportedError } from 'vs/base/common/errors';
|
||||
import { LineTokens } from 'vs/editor/common/tokens/lineTokens';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { SmallImmutableSet } from './smallImmutableSet';
|
||||
import { StandardTokenType, TokenMetadata } from 'vs/editor/common/languages';
|
||||
import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens';
|
||||
import { BracketAstNode, TextAstNode } from './ast';
|
||||
import { BracketTokens, LanguageAgnosticBracketTokens } from './brackets';
|
||||
import { lengthGetColumnCountIfZeroLineCount, Length, lengthAdd, lengthDiff, lengthToObj, lengthZero, toLength } from './length';
|
||||
import { Length, lengthAdd, lengthDiff, lengthGetColumnCountIfZeroLineCount, lengthToObj, lengthZero, toLength } from './length';
|
||||
import { SmallImmutableSet } from './smallImmutableSet';
|
||||
|
||||
export interface Tokenizer {
|
||||
readonly offset: Length;
|
||||
@@ -51,6 +50,13 @@ export class Token {
|
||||
) { }
|
||||
}
|
||||
|
||||
export interface ITokenizerSource {
|
||||
getValue(): string;
|
||||
getLineCount(): number;
|
||||
getLineLength(lineNumber: number): number;
|
||||
getLineTokens(lineNumber: number): IViewLineTokens;
|
||||
}
|
||||
|
||||
export class TextBufferTokenizer implements Tokenizer {
|
||||
private readonly textBufferLineCount: number;
|
||||
private readonly textBufferLastLineLength: number;
|
||||
@@ -58,7 +64,7 @@ export class TextBufferTokenizer implements Tokenizer {
|
||||
private readonly reader = new NonPeekableTextBufferTokenizer(this.textModel, this.bracketTokens);
|
||||
|
||||
constructor(
|
||||
private readonly textModel: ITextModel,
|
||||
private readonly textModel: ITokenizerSource,
|
||||
private readonly bracketTokens: LanguageAgnosticBracketTokens
|
||||
) {
|
||||
this.textBufferLineCount = textModel.getLineCount();
|
||||
@@ -119,7 +125,7 @@ class NonPeekableTextBufferTokenizer {
|
||||
private readonly textBufferLineCount: number;
|
||||
private readonly textBufferLastLineLength: number;
|
||||
|
||||
constructor(private readonly textModel: ITextModel, private readonly bracketTokens: LanguageAgnosticBracketTokens) {
|
||||
constructor(private readonly textModel: ITokenizerSource, private readonly bracketTokens: LanguageAgnosticBracketTokens) {
|
||||
this.textBufferLineCount = textModel.getLineCount();
|
||||
this.textBufferLastLineLength = textModel.getLineLength(this.textBufferLineCount);
|
||||
}
|
||||
@@ -127,7 +133,7 @@ class NonPeekableTextBufferTokenizer {
|
||||
private lineIdx = 0;
|
||||
private line: string | null = null;
|
||||
private lineCharOffset = 0;
|
||||
private lineTokens: LineTokens | null = null;
|
||||
private lineTokens: IViewLineTokens | null = null;
|
||||
private lineTokenOffset = 0;
|
||||
|
||||
public setPosition(lineIdx: number, column: number): void {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
|
||||
import { AstNode, AstNodeKind } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast';
|
||||
import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets';
|
||||
import { Length, lengthAdd, lengthGetColumnCountIfZeroLineCount, lengthZero } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length';
|
||||
import { parseDocument } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser';
|
||||
import { DenseKeyProvider } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet';
|
||||
import { ITokenizerSource, TextBufferTokenizer } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer';
|
||||
import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens';
|
||||
|
||||
export function fixBracketsInLine(tokens: IViewLineTokens, languageConfigurationService: ILanguageConfigurationService): string {
|
||||
const denseKeyProvider = new DenseKeyProvider<string>();
|
||||
const bracketTokens = new LanguageAgnosticBracketTokens(denseKeyProvider, (languageId) =>
|
||||
languageConfigurationService.getLanguageConfiguration(languageId)
|
||||
);
|
||||
const tokenizer = new TextBufferTokenizer(
|
||||
new StaticTokenizerSource([tokens]),
|
||||
bracketTokens
|
||||
);
|
||||
const node = parseDocument(tokenizer, [], undefined, true);
|
||||
|
||||
let str = '';
|
||||
const line = tokens.getLineContent();
|
||||
|
||||
function processNode(node: AstNode, offset: Length) {
|
||||
if (node.kind === AstNodeKind.Pair) {
|
||||
processNode(node.openingBracket, offset);
|
||||
offset = lengthAdd(offset, node.openingBracket.length);
|
||||
|
||||
if (node.child) {
|
||||
processNode(node.child, offset);
|
||||
offset = lengthAdd(offset, node.child.length);
|
||||
}
|
||||
if (node.closingBracket) {
|
||||
processNode(node.closingBracket, offset);
|
||||
offset = lengthAdd(offset, node.closingBracket.length);
|
||||
} else {
|
||||
const singleLangBracketTokens = bracketTokens.getSingleLanguageBracketTokens(node.openingBracket.languageId);
|
||||
|
||||
const closingTokenText = singleLangBracketTokens.findClosingTokenText(node.openingBracket.bracketIds);
|
||||
str += closingTokenText;
|
||||
}
|
||||
} else if (node.kind === AstNodeKind.UnexpectedClosingBracket) {
|
||||
// remove the bracket
|
||||
} else if (node.kind === AstNodeKind.Text || node.kind === AstNodeKind.Bracket) {
|
||||
str += line.substring(
|
||||
lengthGetColumnCountIfZeroLineCount(offset),
|
||||
lengthGetColumnCountIfZeroLineCount(lengthAdd(offset, node.length))
|
||||
);
|
||||
} else if (node.kind === AstNodeKind.List) {
|
||||
for (const child of node.children) {
|
||||
processNode(child, offset);
|
||||
offset = lengthAdd(offset, child.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processNode(node, lengthZero);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
class StaticTokenizerSource implements ITokenizerSource {
|
||||
constructor(private readonly lines: IViewLineTokens[]) { }
|
||||
|
||||
getValue(): string {
|
||||
return this.lines.map(l => l.getLineContent()).join('\n');
|
||||
}
|
||||
getLineCount(): number {
|
||||
return this.lines.length;
|
||||
}
|
||||
getLineLength(lineNumber: number): number {
|
||||
return this.lines[lineNumber - 1].getLineContent().length;
|
||||
}
|
||||
getLineTokens(lineNumber: number): IViewLineTokens {
|
||||
return this.lines[lineNumber - 1];
|
||||
}
|
||||
}
|
||||
@@ -2139,6 +2139,11 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
||||
return this._tokenization.getTokenTypeIfInsertingCharacter(position, character);
|
||||
}
|
||||
|
||||
tokenizeLineWithEdit(position: IPosition, length: number, newText: string): LineTokens | null {
|
||||
const validatedPosition = this.validatePosition(position);
|
||||
return this._tokenization.tokenizeLineWithEdit(validatedPosition, length, newText);
|
||||
}
|
||||
|
||||
private getLanguageConfiguration(languageId: string): ResolvedLanguageConfiguration {
|
||||
return this._languageConfigurationService.getLanguageConfiguration(languageId);
|
||||
}
|
||||
|
||||
@@ -372,6 +372,38 @@ export class TextModelTokenization extends Disposable {
|
||||
return lineTokens.getStandardTokenType(tokenIndex);
|
||||
}
|
||||
|
||||
public tokenizeLineWithEdit(position: Position, length: number, newText: string): LineTokens | null {
|
||||
const lineNumber = position.lineNumber;
|
||||
const column = position.column;
|
||||
|
||||
if (!this._tokenizationSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.forceTokenization(lineNumber);
|
||||
const lineStartState = this._tokenizationStateStore.getBeginState(lineNumber - 1);
|
||||
if (!lineStartState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const curLineContent = this._textModel.getLineContent(lineNumber);
|
||||
const newLineContent = curLineContent.substring(0, column - 1)
|
||||
+ newText + curLineContent.substring(column - 1 + length);
|
||||
|
||||
const languageId = this._textModel.getLanguageIdAtPosition(lineNumber, 0);
|
||||
const result = safeTokenize(
|
||||
this._languageIdCodec,
|
||||
languageId,
|
||||
this._tokenizationSupport,
|
||||
newLineContent,
|
||||
true,
|
||||
lineStartState
|
||||
);
|
||||
|
||||
const lineTokens = new LineTokens(result.tokens, newLineContent, this._languageIdCodec);
|
||||
return lineTokens;
|
||||
}
|
||||
|
||||
public isCheapToTokenize(lineNumber: number): boolean {
|
||||
if (!this._tokenizationSupport) {
|
||||
return true;
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface IViewLineTokens {
|
||||
getPresentation(tokenIndex: number): ITokenPresentation;
|
||||
findTokenIndexAtOffset(offset: number): number;
|
||||
getLineContent(): string;
|
||||
getMetadata(tokenIndex: number): number;
|
||||
getLanguageId(tokenIndex: number): string;
|
||||
}
|
||||
|
||||
export class LineTokens implements IViewLineTokens {
|
||||
@@ -253,6 +255,14 @@ class SliceLineTokens implements IViewLineTokens {
|
||||
}
|
||||
}
|
||||
|
||||
public getMetadata(tokenIndex: number): number {
|
||||
return this._source.getMetadata(this._firstTokenIndex + tokenIndex);
|
||||
}
|
||||
|
||||
public getLanguageId(tokenIndex: number): string {
|
||||
return this._source.getLanguageId(this._firstTokenIndex + tokenIndex);
|
||||
}
|
||||
|
||||
public getLineContent(): string {
|
||||
return this._source.getLineContent().substring(this._startOffset, this._endOffset);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { InlineCompletionTriggerKind } from 'vs/editor/common/languages';
|
||||
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
|
||||
import { GhostText, GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/browser/ghostText';
|
||||
import { InlineCompletionsModel, LiveInlineCompletions, SynchronizedInlineCompletionsCache } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel';
|
||||
import { InlineCompletionsModel, SynchronizedInlineCompletionsCache, TrackedInlineCompletions } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel';
|
||||
import { SuggestWidgetPreviewModel } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetPreviewModel';
|
||||
import { createDisposableRef } from 'vs/editor/contrib/inlineCompletions/browser/utils';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
@@ -68,7 +69,7 @@ export abstract class DelegatingModel extends Disposable implements GhostTextWid
|
||||
export class GhostTextModel extends DelegatingModel implements GhostTextWidgetModel {
|
||||
public readonly sharedCache = this._register(new SharedInlineCompletionCache());
|
||||
public readonly suggestWidgetAdapterModel = this._register(new SuggestWidgetPreviewModel(this.editor, this.sharedCache));
|
||||
public readonly inlineCompletionsModel = this._register(new InlineCompletionsModel(this.editor, this.sharedCache, this.commandService));
|
||||
public readonly inlineCompletionsModel = this._register(new InlineCompletionsModel(this.editor, this.sharedCache, this.commandService, this.languageConfigurationService));
|
||||
|
||||
public get activeInlineCompletionsModel(): InlineCompletionsModel | undefined {
|
||||
if (this.targetModel === this.inlineCompletionsModel) {
|
||||
@@ -79,7 +80,8 @@ export class GhostTextModel extends DelegatingModel implements GhostTextWidgetMo
|
||||
|
||||
constructor(
|
||||
private readonly editor: IActiveCodeEditor,
|
||||
@ICommandService private readonly commandService: ICommandService
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -143,7 +145,7 @@ export class SharedInlineCompletionCache extends Disposable {
|
||||
}
|
||||
|
||||
public setValue(editor: IActiveCodeEditor,
|
||||
completionsSource: LiveInlineCompletions,
|
||||
completionsSource: TrackedInlineCompletions,
|
||||
triggerKind: InlineCompletionTriggerKind
|
||||
) {
|
||||
this.cache.value = new SynchronizedInlineCompletionsCache(
|
||||
|
||||
@@ -11,6 +11,9 @@ import { ITextModel } from 'vs/editor/common/model';
|
||||
import { InlineCompletion } from 'vs/editor/common/languages';
|
||||
import { GhostText, GhostTextPart } from 'vs/editor/contrib/inlineCompletions/browser/ghostText';
|
||||
|
||||
/**
|
||||
* A normalized inline completion is an inline completion with a defined range.
|
||||
*/
|
||||
export interface NormalizedInlineCompletion extends InlineCompletion {
|
||||
range: Range;
|
||||
}
|
||||
|
||||
@@ -19,9 +19,11 @@ import { ITextModel } from 'vs/editor/common/model';
|
||||
import { InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, InlineCompletionsProviderRegistry, InlineCompletionTriggerKind } from 'vs/editor/common/languages';
|
||||
import { BaseGhostTextWidgetModel, GhostText, GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/browser/ghostText';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { inlineSuggestCommitId } from './consts';
|
||||
import { SharedInlineCompletionCache } from './ghostTextModel';
|
||||
import { inlineCompletionToGhostText, NormalizedInlineCompletion } from './inlineCompletionToGhostText';
|
||||
import { inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/consts';
|
||||
import { SharedInlineCompletionCache } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextModel';
|
||||
import { inlineCompletionToGhostText, NormalizedInlineCompletion } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionToGhostText';
|
||||
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
|
||||
import { fixBracketsInLine } from 'vs/editor/common/model/bracketPairsTextModelPart/fixBrackets';
|
||||
|
||||
export class InlineCompletionsModel extends Disposable implements GhostTextWidgetModel {
|
||||
protected readonly onDidChangeEmitter = new Emitter<void>();
|
||||
@@ -36,6 +38,7 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge
|
||||
private readonly editor: IActiveCodeEditor,
|
||||
private readonly cache: SharedInlineCompletionCache,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -138,7 +141,8 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge
|
||||
() => this.active,
|
||||
this.commandService,
|
||||
this.cache,
|
||||
triggerKind
|
||||
triggerKind,
|
||||
this.languageConfigurationService
|
||||
);
|
||||
this.completionSession.value.takeOwnership(
|
||||
this.completionSession.value.onDidChange(() => {
|
||||
@@ -189,7 +193,8 @@ export class InlineCompletionsSession extends BaseGhostTextWidgetModel {
|
||||
private readonly shouldUpdate: () => boolean,
|
||||
private readonly commandService: ICommandService,
|
||||
private readonly cache: SharedInlineCompletionCache,
|
||||
private initialTriggerKind: InlineCompletionTriggerKind
|
||||
private initialTriggerKind: InlineCompletionTriggerKind,
|
||||
private readonly languageConfigurationService: ILanguageConfigurationService,
|
||||
) {
|
||||
super(editor);
|
||||
|
||||
@@ -310,7 +315,7 @@ export class InlineCompletionsSession extends BaseGhostTextWidgetModel {
|
||||
return currentCompletion ? inlineCompletionToGhostText(currentCompletion, this.editor.getModel(), mode, this.editor.getPosition()) : undefined;
|
||||
}
|
||||
|
||||
get currentCompletion(): LiveInlineCompletion | undefined {
|
||||
get currentCompletion(): TrackedInlineCompletion | undefined {
|
||||
const completion = this.currentCachedCompletion;
|
||||
if (!completion) {
|
||||
return undefined;
|
||||
@@ -342,7 +347,8 @@ export class InlineCompletionsSession extends BaseGhostTextWidgetModel {
|
||||
result = await provideInlineCompletions(position,
|
||||
this.editor.getModel(),
|
||||
{ triggerKind, selectedSuggestionInfo: undefined },
|
||||
token
|
||||
token,
|
||||
this.languageConfigurationService
|
||||
);
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
@@ -384,7 +390,7 @@ export class InlineCompletionsSession extends BaseGhostTextWidgetModel {
|
||||
}
|
||||
}
|
||||
|
||||
public commit(completion: LiveInlineCompletion): void {
|
||||
public commit(completion: TrackedInlineCompletion): void {
|
||||
// Mark the cache as stale, but don't dispose it yet,
|
||||
// otherwise command args might get disposed.
|
||||
const cache = this.cache.clearAndLeak();
|
||||
@@ -428,7 +434,7 @@ export class SynchronizedInlineCompletionsCache extends Disposable {
|
||||
|
||||
constructor(
|
||||
editor: IActiveCodeEditor,
|
||||
completionsSource: LiveInlineCompletions,
|
||||
completionsSource: TrackedInlineCompletions,
|
||||
onChange: () => void,
|
||||
public readonly triggerKind: InlineCompletionTriggerKind,
|
||||
) {
|
||||
@@ -486,13 +492,13 @@ class CachedInlineCompletion {
|
||||
public synchronizedRange: Range;
|
||||
|
||||
constructor(
|
||||
public readonly inlineCompletion: LiveInlineCompletion,
|
||||
public readonly inlineCompletion: TrackedInlineCompletion,
|
||||
public readonly decorationId: string,
|
||||
) {
|
||||
this.synchronizedRange = inlineCompletion.range;
|
||||
}
|
||||
|
||||
public toLiveInlineCompletion(): LiveInlineCompletion | undefined {
|
||||
public toLiveInlineCompletion(): TrackedInlineCompletion | undefined {
|
||||
return {
|
||||
text: this.inlineCompletion.text,
|
||||
range: this.synchronizedRange,
|
||||
@@ -504,16 +510,29 @@ class CachedInlineCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
export interface LiveInlineCompletion extends NormalizedInlineCompletion {
|
||||
/**
|
||||
* A normalized inline completion that tracks which inline completion it has been constructed from.
|
||||
*/
|
||||
export interface TrackedInlineCompletion extends NormalizedInlineCompletion {
|
||||
sourceProvider: InlineCompletionsProvider;
|
||||
|
||||
/**
|
||||
* A reference to the original inline completion this inline completion has been constructed from.
|
||||
* Used for event data to ensure referential equality.
|
||||
*/
|
||||
sourceInlineCompletion: InlineCompletion;
|
||||
|
||||
/**
|
||||
* A reference to the original inline completion list this inline completion has been constructed from.
|
||||
* Used for event data to ensure referential equality.
|
||||
*/
|
||||
sourceInlineCompletions: InlineCompletions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains no duplicated items.
|
||||
*/
|
||||
export interface LiveInlineCompletions extends InlineCompletions<LiveInlineCompletion> {
|
||||
export interface TrackedInlineCompletions extends InlineCompletions<TrackedInlineCompletion> {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@@ -531,8 +550,9 @@ export async function provideInlineCompletions(
|
||||
position: Position,
|
||||
model: ITextModel,
|
||||
context: InlineCompletionContext,
|
||||
token: CancellationToken = CancellationToken.None
|
||||
): Promise<LiveInlineCompletions> {
|
||||
token: CancellationToken = CancellationToken.None,
|
||||
languageConfigurationService?: ILanguageConfigurationService
|
||||
): Promise<TrackedInlineCompletions> {
|
||||
const defaultReplaceRange = getDefaultRange(position, model);
|
||||
|
||||
const providers = InlineCompletionsProviderRegistry.all(model);
|
||||
@@ -553,23 +573,38 @@ export async function provideInlineCompletions(
|
||||
)
|
||||
);
|
||||
|
||||
const itemsByHash = new Map<string, LiveInlineCompletion>();
|
||||
const itemsByHash = new Map<string, TrackedInlineCompletion>();
|
||||
for (const result of results) {
|
||||
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,
|
||||
command: item.command,
|
||||
sourceProvider: result.provider,
|
||||
sourceInlineCompletions: completions,
|
||||
sourceInlineCompletion: item
|
||||
}))) {
|
||||
if (item.range.startLineNumber !== item.range.endLineNumber) {
|
||||
for (const item of completions.items) {
|
||||
const range = item.range ? Range.lift(item.range) : defaultReplaceRange;
|
||||
|
||||
if (range.startLineNumber !== range.endLineNumber) {
|
||||
// Ignore invalid ranges.
|
||||
continue;
|
||||
}
|
||||
itemsByHash.set(JSON.stringify({ text: item.text, range: item.range }), item);
|
||||
|
||||
const text =
|
||||
languageConfigurationService && item.completeBracketPairs
|
||||
? closeBrackets(
|
||||
item.text,
|
||||
range.getStartPosition(),
|
||||
model,
|
||||
languageConfigurationService
|
||||
)
|
||||
: item.text;
|
||||
|
||||
const trackedItem: TrackedInlineCompletion = ({
|
||||
text,
|
||||
range,
|
||||
command: item.command,
|
||||
sourceProvider: result.provider,
|
||||
sourceInlineCompletions: completions,
|
||||
sourceInlineCompletion: item
|
||||
});
|
||||
|
||||
itemsByHash.set(JSON.stringify({ text, range: item.range }), trackedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -584,6 +619,22 @@ export async function provideInlineCompletions(
|
||||
};
|
||||
}
|
||||
|
||||
function closeBrackets(text: string, position: Position, model: ITextModel, languageConfigurationService: ILanguageConfigurationService): string {
|
||||
const lineStart = model.getLineContent(position.lineNumber).substring(0, position.column - 1);
|
||||
const newLine = lineStart + text;
|
||||
|
||||
const newTokens = model.tokenizeLineWithEdit(position, newLine.length - (position.column - 1), text);
|
||||
const slicedTokens = newTokens?.sliceAndInflate(position.column - 1, newLine.length, 0);
|
||||
if (!slicedTokens) {
|
||||
return text;
|
||||
}
|
||||
|
||||
console.log(slicedTokens);
|
||||
const newText = fixBracketsInLine(slicedTokens, languageConfigurationService);
|
||||
|
||||
return newText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrinks the range if the text has a suffix/prefix that agrees with the text buffer.
|
||||
* E.g. text buffer: `ab[cdef]ghi`, [...] is the replace range, `cxyzf` is the new text.
|
||||
|
||||
@@ -107,6 +107,13 @@ export class TestLineTokens implements IViewLineTokens {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
public getMetadata(tokenIndex: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public getLanguageId(tokenIndex: number): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class TestLineTokenFactory {
|
||||
|
||||
Vendored
+5
@@ -6191,6 +6191,11 @@ declare namespace monaco.languages {
|
||||
*/
|
||||
readonly range?: IRange;
|
||||
readonly command?: Command;
|
||||
/**
|
||||
* If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed.
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
readonly completeBracketPairs?: boolean;
|
||||
}
|
||||
|
||||
export interface InlineCompletions<TItem extends InlineCompletion = InlineCompletion> {
|
||||
|
||||
@@ -1083,6 +1083,7 @@ class InlineCompletionAdapter {
|
||||
range: item.range ? typeConvert.Range.from(item.range) : undefined,
|
||||
command,
|
||||
idx: idx,
|
||||
completeBracketPairs: item.completeBracketPairs
|
||||
});
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -103,6 +103,11 @@ declare module 'vscode' {
|
||||
*/
|
||||
command?: Command;
|
||||
|
||||
/**
|
||||
* If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed.
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
completeBracketPairs?: boolean;
|
||||
constructor(text: string, range?: Range, command?: Command);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user