Path: blob/main/src/vs/editor/contrib/peekView/browser/peekView.ts
5260 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 * as dom from '../../../../base/browser/dom.js';6import { IMouseEvent } from '../../../../base/browser/mouseEvent.js';7import { ActionBar, ActionsOrientation, IActionBarOptions } from '../../../../base/browser/ui/actionbar/actionbar.js';8import { Action } from '../../../../base/common/actions.js';9import { Codicon } from '../../../../base/common/codicons.js';10import { ThemeIcon } from '../../../../base/common/themables.js';11import { Color } from '../../../../base/common/color.js';12import { Emitter } from '../../../../base/common/event.js';13import { IDisposable } from '../../../../base/common/lifecycle.js';14import * as objects from '../../../../base/common/objects.js';15import './media/peekViewWidget.css';16import { ICodeEditor } from '../../../browser/editorBrowser.js';17import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js';18import { EmbeddedCodeEditorWidget } from '../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js';19import { EditorOption } from '../../../common/config/editorOptions.js';20import { IEditorContribution } from '../../../common/editorCommon.js';21import { IOptions, IStyles, ZoneWidget } from '../../zoneWidget/browser/zoneWidget.js';22import * as nls from '../../../../nls.js';23import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';24import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';25import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';26import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';27import { activeContrastBorder, contrastBorder, editorForeground, editorInfoForeground, registerColor } from '../../../../platform/theme/common/colorRegistry.js';28import { observableCodeEditor } from '../../../browser/observableCodeEditor.js';2930export const IPeekViewService = createDecorator<IPeekViewService>('IPeekViewService');31export interface IPeekViewService {32readonly _serviceBrand: undefined;33addExclusiveWidget(editor: ICodeEditor, widget: PeekViewWidget): void;34}3536registerSingleton(IPeekViewService, class implements IPeekViewService {37declare readonly _serviceBrand: undefined;3839private readonly _widgets = new Map<ICodeEditor, { widget: PeekViewWidget; listener: IDisposable }>();4041addExclusiveWidget(editor: ICodeEditor, widget: PeekViewWidget): void {42const existing = this._widgets.get(editor);43if (existing) {44existing.listener.dispose();45existing.widget.dispose();46}47const remove = () => {48const data = this._widgets.get(editor);49if (data && data.widget === widget) {50data.listener.dispose();51this._widgets.delete(editor);52}53};54this._widgets.set(editor, { widget, listener: widget.onDidClose(remove) });55}56}, InstantiationType.Delayed);5758export namespace PeekContext {59export const inPeekEditor = new RawContextKey<boolean>('inReferenceSearchEditor', true, nls.localize('inReferenceSearchEditor', "Whether the current code editor is embedded inside peek"));60export const notInPeekEditor = inPeekEditor.toNegated();61}6263class PeekContextController implements IEditorContribution {6465static readonly ID = 'editor.contrib.referenceController';6667constructor(68editor: ICodeEditor,69@IContextKeyService contextKeyService: IContextKeyService70) {71if (editor instanceof EmbeddedCodeEditorWidget) {72PeekContext.inPeekEditor.bindTo(contextKeyService);73}74}7576dispose(): void { }77}7879registerEditorContribution(PeekContextController.ID, PeekContextController, EditorContributionInstantiation.Eager); // eager because it needs to define a context key8081export interface IPeekViewStyles extends IStyles {82headerBackgroundColor?: Color;83primaryHeadingColor?: Color;84secondaryHeadingColor?: Color;85}8687export type IPeekViewOptions = IOptions & IPeekViewStyles & {88supportOnTitleClick?: boolean;89};9091const defaultOptions: IPeekViewOptions = {92headerBackgroundColor: Color.white,93primaryHeadingColor: Color.fromHex('#333333'),94secondaryHeadingColor: Color.fromHex('#6c6c6cb3')95};9697export abstract class PeekViewWidget extends ZoneWidget {9899declare readonly _serviceBrand: undefined;100101private readonly _onDidClose = new Emitter<PeekViewWidget>();102readonly onDidClose = this._onDidClose.event;103private disposed?: true;104105protected _headElement?: HTMLDivElement;106protected _titleElement?: HTMLDivElement;107protected _primaryHeading?: HTMLElement;108protected _secondaryHeading?: HTMLElement;109protected _metaHeading?: HTMLElement;110protected _actionbarWidget?: ActionBar;111protected _bodyElement?: HTMLDivElement;112113constructor(114editor: ICodeEditor,115options: IPeekViewOptions,116@IInstantiationService protected readonly instantiationService: IInstantiationService117) {118super(editor, options);119objects.mixin(this.options, defaultOptions, false);120121const e = observableCodeEditor(this.editor);122e.openedPeekWidgets.set(e.openedPeekWidgets.get() + 1, undefined);123}124125override dispose(): void {126if (!this.disposed) {127this.disposed = true; // prevent consumers who dispose on onDidClose from looping128super.dispose();129this._onDidClose.fire(this);130this._onDidClose.dispose();131132const e = observableCodeEditor(this.editor);133e.openedPeekWidgets.set(e.openedPeekWidgets.get() - 1, undefined);134}135}136137override style(styles: IPeekViewStyles): void {138const options = <IPeekViewOptions>this.options;139if (styles.headerBackgroundColor) {140options.headerBackgroundColor = styles.headerBackgroundColor;141}142if (styles.primaryHeadingColor) {143options.primaryHeadingColor = styles.primaryHeadingColor;144}145if (styles.secondaryHeadingColor) {146options.secondaryHeadingColor = styles.secondaryHeadingColor;147}148super.style(styles);149}150151protected override _applyStyles(): void {152super._applyStyles();153const options = <IPeekViewOptions>this.options;154if (this._headElement && options.headerBackgroundColor) {155this._headElement.style.backgroundColor = options.headerBackgroundColor.toString();156}157if (this._primaryHeading && options.primaryHeadingColor) {158this._primaryHeading.style.color = options.primaryHeadingColor.toString();159}160if (this._secondaryHeading && options.secondaryHeadingColor) {161this._secondaryHeading.style.color = options.secondaryHeadingColor.toString();162}163if (this._bodyElement && options.frameColor) {164this._bodyElement.style.borderColor = options.frameColor.toString();165}166}167168protected _fillContainer(container: HTMLElement): void {169this.setCssClass('peekview-widget');170171this._headElement = dom.$<HTMLDivElement>('.head');172this._bodyElement = dom.$<HTMLDivElement>('.body');173174this._fillHead(this._headElement);175this._fillBody(this._bodyElement);176177container.appendChild(this._headElement);178container.appendChild(this._bodyElement);179}180181protected _fillHead(container: HTMLElement, noCloseAction?: boolean): void {182this._titleElement = dom.$('.peekview-title');183if ((this.options as IPeekViewOptions).supportOnTitleClick) {184this._titleElement.classList.add('clickable');185dom.addStandardDisposableListener(this._titleElement, 'click', event => this._onTitleClick(event));186}187dom.append(this._headElement!, this._titleElement);188189this._fillTitleIcon(this._titleElement);190this._primaryHeading = dom.$('span.filename');191this._secondaryHeading = dom.$('span.dirname');192this._metaHeading = dom.$('span.meta');193dom.append(this._titleElement, this._primaryHeading, this._secondaryHeading, this._metaHeading);194195const actionsContainer = dom.$('.peekview-actions');196dom.append(this._headElement!, actionsContainer);197198const actionBarOptions = this._getActionBarOptions();199this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions);200this._disposables.add(this._actionbarWidget);201202if (!noCloseAction) {203this._actionbarWidget.push(this._disposables.add(new Action('peekview.close', nls.localize('label.close', "Close"), ThemeIcon.asClassName(Codicon.close), true, () => {204this.dispose();205return Promise.resolve();206})), { label: false, icon: true });207}208}209210protected _fillTitleIcon(container: HTMLElement): void {211}212213protected _getActionBarOptions(): IActionBarOptions {214return {215actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService),216orientation: ActionsOrientation.HORIZONTAL217};218}219220protected _onTitleClick(event: IMouseEvent): void {221// implement me if supportOnTitleClick option is set222}223224setTitle(primaryHeading: string, secondaryHeading?: string): void {225if (this._primaryHeading && this._secondaryHeading) {226this._primaryHeading.innerText = primaryHeading;227this._primaryHeading.setAttribute('title', primaryHeading);228if (secondaryHeading) {229this._secondaryHeading.innerText = secondaryHeading;230} else {231dom.clearNode(this._secondaryHeading);232}233}234}235236setMetaTitle(value: string): void {237if (this._metaHeading) {238if (value) {239this._metaHeading.innerText = value;240dom.show(this._metaHeading);241} else {242dom.hide(this._metaHeading);243}244}245}246247protected abstract _fillBody(container: HTMLElement): void;248249protected override _doLayout(heightInPixel: number, widthInPixel: number): void {250251if (!this._isShowing && heightInPixel < 0) {252// Looks like the view zone got folded away!253this.dispose();254return;255}256257const headHeight = Math.ceil(this.editor.getOption(EditorOption.lineHeight) * 1.2);258const bodyHeight = Math.round(heightInPixel - (headHeight + 1 /* the border-top width */));259260this._doLayoutHead(headHeight, widthInPixel);261this._doLayoutBody(bodyHeight, widthInPixel);262}263264protected _doLayoutHead(heightInPixel: number, widthInPixel: number): void {265if (this._headElement) {266this._headElement.style.height = `${heightInPixel}px`;267this._headElement.style.lineHeight = this._headElement.style.height;268}269}270271protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void {272if (this._bodyElement) {273this._bodyElement.style.height = `${heightInPixel}px`;274}275}276}277278279export const peekViewTitleBackground = registerColor('peekViewTitle.background', { dark: '#252526', light: '#F3F3F3', hcDark: Color.black, hcLight: Color.white }, nls.localize('peekViewTitleBackground', 'Background color of the peek view title area.'));280export const peekViewTitleForeground = registerColor('peekViewTitleLabel.foreground', { dark: Color.white, light: Color.black, hcDark: Color.white, hcLight: editorForeground }, nls.localize('peekViewTitleForeground', 'Color of the peek view title.'));281export const peekViewTitleInfoForeground = registerColor('peekViewTitleDescription.foreground', { dark: '#ccccccb3', light: '#616161', hcDark: '#FFFFFF99', hcLight: '#292929' }, nls.localize('peekViewTitleInfoForeground', 'Color of the peek view title info.'));282export const peekViewBorder = registerColor('peekView.border', { dark: editorInfoForeground, light: editorInfoForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('peekViewBorder', 'Color of the peek view borders and arrow.'));283284export const peekViewResultsBackground = registerColor('peekViewResult.background', { dark: '#252526', light: '#F3F3F3', hcDark: Color.black, hcLight: Color.white }, nls.localize('peekViewResultsBackground', 'Background color of the peek view result list.'));285export const peekViewResultsMatchForeground = registerColor('peekViewResult.lineForeground', { dark: '#bbbbbb', light: '#646465', hcDark: Color.white, hcLight: editorForeground }, nls.localize('peekViewResultsMatchForeground', 'Foreground color for line nodes in the peek view result list.'));286export const peekViewResultsFileForeground = registerColor('peekViewResult.fileForeground', { dark: Color.white, light: '#1E1E1E', hcDark: Color.white, hcLight: editorForeground }, nls.localize('peekViewResultsFileForeground', 'Foreground color for file nodes in the peek view result list.'));287export const peekViewResultsSelectionBackground = registerColor('peekViewResult.selectionBackground', { dark: '#3399ff33', light: '#3399ff33', hcDark: null, hcLight: null }, nls.localize('peekViewResultsSelectionBackground', 'Background color of the selected entry in the peek view result list.'));288export const peekViewResultsSelectionForeground = registerColor('peekViewResult.selectionForeground', { dark: Color.white, light: '#6C6C6C', hcDark: Color.white, hcLight: editorForeground }, nls.localize('peekViewResultsSelectionForeground', 'Foreground color of the selected entry in the peek view result list.'));289export const peekViewEditorBackground = registerColor('peekViewEditor.background', { dark: '#001F33', light: '#F2F8FC', hcDark: Color.black, hcLight: Color.white }, nls.localize('peekViewEditorBackground', 'Background color of the peek view editor.'));290export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', peekViewEditorBackground, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.'));291export const peekViewEditorStickyScrollBackground = registerColor('peekViewEditorStickyScroll.background', peekViewEditorBackground, nls.localize('peekViewEditorStickScrollBackground', 'Background color of sticky scroll in the peek view editor.'));292export const peekViewEditorStickyScrollGutterBackground = registerColor('peekViewEditorStickyScrollGutter.background', peekViewEditorBackground, nls.localize('peekViewEditorStickyScrollGutterBackground', 'Background color of the gutter part of sticky scroll in the peek view editor.'));293294export const peekViewResultsMatchHighlight = registerColor('peekViewResult.matchHighlightBackground', { dark: '#ea5c004d', light: '#ea5c004d', hcDark: null, hcLight: null }, nls.localize('peekViewResultsMatchHighlight', 'Match highlight color in the peek view result list.'));295export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hcDark: null, hcLight: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.'));296export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor.matchHighlightBorder', { dark: null, light: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('peekViewEditorMatchHighlightBorder', 'Match highlight border in the peek view editor.'));297298299