diff --git a/terminus-core/src/api/index.ts b/terminus-core/src/api/index.ts index 7112d09e..948a2ccc 100644 --- a/terminus-core/src/api/index.ts +++ b/terminus-core/src/api/index.ts @@ -1,4 +1,5 @@ export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component' +export { TabHeaderComponent } from '../components/tabHeader.component' export { SplitTabComponent, SplitContainer } from '../components/splitTab.component' export { TabRecoveryProvider, RecoveredTab } from './tabRecovery' export { ToolbarButtonProvider, ToolbarButton } from './toolbarButtonProvider' diff --git a/terminus-core/src/tabContextMenu.ts b/terminus-core/src/tabContextMenu.ts index f432836e..5c555d66 100644 --- a/terminus-core/src/tabContextMenu.ts +++ b/terminus-core/src/tabContextMenu.ts @@ -17,39 +17,49 @@ export class CloseContextMenu extends TabContextMenuItemProvider { super() } - async getItems (tab: BaseTabComponent): Promise { - return [ + async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + let items = [ { label: 'Close', click: () => this.zone.run(() => { - this.app.closeTab(tab, true) - }), - }, - { - label: 'Close other tabs', - click: () => this.zone.run(() => { - for (const t of this.app.tabs.filter(x => x !== tab)) { - this.app.closeTab(t, true) - } - }), - }, - { - label: 'Close tabs to the right', - click: () => this.zone.run(() => { - for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) { - this.app.closeTab(t, true) - } - }), - }, - { - label: 'Close tabs to the left', - click: () => this.zone.run(() => { - for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) { - this.app.closeTab(t, true) + if (this.app.tabs.includes(tab)) { + this.app.closeTab(tab, true) + } else { + tab.destroy() } }), }, ] + if (tabHeader) { + items = [ + ...items, + { + label: 'Close other tabs', + click: () => this.zone.run(() => { + for (const t of this.app.tabs.filter(x => x !== tab)) { + this.app.closeTab(t, true) + } + }), + }, + { + label: 'Close tabs to the right', + click: () => this.zone.run(() => { + for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) { + this.app.closeTab(t, true) + } + }), + }, + { + label: 'Close tabs to the left', + click: () => this.zone.run(() => { + for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) { + this.app.closeTab(t, true) + } + }), + }, + ] + } + return items } } @@ -76,28 +86,31 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider { } async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { - return [ - { - label: 'Rename', - click: () => this.zone.run(() => tabHeader?.showRenameTabModal()), - }, - { - label: 'Duplicate', - click: () => this.zone.run(() => this.app.duplicateTab(tab)), - }, - { - label: 'Color', - sublabel: COLORS.find(x => x.value === tab.color)!.name, - submenu: COLORS.map(color => ({ - label: color.name, - type: 'radio', - checked: tab.color === color.value, - click: () => this.zone.run(() => { - tab.color = color.value - }), - })) as Electron.MenuItemConstructorOptions[], - }, - ] + if (tabHeader) { + return [ + { + label: 'Rename', + click: () => this.zone.run(() => tabHeader?.showRenameTabModal()), + }, + { + label: 'Duplicate', + click: () => this.zone.run(() => this.app.duplicateTab(tab)), + }, + { + label: 'Color', + sublabel: COLORS.find(x => x.value === tab.color)!.name, + submenu: COLORS.map(color => ({ + label: color.name, + type: 'radio', + checked: tab.color === color.value, + click: () => this.zone.run(() => { + tab.color = color.value + }), + })) as Electron.MenuItemConstructorOptions[], + }, + ] + } + return [] } } diff --git a/terminus-terminal/src/api/baseTerminalTab.component.ts b/terminus-terminal/src/api/baseTerminalTab.component.ts index 406a8a0b..86a96327 100644 --- a/terminus-terminal/src/api/baseTerminalTab.component.ts +++ b/terminus-terminal/src/api/baseTerminalTab.component.ts @@ -3,7 +3,7 @@ import { first } from 'rxjs/operators' import { ToastrService } from 'ngx-toastr' import { NgZone, OnInit, OnDestroy, Inject, Injector, Optional, ViewChild, HostBinding, Input, ElementRef } from '@angular/core' import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations' -import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, Platform, LogService, Logger } from 'terminus-core' +import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, Platform, LogService, Logger, TabContextMenuItemProvider } from 'terminus-core' import { BaseSession, SessionsService } from '../services/sessions.service' import { TerminalFrontendService } from '../services/terminalFrontend.service' @@ -11,7 +11,6 @@ import { TerminalFrontendService } from '../services/terminalFrontend.service' import { Frontend } from '../frontends/frontend' import { ResizeEvent } from './interfaces' import { TerminalDecorator } from './decorator' -import { TerminalContextMenuItemProvider } from './contextMenuProvider' /** @hidden */ @@ -88,7 +87,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit @Inject(ToastrService) protected toastr: ToastrServiceProxy, protected log: LogService, @Optional() @Inject(TerminalDecorator) protected decorators: TerminalDecorator[], - @Optional() @Inject(TerminalContextMenuItemProvider) protected contextMenuProviders: TerminalContextMenuItemProvider[], + @Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[], ) { super() this.logger = log.create('baseTerminalTab') diff --git a/terminus-terminal/src/api/contextMenuProvider.ts b/terminus-terminal/src/api/contextMenuProvider.ts index f76e7d6a..1a16448d 100644 --- a/terminus-terminal/src/api/contextMenuProvider.ts +++ b/terminus-terminal/src/api/contextMenuProvider.ts @@ -2,6 +2,7 @@ import { BaseTerminalTabComponent } from './baseTerminalTab.component' /** * Extend to add more terminal context menu items + * @deprecated */ export abstract class TerminalContextMenuItemProvider { weight: number diff --git a/terminus-terminal/src/contextMenu.ts b/terminus-terminal/src/contextMenu.ts deleted file mode 100644 index 7e5fe089..00000000 --- a/terminus-terminal/src/contextMenu.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { NgZone, Injectable } from '@angular/core' -import { ToastrService } from 'ngx-toastr' -import { ConfigService } from 'terminus-core' -import { UACService } from './services/uac.service' -import { TerminalService } from './services/terminal.service' -import { TerminalContextMenuItemProvider } from './api/contextMenuProvider' -import { BaseTerminalTabComponent } from './api/baseTerminalTab.component' - -/** @hidden */ -@Injectable() -export class NewTabContextMenu extends TerminalContextMenuItemProvider { - weight = 0 - - constructor ( - public config: ConfigService, - private zone: NgZone, - private terminalService: TerminalService, - private uac: UACService, - ) { - super() - } - - async getItems (tab: BaseTerminalTabComponent): Promise { - const profiles = await this.terminalService.getProfiles() - - const items: Electron.MenuItemConstructorOptions[] = [ - { - label: 'New terminal', - click: () => this.zone.run(() => { - this.terminalService.openTabWithOptions((tab as any).sessionOptions) - }), - }, - { - label: 'New with profile', - submenu: profiles.map(profile => ({ - label: profile.name, - click: () => this.zone.run(async () => { - const workingDirectory = this.config.store.terminal.alwaysUseWorkingDirectory === true ? - this.config.store.terminal.workingDirectory : await tab.session.getWorkingDirectory() - await this.terminalService.openTab(profile, workingDirectory) - }), - })), - }, - ] - - if (this.uac.isAvailable) { - items.push({ - label: 'New admin tab', - submenu: profiles.map(profile => ({ - label: profile.name, - click: () => this.zone.run(async () => { - this.terminalService.openTabWithOptions({ - ...profile.sessionOptions, - runAsAdministrator: true, - }) - }), - })), - }) - } - - return items - } -} - -/** @hidden */ -@Injectable() -export class CopyPasteContextMenu extends TerminalContextMenuItemProvider { - weight = 1 - - constructor ( - private zone: NgZone, - private toastr: ToastrService, - ) { - super() - } - - async getItems (tab: BaseTerminalTabComponent): Promise { - return [ - { - label: 'Copy', - click: () => { - this.zone.run(() => { - setTimeout(() => { - tab.frontend.copySelection() - this.toastr.info('Copied') - }) - }) - }, - }, - { - label: 'Paste', - click: () => { - this.zone.run(() => tab.paste()) - }, - }, - ] - } -} diff --git a/terminus-terminal/src/index.ts b/terminus-terminal/src/index.ts index 03eb55d2..40f918b1 100644 --- a/terminus-terminal/src/index.ts +++ b/terminus-terminal/src/index.ts @@ -35,8 +35,7 @@ import { PathDropDecorator } from './pathDrop' import { TerminalConfigProvider } from './config' import { TerminalHotkeyProvider } from './hotkeys' import { HyperColorSchemes } from './colorSchemes' -import { NewTabContextMenu, CopyPasteContextMenu } from './contextMenu' -import { SaveAsProfileContextMenu } from './tabContextMenu' +import { NewTabContextMenu, CopyPasteContextMenu, SaveAsProfileContextMenu, LegacyContextMenu } from './tabContextMenu' import { ZModemDecorator } from './zmodem' import { CmderShellProvider } from './shells/cmder' @@ -92,10 +91,10 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend' { provide: ShellProvider, useClass: POSIXShellsProvider, multi: true }, { provide: ShellProvider, useClass: WSLShellProvider, multi: true }, - { provide: TerminalContextMenuItemProvider, useClass: NewTabContextMenu, multi: true }, - { provide: TerminalContextMenuItemProvider, useClass: CopyPasteContextMenu, multi: true }, - + { provide: TabContextMenuItemProvider, useClass: NewTabContextMenu, multi: true }, + { provide: TabContextMenuItemProvider, useClass: CopyPasteContextMenu, multi: true }, { provide: TabContextMenuItemProvider, useClass: SaveAsProfileContextMenu, multi: true }, + { provide: TabContextMenuItemProvider, useClass: LegacyContextMenu, multi: true }, // For WindowsDefaultShellProvider PowerShellCoreShellProvider, diff --git a/terminus-terminal/src/tabContextMenu.ts b/terminus-terminal/src/tabContextMenu.ts index 3c0e0652..fcfecf85 100644 --- a/terminus-terminal/src/tabContextMenu.ts +++ b/terminus-terminal/src/tabContextMenu.ts @@ -1,9 +1,11 @@ -import { Injectable, NgZone } from '@angular/core' +import { Injectable, NgZone, Optional, Inject } from '@angular/core' import { ToastrService } from 'ngx-toastr' -import { ConfigService, BaseTabComponent, TabContextMenuItemProvider } from 'terminus-core' +import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent } from 'terminus-core' import { TerminalTabComponent } from './components/terminalTab.component' import { UACService } from './services/uac.service' import { TerminalService } from './services/terminal.service' +import { BaseTerminalTabComponent } from './api/baseTerminalTab.component' +import { TerminalContextMenuItemProvider } from './api/contextMenuProvider' /** @hidden */ @Injectable() @@ -12,13 +14,11 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider { private config: ConfigService, private zone: NgZone, private toastr: ToastrService, - private uac: UACService, - private terminalService: TerminalService, ) { super() } - async getItems (tab: BaseTabComponent): Promise { + async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise { if (!(tab instanceof TerminalTabComponent)) { return [] } @@ -43,18 +43,140 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider { }, ] + return items + } +} + +/** @hidden */ +@Injectable() +export class NewTabContextMenu extends TabContextMenuItemProvider { + weight = 10 + + constructor ( + public config: ConfigService, + private zone: NgZone, + private terminalService: TerminalService, + private uac: UACService, + ) { + super() + } + + async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + const profiles = await this.terminalService.getProfiles() + + const items: Electron.MenuItemConstructorOptions[] = [ + { + label: 'New terminal', + click: () => this.zone.run(() => { + this.terminalService.openTabWithOptions((tab as any).sessionOptions) + }), + }, + { + label: 'New with profile', + submenu: profiles.map(profile => ({ + label: profile.name, + click: () => this.zone.run(async () => { + let workingDirectory = this.config.store.terminal.workingDirectory + if (this.config.store.terminal.alwaysUseWorkingDirectory !== true && tab instanceof TerminalTabComponent) { + workingDirectory = await tab.session.getWorkingDirectory() + } + await this.terminalService.openTab(profile, workingDirectory) + }), + })), + }, + ] if (this.uac.isAvailable) { + items.push({ + label: 'New admin tab', + submenu: profiles.map(profile => ({ + label: profile.name, + click: () => this.zone.run(async () => { + this.terminalService.openTabWithOptions({ + ...profile.sessionOptions, + runAsAdministrator: true, + }) + }), + })), + }) + } + + if (tab instanceof TerminalTabComponent && tabHeader && this.uac.isAvailable) { items.push({ label: 'Duplicate as administrator', click: () => this.zone.run(async () => { this.terminalService.openTabWithOptions({ - ...tab.sessionOptions, + ...(tab as TerminalTabComponent).sessionOptions, runAsAdministrator: true, }) }), }) } + return items } } + +/** @hidden */ +@Injectable() +export class CopyPasteContextMenu extends TabContextMenuItemProvider { + weight = 1 + + constructor ( + private zone: NgZone, + private toastr: ToastrService, + ) { + super() + } + + async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise { + if (tabHeader) { + return [] + } + if (tab instanceof BaseTerminalTabComponent) { + return [ + { + label: 'Copy', + click: () => { + this.zone.run(() => { + setTimeout(() => { + (tab as BaseTerminalTabComponent).frontend.copySelection() + this.toastr.info('Copied') + }) + }) + }, + }, + { + label: 'Paste', + click: () => { + this.zone.run(() => (tab as BaseTerminalTabComponent).paste()) + }, + }, + ] + } + return [] + } +} + +/** @hidden */ +@Injectable() +export class LegacyContextMenu extends TabContextMenuItemProvider { + weight = 1 + + constructor ( + @Optional() @Inject(TerminalContextMenuItemProvider) protected contextMenuProviders: TerminalContextMenuItemProvider[], + ) { + super() + } + + async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise { + if (tab instanceof BaseTerminalTabComponent) { + let items: Electron.MenuItemConstructorOptions[] = [] + for (const p of this.contextMenuProviders) { + items = items.concat(await p.getItems(tab)) + } + return items + } + return [] + } +}