diff --git a/terminus-terminal/src/api.ts b/terminus-terminal/src/api.ts index f4d22391..fef5fc9c 100644 --- a/terminus-terminal/src/api.ts +++ b/terminus-terminal/src/api.ts @@ -41,6 +41,12 @@ export abstract class TerminalColorSchemeProvider { abstract async getSchemes (): Promise } +export abstract class TerminalContextMenuItemProvider { + weight: number + + abstract async getItems (tab: TerminalTabComponent): Promise +} + export interface IShell { id: string name?: string diff --git a/terminus-terminal/src/components/terminalTab.component.ts b/terminus-terminal/src/components/terminalTab.component.ts index 29ef02e6..e6a94149 100644 --- a/terminus-terminal/src/components/terminalTab.component.ts +++ b/terminus-terminal/src/components/terminalTab.component.ts @@ -5,11 +5,9 @@ import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } fr import { AppService, ConfigService, BaseTabComponent, BaseTabProcess, ElectronService, HostAppService, HotkeysService, Platform } from 'terminus-core' import { Session, SessionsService } from '../services/sessions.service' -import { TerminalService } from '../services/terminal.service' import { TerminalFrontendService } from '../services/terminalFrontend.service' -import { UACService } from '../services/uac.service' -import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api' +import { TerminalDecorator, ResizeEvent, SessionOptions, TerminalContextMenuItemProvider } from '../api' import { Frontend } from '../frontends/frontend' @Component({ @@ -49,12 +47,11 @@ export class TerminalTabComponent extends BaseTabComponent { private hotkeys: HotkeysService, private sessions: SessionsService, private electron: ElectronService, - private terminalService: TerminalService, private terminalContainersService: TerminalFrontendService, public config: ConfigService, private toastr: ToastrService, - private uac: UACService, @Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[], + @Optional() @Inject(TerminalContextMenuItemProvider) private contextMenuProviders: TerminalContextMenuItemProvider[], ) { super() this.decorators = this.decorators || [] @@ -117,6 +114,8 @@ export class TerminalTabComponent extends BaseTabComponent { }) this.bellPlayer = document.createElement('audio') this.bellPlayer.src = require('../bell.ogg') + + this.contextMenuProviders.sort((a, b) => a.weight - b.weight) } initializeSession (columns: number, rows: number) { @@ -206,76 +205,12 @@ export class TerminalTabComponent extends BaseTabComponent { } async buildContextMenu (): Promise { - let shells = await this.terminalService.shells$.toPromise() - let items: Electron.MenuItemConstructorOptions[] = [ - { - label: 'New terminal', - click: () => this.zone.run(() => { - this.terminalService.openTabWithOptions(this.sessionOptions) - }) - }, - { - label: 'New with shell', - submenu: shells.map(shell => ({ - label: shell.name, - click: () => this.zone.run(async () => { - this.terminalService.openTab(shell, await this.session.getWorkingDirectory()) - }), - })), - }, - ] - - if (this.uac.isAvailable) { - items.push({ - label: 'New as admin', - submenu: shells.map(shell => ({ - label: shell.name, - click: () => this.zone.run(async () => { - let options = this.terminalService.optionsFromShell(shell) - options.runAsAdministrator = true - this.terminalService.openTabWithOptions(options) - }), - })), - }) + let items: Electron.MenuItemConstructorOptions[] = [] + for (let section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this)))) { + items = items.concat(section) + items.push({ type: 'separator' }) } - - items = items.concat([ - { - label: 'New with profile', - submenu: this.config.store.terminal.profiles.length ? this.config.store.terminal.profiles.map(profile => ({ - label: profile.name, - click: () => this.zone.run(() => { - this.terminalService.openTabWithOptions(profile.sessionOptions) - }), - })) : [{ - label: 'No profiles saved', - enabled: false, - }], - }, - { - type: 'separator', - }, - { - label: 'Copy', - click: () => { - this.zone.run(() => { - setTimeout(() => { - this.frontend.copySelection() - this.toastr.info('Copied') - }) - }) - } - }, - { - label: 'Paste', - click: () => { - this.zone.run(() => { - this.paste() - }) - } - }, - ]) - + items.splice(items.length - 1, 1) return items } diff --git a/terminus-terminal/src/contextMenu.ts b/terminus-terminal/src/contextMenu.ts new file mode 100644 index 00000000..0f919063 --- /dev/null +++ b/terminus-terminal/src/contextMenu.ts @@ -0,0 +1,108 @@ +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 { TerminalTabComponent } from './components/terminalTab.component' +import { TerminalContextMenuItemProvider } from './api' + +@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: TerminalTabComponent): Promise { + let shells = await this.terminalService.shells$.toPromise() + + let items: Electron.MenuItemConstructorOptions[] = [ + { + label: 'New terminal', + click: () => this.zone.run(() => { + this.terminalService.openTabWithOptions(tab.sessionOptions) + }) + }, + { + label: 'New with shell', + submenu: shells.map(shell => ({ + label: shell.name, + click: () => this.zone.run(async () => { + this.terminalService.openTab(shell, await tab.session.getWorkingDirectory()) + }), + })), + }, + ] + + if (this.uac.isAvailable) { + items.push({ + label: 'New as admin', + submenu: shells.map(shell => ({ + label: shell.name, + click: () => this.zone.run(async () => { + let options = this.terminalService.optionsFromShell(shell) + options.runAsAdministrator = true + this.terminalService.openTabWithOptions(options) + }), + })), + }) + } + + items = items.concat([ + { + label: 'New with profile', + submenu: this.config.store.terminal.profiles.length ? this.config.store.terminal.profiles.map(profile => ({ + label: profile.name, + click: () => this.zone.run(() => { + this.terminalService.openTabWithOptions(profile.sessionOptions) + }), + })) : [{ + label: 'No profiles saved', + enabled: false, + }], + }, + ]) + + return items + } +} + +@Injectable() +export class CopyPasteContextMenu extends TerminalContextMenuItemProvider { + weight = 1 + + constructor ( + private zone: NgZone, + private toastr: ToastrService, + ) { + super() + } + + async getItems (tab: TerminalTabComponent): 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 eff7ced1..f7c3e6c2 100644 --- a/terminus-terminal/src/index.ts +++ b/terminus-terminal/src/index.ts @@ -24,12 +24,13 @@ import { DockMenuService } from './services/dockMenu.service' import { ButtonProvider } from './buttonProvider' import { RecoveryProvider } from './recoveryProvider' -import { TerminalColorSchemeProvider, TerminalDecorator, ShellProvider } from './api' +import { TerminalColorSchemeProvider, TerminalDecorator, ShellProvider, TerminalContextMenuItemProvider } from './api' import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ShellSettingsTabProvider } from './settings' import { PathDropDecorator } from './pathDrop' import { TerminalConfigProvider } from './config' import { TerminalHotkeyProvider } from './hotkeys' import { HyperColorSchemes } from './colorSchemes' +import { NewTabContextMenu, CopyPasteContextMenu } from './contextMenu' import { CmderShellProvider } from './shells/cmder' import { CustomShellProvider } from './shells/custom' @@ -79,6 +80,9 @@ import { hterm } from './hterm' { provide: ShellProvider, useClass: PowerShellCoreShellProvider, multi: true }, { provide: ShellProvider, useClass: WSLShellProvider, multi: true }, + { provide: TerminalContextMenuItemProvider, useClass: NewTabContextMenu, multi: true }, + { provide: TerminalContextMenuItemProvider, useClass: CopyPasteContextMenu, multi: true }, + // For WindowsDefaultShellProvider PowerShellCoreShellProvider, WSLShellProvider,