made cli handling extensible - fixes #3763

This commit is contained in:
Eugene Pankov
2021-05-16 15:44:04 +02:00
parent 8fb2bc1ba0
commit fa07fdcb64
11 changed files with 203 additions and 125 deletions

View File

@@ -22,12 +22,14 @@
},
"devDependencies": {
"@types/deep-equal": "^1.0.0",
"@types/shell-escape": "^0.2.0",
"ansi-colors": "^4.1.1",
"dataurl": "0.1.0",
"deep-equal": "2.0.5",
"mz": "^2.6.0",
"ps-node": "^0.1.6",
"runes": "^0.4.2",
"shell-escape": "^0.2.0",
"slugify": "^1.4.0",
"utils-decorators": "^1.8.1",
"xterm": "^4.9.0-beta.7",

View File

@@ -0,0 +1,135 @@
import * as path from 'path'
import * as fs from 'mz/fs'
import shellEscape from 'shell-escape'
import { Injectable } from '@angular/core'
import { CLIHandler, CLIEvent, HostAppService, AppService, ConfigService } from 'terminus-core'
import { TerminalTabComponent } from './components/terminalTab.component'
import { TerminalService } from './services/terminal.service'
@Injectable()
export class TerminalCLIHandler extends CLIHandler {
firstMatchOnly = true
priority = 0
constructor (
private app: AppService,
private config: ConfigService,
private hostApp: HostAppService,
private terminal: TerminalService,
) {
super()
}
async handle (event: CLIEvent): Promise<boolean> {
const op = event.argv._[0]
if (op === 'open') {
this.handleOpenDirectory(path.resolve(event.cwd, event.argv.directory))
} else if (op === 'run') {
this.handleRunCommand(event.argv.command)
} else if (op === 'paste') {
let text = event.argv.text
if (event.argv.escape) {
text = shellEscape([text])
}
this.handlePaste(text)
} else if (op === 'profile') {
this.handleOpenProfile(event.argv.profileName)
} else {
return false
}
return true
}
private async handleOpenDirectory (directory: string) {
if (directory.length > 1 && (directory.endsWith('/') || directory.endsWith('\\'))) {
directory = directory.substring(0, directory.length - 1)
}
if (await fs.exists(directory)) {
if ((await fs.stat(directory)).isDirectory()) {
this.terminal.openTab(undefined, directory)
this.hostApp.bringToFront()
}
}
}
private handleRunCommand (command: string[]) {
this.terminal.openTab({
name: '',
sessionOptions: {
command: command[0],
args: command.slice(1),
},
}, null, true)
this.hostApp.bringToFront()
}
private handleOpenProfile (profileName: string) {
const profile = this.config.store.terminal.profiles.find(x => x.name === profileName)
if (!profile) {
console.error('Requested profile', profileName, 'not found')
return
}
this.terminal.openTabWithOptions(profile.sessionOptions)
this.hostApp.bringToFront()
}
private handlePaste (text: string) {
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
this.app.activeTab.sendInput(text)
this.hostApp.bringToFront()
}
}
}
@Injectable()
export class OpenPathCLIHandler extends CLIHandler {
firstMatchOnly = true
priority = -100
constructor (
private terminal: TerminalService,
private hostApp: HostAppService,
) {
super()
}
async handle (event: CLIEvent): Promise<boolean> {
const op = event.argv._[0]
const opAsPath = op ? path.resolve(event.cwd, op) : null
if (opAsPath && (await fs.lstat(opAsPath)).isDirectory()) {
this.terminal.openTab(undefined, opAsPath)
this.hostApp.bringToFront()
return true
}
return false
}
}
@Injectable()
export class AutoOpenTabCLIHandler extends CLIHandler {
firstMatchOnly = true
priority = -1000
constructor (
private app: AppService,
private config: ConfigService,
private terminal: TerminalService,
) {
super()
}
async handle (event: CLIEvent): Promise<boolean> {
if (!event.secondInstance && this.config.store.terminal.autoOpen) {
this.app.ready$.subscribe(() => {
this.terminal.openTab()
})
return true
}
return false
}
}

View File

@@ -1,12 +1,10 @@
import * as fs from 'mz/fs'
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { ToastrModule } from 'ngx-toastr'
import TerminusCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService, TabContextMenuItemProvider, ElectronService } from 'terminus-core'
import TerminusCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, TabContextMenuItemProvider, CLIHandler } from 'terminus-core'
import { SettingsTabProvider } from 'terminus-settings'
import { AppearanceSettingsTabComponent } from './components/appearanceSettingsTab.component'
@@ -57,6 +55,7 @@ import { hterm } from './frontends/hterm'
import { Frontend } from './frontends/frontend'
import { HTermFrontend } from './frontends/htermFrontend'
import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from './cli'
/** @hidden */
@NgModule({
@@ -100,6 +99,10 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
{ provide: TabContextMenuItemProvider, useClass: SaveAsProfileContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: LegacyContextMenu, multi: true },
{ provide: CLIHandler, useClass: TerminalCLIHandler, multi: true },
{ provide: CLIHandler, useClass: OpenPathCLIHandler, multi: true },
{ provide: CLIHandler, useClass: AutoOpenTabCLIHandler, multi: true },
// For WindowsDefaultShellProvider
PowerShellCoreShellProvider,
WSLShellProvider,
@@ -133,13 +136,10 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
})
export default class TerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
private constructor (
app: AppService,
config: ConfigService,
hotkeys: HotkeysService,
terminal: TerminalService,
hostApp: HostAppService,
dockMenu: DockMenuService,
electron: ElectronService,
) {
const events = [
{
@@ -165,18 +165,6 @@ export default class TerminalModule { // eslint-disable-line @typescript-eslint/
hotkeys.emitKeyEvent(nativeEvent)
}
})
if (config.store.terminal.autoOpen) {
let argv = electron.process.argv
if (argv[0].includes('node')) {
argv = argv.slice(1)
}
if (require('yargs/yargs')(argv.slice(1)).parse()._[0] !== 'open'){
app.ready$.subscribe(() => {
terminal.openTab()
})
}
}
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
if (hotkey === 'new-tab') {
@@ -193,46 +181,6 @@ export default class TerminalModule { // eslint-disable-line @typescript-eslint/
}
})
hostApp.cliOpenDirectory$.subscribe(async directory => {
if (directory.length > 1 && (directory.endsWith('/') || directory.endsWith('\\'))) {
directory = directory.substring(0, directory.length - 1)
}
if (await fs.exists(directory)) {
if ((await fs.stat(directory)).isDirectory()) {
terminal.openTab(undefined, directory)
hostApp.bringToFront()
}
}
})
hostApp.cliRunCommand$.subscribe(async command => {
terminal.openTab({
name: '',
sessionOptions: {
command: command[0],
args: command.slice(1),
},
}, null, true)
hostApp.bringToFront()
})
hostApp.cliPaste$.subscribe(text => {
if (app.activeTab instanceof TerminalTabComponent && app.activeTab.session) {
app.activeTab.sendInput(text)
hostApp.bringToFront()
}
})
hostApp.cliOpenProfile$.subscribe(async profileName => {
const profile = config.store.terminal.profiles.find(x => x.name === profileName)
if (!profile) {
console.error('Requested profile', profileName, 'not found')
return
}
terminal.openTabWithOptions(profile.sessionOptions)
hostApp.bringToFront()
})
dockMenu.update()
}
}

View File

@@ -7,6 +7,11 @@
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==
"@types/shell-escape@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@types/shell-escape/-/shell-escape-0.2.0.tgz#cd2f0df814388599dd07196dcc510de2669d1ed2"
integrity sha512-7kUdtJtUylvyISJbe9FMcvMTjRdP0EvNDO1WbT0lT22k/IPBiPRTpmWaKu5HTWLCGLQRWVHrzVHZktTDvvR23g==
ansi-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@@ -430,6 +435,11 @@ runes@^0.4.2:
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==
shell-escape@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/shell-escape/-/shell-escape-0.2.0.tgz#68fd025eb0490b4f567a027f0bf22480b5f84133"
integrity sha1-aP0CXrBJC09WegJ/C/IkgLX4QTM=
side-channel@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"