Path: blob/main/extensions/copilot/test/simulation/fixtures/edit/6276.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { h } from 'vs/base/browser/dom';6import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';7import { KeybindingLabel, unthemedKeybindingLabelOptions } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';8import { Action, IAction, Separator } from 'vs/base/common/actions';9import { equals } from 'vs/base/common/arrays';10import { RunOnceScheduler } from 'vs/base/common/async';11import { Codicon } from 'vs/base/common/codicons';12import { Disposable, toDisposable } from 'vs/base/common/lifecycle';13import { OS } from 'vs/base/common/platform';14import { ThemeIcon } from 'vs/base/common/themables';15import 'vs/css!./inlineSuggestionHintsWidget';16import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';17import { EditorOption } from 'vs/editor/common/config/editorOptions';18import { Position } from 'vs/editor/common/core/position';19import { Command } from 'vs/editor/common/languages';20import { PositionAffinity } from 'vs/editor/common/model';21import { showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId } from 'vs/editor/contrib/inlineCompletions/browser/consts';22import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel';23import { localize } from 'vs/nls';24import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';25import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';26import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';27import { ICommandService } from 'vs/platform/commands/common/commands';28import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';29import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';30import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';31import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';32import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';33import { registerIcon } from 'vs/platform/theme/common/iconRegistry';3435export class InlineSuggestionHintsWidget extends Disposable {36private readonly widget = this._register(this.instantiationService.createInstance(InlineSuggestionHintsContentWidget, this.editor, true));3738private sessionPosition: Position | undefined = undefined;39private isDisposed = false;4041constructor(42private readonly editor: ICodeEditor,43private readonly model: InlineCompletionsModel,44@IInstantiationService private readonly instantiationService: IInstantiationService,45) {46super();4748editor.addContentWidget(this.widget);49this._register(toDisposable(() => editor.removeContentWidget(this.widget)));50this._register(model.onDidChange(() => this.update()));51this._register(editor.onDidChangeConfiguration(() => this.update()));52this.update();53}5455override dispose(): void {56this.isDisposed = true;57super.dispose();58}5960private update(): void {61if (this.isDisposed) {62return;63}6465const options = this.editor.getOption(EditorOption.inlineSuggest);66if (options.showToolbar !== 'always' || !this.model.ghostText) {67this.widget.update(null, 0, undefined, []);68this.sessionPosition = undefined;69return;70}7172if (!this.model.completionSession.value) {73return;74}7576if (!this.model.completionSession.value.hasBeenTriggeredExplicitly) {77this.model.completionSession.value.ensureUpdateWithExplicitContext();78}7980const ghostText = this.model.ghostText;8182const firstColumn = ghostText.parts[0].column;83if (this.sessionPosition && this.sessionPosition.lineNumber !== ghostText.lineNumber) {84this.sessionPosition = undefined;85}8687const position = new Position(ghostText.lineNumber, Math.min(firstColumn, this.sessionPosition?.column ?? Number.MAX_SAFE_INTEGER));88this.sessionPosition = position;8990this.widget.update(91this.sessionPosition,92this.model.completionSession.value.currentlySelectedIndex,93this.model.completionSession.value.hasBeenTriggeredExplicitly ? this.model.completionSession.value.getInlineCompletionsCountSync() : undefined,94this.model.completionSession.value.commands,95);96}97}9899const inlineSuggestionHintsNextIcon = registerIcon('inline-suggestion-hints-next', Codicon.chevronRight, localize('parameterHintsNextIcon', 'Icon for show next parameter hint.'));100const inlineSuggestionHintsPreviousIcon = registerIcon('inline-suggestion-hints-previous', Codicon.chevronLeft, localize('parameterHintsPreviousIcon', 'Icon for show previous parameter hint.'));101102export class InlineSuggestionHintsContentWidget extends Disposable implements IContentWidget {103private static _dropDownVisible = false;104public static get dropDownVisible() { return this._dropDownVisible; }105106private static id = 0;107108private readonly id = `InlineSuggestionHintsContentWidget${InlineSuggestionHintsContentWidget.id++}`;109public readonly allowEditorOverflow = true;110public readonly suppressMouseDown = false;111112private readonly nodes = h('div.inlineSuggestionsHints', { className: this.withBorder ? '.withBorder' : '' }, [113h('div', { style: { display: 'flex' } }, [114h('div@actionBar', { className: 'custom-actions' }),115h('div@toolBar'),116])117]);118private position: Position | null = null;119120private createCommandAction(commandId: string, label: string, iconClassName: string): Action {121const action = new Action(122commandId,123label,124iconClassName,125true,126() => this._commandService.executeCommand(commandId),127);128const kb = this.keybindingService.lookupKeybinding(commandId, this._contextKeyService);129let tooltip = label;130if (kb) {131tooltip = localize({ key: 'content', comment: ['A label', 'A keybinding'] }, '{0} ({1})', label, kb.getLabel());132}133action.tooltip = tooltip;134return action;135}136137private readonly previousAction = this.createCommandAction(showPreviousInlineSuggestionActionId, localize('previous', 'Previous'), ThemeIcon.asClassName(inlineSuggestionHintsPreviousIcon));138private readonly availableSuggestionCountAction = new Action('inlineSuggestionHints.availableSuggestionCount', '', undefined, false);139private readonly nextAction = this.createCommandAction(showNextInlineSuggestionActionId, localize('next', 'Next'), ThemeIcon.asClassName(inlineSuggestionHintsNextIcon));140141private readonly toolBar: CustomizedMenuWorkbenchToolBar;142143// TODO@hediet: deprecate MenuId.InlineCompletionsActions144private readonly inlineCompletionsActionsMenus = this._register(this._menuService.createMenu(145MenuId.InlineCompletionsActions,146this._contextKeyService147));148149private readonly clearAvailableSuggestionCountLabelDebounced = this._register(new RunOnceScheduler(() => {150this.availableSuggestionCountAction.label = '';151}, 100));152153private readonly disableButtonsDebounced = this._register(new RunOnceScheduler(() => {154this.previousAction.enabled = this.nextAction.enabled = false;155}, 100));156157private lastCurrentSuggestionIdx = -1;158private lastSuggestionCount = -1;159private lastCommands: Command[] = [];160161constructor(162private readonly editor: ICodeEditor,163private readonly withBorder: boolean,164@ICommandService private readonly _commandService: ICommandService,165@IInstantiationService instantiationService: IInstantiationService,166@IKeybindingService private readonly keybindingService: IKeybindingService,167@IContextKeyService private readonly _contextKeyService: IContextKeyService,168@IMenuService private readonly _menuService: IMenuService,169) {170super();171172const actionBar = this._register(new ActionBar(this.nodes.actionBar));173174actionBar.push(this.previousAction, { icon: true, label: false });175actionBar.push(this.availableSuggestionCountAction);176actionBar.push(this.nextAction, { icon: true, label: false });177178this.toolBar = this._register(instantiationService.createInstance(CustomizedMenuWorkbenchToolBar, this.nodes.toolBar, MenuId.InlineSuggestionToolbar, {179menuOptions: { renderShortTitle: true },180toolbarOptions: { primaryGroup: g => g.startsWith('primary') },181actionViewItemProvider: (action, options) => {182return action instanceof MenuItemAction ? instantiationService.createInstance(StatusBarViewItem, action, undefined) : undefined;183},184telemetrySource: 'InlineSuggestionToolbar',185}));186187this._register(this.toolBar.onDidChangeDropdownVisibility(e => {188InlineSuggestionHintsContentWidget._dropDownVisible = e;189}));190}191192public update(position: Position | null, currentSuggestionIdx: number, suggestionCount: number | undefined, extraCommands: Command[]): void {193if (this.position === position194&& this.lastCurrentSuggestionIdx === currentSuggestionIdx195&& this.lastSuggestionCount === suggestionCount196&& equals(this.lastCommands, extraCommands)) {197// nothing to update198return;199}200201this.position = position;202this.lastCurrentSuggestionIdx = currentSuggestionIdx;203this.lastSuggestionCount = suggestionCount ?? -1;204this.lastCommands = extraCommands;205206if (suggestionCount !== undefined && suggestionCount > 1) {207this.disableButtonsDebounced.cancel();208this.previousAction.enabled = this.nextAction.enabled = true;209} else {210this.disableButtonsDebounced.schedule();211}212213if (suggestionCount !== undefined) {214this.clearAvailableSuggestionCountLabelDebounced.cancel();215this.availableSuggestionCountAction.label = `${currentSuggestionIdx + 1}/${suggestionCount}`;216} else {217this.clearAvailableSuggestionCountLabelDebounced.schedule();218}219220this.editor.layoutContentWidget(this);221222const extraActions = extraCommands.map<IAction>(c => ({223class: undefined,224id: c.id,225enabled: true,226tooltip: c.tooltip || '',227label: c.title,228run: (event) => {229return this._commandService.executeCommand(c.id);230},231}));232233for (const [_, group] of this.inlineCompletionsActionsMenus.getActions()) {234for (const action of group) {235if (action instanceof MenuItemAction) {236extraActions.push(action);237}238}239}240241if (extraActions.length > 0) {242extraActions.unshift(new Separator());243}244245this.toolBar.setAdditionalSecondaryActions(extraActions);246}247248getId(): string { return this.id; }249250getDomNode(): HTMLElement {251return this.nodes.root;252}253254getPosition(): IContentWidgetPosition | null {255return {256position: this.position,257preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW],258positionAffinity: PositionAffinity.LeftOfInjectedText,259};260}261}262263class StatusBarViewItem extends MenuEntryActionViewItem {264protected override updateLabel() {265const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService);266if (!kb) {267return super.updateLabel();268}269if (this.label) {270const div = h('div.keybinding').root;271272const k = new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions });273k.set(kb);274this.label.textContent = this._action.label;275this.label.appendChild(div);276this.label.classList.add('inlineSuggestionStatusBarItemLabel');277}278}279}280281export class CustomizedMenuWorkbenchToolBar extends WorkbenchToolBar {282private readonly menu = this._store.add(this.menuService.createMenu(this.menuId, this.contextKeyService, { emitEventsForSubmenuChanges: true }));283private additionalActions: IAction[] = [];284285constructor(286container: HTMLElement,287private readonly menuId: MenuId,288private readonly options2: IMenuWorkbenchToolBarOptions | undefined,289@IMenuService private readonly menuService: IMenuService,290@IContextKeyService private readonly contextKeyService: IContextKeyService,291@IContextMenuService contextMenuService: IContextMenuService,292@IKeybindingService keybindingService: IKeybindingService,293@ITelemetryService telemetryService: ITelemetryService,294) {295super(container, { resetMenu: menuId, ...options2 }, menuService, contextKeyService, contextMenuService, keybindingService, telemetryService);296297this._store.add(this.menu.onDidChange(() => this.updateToolbar()));298this.updateToolbar();299}300301private updateToolbar(): void {302const primary: IAction[] = [];303const secondary: IAction[] = [];304createAndFillInActionBarActions(305this.menu,306this.options2?.menuOptions,307{ primary, secondary },308this.options2?.toolbarOptions?.primaryGroup, this.options2?.toolbarOptions?.shouldInlineSubmenu, this.options2?.toolbarOptions?.useSeparatorsInPrimaryActions309);310311secondary.push(...this.additionalActions);312this.setActions(primary, secondary);313}314315setAdditionalSecondaryActions(actions: IAction[]): void {316if (equals(this.additionalActions, actions, (a, b) => a === b)) {317// don't update if the actions are the same318return;319}320321this.additionalActions = actions;322this.updateToolbar();323}324}325326327