Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/peekView/browser/peekView.ts
5263 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../../../base/browser/dom.js';
7
import { IMouseEvent } from '../../../../base/browser/mouseEvent.js';
8
import { ActionBar, ActionsOrientation, IActionBarOptions } from '../../../../base/browser/ui/actionbar/actionbar.js';
9
import { Action } from '../../../../base/common/actions.js';
10
import { Codicon } from '../../../../base/common/codicons.js';
11
import { ThemeIcon } from '../../../../base/common/themables.js';
12
import { Color } from '../../../../base/common/color.js';
13
import { Emitter } from '../../../../base/common/event.js';
14
import { IDisposable } from '../../../../base/common/lifecycle.js';
15
import * as objects from '../../../../base/common/objects.js';
16
import './media/peekViewWidget.css';
17
import { ICodeEditor } from '../../../browser/editorBrowser.js';
18
import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js';
19
import { EmbeddedCodeEditorWidget } from '../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js';
20
import { EditorOption } from '../../../common/config/editorOptions.js';
21
import { IEditorContribution } from '../../../common/editorCommon.js';
22
import { IOptions, IStyles, ZoneWidget } from '../../zoneWidget/browser/zoneWidget.js';
23
import * as nls from '../../../../nls.js';
24
import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
25
import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
26
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
27
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
28
import { activeContrastBorder, contrastBorder, editorForeground, editorInfoForeground, registerColor } from '../../../../platform/theme/common/colorRegistry.js';
29
import { observableCodeEditor } from '../../../browser/observableCodeEditor.js';
30
31
export const IPeekViewService = createDecorator<IPeekViewService>('IPeekViewService');
32
export interface IPeekViewService {
33
readonly _serviceBrand: undefined;
34
addExclusiveWidget(editor: ICodeEditor, widget: PeekViewWidget): void;
35
}
36
37
registerSingleton(IPeekViewService, class implements IPeekViewService {
38
declare readonly _serviceBrand: undefined;
39
40
private readonly _widgets = new Map<ICodeEditor, { widget: PeekViewWidget; listener: IDisposable }>();
41
42
addExclusiveWidget(editor: ICodeEditor, widget: PeekViewWidget): void {
43
const existing = this._widgets.get(editor);
44
if (existing) {
45
existing.listener.dispose();
46
existing.widget.dispose();
47
}
48
const remove = () => {
49
const data = this._widgets.get(editor);
50
if (data && data.widget === widget) {
51
data.listener.dispose();
52
this._widgets.delete(editor);
53
}
54
};
55
this._widgets.set(editor, { widget, listener: widget.onDidClose(remove) });
56
}
57
}, InstantiationType.Delayed);
58
59
export namespace PeekContext {
60
export const inPeekEditor = new RawContextKey<boolean>('inReferenceSearchEditor', true, nls.localize('inReferenceSearchEditor', "Whether the current code editor is embedded inside peek"));
61
export const notInPeekEditor = inPeekEditor.toNegated();
62
}
63
64
class PeekContextController implements IEditorContribution {
65
66
static readonly ID = 'editor.contrib.referenceController';
67
68
constructor(
69
editor: ICodeEditor,
70
@IContextKeyService contextKeyService: IContextKeyService
71
) {
72
if (editor instanceof EmbeddedCodeEditorWidget) {
73
PeekContext.inPeekEditor.bindTo(contextKeyService);
74
}
75
}
76
77
dispose(): void { }
78
}
79
80
registerEditorContribution(PeekContextController.ID, PeekContextController, EditorContributionInstantiation.Eager); // eager because it needs to define a context key
81
82
export interface IPeekViewStyles extends IStyles {
83
headerBackgroundColor?: Color;
84
primaryHeadingColor?: Color;
85
secondaryHeadingColor?: Color;
86
}
87
88
export type IPeekViewOptions = IOptions & IPeekViewStyles & {
89
supportOnTitleClick?: boolean;
90
};
91
92
const defaultOptions: IPeekViewOptions = {
93
headerBackgroundColor: Color.white,
94
primaryHeadingColor: Color.fromHex('#333333'),
95
secondaryHeadingColor: Color.fromHex('#6c6c6cb3')
96
};
97
98
export abstract class PeekViewWidget extends ZoneWidget {
99
100
declare readonly _serviceBrand: undefined;
101
102
private readonly _onDidClose = new Emitter<PeekViewWidget>();
103
readonly onDidClose = this._onDidClose.event;
104
private disposed?: true;
105
106
protected _headElement?: HTMLDivElement;
107
protected _titleElement?: HTMLDivElement;
108
protected _primaryHeading?: HTMLElement;
109
protected _secondaryHeading?: HTMLElement;
110
protected _metaHeading?: HTMLElement;
111
protected _actionbarWidget?: ActionBar;
112
protected _bodyElement?: HTMLDivElement;
113
114
constructor(
115
editor: ICodeEditor,
116
options: IPeekViewOptions,
117
@IInstantiationService protected readonly instantiationService: IInstantiationService
118
) {
119
super(editor, options);
120
objects.mixin(this.options, defaultOptions, false);
121
122
const e = observableCodeEditor(this.editor);
123
e.openedPeekWidgets.set(e.openedPeekWidgets.get() + 1, undefined);
124
}
125
126
override dispose(): void {
127
if (!this.disposed) {
128
this.disposed = true; // prevent consumers who dispose on onDidClose from looping
129
super.dispose();
130
this._onDidClose.fire(this);
131
this._onDidClose.dispose();
132
133
const e = observableCodeEditor(this.editor);
134
e.openedPeekWidgets.set(e.openedPeekWidgets.get() - 1, undefined);
135
}
136
}
137
138
override style(styles: IPeekViewStyles): void {
139
const options = <IPeekViewOptions>this.options;
140
if (styles.headerBackgroundColor) {
141
options.headerBackgroundColor = styles.headerBackgroundColor;
142
}
143
if (styles.primaryHeadingColor) {
144
options.primaryHeadingColor = styles.primaryHeadingColor;
145
}
146
if (styles.secondaryHeadingColor) {
147
options.secondaryHeadingColor = styles.secondaryHeadingColor;
148
}
149
super.style(styles);
150
}
151
152
protected override _applyStyles(): void {
153
super._applyStyles();
154
const options = <IPeekViewOptions>this.options;
155
if (this._headElement && options.headerBackgroundColor) {
156
this._headElement.style.backgroundColor = options.headerBackgroundColor.toString();
157
}
158
if (this._primaryHeading && options.primaryHeadingColor) {
159
this._primaryHeading.style.color = options.primaryHeadingColor.toString();
160
}
161
if (this._secondaryHeading && options.secondaryHeadingColor) {
162
this._secondaryHeading.style.color = options.secondaryHeadingColor.toString();
163
}
164
if (this._bodyElement && options.frameColor) {
165
this._bodyElement.style.borderColor = options.frameColor.toString();
166
}
167
}
168
169
protected _fillContainer(container: HTMLElement): void {
170
this.setCssClass('peekview-widget');
171
172
this._headElement = dom.$<HTMLDivElement>('.head');
173
this._bodyElement = dom.$<HTMLDivElement>('.body');
174
175
this._fillHead(this._headElement);
176
this._fillBody(this._bodyElement);
177
178
container.appendChild(this._headElement);
179
container.appendChild(this._bodyElement);
180
}
181
182
protected _fillHead(container: HTMLElement, noCloseAction?: boolean): void {
183
this._titleElement = dom.$('.peekview-title');
184
if ((this.options as IPeekViewOptions).supportOnTitleClick) {
185
this._titleElement.classList.add('clickable');
186
dom.addStandardDisposableListener(this._titleElement, 'click', event => this._onTitleClick(event));
187
}
188
dom.append(this._headElement!, this._titleElement);
189
190
this._fillTitleIcon(this._titleElement);
191
this._primaryHeading = dom.$('span.filename');
192
this._secondaryHeading = dom.$('span.dirname');
193
this._metaHeading = dom.$('span.meta');
194
dom.append(this._titleElement, this._primaryHeading, this._secondaryHeading, this._metaHeading);
195
196
const actionsContainer = dom.$('.peekview-actions');
197
dom.append(this._headElement!, actionsContainer);
198
199
const actionBarOptions = this._getActionBarOptions();
200
this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions);
201
this._disposables.add(this._actionbarWidget);
202
203
if (!noCloseAction) {
204
this._actionbarWidget.push(this._disposables.add(new Action('peekview.close', nls.localize('label.close', "Close"), ThemeIcon.asClassName(Codicon.close), true, () => {
205
this.dispose();
206
return Promise.resolve();
207
})), { label: false, icon: true });
208
}
209
}
210
211
protected _fillTitleIcon(container: HTMLElement): void {
212
}
213
214
protected _getActionBarOptions(): IActionBarOptions {
215
return {
216
actionViewItemProvider: createActionViewItem.bind(undefined, this.instantiationService),
217
orientation: ActionsOrientation.HORIZONTAL
218
};
219
}
220
221
protected _onTitleClick(event: IMouseEvent): void {
222
// implement me if supportOnTitleClick option is set
223
}
224
225
setTitle(primaryHeading: string, secondaryHeading?: string): void {
226
if (this._primaryHeading && this._secondaryHeading) {
227
this._primaryHeading.innerText = primaryHeading;
228
this._primaryHeading.setAttribute('title', primaryHeading);
229
if (secondaryHeading) {
230
this._secondaryHeading.innerText = secondaryHeading;
231
} else {
232
dom.clearNode(this._secondaryHeading);
233
}
234
}
235
}
236
237
setMetaTitle(value: string): void {
238
if (this._metaHeading) {
239
if (value) {
240
this._metaHeading.innerText = value;
241
dom.show(this._metaHeading);
242
} else {
243
dom.hide(this._metaHeading);
244
}
245
}
246
}
247
248
protected abstract _fillBody(container: HTMLElement): void;
249
250
protected override _doLayout(heightInPixel: number, widthInPixel: number): void {
251
252
if (!this._isShowing && heightInPixel < 0) {
253
// Looks like the view zone got folded away!
254
this.dispose();
255
return;
256
}
257
258
const headHeight = Math.ceil(this.editor.getOption(EditorOption.lineHeight) * 1.2);
259
const bodyHeight = Math.round(heightInPixel - (headHeight + 1 /* the border-top width */));
260
261
this._doLayoutHead(headHeight, widthInPixel);
262
this._doLayoutBody(bodyHeight, widthInPixel);
263
}
264
265
protected _doLayoutHead(heightInPixel: number, widthInPixel: number): void {
266
if (this._headElement) {
267
this._headElement.style.height = `${heightInPixel}px`;
268
this._headElement.style.lineHeight = this._headElement.style.height;
269
}
270
}
271
272
protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void {
273
if (this._bodyElement) {
274
this._bodyElement.style.height = `${heightInPixel}px`;
275
}
276
}
277
}
278
279
280
export 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.'));
281
export 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.'));
282
export const peekViewTitleInfoForeground = registerColor('peekViewTitleDescription.foreground', { dark: '#ccccccb3', light: '#616161', hcDark: '#FFFFFF99', hcLight: '#292929' }, nls.localize('peekViewTitleInfoForeground', 'Color of the peek view title info.'));
283
export const peekViewBorder = registerColor('peekView.border', { dark: editorInfoForeground, light: editorInfoForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('peekViewBorder', 'Color of the peek view borders and arrow.'));
284
285
export 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.'));
286
export 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.'));
287
export 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.'));
288
export 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.'));
289
export 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.'));
290
export 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.'));
291
export const peekViewEditorGutterBackground = registerColor('peekViewEditorGutter.background', peekViewEditorBackground, nls.localize('peekViewEditorGutterBackground', 'Background color of the gutter in the peek view editor.'));
292
export const peekViewEditorStickyScrollBackground = registerColor('peekViewEditorStickyScroll.background', peekViewEditorBackground, nls.localize('peekViewEditorStickScrollBackground', 'Background color of sticky scroll in the peek view editor.'));
293
export const peekViewEditorStickyScrollGutterBackground = registerColor('peekViewEditorStickyScrollGutter.background', peekViewEditorBackground, nls.localize('peekViewEditorStickyScrollGutterBackground', 'Background color of the gutter part of sticky scroll in the peek view editor.'));
294
295
export 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.'));
296
export const peekViewEditorMatchHighlight = registerColor('peekViewEditor.matchHighlightBackground', { dark: '#ff8f0099', light: '#f5d802de', hcDark: null, hcLight: null }, nls.localize('peekViewEditorMatchHighlight', 'Match highlight color in the peek view editor.'));
297
export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor.matchHighlightBorder', { dark: null, light: null, hcDark: activeContrastBorder, hcLight: activeContrastBorder }, nls.localize('peekViewEditorMatchHighlightBorder', 'Match highlight border in the peek view editor.'));
298
299