diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ebaa7d6cf51..ba6a5453aac 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1613,6 +1613,35 @@ declare module 'vscode' { resolveKernel?(kernel: T, document: NotebookDocument, webview: NotebookCommunication, token: CancellationToken): ProviderResult; } + /** + * Represents the alignment of status bar items. + */ + export enum NotebookCellStatusBarAlignment { + + /** + * Aligned to the left side. + */ + Left = 1, + + /** + * Aligned to the right side. + */ + Right = 2 + } + + export interface NotebookCellStatusBarItem { + readonly cell: NotebookCell; + readonly alignment: NotebookCellStatusBarAlignment; + readonly priority?: number; + text: string; + tooltip: string | undefined; + command: string | Command | undefined; + accessibilityInformation?: AccessibilityInformation; + show(): void; + hide(): void; + dispose(): void; + } + export namespace notebook { export function registerNotebookContentProvider( notebookType: string, @@ -1670,6 +1699,17 @@ declare module 'vscode' { export function createConcatTextDocument(notebook: NotebookDocument, selector?: DocumentSelector): NotebookConcatTextDocument; export const onDidChangeActiveNotebookKernel: Event<{ document: NotebookDocument, kernel: NotebookKernel | undefined }>; + + /** + * Creates a notebook cell status bar [item](#NotebookCellStatusBarItem). + * It will be disposed automatically when the notebook document is closed or the cell is deleted. + * + * @param cell The cell on which this item should be shown. + * @param alignment The alignment of the item. + * @param priority The priority of the item. Higher values mean the item should be shown more to the left. + * @return A new status bar item. + */ + export function createCellStatusBarItem(cell: NotebookCell, alignment?: NotebookCellStatusBarAlignment, priority?: number): NotebookCellStatusBarItem; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index a377146ad2b..cda01b3eab6 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -4,23 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta } from '../common/extHost.protocol'; -import { Disposable, IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, INotebookKernelInfo, INotebookKernelInfoDto, IEditor, INotebookDocumentFilter, DisplayOrderKey, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IRelativePattern } from 'vs/base/common/glob'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { Emitter } from 'vs/base/common/event'; +import { IRelativePattern } from 'vs/base/common/glob'; +import { combinedDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; - +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, IEditor, INotebookDocumentFilter, INotebookKernelInfo, INotebookKernelInfoDto, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookDocumentMetadata, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol'; class DocumentAndEditorState { static ofSets(before: Set, after: Set): { removed: T[], added: T[] } { @@ -137,6 +137,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo private _toDisposeOnEditorRemove = new Map(); private _currentState?: DocumentAndEditorState; private _editorEventListenersMapping: Map = new Map(); + private readonly _cellStatusBarEntries: Map = new Map(); constructor( extHostContext: IExtHostContext, @@ -144,8 +145,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @ILogService private readonly logService: ILogService - + @ILogService private readonly logService: ILogService, + @INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); @@ -603,6 +604,24 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); textModel?.handleUnknownChange(); } + + async $setStatusBarEntry(id: number, rawStatusBarEntry: INotebookCellStatusBarEntryDto): Promise { + const statusBarEntry = { + ...rawStatusBarEntry, + ...{ cellResource: URI.revive(rawStatusBarEntry.cellResource) } + }; + + const existingEntry = this._cellStatusBarEntries.get(id); + if (existingEntry) { + existingEntry.dispose(); + } + + if (statusBarEntry.visible) { + this._cellStatusBarEntries.set( + id, + this.cellStatusBarService.addEntry(statusBarEntry)); + } + } } export class MainThreadNotebookKernel implements INotebookKernelInfo { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 864c337d345..2f4ce5afda9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -992,6 +992,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I createConcatTextDocument(notebook, selector) { checkProposedApiEnabled(extension); return new ExtHostNotebookConcatDocument(extHostNotebook, extHostDocuments, notebook, selector); + }, + createCellStatusBarItem(cell: vscode.NotebookCell, alignment?: vscode.NotebookCellStatusBarAlignment, priority?: number): vscode.NotebookCellStatusBarItem { + checkProposedApiEnabled(extension); + return extHostNotebook.createNotebookCellStatusBarItemInternal(cell, alignment, priority); } }; @@ -1126,7 +1130,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CellKind: extHostTypes.CellKind, CellOutputKind: extHostTypes.CellOutputKind, NotebookCellRunState: extHostTypes.NotebookCellRunState, - NotebookRunState: extHostTypes.NotebookRunState + NotebookRunState: extHostTypes.NotebookRunState, + NotebookCellStatusBarAlignment: extHostTypes.NotebookCellStatusBarAlignment }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5156730c7f6..398a6fb71db 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -719,6 +719,8 @@ export type NotebookCellOutputsSplice = [ IProcessedOutput[] ]; +export type INotebookCellStatusBarEntryDto = Dto; + export interface MainThreadNotebookShape extends IDisposable { $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernelInfoDto: INotebookKernelInfoDto | undefined, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise; $onNotebookChange(viewType: string, resource: UriComponents): Promise; @@ -734,6 +736,7 @@ export interface MainThreadNotebookShape extends IDisposable { $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise; $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise; $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; + $setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise; $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; $onContentChange(resource: UriComponents, viewType: string): void; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index bd8fad9da0e..8576ca499e1 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -14,15 +14,15 @@ import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { CellKind, ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol'; import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; -import { CellEditType, CellOutputKind, diff, ICellDeleteEdit, ICellDto2, ICellEditOperation, ICellInsertEdit, IMainCellDto, INotebookDisplayOrder, INotebookEditData, INotebookKernelInfoDto2, IProcessedOutput, IRawOutput, NotebookCellMetadata, NotebookCellsChangedEvent, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellOutputKind, CellStatusbarAlignment, CellUri, diff, ICellDeleteEdit, ICellDto2, ICellEditOperation, ICellInsertEdit, IMainCellDto, INotebookCellStatusBarEntry, INotebookDisplayOrder, INotebookEditData, INotebookKernelInfoDto2, IProcessedOutput, IRawOutput, NotebookCellMetadata, NotebookCellsChangedEvent, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as vscode from 'vscode'; import { Cache } from './cache'; import { ResourceMap } from 'vs/base/common/map'; @@ -72,6 +72,9 @@ export class ExtHostCell extends Disposable { }; } + private _onDidDispose = new Emitter(); + readonly onDidDispose: Event = this._onDidDispose.event; + private _onDidChangeOutputs = new Emitter[]>(); readonly onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; @@ -134,6 +137,11 @@ export class ExtHostCell extends Disposable { return this._cell; } + dispose() { + super.dispose(); + this._onDidDispose.fire(); + } + private _updateOutputs(newOutputs: vscode.CellOutput[]) { const rawDiffs = diff(this._outputs || [], newOutputs || [], (a) => { return this._outputMapping.has(a); @@ -345,7 +353,9 @@ export class ExtHostNotebookDocument extends Disposable { } if (!this._cellDisposableMapping.has(extCell.handle)) { - this._cellDisposableMapping.set(extCell.handle, new DisposableStore()); + const store = new DisposableStore(); + store.add(extCell); + this._cellDisposableMapping.set(extCell.handle, store); } const store = this._cellDisposableMapping.get(extCell.handle)!; @@ -880,6 +890,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _unInitializedDocuments = new ResourceMap(); private readonly _editors = new Map(); private readonly _webviewComm = new Map(); + private readonly _commandsConverter: CommandsConverter; private readonly _onDidChangeNotebookCells = new Emitter(); readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event; private readonly _onDidChangeCellOutputs = new Emitter(); @@ -928,6 +939,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _extensionStoragePaths: IExtensionStoragePaths, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); + this._commandsConverter = commands.converter; commands.registerArgumentProcessor({ // Serialized INotebookCellActionContext @@ -1542,6 +1554,24 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor); } } + + createNotebookCellStatusBarItemInternal(cell: vscode.NotebookCell, alignment: extHostTypes.NotebookCellStatusBarAlignment | undefined, priority: number | undefined) { + const statusBarItem = new NotebookCellStatusBarItemInternal(this._proxy, this._commandsConverter, cell, alignment, priority); + + // Look up the ExtHostCell for this NotebookCell URI, bind to its disposable lifecycle + const parsedUri = CellUri.parse(cell.uri); + if (parsedUri) { + const document = this._documents.get(parsedUri.notebook); + if (document) { + const cell = document.getCell(parsedUri.handle); + if (cell) { + Event.once(cell.onDidDispose)(() => statusBarItem.dispose()); + } + } + } + + return statusBarItem; + } } function hashPath(resource: URI): string { @@ -1553,3 +1583,178 @@ function isEditEvent(e: vscode.NotebookDocumentEditEvent | vscode.NotebookDocume return typeof (e as vscode.NotebookDocumentEditEvent).undo === 'function' && typeof (e as vscode.NotebookDocumentEditEvent).redo === 'function'; } + +export class NotebookCellStatusBarItemInternal extends Disposable { + private static NEXT_ID = 0; + + private readonly _id = NotebookCellStatusBarItemInternal.NEXT_ID++; + private readonly _internalCommandRegistration: DisposableStore; + + private _isDisposed = false; + private _alignment: extHostTypes.NotebookCellStatusBarAlignment; + + constructor( + private readonly _proxy: MainThreadNotebookShape, + private readonly _commands: CommandsConverter, + private readonly _cell: vscode.NotebookCell, + alignment: extHostTypes.NotebookCellStatusBarAlignment | undefined, + private _priority: number | undefined) { + super(); + this._internalCommandRegistration = this._register(new DisposableStore()); + this._alignment = alignment ?? extHostTypes.NotebookCellStatusBarAlignment.Left; + } + + private _apiItem: vscode.NotebookCellStatusBarItem | undefined; + get apiItem(): vscode.NotebookCellStatusBarItem { + if (!this._apiItem) { + this._apiItem = createNotebookCellStatusBarApiItem(this); + } + + return this._apiItem; + } + + get cell(): vscode.NotebookCell { + return this._cell; + } + + get alignment(): extHostTypes.NotebookCellStatusBarAlignment { + return this._alignment; + } + + set alignment(v: extHostTypes.NotebookCellStatusBarAlignment) { + this._alignment = v; + this.update(); + } + + get priority(): number | undefined { + return this._priority; + } + + set priority(v: number | undefined) { + this._priority = v; + this.update(); + } + + private _text: string = ''; + get text(): string { + return this._text; + } + + set text(v: string) { + this._text = v; + this.update(); + } + + private _tooltip: string | undefined; + get tooltip(): string | undefined { + return this._tooltip; + } + + set tooltip(v: string | undefined) { + this._tooltip = v; + this.update(); + } + + private _command?: { + readonly fromApi: string | vscode.Command, + readonly internal: ICommandDto, + }; + get command(): string | vscode.Command | undefined { + return this._command?.fromApi; + } + + set command(command: string | vscode.Command | undefined) { + if (this._command?.fromApi === command) { + return; + } + + this._internalCommandRegistration.clear(); + if (typeof command === 'string') { + this._command = { + fromApi: command, + internal: this._commands.toInternal({ title: '', command }, this._internalCommandRegistration), + }; + } else if (command) { + this._command = { + fromApi: command, + internal: this._commands.toInternal(command, this._internalCommandRegistration), + }; + } else { + this._command = undefined; + } + this.update(); + } + + private _accessibilityInformation: vscode.AccessibilityInformation | undefined; + get accessibilityInformation(): vscode.AccessibilityInformation | undefined { + return this._accessibilityInformation; + } + + set accessibilityInformation(v: vscode.AccessibilityInformation | undefined) { + this._accessibilityInformation = v; + this.update(); + } + + private _visible: boolean = false; + show(): void { + this._visible = true; + this.update(); + } + + hide(): void { + this._visible = false; + this.update(); + } + + dispose(): void { + this.hide(); + this._isDisposed = true; + this._internalCommandRegistration.dispose(); + } + + private update(): void { + if (this._isDisposed) { + return; + } + + const entry: INotebookCellStatusBarEntry = { + alignment: this.alignment === extHostTypes.NotebookCellStatusBarAlignment.Left ? CellStatusbarAlignment.LEFT : CellStatusbarAlignment.RIGHT, + cellResource: this.cell.uri, + command: this._command?.internal, + text: this.text, + tooltip: this.tooltip, + accessibilityInformation: this.accessibilityInformation, + priority: this.priority, + visible: this._visible + }; + + this._proxy.$setStatusBarEntry(this._id, entry); + } +} + +function createNotebookCellStatusBarApiItem(internalItem: NotebookCellStatusBarItemInternal): vscode.NotebookCellStatusBarItem { + return Object.freeze({ + cell: internalItem.cell, + get alignment() { return internalItem.alignment; }, + set alignment(v: NotebookCellStatusBarItemInternal['alignment']) { internalItem.alignment = v; }, + + get priority() { return internalItem.priority; }, + set priority(v: NotebookCellStatusBarItemInternal['priority']) { internalItem.priority = v; }, + + get text() { return internalItem.text; }, + set text(v: NotebookCellStatusBarItemInternal['text']) { internalItem.text = v; }, + + get tooltip() { return internalItem.tooltip; }, + set tooltip(v: NotebookCellStatusBarItemInternal['tooltip']) { internalItem.tooltip = v; }, + + get command() { return internalItem.command; }, + set command(v: NotebookCellStatusBarItemInternal['command']) { internalItem.command = v; }, + + get accessibilityInformation() { return internalItem.accessibilityInformation; }, + set accessibilityInformation(v: NotebookCellStatusBarItemInternal['accessibilityInformation']) { internalItem.accessibilityInformation = v; }, + + show() { internalItem.show(); }, + hide() { internalItem.hide(); }, + dispose() { internalItem.dispose(); } + }); +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 1ab689c1850..9fc497082bd 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2757,6 +2757,12 @@ export enum NotebookRunState { Idle = 2 } +export enum NotebookCellStatusBarAlignment { + Left = 1, + Right = 2 +} + + //#endregion //#region Timeline diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 87fc0a2edd7..f0e489a8675 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -362,11 +362,29 @@ .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-right { padding-right: 12px; z-index: 26; + display: flex; +} + +.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-right .cell-contributed-items-right { + display: flex; + flex-wrap: wrap; + overflow: hidden; +} + +.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-item { + display: flex; + align-items: center; + white-space: pre; + + height: 21px; /* Editor outline is -1px in, don't overlap */ + padding: 0px 6px; +} + +.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-item.cell-status-item-has-command { + cursor: pointer; } .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-language-picker { - height: 21px; /* Editor outline is -1px in, don't overlap */ - padding: 0px 6px; cursor: pointer; } @@ -380,6 +398,10 @@ align-items: center; } +.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-message { + margin-right: 6px; +} + .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status { height: 100%; display: flex; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 0ad18223bc8..74df84fbeb1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -46,6 +46,8 @@ import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/n import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; import { NotebookEditorWorkerServiceImpl } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { NotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl'; // Editor Contribution @@ -445,6 +447,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider registerSingleton(INotebookService, NotebookService); registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl); registerSingleton(INotebookEditorModelResolverService, NotebookModelResolverService, true); +registerSingleton(INotebookCellStatusBarService, NotebookCellStatusBarService, true); const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index d7c106c9531..a3b61443510 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -27,10 +27,10 @@ import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IMenu } from 'vs/platform/actions/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { CellLanguageStatusBarItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents'; import { EditorOptions } from 'vs/workbench/common/editor'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; +import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); @@ -525,8 +525,7 @@ export interface BaseCellRenderTemplate { elementDisposables: DisposableStore; bottomCellContainer: HTMLElement; currentRenderedCell?: ICellViewModel; - statusBarContainer: HTMLElement; - languageStatusBarItem: CellLanguageStatusBarItem; + statusBar: CellEditorStatusBar; titleMenu: IMenu; toJSON: () => object; } @@ -539,7 +538,6 @@ export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { cellRunState: RunStateRenderer; - cellStatusMessageContainer: HTMLElement; runToolbar: ToolBar; runButtonContainer: HTMLElement; executionOrderLabel: HTMLElement; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts new file mode 100644 index 00000000000..27abf1c095a --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export class NotebookCellStatusBarService extends Disposable implements INotebookCellStatusBarService { + + private _onDidChangeEntriesForCell = new Emitter(); + readonly onDidChangeEntriesForCell: Event = this._onDidChangeEntriesForCell.event; + + private _entries = new ResourceMap>(); + + private removeEntry(entry: INotebookCellStatusBarEntry) { + const existingEntries = this._entries.get(entry.cellResource); + if (existingEntries) { + existingEntries.delete(entry); + if (!existingEntries.size) { + this._entries.delete(entry.cellResource); + } + } + + this._onDidChangeEntriesForCell.fire(entry.cellResource); + } + + addEntry(entry: INotebookCellStatusBarEntry): IDisposable { + const existingEntries = this._entries.get(entry.cellResource) ?? new Set(); + existingEntries.add(entry); + this._entries.set(entry.cellResource, existingEntries); + + this._onDidChangeEntriesForCell.fire(entry.cellResource); + + return { + dispose: () => { + this.removeEntry(entry); + } + }; + } + + getEntries(cell: URI): INotebookCellStatusBarEntry[] { + const existingEntries = this._entries.get(cell); + return existingEntries ? + Array.from(existingEntries.values()) : + []; + } + + readonly _serviceBrand: undefined; +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index ffa9c892905..7bdc292f3bf 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1928,7 +1928,8 @@ registerThemingParticipant((theme, collector) => { const cellStatusBarHoverBg = theme.getColor(cellStatusBarItemHover); if (cellStatusBarHoverBg) { - collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-language-picker:hover { background-color: ${cellStatusBarHoverBg}; }`); + collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-language-picker:hover, + .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-item.cell-status-item-has-command:hover { background-color: ${cellStatusBarHoverBg}; }`); } const cellInsertionIndicatorColor = theme.getColor(cellInsertionIndicator); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index f8b512e42cf..eb5c7a65cb1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -40,15 +40,16 @@ import { CancelCellAction, DeleteCellAction, ExecuteCellAction, INotebookCellAct import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, EXPAND_CELL_CONTENT_COMMAND_ID, ICellViewModel, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; +import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents'; +import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd'; import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind, NotebookCellMetadata, NotebookCellRunState, ShowCellStatusbarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView'; -import { CodiconActionViewItem, CellLanguageStatusBarItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents'; -import { CellDragAndDropController, DRAGGING_CLASS } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd'; const $ = DOM.$; @@ -403,7 +404,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService)); - const statusBar = this.instantiationService.createInstance(CellEditorStatusBar, editorPart); + const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart)); const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); const templateData: MarkdownCellRenderTemplate = { @@ -422,9 +423,8 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR deleteToolbar, betweenCellToolbar, bottomCellContainer, - statusBarContainer: statusBar.statusBarContainer, - languageStatusBarItem: statusBar.languageStatusBarItem, titleMenu, + statusBar, toJSON: () => { return {}; } }; this.dndController.registerDragHandle(templateData, rootContainer, container, () => this.getDragImage(templateData)); @@ -493,7 +493,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR elementDisposables.add(this.editorOptions.onDidChange(newValue => markdownCell.updateEditorOptions(newValue))); elementDisposables.add(markdownCell); - templateData.languageStatusBarItem.update(element, this.notebookEditor); + templateData.statusBar.update(toolbarContext); } disposeTemplate(templateData: MarkdownCellRenderTemplate): void { @@ -605,27 +605,6 @@ class CodeCellDragImageRenderer { } } -class CellEditorStatusBar { - readonly cellStatusMessageContainer: HTMLElement; - readonly cellRunStatusContainer: HTMLElement; - readonly statusBarContainer: HTMLElement; - readonly languageStatusBarItem: CellLanguageStatusBarItem; - readonly durationContainer: HTMLElement; - - constructor( - container: HTMLElement, - @IInstantiationService instantiationService: IInstantiationService - ) { - this.statusBarContainer = DOM.append(container, $('.cell-statusbar-container')); - const leftStatusBarItems = DOM.append(this.statusBarContainer, $('.cell-status-left')); - const rightStatusBarItems = DOM.append(this.statusBarContainer, $('.cell-status-right')); - this.cellRunStatusContainer = DOM.append(leftStatusBarItems, $('.cell-run-status')); - this.durationContainer = DOM.append(leftStatusBarItems, $('.cell-run-duration')); - this.cellStatusMessageContainer = DOM.append(leftStatusBarItems, $('.cell-status-message')); - this.languageStatusBarItem = instantiationService.createInstance(CellLanguageStatusBarItem, rightStatusBarItems); - } -} - export class CodeCellRenderer extends AbstractCellRenderer implements IListRenderer { static readonly TEMPLATE_ID = 'code_cell'; @@ -692,7 +671,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende progressBar.hide(); disposables.add(progressBar); - const statusBar = this.instantiationService.createInstance(CellEditorStatusBar, editorPart); + const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart)); const timer = new TimerRenderer(statusBar.durationContainer); const cellRunState = new RunStateRenderer(statusBar.cellRunStatusContainer, runToolbar, this.instantiationService); @@ -715,11 +694,9 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende contextKeyService, container, cellContainer, - statusBarContainer: statusBar.statusBarContainer, cellRunState, - cellStatusMessageContainer: statusBar.cellStatusMessageContainer, - languageStatusBarItem: statusBar.languageStatusBarItem, progressBar, + statusBar, focusIndicatorLeft: focusIndicator, focusIndicatorRight, focusIndicatorBottom, @@ -766,7 +743,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); DOM.toggleClass(templateData.container, 'runnable', !!metadata.runnable); this.updateExecutionOrder(metadata, templateData); - templateData.cellStatusMessageContainer.textContent = metadata?.statusMessage || ''; + templateData.statusBar.cellStatusMessageContainer.textContent = metadata?.statusMessage || ''; templateData.cellRunState.renderState(element.metadata?.runState); @@ -880,7 +857,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.setBetweenCellToolbarContext(templateData, element, toolbarContext); - templateData.languageStatusBarItem.update(element, this.notebookEditor); + templateData.statusBar.update(toolbarContext); } disposeTemplate(templateData: CodeCellRenderTemplate): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts new file mode 100644 index 00000000000..9f86d687c07 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts @@ -0,0 +1,217 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel'; +import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; +import { stripCodicons } from 'vs/base/common/codicons'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { extUri } from 'vs/base/common/resources'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ChangeCellLanguageAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; +import { CellKind, CellStatusbarAlignment, INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +const $ = DOM.$; + +export class CellEditorStatusBar extends Disposable { + readonly cellStatusMessageContainer: HTMLElement; + readonly cellRunStatusContainer: HTMLElement; + readonly statusBarContainer: HTMLElement; + readonly languageStatusBarItem: CellLanguageStatusBarItem; + readonly durationContainer: HTMLElement; + + private readonly leftContributedItemsContainer: HTMLElement; + private readonly rightContributedItemsContainer: HTMLElement; + private readonly itemsDisposable: DisposableStore; + + private currentContext: INotebookCellActionContext | undefined; + + constructor( + container: HTMLElement, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @INotebookCellStatusBarService private readonly notebookCellStatusBarService: INotebookCellStatusBarService + ) { + super(); + this.statusBarContainer = DOM.append(container, $('.cell-statusbar-container')); + const leftItemsContainer = DOM.append(this.statusBarContainer, $('.cell-status-left')); + const rightItemsContainer = DOM.append(this.statusBarContainer, $('.cell-status-right')); + this.cellRunStatusContainer = DOM.append(leftItemsContainer, $('.cell-run-status')); + this.durationContainer = DOM.append(leftItemsContainer, $('.cell-run-duration')); + this.cellStatusMessageContainer = DOM.append(leftItemsContainer, $('.cell-status-message')); + this.leftContributedItemsContainer = DOM.append(leftItemsContainer, $('.cell-contributed-items-left')); + this.rightContributedItemsContainer = DOM.append(rightItemsContainer, $('.cell-contributed-items-right')); + this.languageStatusBarItem = instantiationService.createInstance(CellLanguageStatusBarItem, rightItemsContainer); + + this.itemsDisposable = this._register(new DisposableStore()); + this._register(this.notebookCellStatusBarService.onDidChangeEntriesForCell(e => { + if (this.currentContext && extUri.isEqual(e, this.currentContext.cell.uri)) { + this.updateStatusBarItems(); + } + })); + } + + update(context: INotebookCellActionContext) { + this.currentContext = context; + this.languageStatusBarItem.update(context.cell, context.notebookEditor); + this.updateStatusBarItems(); + } + + layout(width: number): void { + this.statusBarContainer.style.width = `${width}px`; + } + + private updateStatusBarItems() { + if (!this.currentContext) { + return; + } + + this.leftContributedItemsContainer.innerHTML = ''; + this.rightContributedItemsContainer.innerHTML = ''; + this.itemsDisposable.clear(); + + const items = this.notebookCellStatusBarService.getEntries(this.currentContext.cell.uri); + items.sort((itemA, itemB) => { + return (itemB.priority ?? 0) - (itemA.priority ?? 0); + }); + items.forEach(item => { + const itemView = this.itemsDisposable.add(this.instantiationService.createInstance(CellStatusBarItem, this.currentContext!, item)); + if (item.alignment === CellStatusbarAlignment.LEFT) { + this.leftContributedItemsContainer.appendChild(itemView.container); + } else { + this.rightContributedItemsContainer.appendChild(itemView.container); + } + }); + } +} + +class CellStatusBarItem extends Disposable { + + readonly container = $('.cell-status-item'); + + constructor( + private readonly _context: INotebookCellActionContext, + private readonly _itemModel: INotebookCellStatusBarEntry, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ICommandService private readonly commandService: ICommandService, + @INotificationService private readonly notificationService: INotificationService + ) { + super(); + new CodiconLabel(this.container).text = this._itemModel.text; + + let ariaLabel: string; + let role: string | undefined; + if (this._itemModel.accessibilityInformation) { + ariaLabel = this._itemModel.accessibilityInformation.label; + role = this._itemModel.accessibilityInformation.role; + } else { + ariaLabel = this._itemModel.text ? stripCodicons(this._itemModel.text).trim() : ''; + } + + if (ariaLabel) { + this.container.setAttribute('aria-label', ariaLabel); + } + + if (role) { + this.container.setAttribute('role', role); + } + + this.container.title = this._itemModel.tooltip ?? ''; + + if (this._itemModel.command) { + this.container.classList.add('cell-status-item-has-command'); + this.container.tabIndex = 0; + + this._register(DOM.addDisposableListener(this.container, DOM.EventType.CLICK, _e => { + this.executeCommand(); + })); + this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, e => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) { + this.executeCommand(); + } + })); + } + } + + private async executeCommand(): Promise { + const command = this._itemModel.command; + if (!command) { + return; + } + + const id = typeof command === 'string' ? command : command.id; + const args = typeof command === 'string' ? [] : command.arguments ?? []; + + args.unshift(this._context); + + this.telemetryService.publicLog2('workbenchActionExecuted', { id, from: 'cell status bar' }); + try { + await this.commandService.executeCommand(id, ...args); + } catch (error) { + this.notificationService.error(toErrorMessage(error)); + } + } +} + +export class CellLanguageStatusBarItem extends Disposable { + private readonly labelElement: HTMLElement; + + private cell: ICellViewModel | undefined; + private editor: INotebookEditor | undefined; + + private cellDisposables: DisposableStore; + + constructor( + readonly container: HTMLElement, + @IModeService private readonly modeService: IModeService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + this.labelElement = DOM.append(container, $('.cell-language-picker.cell-status-item')); + this.labelElement.tabIndex = 0; + + this._register(DOM.addDisposableListener(this.labelElement, DOM.EventType.CLICK, () => { + this.run(); + })); + this._register(DOM.addDisposableListener(this.labelElement, DOM.EventType.KEY_UP, e => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) { + this.run(); + } + })); + this._register(this.cellDisposables = new DisposableStore()); + } + + private run() { + this.instantiationService.invokeFunction(accessor => { + new ChangeCellLanguageAction().run(accessor, { notebookEditor: this.editor!, cell: this.cell! }); + }); + } + + update(cell: ICellViewModel, editor: INotebookEditor): void { + this.cellDisposables.clear(); + this.cell = cell; + this.editor = editor; + + this.render(); + this.cellDisposables.add(this.cell.model.onDidChangeLanguage(() => this.render())); + } + + private render(): void { + const modeId = this.cell?.cellKind === CellKind.Markdown ? 'markdown' : this.modeService.getModeIdForLanguageName(this.cell!.language) || this.cell!.language; + this.labelElement.textContent = this.modeService.getLanguageName(modeId) || this.modeService.getLanguageName('plaintext'); + this.labelElement.title = localize('notebook.cell.status.language', "Select Cell Language Mode"); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index 1c0c22f4482..524f3326aaf 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -401,7 +401,7 @@ export class CodeCell extends Disposable { private layoutEditor(dimension: IDimension): void { this.templateData.editor?.layout(dimension); - this.templateData.statusBarContainer.style.width = `${dimension.width}px`; + this.templateData.statusBar.layout(dimension.width); } private onCellWidthChange(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts index 01db1c49f46..989f392ea4c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts @@ -3,22 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; -import * as DOM from 'vs/base/browser/dom'; +import { renderCodicons } from 'vs/base/common/codicons'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { renderCodicons } from 'vs/base/common/codicons'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ChangeCellLanguageAction } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; - -const $ = DOM.$; export class CodiconActionViewItem extends MenuEntryActionViewItem { constructor( @@ -35,45 +25,3 @@ export class CodiconActionViewItem extends MenuEntryActionViewItem { } } } - - -export class CellLanguageStatusBarItem extends Disposable { - private readonly labelElement: HTMLElement; - - private cell: ICellViewModel | undefined; - private editor: INotebookEditor | undefined; - - private cellDisposables: DisposableStore; - - constructor( - readonly container: HTMLElement, - @IModeService private readonly modeService: IModeService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - this.labelElement = DOM.append(container, $('.cell-language-picker')); - this.labelElement.tabIndex = 0; - - this._register(DOM.addDisposableListener(this.labelElement, DOM.EventType.CLICK, () => { - this.instantiationService.invokeFunction(accessor => { - new ChangeCellLanguageAction().run(accessor, { notebookEditor: this.editor!, cell: this.cell! }); - }); - })); - this._register(this.cellDisposables = new DisposableStore()); - } - - update(cell: ICellViewModel, editor: INotebookEditor): void { - this.cellDisposables.clear(); - this.cell = cell; - this.editor = editor; - - this.render(); - this.cellDisposables.add(this.cell.model.onDidChangeLanguage(() => this.render())); - } - - private render(): void { - const modeId = this.cell?.cellKind === CellKind.Markdown ? 'markdown' : this.modeService.getModeIdForLanguageName(this.cell!.language) || this.cell!.language; - this.labelElement.textContent = this.modeService.getLanguageName(modeId) || this.modeService.getLanguageName('plaintext'); - this.labelElement.title = localize('notebook.cell.status.language', "Select Cell Language Mode"); - } -} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index 260889e19b9..a1e81ae72e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -282,7 +282,7 @@ export class StatefulMarkdownCell extends Disposable { private layoutEditor(dimension: DOM.IDimension): void { this.editor?.layout(dimension); - this.templateData.statusBarContainer.style.width = `${dimension.width}px`; + this.templateData.statusBar.layout(dimension.width); } private onCellEditorWidthChange(): void { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCellStatusBarService.ts b/src/vs/workbench/contrib/notebook/common/notebookCellStatusBarService.ts new file mode 100644 index 00000000000..e99fa061a11 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/notebookCellStatusBarService.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export const INotebookCellStatusBarService = createDecorator('notebookCellStatusBarService'); + +export interface INotebookCellStatusBarService { + readonly _serviceBrand: undefined; + + onDidChangeEntriesForCell: Event; + + addEntry(entry: INotebookCellStatusBarEntry): IDisposable; + getEntries(cell: URI): INotebookCellStatusBarEntry[]; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 81487436421..2e983236512 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -3,22 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDiffResult, ISequence } from 'vs/base/common/diff/diff'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { basename } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { Command } from 'vs/editor/common/modes'; +import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Schemas } from 'vs/base/common/network'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IRevertOptions } from 'vs/workbench/common/editor'; -import { basename } from 'vs/base/common/path'; -import { IDiffResult, ISequence } from 'vs/base/common/diff/diff'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; export enum CellKind { Markdown = 1, @@ -744,7 +746,6 @@ export interface INotebookKernelProvider { cancelNotebook(uri: URI, kernelId: string, handle: number | undefined): Promise; } - export class CellSequence implements ISequence { constructor(readonly textModel: NotebookTextModel) { @@ -764,6 +765,23 @@ export interface INotebookDiffResult { cellsDiff: IDiffResult, linesDiff?: { originalCellhandle: number, modifiedCellhandle: number, lineChanges: editorCommon.ILineChange[] }[]; } + +export interface INotebookCellStatusBarEntry { + readonly cellResource: URI; + readonly alignment: CellStatusbarAlignment; + readonly priority?: number; + readonly text: string; + readonly tooltip: string | undefined; + readonly command: string | Command | undefined; + readonly accessibilityInformation?: IAccessibilityInformation; + readonly visible: boolean; +} + export const DisplayOrderKey = 'notebook.displayOrder'; export const CellToolbarLocKey = 'notebook.cellToolbarLocation'; export const ShowCellStatusbarKey = 'notebook.showCellStatusbar'; + +export const enum CellStatusbarAlignment { + LEFT, + RIGHT +}