Refactors ghost text widget to use a fixed model. If the model changes, the ghost text widget needs to be recreated.
This commit is contained in:
parent
735dd57b16
commit
68e483f8a8
3 changed files with 68 additions and 93 deletions
|
@ -27,7 +27,6 @@ export class GhostTextController extends Disposable {
|
|||
}
|
||||
|
||||
private triggeredExplicitly = false;
|
||||
protected readonly widget: GhostTextWidget;
|
||||
protected readonly activeController = this._register(new MutableDisposable<ActiveGhostTextController>());
|
||||
private get activeModel(): GhostTextModel | undefined {
|
||||
return this.activeController.value?.model;
|
||||
|
@ -39,8 +38,6 @@ export class GhostTextController extends Disposable {
|
|||
) {
|
||||
super();
|
||||
|
||||
this.widget = this._register(instantiationService.createInstance(GhostTextWidget, this.editor));
|
||||
|
||||
this._register(this.editor.onDidChangeModel(() => {
|
||||
this.updateModelController();
|
||||
}));
|
||||
|
@ -63,8 +60,7 @@ export class GhostTextController extends Disposable {
|
|||
this.editor.hasModel() && (suggestOptions.preview || inlineSuggestOptions.enabled || this.triggeredExplicitly)
|
||||
? this.instantiationService.createInstance(
|
||||
ActiveGhostTextController,
|
||||
this.editor,
|
||||
this.widget,
|
||||
this.editor
|
||||
)
|
||||
: undefined;
|
||||
}
|
||||
|
@ -74,7 +70,7 @@ export class GhostTextController extends Disposable {
|
|||
}
|
||||
|
||||
public shouldShowHoverAtViewZone(viewZoneId: string): boolean {
|
||||
return this.widget.shouldShowHoverAtViewZone(viewZoneId);
|
||||
return this.activeController.value?.widget?.shouldShowHoverAtViewZone(viewZoneId) || false;
|
||||
}
|
||||
|
||||
public trigger(): void {
|
||||
|
@ -117,21 +113,20 @@ class GhostTextContextKeys {
|
|||
|
||||
/**
|
||||
* The controller for a text editor with an initialized text model.
|
||||
* Must be disposed as soon as the model detaches from the editor.
|
||||
*/
|
||||
export class ActiveGhostTextController extends Disposable {
|
||||
private readonly contextKeys = new GhostTextContextKeys(this.contextKeyService);
|
||||
public readonly model = this.instantiationService.createInstance(GhostTextModel, this.editor);
|
||||
public readonly widget = this._register(this.instantiationService.createInstance(GhostTextWidget, this.editor, this.model));
|
||||
|
||||
constructor(
|
||||
private readonly editor: IActiveCodeEditor,
|
||||
widget: GhostTextWidget,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super();
|
||||
|
||||
widget.setModel(this.model);
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
this.contextKeys.inlineCompletionVisible.set(false);
|
||||
this.contextKeys.inlineCompletionSuggestsIndentation.set(false);
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
import 'vs/css!./ghostText';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer';
|
||||
import { EditorFontLigatures, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorFontLigatures, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { createStringBuilder } from 'vs/editor/common/core/stringBuilder';
|
||||
import { Configuration } from 'vs/editor/browser/config/configuration';
|
||||
import { LineTokens } from 'vs/editor/common/core/lineTokens';
|
||||
|
@ -22,7 +22,6 @@ import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/com
|
|||
import { ghostTextBorder, ghostTextForeground } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { RGBA, Color } from 'vs/base/common/color';
|
||||
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
|
||||
import { createDisposableRef } from 'vs/editor/contrib/inlineCompletions/utils';
|
||||
|
||||
const ttPolicy = window.trustedTypes?.createPolicy('editorGhostText', { createHTML: value => value });
|
||||
|
||||
|
@ -79,8 +78,8 @@ export abstract class BaseGhostTextWidgetModel extends Disposable implements Gho
|
|||
export class GhostTextWidget extends Disposable {
|
||||
private static decorationTypeCount = 0;
|
||||
|
||||
private disposed = false;
|
||||
private codeEditorDecorationTypeKey: string | null = null;
|
||||
private readonly modelRef = this._register(new MutableDisposable<IReference<GhostTextWidgetModel>>());
|
||||
private decorationIds: string[] = [];
|
||||
private viewZoneId: string | null = null;
|
||||
private viewMoreContentWidget: ViewMoreLinesContentWidget | null = null;
|
||||
|
@ -94,6 +93,7 @@ export class GhostTextWidget extends Disposable {
|
|||
|
||||
constructor(
|
||||
private readonly editor: ICodeEditor,
|
||||
private readonly model: GhostTextWidgetModel,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
) {
|
||||
|
@ -112,29 +112,24 @@ export class GhostTextWidget extends Disposable {
|
|||
this.render();
|
||||
}
|
||||
}));
|
||||
this._register(toDisposable(() => {
|
||||
this.setModel(undefined);
|
||||
}));
|
||||
}
|
||||
|
||||
public get model(): GhostTextWidgetModel | undefined {
|
||||
return this.modelRef.value?.object;
|
||||
this._register(toDisposable(() => {
|
||||
this.disposed = true;
|
||||
this.render();
|
||||
}));
|
||||
|
||||
this._register(model.onDidChange(() => {
|
||||
this.render();
|
||||
}));
|
||||
this.render();
|
||||
}
|
||||
|
||||
public shouldShowHoverAtViewZone(viewZoneId: string): boolean {
|
||||
return (this.viewZoneId === viewZoneId);
|
||||
}
|
||||
|
||||
public setModel(model: GhostTextWidgetModel | undefined): void {
|
||||
if (model === this.model) { return; }
|
||||
this.modelRef.value = model
|
||||
? createDisposableRef(model, model.onDidChange(() => this.render()))
|
||||
: undefined;
|
||||
this.render();
|
||||
}
|
||||
|
||||
private getRenderData() {
|
||||
if (!this.editor.hasModel() || !this.model?.ghostText) {
|
||||
if (!this.editor.hasModel() || !this.model.ghostText || this.disposed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -232,7 +227,7 @@ export class GhostTextWidget extends Disposable {
|
|||
if (heightInLines > 0) {
|
||||
if (renderData.expanded) {
|
||||
const domNode = document.createElement('div');
|
||||
this.renderLines(domNode, renderData.tabSize, remainingLines);
|
||||
renderLines(domNode, renderData.tabSize, remainingLines, this.editor.getOptions());
|
||||
|
||||
this.viewZoneId = changeAccessor.addZone({
|
||||
afterLineNumber: renderData.position.lineNumber,
|
||||
|
@ -279,63 +274,62 @@ export class GhostTextWidget extends Disposable {
|
|||
domNode.append(button);
|
||||
return new ViewMoreLinesContentWidget(this.editor, position, domNode, disposableStore);
|
||||
}
|
||||
}
|
||||
|
||||
private renderLines(domNode: HTMLElement, tabSize: number, lines: string[]): void {
|
||||
const opts = this.editor.getOptions();
|
||||
const disableMonospaceOptimizations = opts.get(EditorOption.disableMonospaceOptimizations);
|
||||
const stopRenderingLineAfter = opts.get(EditorOption.stopRenderingLineAfter);
|
||||
// To avoid visual confusion, we don't want to render visible whitespace
|
||||
const renderWhitespace = 'none';
|
||||
const renderControlCharacters = opts.get(EditorOption.renderControlCharacters);
|
||||
const fontLigatures = opts.get(EditorOption.fontLigatures);
|
||||
const fontInfo = opts.get(EditorOption.fontInfo);
|
||||
const lineHeight = opts.get(EditorOption.lineHeight);
|
||||
function renderLines(domNode: HTMLElement, tabSize: number, lines: string[], opts: IComputedEditorOptions): void {
|
||||
const disableMonospaceOptimizations = opts.get(EditorOption.disableMonospaceOptimizations);
|
||||
const stopRenderingLineAfter = opts.get(EditorOption.stopRenderingLineAfter);
|
||||
// To avoid visual confusion, we don't want to render visible whitespace
|
||||
const renderWhitespace = 'none';
|
||||
const renderControlCharacters = opts.get(EditorOption.renderControlCharacters);
|
||||
const fontLigatures = opts.get(EditorOption.fontLigatures);
|
||||
const fontInfo = opts.get(EditorOption.fontInfo);
|
||||
const lineHeight = opts.get(EditorOption.lineHeight);
|
||||
|
||||
const sb = createStringBuilder(10000);
|
||||
sb.appendASCIIString('<div class="suggest-preview-text">');
|
||||
const sb = createStringBuilder(10000);
|
||||
sb.appendASCIIString('<div class="suggest-preview-text">');
|
||||
|
||||
for (let i = 0, len = lines.length; i < len; i++) {
|
||||
const line = lines[i];
|
||||
sb.appendASCIIString('<div class="view-line');
|
||||
sb.appendASCIIString('" style="top:');
|
||||
sb.appendASCIIString(String(i * lineHeight));
|
||||
sb.appendASCIIString('px;width:1000000px;">');
|
||||
for (let i = 0, len = lines.length; i < len; i++) {
|
||||
const line = lines[i];
|
||||
sb.appendASCIIString('<div class="view-line');
|
||||
sb.appendASCIIString('" style="top:');
|
||||
sb.appendASCIIString(String(i * lineHeight));
|
||||
sb.appendASCIIString('px;width:1000000px;">');
|
||||
|
||||
const isBasicASCII = strings.isBasicASCII(line);
|
||||
const containsRTL = strings.containsRTL(line);
|
||||
const lineTokens = LineTokens.createEmpty(line);
|
||||
const isBasicASCII = strings.isBasicASCII(line);
|
||||
const containsRTL = strings.containsRTL(line);
|
||||
const lineTokens = LineTokens.createEmpty(line);
|
||||
|
||||
renderViewLine(new RenderLineInput(
|
||||
(fontInfo.isMonospace && !disableMonospaceOptimizations),
|
||||
fontInfo.canUseHalfwidthRightwardsArrow,
|
||||
line,
|
||||
false,
|
||||
isBasicASCII,
|
||||
containsRTL,
|
||||
0,
|
||||
lineTokens,
|
||||
[],
|
||||
tabSize,
|
||||
0,
|
||||
fontInfo.spaceWidth,
|
||||
fontInfo.middotWidth,
|
||||
fontInfo.wsmiddotWidth,
|
||||
stopRenderingLineAfter,
|
||||
renderWhitespace,
|
||||
renderControlCharacters,
|
||||
fontLigatures !== EditorFontLigatures.OFF,
|
||||
null
|
||||
), sb);
|
||||
renderViewLine(new RenderLineInput(
|
||||
(fontInfo.isMonospace && !disableMonospaceOptimizations),
|
||||
fontInfo.canUseHalfwidthRightwardsArrow,
|
||||
line,
|
||||
false,
|
||||
isBasicASCII,
|
||||
containsRTL,
|
||||
0,
|
||||
lineTokens,
|
||||
[],
|
||||
tabSize,
|
||||
0,
|
||||
fontInfo.spaceWidth,
|
||||
fontInfo.middotWidth,
|
||||
fontInfo.wsmiddotWidth,
|
||||
stopRenderingLineAfter,
|
||||
renderWhitespace,
|
||||
renderControlCharacters,
|
||||
fontLigatures !== EditorFontLigatures.OFF,
|
||||
null
|
||||
), sb);
|
||||
|
||||
sb.appendASCIIString('</div>');
|
||||
}
|
||||
sb.appendASCIIString('</div>');
|
||||
|
||||
Configuration.applyFontInfoSlow(domNode, fontInfo);
|
||||
const html = sb.build();
|
||||
const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html;
|
||||
domNode.innerHTML = trustedhtml as string;
|
||||
}
|
||||
sb.appendASCIIString('</div>');
|
||||
|
||||
Configuration.applyFontInfoSlow(domNode, fontInfo);
|
||||
const html = sb.build();
|
||||
const trustedhtml = ttPolicy ? ttPolicy.createHTML(html) : html;
|
||||
domNode.innerHTML = trustedhtml as string;
|
||||
}
|
||||
|
||||
function renderSingleLineText(text: string, lineStart: string, tabSize: number, renderWhitespace: boolean): string {
|
||||
|
|
|
@ -10,9 +10,7 @@ import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
|||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { InlineCompletionsProvider, InlineCompletionsProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
|
||||
import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/ghostTextController';
|
||||
import { GhostTextWidget } from 'vs/editor/contrib/inlineCompletions/ghostTextWidget';
|
||||
import { InlineCompletionsModel, InlineCompletionsSession, inlineCompletionToGhostText } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsModel';
|
||||
import { InlineCompletionsModel, inlineCompletionToGhostText } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsModel';
|
||||
import { GhostTextContext, MockInlineCompletionsProvider } from 'vs/editor/contrib/inlineCompletions/test/utils';
|
||||
import { ITestCodeEditor, TestCodeEditorCreationOptions, withAsyncTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||
|
@ -447,15 +445,3 @@ async function withAsyncTestCodeEditorAndInlineCompletionsModel(
|
|||
disposableStore.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class MockedGhostTextController extends GhostTextController {
|
||||
public getInlineCompletionSession(): InlineCompletionsSession | undefined {
|
||||
return this.activeController.value?.activeInlineCompletionsModel?.completionSession.value;
|
||||
}
|
||||
|
||||
public get ghostTextWidget(): GhostTextWidget {
|
||||
return this.widget;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue