diff --git a/packages/core/src/browser/shell/application-shell.ts b/packages/core/src/browser/shell/application-shell.ts index 4a63dbe6cc3a3..bb58e2ad4ed7d 100644 --- a/packages/core/src/browser/shell/application-shell.ts +++ b/packages/core/src/browser/shell/application-shell.ts @@ -89,6 +89,8 @@ export class DockPanelRenderer implements DockLayout.IRenderer { readonly tabBarClasses: string[] = []; + private readonly onDidCreateTabBarEmitter = new Emitter>(); + constructor( @inject(TabBarRendererFactory) protected readonly tabBarRendererFactory: TabBarRendererFactory, @inject(TabBarToolbarRegistry) protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry, @@ -96,6 +98,10 @@ export class DockPanelRenderer implements DockLayout.IRenderer { @inject(BreadcrumbsRendererFactory) protected readonly breadcrumbsRendererFactory: BreadcrumbsRendererFactory, ) { } + get onDidCreateTabBar(): CommonEvent> { + return this.onDidCreateTabBarEmitter.event; + } + createTabBar(): TabBar { const renderer = this.tabBarRendererFactory(); const tabBar = new ToolbarAwareTabBar( @@ -115,6 +121,7 @@ export class DockPanelRenderer implements DockLayout.IRenderer { tabBar.disposed.connect(() => renderer.dispose()); renderer.contextMenuPath = SHELL_TABBAR_CONTEXT_MENU; tabBar.currentChanged.connect(this.onCurrentTabChanged, this); + this.onDidCreateTabBarEmitter.fire(tabBar); return tabBar; } @@ -221,6 +228,11 @@ export class ApplicationShell extends Widget { @inject(TheiaDockPanel.Factory) protected readonly dockPanelFactory: TheiaDockPanel.Factory; + private _mainPanelRenderer: DockPanelRenderer; + get mainPanelRenderer(): DockPanelRenderer { + return this._mainPanelRenderer; + } + /** * Construct a new application shell. */ @@ -496,6 +508,7 @@ export class ApplicationShell extends Widget { const renderer = this.dockPanelRendererFactory(); renderer.tabBarClasses.push(MAIN_BOTTOM_AREA_CLASS); renderer.tabBarClasses.push(MAIN_AREA_CLASS); + this._mainPanelRenderer = renderer; const dockPanel = this.dockPanelFactory({ mode: 'multiple-document', renderer, diff --git a/packages/plugin-ext/package.json b/packages/plugin-ext/package.json index d46b1f96dbc46..d1deea7abfa0c 100644 --- a/packages/plugin-ext/package.json +++ b/packages/plugin-ext/package.json @@ -11,6 +11,7 @@ "@theia/core": "1.34.0", "@theia/debug": "1.34.0", "@theia/editor": "1.34.0", + "@theia/editor-preview": "1.34.0", "@theia/file-search": "1.34.0", "@theia/filesystem": "1.34.0", "@theia/markers": "1.34.0", diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 12ecf23c62ea2..69137e93d1948 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -2037,7 +2037,7 @@ export interface TabOperation { export interface TabDto { id: string; label: string; - input: any; + input: AnyInputDto; editorId?: string; isActive: boolean; isPinned: boolean; diff --git a/packages/plugin-ext/src/main/browser/main-context.ts b/packages/plugin-ext/src/main/browser/main-context.ts index eee7fd8cd7335..78b292d43a580 100644 --- a/packages/plugin-ext/src/main/browser/main-context.ts +++ b/packages/plugin-ext/src/main/browser/main-context.ts @@ -58,6 +58,7 @@ import { WebviewViewsMainImpl } from './webview-views/webview-views-main'; import { MonacoLanguages } from '@theia/monaco/lib/browser/monaco-languages'; import { UntitledResourceResolver } from '@theia/core/lib/common/resource'; import { ThemeService } from '@theia/core/lib/browser/theming'; +import { TabsMainImpl } from './tabs/tabs-main'; export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void { const authenticationMain = new AuthenticationMainImpl(rpc, container); @@ -180,4 +181,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container const commentsMain = new CommentsMainImp(rpc, container); rpc.set(PLUGIN_RPC_CONTEXT.COMMENTS_MAIN, commentsMain); + + const tabsMain = new TabsMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.TABS_MAIN, tabsMain); } diff --git a/packages/plugin-ext/src/main/browser/tabs/tabs-main.ts b/packages/plugin-ext/src/main/browser/tabs/tabs-main.ts index 7d85c44347aec..63c3e567e06df 100644 --- a/packages/plugin-ext/src/main/browser/tabs/tabs-main.ts +++ b/packages/plugin-ext/src/main/browser/tabs/tabs-main.ts @@ -15,16 +15,275 @@ // ***************************************************************************** import { interfaces } from '@theia/core/shared/inversify'; - -import { TabsMain } from '../../../common/plugin-api-rpc'; +import { ApplicationShell, PINNED_CLASS, Saveable, TabBar, Title, ViewContainer, Widget } from '@theia/core/lib/browser'; +import { AnyInputDto, MAIN_RPC_CONTEXT, TabDto, TabGroupDto, TabInputKind, TabModelOperationKind, TabsExt, TabsMain } from '../../../common/plugin-api-rpc'; import { RPCProtocol } from '../../../common/rpc-protocol'; +import { EditorPreviewWidget } from '@theia/editor-preview/lib/browser/editor-preview-widget'; +import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol'; +import { MonacoDiffEditor } from '@theia/monaco/lib/browser/monaco-diff-editor'; +import { toUriComponents } from '../hierarchy/hierarchy-types-converters'; +import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget'; +import { DisposableCollection } from '@theia/core'; + +interface TabInfo { + tab: TabDto; + tabIndex: number; + group: TabGroupDto; +} + +export class TabsMainImpl implements TabsMain, Disposable { + + private readonly proxy: TabsExt; + private tabGroupModel = new Map, TabGroupDto>(); + private tabInfoLookup = new Map, TabInfo>(); + + private applicationShell: ApplicationShell; + + private disposableTabBarListeners: DisposableCollection = new DisposableCollection(); + private toDisposeOnDestroy: DisposableCollection = new DisposableCollection(); -export class TabsMainImp implements TabsMain { + private groupIdCounter = 0; + private currentActiveGroup: TabGroupDto; + + private tabGroupChanged: boolean = false; constructor( rpc: RPCProtocol, container: interfaces.Container - ) {} + ) { + this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.TABS_EXT); + + this.applicationShell = container.get(ApplicationShell); + this.createTabsModel(); + + const tabBars = this.applicationShell.mainPanel.tabBars(); + for (let tabBar; tabBar = tabBars.next();) { + this.attachListenersToTabBar(tabBar); + } + + this.toDisposeOnDestroy.push( + this.applicationShell.mainPanelRenderer.onDidCreateTabBar(tabBar => { + this.attachListenersToTabBar(tabBar); + this.onTabGroupCreated(tabBar); + }) + ); + + this.connectToSignal(this.toDisposeOnDestroy, this.applicationShell.mainPanel.widgetAdded, (mainPanel, widget) => { + if (this.tabGroupChanged || this.tabGroupModel.size === 0) { + this.tabGroupChanged = false; + this.createTabsModel(); + // tab Open event is done in backend + } else { + const tabBar = mainPanel.findTabBar(widget.title)!; + const oldTabInfo = this.tabInfoLookup.get(widget.title); + const group = this.tabGroupModel.get(tabBar); + if (group !== oldTabInfo?.group) { + if (oldTabInfo) { + this.onTabClosed(oldTabInfo, widget.title); + } + + this.onTabCreated(tabBar, { index: tabBar.titles.indexOf(widget.title), title: widget.title }); + } + } + }); + + this.connectToSignal(this.toDisposeOnDestroy, this.applicationShell.mainPanel.widgetRemoved, (mainPanel, widget) => { + if (!(widget instanceof TabBar)) { + const tabInfo = this.getOrRebuildModel(this.tabInfoLookup, widget.title)!; + this.onTabClosed(tabInfo, widget.title); + if (this.tabGroupChanged) { + this.tabGroupChanged = false; + this.createTabsModel(); + } + } + }); + } + + protected createTabsModel(): void { + const newTabGroupModel = new Map, TabGroupDto>(); + this.tabInfoLookup.clear(); + this.disposableTabBarListeners.dispose(); + this.applicationShell.mainAreaTabBars.forEach(tabBar => { + this.attachListenersToTabBar(tabBar); + const groupDto = this.createTabGroupDto(tabBar); + tabBar.titles.forEach((title, index) => this.tabInfoLookup.set(title, { group: groupDto, tab: groupDto.tabs[index], tabIndex: index })); + newTabGroupModel.set(tabBar, groupDto); + }); + if (newTabGroupModel.size > 0 && Array.from(newTabGroupModel.values()).indexOf(this.currentActiveGroup) < 0) { + this.currentActiveGroup = this.tabInfoLookup.get(this.applicationShell.mainPanel.currentTitle!)?.group ?? newTabGroupModel.values().next().value; + this.currentActiveGroup.isActive = true; + } + this.tabGroupModel = newTabGroupModel; + this.proxy.$acceptEditorTabModel(Array.from(this.tabGroupModel.values())); + } + + protected createTabDto(tabTitle: Title, groupId: number): TabDto { + const widget = tabTitle.owner; + return { + id: this.createTabId(tabTitle, groupId), + label: tabTitle.label, + input: this.evaluateTabDtoInput(widget), + isActive: tabTitle.owner.isVisible, + isPinned: tabTitle.className.includes(PINNED_CLASS), + isDirty: Saveable.isDirty(widget), + isPreview: widget instanceof EditorPreviewWidget && widget.isPreview + }; + } + + protected createTabId(tabTitle: Title, groupId: number): string { + return `${groupId}~${tabTitle.owner.id}`; + } + + protected createTabGroupDto(tabBar: TabBar): TabGroupDto { + const oldDto = this.tabGroupModel.get(tabBar); + const groupId = oldDto?.groupId ?? this.groupIdCounter++; + const tabs = tabBar.titles.map(title => this.createTabDto(title, groupId)); + return { + groupId, + tabs, + isActive: false, + viewColumn: 1 + }; + } + + protected attachListenersToTabBar(tabBar: TabBar | undefined): void { + if (!tabBar) { + return; + } + tabBar.titles.forEach(title => { + this.connectToSignal(this.disposableTabBarListeners, title.changed, this.onTabTitleChanged); + }); + + this.connectToSignal(this.disposableTabBarListeners, tabBar.tabMoved, this.onTabMoved); + this.connectToSignal(this.disposableTabBarListeners, tabBar.disposed, this.onTabGroupClosed); + } + + protected evaluateTabDtoInput(widget: Widget): AnyInputDto { + if (widget instanceof EditorPreviewWidget) { + if (widget.editor instanceof MonacoDiffEditor) { + return { + kind: TabInputKind.TextDiffInput, + original: toUriComponents(widget.editor.originalModel.uri), + modified: toUriComponents(widget.editor.modifiedModel.uri) + }; + } else { + return { + kind: TabInputKind.TextInput, + uri: toUriComponents(widget.editor.uri.toString()) + }; + } + // TODO notebook support when implemented + } else if (widget instanceof ViewContainer) { + return { + kind: TabInputKind.WebviewEditorInput, + viewType: widget.id + }; + } else if (widget instanceof TerminalWidget) { + return { + kind: TabInputKind.TerminalEditorInput + }; + } + + return { kind: TabInputKind.UnknownInput }; + } + + protected connectToSignal(disposableList: DisposableCollection, signal: { connect(listener: T, context: unknown): void, disconnect(listener: T): void }, listener: T): void { + signal.connect(listener, this); + disposableList.push(Disposable.create(() => signal.disconnect(listener))); + } + + protected tabDtosEqual(a: TabDto, b: TabDto): boolean { + return a.isActive === b.isActive && + a.isDirty === b.isDirty && + a.isPinned === b.isPinned && + a.isPreview === b.isPreview && + a.id === b.id; + } + + protected getOrRebuildModel(map: Map, key: T): R { + // something broke so we rebuild the model + let item = map.get(key); + if (!item) { + this.createTabsModel(); + item = map.get(key)!; + } + return item; + } + + // #region event listeners + private onTabCreated(tabBar: TabBar, args: TabBar.ITabActivateRequestedArgs): void { + const group = this.getOrRebuildModel(this.tabGroupModel, tabBar); + this.connectToSignal(this.disposableTabBarListeners, args.title.changed, this.onTabTitleChanged); + const tabDto = this.createTabDto(args.title, group.groupId); + this.tabInfoLookup.set(args.title, { group, tab: tabDto, tabIndex: args.index }); + group.tabs.splice(args.index, 0, tabDto); + this.proxy.$acceptTabOperation({ + kind: TabModelOperationKind.TAB_OPEN, + index: args.index, + tabDto, + groupId: group.groupId + }); + } + + private onTabTitleChanged(title: Title): void { + const tabInfo = this.getOrRebuildModel(this.tabInfoLookup, title); + if (!tabInfo) { + return; + } + const oldTabDto = tabInfo.tab; + const newTabDto = this.createTabDto(title, tabInfo.group.groupId); + if (newTabDto.isActive && !tabInfo.group.isActive) { + tabInfo.group.isActive = true; + this.currentActiveGroup.isActive = false; + this.currentActiveGroup = tabInfo.group; + this.proxy.$acceptTabGroupUpdate(tabInfo.group); + } + if (!this.tabDtosEqual(oldTabDto, newTabDto)) { + tabInfo.group.tabs[tabInfo.tabIndex] = newTabDto; + tabInfo.tab = newTabDto; + this.proxy.$acceptTabOperation({ + kind: TabModelOperationKind.TAB_UPDATE, + index: tabInfo.tabIndex, + tabDto: newTabDto, + groupId: tabInfo.group.groupId + }); + } + } + + private onTabClosed(tabInfo: TabInfo, title: Title): void { + tabInfo.group.tabs.splice(tabInfo.tabIndex, 1); + this.tabInfoLookup.delete(title); + this.proxy.$acceptTabOperation({ + kind: TabModelOperationKind.TAB_CLOSE, + index: tabInfo.tabIndex, + tabDto: this.createTabDto(title, tabInfo.group.groupId), + groupId: tabInfo.group.groupId + }); + } + + private onTabMoved(tabBar: TabBar, args: TabBar.ITabMovedArgs): void { + const tabInfo = this.getOrRebuildModel(this.tabInfoLookup, args.title)!; + tabInfo.tabIndex = args.toIndex; + const tabDto = this.createTabDto(args.title, tabInfo.group.groupId); + tabInfo.group.tabs.splice(args.fromIndex, 1); + tabInfo.group.tabs.splice(args.toIndex, 0, tabDto); + this.proxy.$acceptTabOperation({ + kind: TabModelOperationKind.TAB_MOVE, + index: args.toIndex, + tabDto, + groupId: tabInfo.group.groupId, + oldIndex: args.fromIndex + }); + } + + private onTabGroupCreated(tabBar: TabBar): void { + this.tabGroupChanged = true; + } + + private onTabGroupClosed(tabBar: TabBar): void { + this.tabGroupChanged = true; + } + // #endregion // #region Messages received from Ext Host $moveTab(tabId: string, index: number, viewColumn: number, preserveFocus?: boolean): void { @@ -32,11 +291,33 @@ export class TabsMainImp implements TabsMain { } async $closeTab(tabIds: string[], preserveFocus?: boolean): Promise { - return false; + const widgets: Widget[] = []; + for (const tabId of tabIds) { + const cleanedId = tabId.substring(tabId.indexOf('~') + 1); + const widget = this.applicationShell.getWidgetById(cleanedId); + if (widget) { + widgets.push(widget); + } + } + await this.applicationShell.closeMany(widgets); + return true; } async $closeGroup(groupIds: number[], preserveFocus?: boolean): Promise { - return false; + for (const groupId of groupIds) { + tabGroupModel: for (const [bar, groupDto] of this.tabGroupModel) { + if (groupDto.groupId === groupId) { + this.applicationShell.closeTabs(bar); + break tabGroupModel; + } + } + } + return true; } // #endregion + + dispose(): void { + this.toDisposeOnDestroy.dispose(); + this.disposableTabBarListeners.dispose(); + } } diff --git a/packages/plugin-ext/src/plugin/tabs.ts b/packages/plugin-ext/src/plugin/tabs.ts index 03827e6304ae1..96071fcb22ae4 100644 --- a/packages/plugin-ext/src/plugin/tabs.ts +++ b/packages/plugin-ext/src/plugin/tabs.ts @@ -291,14 +291,6 @@ export class TabsExtImpl implements TabsExt { return this._closeTabs(tabsOrTabGroups as theia.Tab[], preserveFocus); } }, - // move: async (tab: theia.Tab, viewColumn: ViewColumn, index: number, preserveFocus?: boolean) => { - // const extHostTab = this._findExtHostTabFromApi(tab); - // if (!extHostTab) { - // throw new Error('Invalid tab'); - // } - // this._proxy.$moveTab(extHostTab.tabId, index, typeConverters.ViewColumn.from(viewColumn), preserveFocus); - // return; - // } }; this.apiObject = Object.freeze(obj); } @@ -306,7 +298,6 @@ export class TabsExtImpl implements TabsExt { } $acceptEditorTabModel(tabGroups: TabGroupDto[]): void { - const groupIdsBefore = new Set(this.tabGroupArr.map(group => group.groupId)); const groupIdsAfter = new Set(tabGroups.map(dto => dto.groupId)); const diff = diffSets(groupIdsBefore, groupIdsAfter); @@ -314,23 +305,28 @@ export class TabsExtImpl implements TabsExt { const closed: theia.TabGroup[] = this.tabGroupArr.filter(group => diff.removed.includes(group.groupId)).map(group => group.apiObject); const opened: theia.TabGroup[] = []; const changed: theia.TabGroup[] = []; + const tabsOpened: theia.Tab[] = []; this.tabGroupArr = tabGroups.map(tabGroup => { const group = new TabGroupExt(tabGroup, () => this.activeGroupId); if (diff.added.includes(group.groupId)) { - opened.push(group.apiObject); + opened.push({ activeTab: undefined, isActive: group.apiObject.isActive, tabs: [], viewColumn: group.apiObject.viewColumn }); + tabsOpened.push(...group.apiObject.tabs); } else { changed.push(group.apiObject); } return group; }); - // Set the active tab group id - const activeTabGroupId = assertIsDefined(tabGroups.find(group => group.isActive === true)?.groupId); - if (activeTabGroupId !== undefined && this.activeGroupId !== activeTabGroupId) { - this.activeGroupId = activeTabGroupId; + // Set the active tab group id. skip if no tabgroups are open + if (tabGroups.length > 0) { + const activeTabGroupId = assertIsDefined(tabGroups.find(group => group.isActive === true)?.groupId); + if (this.activeGroupId !== activeTabGroupId) { + this.activeGroupId = activeTabGroupId; + } } this.onDidChangeTabGroups.fire(Object.freeze({ opened, closed, changed })); + this.onDidChangeTabs.fire({ opened: tabsOpened, changed: [], closed: [] }); } $acceptTabGroupUpdate(groupDto: TabGroupDto): void { diff --git a/packages/plugin-ext/tsconfig.json b/packages/plugin-ext/tsconfig.json index ceeb7e9ff55f2..91c36231e1784 100644 --- a/packages/plugin-ext/tsconfig.json +++ b/packages/plugin-ext/tsconfig.json @@ -32,6 +32,9 @@ { "path": "../editor" }, + { + "path": "../editor-preview" + }, { "path": "../file-search" }, diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index f087b43c7e863..dfa0a03e9a02d 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -13471,331 +13471,291 @@ export module '@theia/plugin' { /** * The tab represents a single text based resource. */ - export class TabInputText { - /** - * The uri represented by the tab. - * @stubbed - */ - readonly uri: Uri; - /** - * Constructs a text tab input with the given URI. - * @param uri The URI of the tab. - * @stubbed - */ - constructor(uri: Uri); - } - - /** - * The tab represents two text based resources - * being rendered as a diff. - */ - export class TabInputTextDiff { - /** - * The uri of the original text resource. - * @stubbed - */ - readonly original: Uri; - /** - * The uri of the modified text resource. - * @stubbed - */ - readonly modified: Uri; - /** - * Constructs a new text diff tab input with the given URIs. - * @param original The uri of the original text resource. - * @param modified The uri of the modified text resource. - * @stubbed - */ - constructor(original: Uri, modified: Uri); - } - - /** - * The tab represents a custom editor. - */ - export class TabInputCustom { - /** - * The uri that the tab is representing. - * @stubbed - */ - readonly uri: Uri; - /** - * The type of custom editor. - * @stubbed - */ - readonly viewType: string; - /** - * Constructs a custom editor tab input. - * @param uri The uri of the tab. - * @param viewType The viewtype of the custom editor. - * @stubbed - */ - constructor(uri: Uri, viewType: string); - } - - /** - * The tab represents a webview. - */ - export class TabInputWebview { - /** - * The type of webview. Maps to WebviewPanel's viewType - * @stubbed - */ - readonly viewType: string; - /** - * Constructs a webview tab input with the given view type. - * @param viewType The type of webview. Maps to WebviewPanel's viewType - * @stubbed - */ - constructor(viewType: string); - } - - /** - * The tab represents a notebook. - */ - export class TabInputNotebook { - /** - * The uri that the tab is representing. - * @stubbed - */ - readonly uri: Uri; - /** - * The type of notebook. Maps to NotebookDocuments's notebookType - * @stubbed - */ - readonly notebookType: string; - /** - * Constructs a new tab input for a notebook. - * @param uri The uri of the notebook. - * @param notebookType The type of notebook. Maps to NotebookDocuments's notebookType - * @stubbed - */ - constructor(uri: Uri, notebookType: string); - } - - /** - * The tabs represents two notebooks in a diff configuration. - */ - export class TabInputNotebookDiff { - /** - * The uri of the original notebook. - * @stubbed - */ - readonly original: Uri; - /** - * The uri of the modified notebook. - * @stubbed - */ - readonly modified: Uri; - /** - * The type of notebook. Maps to NotebookDocuments's notebookType - * @stubbed - */ - readonly notebookType: string; - /** - * Constructs a notebook diff tab input. - * @param original The uri of the original unmodified notebook. - * @param modified The uri of the modified notebook. - * @param notebookType The type of notebook. Maps to NotebookDocuments's notebookType - * @stubbed - */ - constructor(original: Uri, modified: Uri, notebookType: string); - } - - /** - * The tab represents a terminal in the editor area. - */ - export class TabInputTerminal { - /** - * Constructs a terminal tab input. - * @stubbed - */ - constructor(); - } - - /** - * Represents a tab within a {@link TabGroup group of tabs}. - * Tabs are merely the graphical representation within the editor area. - * A backing editor is not a guarantee. - */ - export interface Tab { - - /** - * The text displayed on the tab. - * @stubbed - */ - readonly label: string; - - /** - * The group which the tab belongs to. - * @stubbed - */ - readonly group: TabGroup; - - /** - * Defines the structure of the tab i.e. text, notebook, custom, etc. - * Resource and other useful properties are defined on the tab kind. - * @stubbed - */ - readonly input: TabInputText | TabInputTextDiff | TabInputCustom | TabInputWebview | TabInputNotebook | TabInputNotebookDiff | TabInputTerminal | unknown; - - /** - * Whether or not the tab is currently active. - * This is dictated by being the selected tab in the group. - * @stubbed - */ - readonly isActive: boolean; - - /** - * Whether or not the dirty indicator is present on the tab. - * @stubbed - */ - readonly isDirty: boolean; - - /** - * Whether or not the tab is pinned (pin icon is present). - * @stubbed - */ - readonly isPinned: boolean; - - /** - * Whether or not the tab is in preview mode. - * @stubbed - */ - readonly isPreview: boolean; - } - - /** - * An event describing change to tabs. - */ - export interface TabChangeEvent { - /** - * The tabs that have been opened. - * @stubbed - */ - readonly opened: readonly Tab[]; - /** - * The tabs that have been closed. - * @stubbed - */ - readonly closed: readonly Tab[]; - /** - * Tabs that have changed, e.g have changed - * their {@link Tab.isActive active} state. - * @stubbed - */ - readonly changed: readonly Tab[]; - } - - /** - * An event describing changes to tab groups. - */ - export interface TabGroupChangeEvent { - /** - * Tab groups that have been opened. - * @stubbed - */ - readonly opened: readonly TabGroup[]; - /** - * Tab groups that have been closed. - * @stubbed - */ - readonly closed: readonly TabGroup[]; - /** - * Tab groups that have changed, e.g have changed - * their {@link TabGroup.isActive active} state. - * @stubbed - */ - readonly changed: readonly TabGroup[]; - } - - /** - * Represents a group of tabs. A tab group itself consists of multiple tabs. - */ - export interface TabGroup { - /** - * Whether or not the group is currently active. - * - * *Note* that only one tab group is active at a time, but that multiple tab - * groups can have an {@link TabGroup.aciveTab active tab}. - * - * @see {@link Tab.isActive} - * @stubbed - */ - readonly isActive: boolean; - - /** - * The view column of the group. - * @stubbed - */ - readonly viewColumn: ViewColumn; - - /** - * The active {@link Tab tab} in the group. This is the tab whose contents are currently - * being rendered. - * - * *Note* that there can be one active tab per group but there can only be one {@link TabGroups.activeTabGroup active group}. - * @stubbed - */ - readonly activeTab: Tab | undefined; - - /** - * The list of tabs contained within the group. - * This can be empty if the group has no tabs open. - * @stubbed - */ - readonly tabs: readonly Tab[]; - } - - /** - * Represents the main editor area which consists of multple groups which contain tabs. - */ - export interface TabGroups { - /** - * All the groups within the group container. - * @stubbed - */ - readonly all: readonly TabGroup[]; - - /** - * The currently active group. - * @stubbed - */ - readonly activeTabGroup: TabGroup; - - /** - * An {@link Event event} which fires when {@link TabGroup tab groups} have changed. - * @stubbed - */ - readonly onDidChangeTabGroups: Event; - - /** - * An {@link Event event} which fires when {@link Tab tabs} have changed. - * @stubbed - */ - readonly onDidChangeTabs: Event; - - /** - * Closes the tab. This makes the tab object invalid and the tab - * should no longer be used for further actions. - * Note: In the case of a dirty tab, a confirmation dialog will be shown which may be cancelled. If cancelled the tab is still valid - * - * @param tab The tab to close. - * @param preserveFocus When `true` focus will remain in its current position. If `false` it will jump to the next tab. - * @returns A promise that resolves to `true` when all tabs have been closed. - * @stubbed - */ - close(tab: Tab | readonly Tab[], preserveFocus?: boolean): Thenable; - - /** - * Closes the tab group. This makes the tab group object invalid and the tab group - * should no longer be used for further actions. - * @param tabGroup The tab group to close. - * @param preserveFocus When `true` focus will remain in its current position. - * @returns A promise that resolves to `true` when all tab groups have been closed. - * @stubbed - */ - close(tabGroup: TabGroup | readonly TabGroup[], preserveFocus?: boolean): Thenable; - } + export class TabInputText { + /** + * The uri represented by the tab. + */ + readonly uri: Uri; + /** + * Constructs a text tab input with the given URI. + * @param uri The URI of the tab. + */ + constructor(uri: Uri); + } + + /** + * The tab represents two text based resources + * being rendered as a diff. + */ + export class TabInputTextDiff { + /** + * The uri of the original text resource. + */ + readonly original: Uri; + /** + * The uri of the modified text resource. + */ + readonly modified: Uri; + /** + * Constructs a new text diff tab input with the given URIs. + * @param original The uri of the original text resource. + * @param modified The uri of the modified text resource. + */ + constructor(original: Uri, modified: Uri); + } + + /** + * The tab represents a custom editor. + */ + export class TabInputCustom { + /** + * The uri that the tab is representing. + */ + readonly uri: Uri; + /** + * The type of custom editor. + */ + readonly viewType: string; + /** + * Constructs a custom editor tab input. + * @param uri The uri of the tab. + * @param viewType The viewtype of the custom editor. + */ + constructor(uri: Uri, viewType: string); + } + + /** + * The tab represents a webview. + */ + export class TabInputWebview { + /** + * The type of webview. Maps to WebviewPanel's viewType + */ + readonly viewType: string; + /** + * Constructs a webview tab input with the given view type. + * @param viewType The type of webview. Maps to WebviewPanel's viewType + */ + constructor(viewType: string); + } + + /** + * The tab represents a notebook. + */ + export class TabInputNotebook { + /** + * The uri that the tab is representing. + */ + readonly uri: Uri; + /** + * The type of notebook. Maps to NotebookDocuments's notebookType + */ + readonly notebookType: string; + /** + * Constructs a new tab input for a notebook. + * @param uri The uri of the notebook. + * @param notebookType The type of notebook. Maps to NotebookDocuments's notebookType + */ + constructor(uri: Uri, notebookType: string); + } + + /** + * The tabs represents two notebooks in a diff configuration. + */ + export class TabInputNotebookDiff { + /** + * The uri of the original notebook. + */ + readonly original: Uri; + /** + * The uri of the modified notebook. + */ + readonly modified: Uri; + /** + * The type of notebook. Maps to NotebookDocuments's notebookType + */ + readonly notebookType: string; + /** + * Constructs a notebook diff tab input. + * @param original The uri of the original unmodified notebook. + * @param modified The uri of the modified notebook. + * @param notebookType The type of notebook. Maps to NotebookDocuments's notebookType + */ + constructor(original: Uri, modified: Uri, notebookType: string); + } + + /** + * The tab represents a terminal in the editor area. + */ + export class TabInputTerminal { + /** + * Constructs a terminal tab input. + */ + constructor(); + } + + /** + * Represents a tab within a {@link TabGroup group of tabs}. + * Tabs are merely the graphical representation within the editor area. + * A backing editor is not a guarantee. + */ + export interface Tab { + + /** + * The text displayed on the tab. + */ + readonly label: string; + + /** + * The group which the tab belongs to. + */ + readonly group: TabGroup; + + /** + * Defines the structure of the tab i.e. text, notebook, custom, etc. + * Resource and other useful properties are defined on the tab kind. + */ + readonly input: TabInputText | TabInputTextDiff | TabInputCustom | TabInputWebview | TabInputNotebook | TabInputNotebookDiff | TabInputTerminal | unknown; + + /** + * Whether or not the tab is currently active. + * This is dictated by being the selected tab in the group. + */ + readonly isActive: boolean; + + /** + * Whether or not the dirty indicator is present on the tab. + */ + readonly isDirty: boolean; + + /** + * Whether or not the tab is pinned (pin icon is present). + */ + readonly isPinned: boolean; + + /** + * Whether or not the tab is in preview mode. + */ + readonly isPreview: boolean; + } + + /** + * An event describing change to tabs. + */ + export interface TabChangeEvent { + /** + * The tabs that have been opened. + */ + readonly opened: readonly Tab[]; + /** + * The tabs that have been closed. + */ + readonly closed: readonly Tab[]; + /** + * Tabs that have changed, e.g have changed + * their {@link Tab.isActive active} state. + */ + readonly changed: readonly Tab[]; + } + + /** + * An event describing changes to tab groups. + */ + export interface TabGroupChangeEvent { + /** + * Tab groups that have been opened. + */ + readonly opened: readonly TabGroup[]; + /** + * Tab groups that have been closed. + */ + readonly closed: readonly TabGroup[]; + /** + * Tab groups that have changed, e.g have changed + * their {@link TabGroup.isActive active} state. + */ + readonly changed: readonly TabGroup[]; + } + + /** + * Represents a group of tabs. A tab group itself consists of multiple tabs. + */ + export interface TabGroup { + /** + * Whether or not the group is currently active. + * + * *Note* that only one tab group is active at a time, but that multiple tab + * groups can have an {@link TabGroup.aciveTab active tab}. + * + * @see {@link Tab.isActive} + */ + readonly isActive: boolean; + + /** + * The view column of the group. + */ + readonly viewColumn: ViewColumn; + + /** + * The active {@link Tab tab} in the group. This is the tab whose contents are currently + * being rendered. + * + * *Note* that there can be one active tab per group but there can only be one {@link TabGroups.activeTabGroup active group}. + */ + readonly activeTab: Tab | undefined; + + /** + * The list of tabs contained within the group. + * This can be empty if the group has no tabs open. + */ + readonly tabs: readonly Tab[]; + } + + /** + * Represents the main editor area which consists of multple groups which contain tabs. + */ + export interface TabGroups { + /** + * All the groups within the group container. + */ + readonly all: readonly TabGroup[]; + + /** + * The currently active group. + */ + readonly activeTabGroup: TabGroup; + + /** + * An {@link Event event} which fires when {@link TabGroup tab groups} have changed. + */ + readonly onDidChangeTabGroups: Event; + + /** + * An {@link Event event} which fires when {@link Tab tabs} have changed. + */ + readonly onDidChangeTabs: Event; + + /** + * Closes the tab. This makes the tab object invalid and the tab + * should no longer be used for further actions. + * Note: In the case of a dirty tab, a confirmation dialog will be shown which may be cancelled. If cancelled the tab is still valid + * + * @param tab The tab to close. + * @param preserveFocus When `true` focus will remain in its current position. If `false` it will jump to the next tab. + * @returns A promise that resolves to `true` when all tabs have been closed. + */ + close(tab: Tab | readonly Tab[], preserveFocus?: boolean): Thenable; + + /** + * Closes the tab group. This makes the tab group object invalid and the tab group + * should no longer be used for further actions. + * @param tabGroup The tab group to close. + * @param preserveFocus When `true` focus will remain in its current position. + * @returns A promise that resolves to `true` when all tab groups have been closed. + * @stubbed + */ + close(tabGroup: TabGroup | readonly TabGroup[], preserveFocus?: boolean): Thenable; + } /** * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. @@ -13933,7 +13893,6 @@ export module '@theia/plugin' { /** * The {@link NotebookDocument notebook} that contains this cell. - * @stubbed */ readonly notebook: NotebookDocument; @@ -13994,26 +13953,22 @@ export module '@theia/plugin' { /** * The version number of this notebook (it will strictly increase after each * change, including undo/redo). - * @stubbed */ readonly version: number; /** * `true` if there are unpersisted changes. - * @stubbed */ readonly isDirty: boolean; /** * Is this notebook representing an untitled file which has not been saved yet. - * @stubbed */ readonly isUntitled: boolean; /** * `true` if the notebook has been closed. A closed notebook isn't synchronized anymore * and won't be re-used when the same resource is opened again. - * @stubbed */ readonly isClosed: boolean;