Browse Source

split terminus-local out of terminus-terminal

Eugene Pankov 4 years ago
parent
commit
3ef4a8aa73
83 changed files with 1315 additions and 625 deletions
  1. 6 0
      .github/dependabot.yml
  2. 3 2
      HACKING.md
  3. 1 0
      app/package.json
  4. 5 0
      app/src/plugins.ts
  5. 2 2
      package.json
  6. 1 1
      scripts/build-native.js
  7. 1 0
      scripts/vars.js
  8. 1 1
      terminus-core/README.md
  9. 13 8
      terminus-core/src/services/config.service.ts
  10. 1 0
      terminus-local/.gitignore
  11. 23 0
      terminus-local/README.md
  12. 48 0
      terminus-local/package.json
  13. 57 0
      terminus-local/src/api.ts
  14. 0 0
      terminus-local/src/buttonProvider.ts
  15. 119 0
      terminus-local/src/cli.ts
  16. 0 0
      terminus-local/src/components/editProfileModal.component.pug
  17. 1 1
      terminus-local/src/components/editProfileModal.component.ts
  18. 0 0
      terminus-local/src/components/environmentEditor.component.pug
  19. 0 0
      terminus-local/src/components/environmentEditor.component.scss
  20. 0 0
      terminus-local/src/components/environmentEditor.component.ts
  21. 0 0
      terminus-local/src/components/shellSettingsTab.component.pug
  22. 1 1
      terminus-local/src/components/shellSettingsTab.component.ts
  23. 8 10
      terminus-local/src/components/terminalTab.component.ts
  24. 62 0
      terminus-local/src/config.ts
  25. 29 0
      terminus-local/src/hotkeys.ts
  26. 0 0
      terminus-local/src/icons/alpine.svg
  27. 0 0
      terminus-local/src/icons/clink.svg
  28. 0 0
      terminus-local/src/icons/cmd.svg
  29. 0 0
      terminus-local/src/icons/cmder-powershell.svg
  30. 0 0
      terminus-local/src/icons/cmder.svg
  31. 0 0
      terminus-local/src/icons/cygwin.svg
  32. 0 0
      terminus-local/src/icons/debian.svg
  33. 0 0
      terminus-local/src/icons/git-bash.svg
  34. 0 0
      terminus-local/src/icons/linux.svg
  35. 0 0
      terminus-local/src/icons/plus.svg
  36. 0 0
      terminus-local/src/icons/powershell-core.svg
  37. 0 0
      terminus-local/src/icons/powershell.svg
  38. 0 0
      terminus-local/src/icons/profiles.svg
  39. 0 0
      terminus-local/src/icons/suse.svg
  40. 0 0
      terminus-local/src/icons/ubuntu.svg
  41. 129 0
      terminus-local/src/index.ts
  42. 0 0
      terminus-local/src/recoveryProvider.ts
  43. 0 0
      terminus-local/src/services/dockMenu.service.ts
  44. 2 3
      terminus-local/src/services/terminal.service.ts
  45. 1 1
      terminus-local/src/services/uac.service.ts
  46. 3 89
      terminus-local/src/session.ts
  47. 16 0
      terminus-local/src/settings.ts
  48. 1 2
      terminus-local/src/shells/cmder.ts
  49. 1 2
      terminus-local/src/shells/custom.ts
  50. 1 2
      terminus-local/src/shells/cygwin32.ts
  51. 1 2
      terminus-local/src/shells/cygwin64.ts
  52. 1 2
      terminus-local/src/shells/gitBash.ts
  53. 1 2
      terminus-local/src/shells/linuxDefault.ts
  54. 1 2
      terminus-local/src/shells/macDefault.ts
  55. 1 2
      terminus-local/src/shells/posix.ts
  56. 2 2
      terminus-local/src/shells/powershellCore.ts
  57. 1 2
      terminus-local/src/shells/winDefault.ts
  58. 1 2
      terminus-local/src/shells/windowsStock.ts
  59. 1 2
      terminus-local/src/shells/wsl.ts
  60. 132 0
      terminus-local/src/tabContextMenu.ts
  61. 7 0
      terminus-local/tsconfig.json
  62. 16 0
      terminus-local/tsconfig.typings.json
  63. 5 0
      terminus-local/webpack.config.js
  64. 515 0
      terminus-local/yarn.lock
  65. 1 0
      terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug
  66. 0 20
      terminus-terminal/README.md
  67. 2 4
      terminus-terminal/src/api/baseTerminalTab.component.ts
  68. 0 50
      terminus-terminal/src/api/interfaces.ts
  69. 0 8
      terminus-terminal/src/api/shellProvider.ts
  70. 6 101
      terminus-terminal/src/cli.ts
  71. 0 0
      terminus-terminal/src/components/baseTerminalTab.component.scss
  72. 2 9
      terminus-terminal/src/components/terminalSettingsTab.component.ts
  73. 0 32
      terminus-terminal/src/config.ts
  74. 8 8
      terminus-terminal/src/features/debug.ts
  75. 3 3
      terminus-terminal/src/features/pathDrop.ts
  76. 2 2
      terminus-terminal/src/features/zmodem.ts
  77. 1 17
      terminus-terminal/src/hotkeys.ts
  78. 6 84
      terminus-terminal/src/index.ts
  79. 1 1
      terminus-terminal/src/services/terminalFrontend.service.ts
  80. 60 0
      terminus-terminal/src/session.ts
  81. 0 13
      terminus-terminal/src/settings.ts
  82. 1 130
      terminus-terminal/src/tabContextMenu.ts
  83. 1 0
      webpack.config.js

+ 6 - 0
.github/dependabot.yml

@@ -30,6 +30,12 @@ updates:
     interval: daily
     time: "04:00"
   open-pull-requests-limit: 20
+- package-ecosystem: npm
+  directory: "/terminus-local"
+  schedule:
+    interval: daily
+    time: "04:00"
+  open-pull-requests-limit: 20
 - package-ecosystem: npm
   directory: "/terminus-community-color-schemes"
   schedule:

+ 3 - 2
HACKING.md

@@ -42,6 +42,7 @@ terminus
 ├─ scripts                              # Maintenance scripts
 ├─ terminus-community-color-schemes     # Plugin that provides color schemes
 ├─ terminus-core                        # Plugin that provides base UI and tab management
+└─ terminus-local                       # Plugin that provides local shells and profiles
 ├─ terminus-plugin-manager              # Plugin that installs other plugins
 ├─ terminus-settings                    # Plugin that provides the settings tab
 └─ terminus-terminal                    # Plugin that provides terminal tabs
@@ -61,7 +62,7 @@ terminus-pluginname
 |  └─ index.ts                          # Module entry point
 ├─ package.json
 ├─ tsconfig.json
-└─ webpack.config.js                         
+└─ webpack.config.js
 ```
 
 # Plugins
@@ -115,6 +116,6 @@ export default class MyModule { }
 ```
 
 
-See `terminus-core/src/api.ts`, `terminus-settings/src/api.ts` and `terminus-terminal/src/api.ts` for the available extension points.
+See `terminus-core/src/api.ts`, `terminus-settings/src/api.ts`, `terminus-local/src/api.ts` and `terminus-terminal/src/api.ts` for the available extension points.
 
 Publish your plugin on NPM with a `terminus-plugin` keyword to make it appear in the Plugin Manager.

+ 1 - 0
app/package.json

@@ -57,6 +57,7 @@
   "peerDependencies": {
     "terminus-community-color-schemes": "*",
     "terminus-core": "*",
+    "terminus-local": "*",
     "terminus-plugin-manager": "*",
     "terminus-serial": "*",
     "terminus-settings": "*",

+ 5 - 0
app/src/plugins.ts

@@ -65,6 +65,7 @@ const builtinModules = [
     'rxjs',
     'rxjs/operators',
     'terminus-core',
+    'terminus-local',
     'terminus-settings',
     'terminus-terminal',
     'zone.js/dist/zone.js',
@@ -128,6 +129,10 @@ export async function findPlugins (): Promise<PluginInfo[]> {
 
         const name = packageName.substring(PREFIX.length)
 
+        if (builtinModules.includes(packageName) && pluginDir !== builtinPluginsPath) {
+            continue
+        }
+
         if (foundPlugins.some(x => x.name === name)) {
             console.info(`Plugin ${packageName} already exists, overriding`)
             foundPlugins = foundPlugins.filter(x => x.name !== name)

+ 2 - 2
package.json

@@ -66,13 +66,13 @@
     "**/graceful-fs": "^4.2.4"
   },
   "scripts": {
-    "build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js && webpack --color --config terminus-serial/webpack.config.js",
+    "build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-local/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js && webpack --color --config terminus-serial/webpack.config.js",
     "build:typings": "node scripts/build-typings.js",
     "watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch",
     "start": "cross-env TERMINUS_DEV=1 electron app --debug --inspect",
     "start:prod": "electron app --debug",
     "prod": "cross-env TERMINUS_DEV=1 electron app",
-    "docs": "typedoc --out docs/api --tsconfig terminus-core/src/tsconfig.typings.json terminus-core/src/index.ts && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src/index.ts && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src/index.ts",
+    "docs": "typedoc --out docs/api --tsconfig terminus-core/src/tsconfig.typings.json terminus-core/src/index.ts && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src/index.ts && typedoc --out docs/api/local --tsconfig terminus-local/tsconfig.typings.json terminus-local/src/index.ts && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src/index.ts",
     "lint": "eslint --ext ts */src */lib",
     "postinstall": "node ./scripts/install-deps.js"
   },

+ 1 - 1
scripts/build-native.js

@@ -4,7 +4,7 @@ const path = require('path')
 const vars = require('./vars')
 
 let lifecycles = []
-for (let dir of ['app', 'terminus-core', 'terminus-ssh', 'terminus-terminal']) {
+for (let dir of ['app', 'terminus-core', 'terminus-local', 'terminus-ssh', 'terminus-terminal']) {
     const build = rebuild({
         buildPath: path.resolve(__dirname, '../' + dir),
         electronVersion: vars.electronVersion,

+ 1 - 0
scripts/vars.js

@@ -17,6 +17,7 @@ if (exports.version.includes('-c')) {
 exports.builtinPlugins = [
   'terminus-core',
   'terminus-settings',
+  'terminus-local',
   'terminus-terminal',
   'terminus-community-color-schemes',
   'terminus-plugin-manager',

+ 1 - 1
terminus-core/README.md

@@ -1,7 +1,7 @@
 Terminus Core Plugin
 --------------------
 
-See also: [Settings plugin API](./settings/), [Terminal plugin API](./terminal/)
+See also: [Settings plugin API](./settings/), [Terminal plugin API](./terminal/), [Local terminal API](./local/)
 
 * tabbed interface services
 * toolbar UI

+ 13 - 8
terminus-core/src/services/config.service.ts

@@ -105,16 +105,10 @@ export class ConfigService {
     private constructor (
         electron: ElectronService,
         private hostApp: HostAppService,
-        @Inject(ConfigProvider) configProviders: ConfigProvider[],
+        @Inject(ConfigProvider) private configProviders: ConfigProvider[],
     ) {
         this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
-        this.defaults = configProviders.map(provider => {
-            let defaults = provider.platformDefaults[hostApp.platform] || {}
-            if (provider.defaults) {
-                defaults = configMerge(defaults, provider.defaults)
-            }
-            return defaults
-        }).reduce(configMerge)
+        this.defaults = this.mergeDefaults()
         this.load()
 
         hostApp.configChangeBroadcast$.subscribe(() => {
@@ -123,6 +117,17 @@ export class ConfigService {
         })
     }
 
+    mergeDefaults (): unknown {
+        const providers = this.configProviders
+        return providers.map(provider => {
+            let defaults = provider.platformDefaults[this.hostApp.platform] || {}
+            if (provider.defaults) {
+                defaults = configMerge(defaults, provider.defaults)
+            }
+            return defaults
+        }).reduce(configMerge)
+    }
+
     getDefaults (): Record<string, any> {
         const cleanup = o => {
             if (o instanceof Array) {

+ 1 - 0
terminus-local/.gitignore

@@ -0,0 +1 @@
+dist

+ 23 - 0
terminus-local/README.md

@@ -0,0 +1,23 @@
+Terminus Local Plugin
+---------------------
+
+* local shells
+
+Using the API:
+
+```ts
+import { ShellProvider } from 'terminus-local'
+```
+
+Exporting your subclasses:
+
+```ts
+@NgModule({
+  ...
+  providers: [
+    ...
+    { provide: ShellProvider, useClass: MyShellPlugin, multi: true },
+    ...
+  ]
+})
+```

+ 48 - 0
terminus-local/package.json

@@ -0,0 +1,48 @@
+{
+  "name": "terminus-local",
+  "version": "1.0.135-nightly.0",
+  "description": "Terminus' local shell plugin",
+  "keywords": [
+    "terminus-builtin-plugin"
+  ],
+  "main": "dist/index.js",
+  "typings": "typings/index.d.ts",
+  "scripts": {
+    "build": "webpack --progress --color --display-modules",
+    "watch": "webpack --progress --color --watch"
+  },
+  "files": [
+    "typings"
+  ],
+  "author": "Eugene Pankov",
+  "license": "MIT",
+  "dependencies": {
+    "hterm-umdjs": "1.4.1",
+    "opentype.js": "^1.3.3"
+  },
+  "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"
+  },
+  "peerDependencies": {
+    "@angular/animations": "^9.1.9",
+    "@angular/common": "^9.1.11",
+    "@angular/core": "^9.1.9",
+    "@angular/forms": "^9.1.11",
+    "@angular/platform-browser": "^9.1.11",
+    "@ng-bootstrap/ng-bootstrap": "^6.1.0",
+    "rxjs": "^6.5.5",
+    "terminus-core": "*",
+    "terminus-settings": "*",
+    "terminus-terminal": "*"
+  }
+}

+ 57 - 0
terminus-local/src/api.ts

@@ -0,0 +1,57 @@
+export interface Shell {
+    id: string
+    name?: string
+    command: string
+    args?: string[]
+    env: Record<string, string>
+
+    /**
+     * Base path to which shell's internal FS is relative
+     * Currently used for WSL only
+     */
+    fsBase?: string
+
+    /**
+     * SVG icon
+     */
+    icon?: string
+
+    hidden?: boolean
+}
+
+/**
+ * Extend to add support for more shells
+ */
+export abstract class ShellProvider {
+    abstract provide (): Promise<Shell[]>
+}
+
+
+export interface SessionOptions {
+    restoreFromPTYID?: string
+    name?: string
+    command: string
+    args?: string[]
+    cwd?: string
+    env?: Record<string, string>
+    width?: number
+    height?: number
+    pauseAfterExit?: boolean
+    runAsAdministrator?: boolean
+}
+
+export interface Profile {
+    name: string
+    color?: string
+    sessionOptions: SessionOptions
+    shell?: string
+    isBuiltin?: boolean
+    icon?: string
+    disableDynamicTitle?: boolean
+}
+
+export interface ChildProcess {
+    pid: number
+    ppid: number
+    command: string
+}

+ 0 - 0
terminus-terminal/src/buttonProvider.ts → terminus-local/src/buttonProvider.ts


+ 119 - 0
terminus-local/src/cli.ts

@@ -0,0 +1,119 @@
+import * as path from 'path'
+import * as fs from 'mz/fs'
+import { Injectable } from '@angular/core'
+import { CLIHandler, CLIEvent, HostAppService, AppService, ConfigService } from 'terminus-core'
+import { TerminalService } from './services/terminal.service'
+
+@Injectable()
+export class TerminalCLIHandler extends CLIHandler {
+    firstMatchOnly = true
+    priority = 0
+
+    constructor (
+        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 === '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()
+    }
+}
+
+
+@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
+    }
+}

+ 0 - 0
terminus-terminal/src/components/editProfileModal.component.pug → terminus-local/src/components/editProfileModal.component.pug


+ 1 - 1
terminus-terminal/src/components/editProfileModal.component.ts → terminus-local/src/components/editProfileModal.component.ts

@@ -2,7 +2,7 @@
 import { Component } from '@angular/core'
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 import { UACService } from '../services/uac.service'
-import { Profile } from '../api/interfaces'
+import { Profile } from '../api'
 
 /** @hidden */
 @Component({

+ 0 - 0
terminus-terminal/src/components/environmentEditor.component.pug → terminus-local/src/components/environmentEditor.component.pug


+ 0 - 0
terminus-terminal/src/components/environmentEditor.component.scss → terminus-local/src/components/environmentEditor.component.scss


+ 0 - 0
terminus-terminal/src/components/environmentEditor.component.ts → terminus-local/src/components/environmentEditor.component.ts


+ 0 - 0
terminus-terminal/src/components/shellSettingsTab.component.pug → terminus-local/src/components/shellSettingsTab.component.pug


+ 1 - 1
terminus-terminal/src/components/shellSettingsTab.component.ts → terminus-local/src/components/shellSettingsTab.component.ts

@@ -3,7 +3,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { Subscription } from 'rxjs'
 import { ConfigService, ElectronService, HostAppService, Platform, WIN_BUILD_CONPTY_SUPPORTED, WIN_BUILD_CONPTY_STABLE, isWindowsBuild } from 'terminus-core'
 import { EditProfileModalComponent } from './editProfileModal.component'
-import { Shell, Profile } from '../api/interfaces'
+import { Shell, Profile } from '../api'
 import { TerminalService } from '../services/terminal.service'
 
 /** @hidden */

+ 8 - 10
terminus-terminal/src/components/terminalTab.component.ts → terminus-local/src/components/terminalTab.component.ts

@@ -1,8 +1,8 @@
 import { Component, Input, Injector } from '@angular/core'
 import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'terminus-core'
-import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
-import { SessionOptions } from '../api/interfaces'
-import { Session } from '../services/sessions.service'
+import { BaseTerminalTabComponent } from 'terminus-terminal'
+import { SessionOptions } from '../api'
+import { Session } from '../session'
 
 /** @hidden */
 @Component({
@@ -52,13 +52,11 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
     }
 
     initializeSession (columns: number, rows: number): void {
-        this.sessions.addSession(
-            this.session!,
-            Object.assign({}, this.sessionOptions, {
-                width: columns,
-                height: rows,
-            })
-        )
+        this.session!.start({
+            ...this.sessionOptions,
+            width: columns,
+            height: rows,
+        })
 
         this.attachSessionHandlers(true)
         this.recoveryStateChangedHint.next()

+ 62 - 0
terminus-local/src/config.ts

@@ -0,0 +1,62 @@
+import { ConfigProvider, Platform } from 'terminus-core'
+
+/** @hidden */
+export class TerminalConfigProvider extends ConfigProvider {
+    defaults = {
+        hotkeys: {
+            'copy-current-path': [],
+            shell: {
+                __nonStructural: true,
+            },
+            profile: {
+                __nonStructural: true,
+            },
+        },
+        terminal: {
+            autoOpen: false,
+            customShell: '',
+            workingDirectory: '',
+            alwaysUseWorkingDirectory: false,
+            useConPTY: true,
+            showDefaultProfiles: true,
+            environment: {},
+            profiles: [],
+        },
+    }
+
+    platformDefaults = {
+        [Platform.macOS]: {
+            terminal: {
+                shell: 'default',
+                profile: 'user-default',
+            },
+            hotkeys: {
+                'new-tab': [
+                    '⌘-T',
+                ],
+            },
+        },
+        [Platform.Windows]: {
+            terminal: {
+                shell: 'clink',
+                profile: 'cmd-clink',
+            },
+            hotkeys: {
+                'new-tab': [
+                    'Ctrl-Shift-T',
+                ],
+            },
+        },
+        [Platform.Linux]: {
+            terminal: {
+                shell: 'default',
+                profile: 'user-default',
+            },
+            hotkeys: {
+                'new-tab': [
+                    'Ctrl-Shift-T',
+                ],
+            },
+        },
+    }
+}

+ 29 - 0
terminus-local/src/hotkeys.ts

@@ -0,0 +1,29 @@
+import { Injectable } from '@angular/core'
+import { HotkeyDescription, HotkeyProvider } from 'terminus-core'
+import { TerminalService } from './services/terminal.service'
+
+/** @hidden */
+@Injectable()
+export class TerminalHotkeyProvider extends HotkeyProvider {
+    hotkeys: HotkeyDescription[] = [
+        {
+            id: 'new-tab',
+            name: 'New tab',
+        },
+    ]
+
+    constructor (
+        private terminal: TerminalService,
+    ) { super() }
+
+    async provide (): Promise<HotkeyDescription[]> {
+        const profiles = await this.terminal.getProfiles()
+        return [
+            ...this.hotkeys,
+            ...profiles.map(profile => ({
+                id: `profile.${this.terminal.getProfileID(profile)}`,
+                name: `New tab: ${profile.name}`,
+            })),
+        ]
+    }
+}

+ 0 - 0
terminus-terminal/src/icons/alpine.svg → terminus-local/src/icons/alpine.svg


+ 0 - 0
terminus-terminal/src/icons/clink.svg → terminus-local/src/icons/clink.svg


+ 0 - 0
terminus-terminal/src/icons/cmd.svg → terminus-local/src/icons/cmd.svg


+ 0 - 0
terminus-terminal/src/icons/cmder-powershell.svg → terminus-local/src/icons/cmder-powershell.svg


+ 0 - 0
terminus-terminal/src/icons/cmder.svg → terminus-local/src/icons/cmder.svg


+ 0 - 0
terminus-terminal/src/icons/cygwin.svg → terminus-local/src/icons/cygwin.svg


+ 0 - 0
terminus-terminal/src/icons/debian.svg → terminus-local/src/icons/debian.svg


+ 0 - 0
terminus-terminal/src/icons/git-bash.svg → terminus-local/src/icons/git-bash.svg


+ 0 - 0
terminus-terminal/src/icons/linux.svg → terminus-local/src/icons/linux.svg


+ 0 - 0
terminus-terminal/src/icons/plus.svg → terminus-local/src/icons/plus.svg


+ 0 - 0
terminus-terminal/src/icons/powershell-core.svg → terminus-local/src/icons/powershell-core.svg


+ 0 - 0
terminus-terminal/src/icons/powershell.svg → terminus-local/src/icons/powershell.svg


+ 0 - 0
terminus-terminal/src/icons/profiles.svg → terminus-local/src/icons/profiles.svg


+ 0 - 0
terminus-terminal/src/icons/suse.svg → terminus-local/src/icons/suse.svg


+ 0 - 0
terminus-terminal/src/icons/ubuntu.svg → terminus-local/src/icons/ubuntu.svg


+ 129 - 0
terminus-local/src/index.ts

@@ -0,0 +1,129 @@
+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, TabContextMenuItemProvider, CLIHandler } from 'terminus-core'
+import TerminusTerminalModule from 'terminus-terminal'
+import { SettingsTabProvider } from 'terminus-settings'
+
+import { TerminalTabComponent } from './components/terminalTab.component'
+import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
+import { EditProfileModalComponent } from './components/editProfileModal.component'
+import { EnvironmentEditorComponent } from './components/environmentEditor.component'
+
+import { TerminalService } from './services/terminal.service'
+import { DockMenuService } from './services/dockMenu.service'
+
+import { ButtonProvider } from './buttonProvider'
+import { RecoveryProvider } from './recoveryProvider'
+import { ShellProvider } from './api'
+import { ShellSettingsTabProvider } from './settings'
+import { TerminalConfigProvider } from './config'
+import { TerminalHotkeyProvider } from './hotkeys'
+import { NewTabContextMenu, SaveAsProfileContextMenu } from './tabContextMenu'
+
+import { CmderShellProvider } from './shells/cmder'
+import { CustomShellProvider } from './shells/custom'
+import { Cygwin32ShellProvider } from './shells/cygwin32'
+import { Cygwin64ShellProvider } from './shells/cygwin64'
+import { GitBashShellProvider } from './shells/gitBash'
+import { LinuxDefaultShellProvider } from './shells/linuxDefault'
+import { MacOSDefaultShellProvider } from './shells/macDefault'
+import { POSIXShellsProvider } from './shells/posix'
+import { PowerShellCoreShellProvider } from './shells/powershellCore'
+import { WindowsDefaultShellProvider } from './shells/winDefault'
+import { WindowsStockShellsProvider } from './shells/windowsStock'
+import { WSLShellProvider } from './shells/wsl'
+
+import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from './cli'
+
+/** @hidden */
+@NgModule({
+    imports: [
+        BrowserModule,
+        FormsModule,
+        NgbModule,
+        ToastrModule,
+        TerminusCorePlugin,
+        TerminusTerminalModule,
+    ],
+    providers: [
+        { provide: SettingsTabProvider, useClass: ShellSettingsTabProvider, multi: true },
+
+        { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
+        { provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
+        { provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
+        { provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
+
+        { provide: ShellProvider, useClass: WindowsDefaultShellProvider, multi: true },
+        { provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
+        { provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
+        { provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
+        { provide: ShellProvider, useClass: PowerShellCoreShellProvider, multi: true },
+        { provide: ShellProvider, useClass: CmderShellProvider, multi: true },
+        { provide: ShellProvider, useClass: CustomShellProvider, multi: true },
+        { provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true },
+        { provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true },
+        { provide: ShellProvider, useClass: GitBashShellProvider, multi: true },
+        { provide: ShellProvider, useClass: POSIXShellsProvider, multi: true },
+        { provide: ShellProvider, useClass: WSLShellProvider, multi: true },
+
+        { provide: TabContextMenuItemProvider, useClass: NewTabContextMenu, multi: true },
+        { provide: TabContextMenuItemProvider, useClass: SaveAsProfileContextMenu, multi: true },
+
+        { provide: CLIHandler, useClass: TerminalCLIHandler, multi: true },
+        { provide: CLIHandler, useClass: OpenPathCLIHandler, multi: true },
+        { provide: CLIHandler, useClass: AutoOpenTabCLIHandler, multi: true },
+
+        // For WindowsDefaultShellProvider
+        PowerShellCoreShellProvider,
+        WSLShellProvider,
+        WindowsStockShellsProvider,
+    ],
+    entryComponents: [
+        TerminalTabComponent,
+        ShellSettingsTabComponent,
+        EditProfileModalComponent,
+    ] as any[],
+    declarations: [
+        TerminalTabComponent,
+        ShellSettingsTabComponent,
+        EditProfileModalComponent,
+        EnvironmentEditorComponent,
+    ] as any[],
+    exports: [
+        TerminalTabComponent,
+        EnvironmentEditorComponent,
+    ],
+})
+export default class LocalTerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
+    private constructor (
+        hotkeys: HotkeysService,
+        terminal: TerminalService,
+        hostApp: HostAppService,
+        dockMenu: DockMenuService,
+    ) {
+        hotkeys.matchedHotkey.subscribe(async (hotkey) => {
+            if (hotkey === 'new-tab') {
+                terminal.openTab()
+            }
+            if (hotkey === 'new-window') {
+                hostApp.newWindow()
+            }
+            if (hotkey.startsWith('profile.')) {
+                const profile = await terminal.getProfileByID(hotkey.split('.')[1])
+                if (profile) {
+                    terminal.openTabWithOptions(profile.sessionOptions)
+                }
+            }
+        })
+
+        dockMenu.update()
+    }
+}
+
+export { TerminalTabComponent }
+export { TerminalService, ShellProvider }
+export * from './api'

+ 0 - 0
terminus-terminal/src/recoveryProvider.ts → terminus-local/src/recoveryProvider.ts


+ 0 - 0
terminus-terminal/src/services/dockMenu.service.ts → terminus-local/src/services/dockMenu.service.ts


+ 2 - 3
terminus-terminal/src/services/terminal.service.ts → terminus-local/src/services/terminal.service.ts

@@ -3,10 +3,9 @@ import slugify from 'slugify'
 import { Observable, AsyncSubject } from 'rxjs'
 import { Injectable, Inject } from '@angular/core'
 import { AppService, Logger, LogService, ConfigService, SplitTabComponent } from 'terminus-core'
-import { ShellProvider } from '../api/shellProvider'
-import { Shell, SessionOptions, Profile } from '../api/interfaces'
 import { TerminalTabComponent } from '../components/terminalTab.component'
-import { UACService } from './uac.service'
+import { ShellProvider, Shell, SessionOptions, Profile } from '../api'
+import { UACService } from '../services/uac.service'
 
 @Injectable({ providedIn: 'root' })
 export class TerminalService {

+ 1 - 1
terminus-terminal/src/services/uac.service.ts → terminus-local/src/services/uac.service.ts

@@ -1,7 +1,7 @@
 import * as path from 'path'
 import { Injectable } from '@angular/core'
 import { ElectronService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'terminus-core'
-import { SessionOptions } from '../api/interfaces'
+import { SessionOptions } from '../api'
 
 /** @hidden */
 @Injectable({ providedIn: 'root' })

+ 3 - 89
terminus-terminal/src/services/sessions.service.ts → terminus-local/src/session.ts

@@ -1,13 +1,11 @@
 import * as psNode from 'ps-node'
 import * as fs from 'mz/fs'
 import * as os from 'os'
+import { BaseSession } from 'terminus-terminal'
 import { ipcRenderer } from 'electron'
 import { getWorkingDirectoryFromPID } from 'native-process-working-directory'
-import { Observable, Subject } from 'rxjs'
-import { first } from 'rxjs/operators'
-import { Injectable } from '@angular/core'
-import { Logger, LogService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'terminus-core'
-import { SessionOptions, ChildProcess } from '../api/interfaces'
+import { ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'terminus-core'
+import { SessionOptions, ChildProcess } from './api'
 
 /* eslint-disable block-scoped-var */
 
@@ -85,65 +83,6 @@ export class PTYProxy {
     }
 }
 
-/**
- * A session object for a [[BaseTerminalTabComponent]]
- * Extend this to implement custom I/O and process management for your terminal tab
- */
-export abstract class BaseSession {
-    open: boolean
-    name: string
-    truePID: number
-    protected output = new Subject<string>()
-    protected binaryOutput = new Subject<Buffer>()
-    protected closed = new Subject<void>()
-    protected destroyed = new Subject<void>()
-    private initialDataBuffer = Buffer.from('')
-    private initialDataBufferReleased = false
-
-    get output$ (): Observable<string> { return this.output }
-    get binaryOutput$ (): Observable<Buffer> { return this.binaryOutput }
-    get closed$ (): Observable<void> { return this.closed }
-    get destroyed$ (): Observable<void> { return this.destroyed }
-
-    emitOutput (data: Buffer): void {
-        if (!this.initialDataBufferReleased) {
-            this.initialDataBuffer = Buffer.concat([this.initialDataBuffer, data])
-        } else {
-            this.output.next(data.toString())
-            this.binaryOutput.next(data)
-        }
-    }
-
-    releaseInitialDataBuffer (): void {
-        this.initialDataBufferReleased = true
-        this.output.next(this.initialDataBuffer.toString())
-        this.binaryOutput.next(this.initialDataBuffer)
-        this.initialDataBuffer = Buffer.from('')
-    }
-
-    async destroy (): Promise<void> {
-        if (this.open) {
-            this.open = false
-            this.closed.next()
-            this.destroyed.next()
-            this.closed.complete()
-            this.destroyed.complete()
-            this.output.complete()
-            this.binaryOutput.complete()
-            await this.gracefullyKillProcess()
-        }
-    }
-
-    abstract start (options: SessionOptions): void
-    abstract resize (columns: number, rows: number): void
-    abstract write (data: Buffer): void
-    abstract kill (signal?: string): void
-    abstract getChildProcesses (): Promise<ChildProcess[]>
-    abstract gracefullyKillProcess (): Promise<void>
-    abstract supportsWorkingDirectory (): boolean
-    abstract getWorkingDirectory (): Promise<string|null>
-}
-
 /** @hidden */
 export class Session extends BaseSession {
     private pty: PTYProxy|null = null
@@ -400,28 +339,3 @@ export class Session extends BaseSession {
         return data
     }
 }
-
-/** @hidden */
-@Injectable({ providedIn: 'root' })
-export class SessionsService {
-    sessions = new Map<string, BaseSession>()
-    logger: Logger
-    private lastID = 0
-
-    private constructor (
-        log: LogService,
-    ) {
-        this.logger = log.create('sessions')
-    }
-
-    addSession (session: BaseSession, options: SessionOptions): BaseSession {
-        this.lastID++
-        options.name = `session-${this.lastID}`
-        session.start(options)
-        session.destroyed$.pipe(first()).subscribe(() => {
-            this.sessions.delete(session.name)
-        })
-        this.sessions.set(session.name, session)
-        return session
-    }
-}

+ 16 - 0
terminus-local/src/settings.ts

@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core'
+import { SettingsTabProvider } from 'terminus-settings'
+
+import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
+
+/** @hidden */
+@Injectable()
+export class ShellSettingsTabProvider extends SettingsTabProvider {
+    id = 'terminal-shell'
+    icon = 'list-ul'
+    title = 'Shell'
+
+    getComponentType (): any {
+        return ShellSettingsTabComponent
+    }
+}

+ 1 - 2
terminus-terminal/src/shells/cmder.ts → terminus-local/src/shells/cmder.ts

@@ -2,8 +2,7 @@ import * as path from 'path'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /** @hidden */
 @Injectable()

+ 1 - 2
terminus-terminal/src/shells/custom.ts → terminus-local/src/shells/custom.ts

@@ -1,8 +1,7 @@
 import { Injectable } from '@angular/core'
 import { ConfigService } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /** @hidden */
 @Injectable()

+ 1 - 2
terminus-terminal/src/shells/cygwin32.ts → terminus-local/src/shells/cygwin32.ts

@@ -2,8 +2,7 @@ import * as path from 'path'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /* eslint-disable block-scoped-var */
 

+ 1 - 2
terminus-terminal/src/shells/cygwin64.ts → terminus-local/src/shells/cygwin64.ts

@@ -2,8 +2,7 @@ import * as path from 'path'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /* eslint-disable block-scoped-var */
 

+ 1 - 2
terminus-terminal/src/shells/gitBash.ts → terminus-local/src/shells/gitBash.ts

@@ -2,8 +2,7 @@ import * as path from 'path'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /* eslint-disable block-scoped-var */
 

+ 1 - 2
terminus-terminal/src/shells/linuxDefault.ts → terminus-local/src/shells/linuxDefault.ts

@@ -2,8 +2,7 @@ import * as fs from 'mz/fs'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform, LogService, Logger } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /** @hidden */
 @Injectable()

+ 1 - 2
terminus-terminal/src/shells/macDefault.ts → terminus-local/src/shells/macDefault.ts

@@ -2,8 +2,7 @@ import { exec } from 'mz/child_process'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /** @hidden */
 @Injectable()

+ 1 - 2
terminus-terminal/src/shells/posix.ts → terminus-local/src/shells/posix.ts

@@ -3,8 +3,7 @@ import slugify from 'slugify'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /** @hidden */
 @Injectable()

+ 2 - 2
terminus-terminal/src/shells/powershellCore.ts → terminus-local/src/shells/powershellCore.ts

@@ -1,7 +1,7 @@
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform } from 'terminus-core'
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+
+import { ShellProvider, Shell } from '../api'
 
 /* eslint-disable block-scoped-var */
 

+ 1 - 2
terminus-terminal/src/shells/winDefault.ts → terminus-local/src/shells/winDefault.ts

@@ -1,8 +1,7 @@
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 import { WSLShellProvider } from './wsl'
 import { PowerShellCoreShellProvider } from './powershellCore'

+ 1 - 2
terminus-terminal/src/shells/windowsStock.ts → terminus-local/src/shells/windowsStock.ts

@@ -2,8 +2,7 @@ import * as path from 'path'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform, ElectronService } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /** @hidden */
 @Injectable()

+ 1 - 2
terminus-terminal/src/shells/wsl.ts → terminus-local/src/shells/wsl.ts

@@ -4,8 +4,7 @@ import slugify from 'slugify'
 import { Injectable } from '@angular/core'
 import { HostAppService, Platform, isWindowsBuild, WIN_BUILD_WSL_EXE_DISTRO_FLAG } from 'terminus-core'
 
-import { ShellProvider } from '../api/shellProvider'
-import { Shell } from '../api/interfaces'
+import { ShellProvider, Shell } from '../api'
 
 /* eslint-disable block-scoped-var */
 

+ 132 - 0
terminus-local/src/tabContextMenu.ts

@@ -0,0 +1,132 @@
+import { MenuItemConstructorOptions } from 'electron'
+import { Injectable, NgZone } from '@angular/core'
+import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService } from 'terminus-core'
+import { TerminalTabComponent } from './components/terminalTab.component'
+import { UACService } from './services/uac.service'
+import { TerminalService } from './services/terminal.service'
+
+/** @hidden */
+@Injectable()
+export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
+    constructor (
+        private config: ConfigService,
+        private zone: NgZone,
+        private notifications: NotificationsService,
+    ) {
+        super()
+    }
+
+    async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
+        if (!(tab instanceof TerminalTabComponent)) {
+            return []
+        }
+        const items: MenuItemConstructorOptions[] = [
+            {
+                label: 'Save as profile',
+                click: () => this.zone.run(async () => {
+                    const profile = {
+                        sessionOptions: {
+                            ...tab.sessionOptions,
+                            cwd: await tab.session?.getWorkingDirectory() ?? tab.sessionOptions.cwd,
+                        },
+                        name: tab.sessionOptions.command,
+                    }
+                    this.config.store.terminal.profiles = [
+                        ...this.config.store.terminal.profiles,
+                        profile,
+                    ]
+                    this.config.save()
+                    this.notifications.info('Saved')
+                }),
+            },
+        ]
+
+        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<MenuItemConstructorOptions[]> {
+        const profiles = await this.terminalService.getProfiles()
+
+        const items: 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,
+                        runAsAdministrator: true,
+                    })
+                }),
+            })
+        }
+
+        if (tab instanceof TerminalTabComponent && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
+            items.push({
+                label: 'Focus all panes',
+                click: () => this.zone.run(() => {
+                    tab.focusAllPanes()
+                }),
+            })
+        }
+
+        if (tab instanceof TerminalTabComponent && tab.session?.supportsWorkingDirectory()) {
+            items.push({
+                label: 'Copy current path',
+                click: () => this.zone.run(() => tab.copyCurrentPath()),
+            })
+        }
+
+        return items
+    }
+}

+ 7 - 0
terminus-local/tsconfig.json

@@ -0,0 +1,7 @@
+{
+  "extends": "../tsconfig.json",
+  "exclude": ["node_modules", "dist", "typings"],
+  "compilerOptions": {
+    "baseUrl": "src"
+  }
+}

+ 16 - 0
terminus-local/tsconfig.typings.json

@@ -0,0 +1,16 @@
+{
+  "extends": "../tsconfig.json",
+  "exclude": ["node_modules", "dist", "typings"],
+  "compilerOptions": {
+    "baseUrl": "src",
+    "emitDeclarationOnly": true,
+    "declaration": true,
+    "declarationDir": "./typings",
+    "paths": {
+      "terminus-*": ["../../terminus-*"],
+      "*": [
+        "../../app/node_modules/*"
+      ]
+    }
+  }
+}

+ 5 - 0
terminus-local/webpack.config.js

@@ -0,0 +1,5 @@
+const config = require('../webpack.plugin.config')
+module.exports = config({
+    name: 'local',
+    dirname: __dirname,
+})

+ 515 - 0
terminus-local/yarn.lock

@@ -0,0 +1,515 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@types/deep-equal@^1.0.0":
+  version "1.0.1"
+  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"
+  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
+any-promise@^1.0.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
+  integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
+
+array-filter@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83"
+  integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=
+
+available-typed-arrays@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
+  integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==
+  dependencies:
+    array-filter "^1.0.0"
+
+call-bind@^1.0.0, call-bind@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+  integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+  dependencies:
+    function-bind "^1.1.1"
+    get-intrinsic "^1.0.2"
+
+connected-domain@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93"
+  integrity sha1-v+dyOMdL5FOnnwy2BY3utPI1jpM=
+
[email protected]:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/dataurl/-/dataurl-0.1.0.tgz#1f4734feddec05ffe445747978d86759c4b33199"
+  integrity sha1-H0c0/t3sBf/kRXR5eNhnWcSzMZk=
+
[email protected]:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9"
+  integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==
+  dependencies:
+    call-bind "^1.0.0"
+    es-get-iterator "^1.1.1"
+    get-intrinsic "^1.0.1"
+    is-arguments "^1.0.4"
+    is-date-object "^1.0.2"
+    is-regex "^1.1.1"
+    isarray "^2.0.5"
+    object-is "^1.1.4"
+    object-keys "^1.1.1"
+    object.assign "^4.1.2"
+    regexp.prototype.flags "^1.3.0"
+    side-channel "^1.0.3"
+    which-boxed-primitive "^1.0.1"
+    which-collection "^1.0.1"
+    which-typed-array "^1.1.2"
+
+define-properties@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+  integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
+  dependencies:
+    object-keys "^1.0.12"
+
+es-abstract@^1.18.0-next.1:
+  version "1.18.0-next.1"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68"
+  integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==
+  dependencies:
+    es-to-primitive "^1.2.1"
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
+    is-callable "^1.2.2"
+    is-negative-zero "^2.0.0"
+    is-regex "^1.1.1"
+    object-inspect "^1.8.0"
+    object-keys "^1.1.1"
+    object.assign "^4.1.1"
+    string.prototype.trimend "^1.0.1"
+    string.prototype.trimstart "^1.0.1"
+
+es-abstract@^1.18.0-next.2:
+  version "1.18.0"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4"
+  integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==
+  dependencies:
+    call-bind "^1.0.2"
+    es-to-primitive "^1.2.1"
+    function-bind "^1.1.1"
+    get-intrinsic "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.2"
+    is-callable "^1.2.3"
+    is-negative-zero "^2.0.1"
+    is-regex "^1.1.2"
+    is-string "^1.0.5"
+    object-inspect "^1.9.0"
+    object-keys "^1.1.1"
+    object.assign "^4.1.2"
+    string.prototype.trimend "^1.0.4"
+    string.prototype.trimstart "^1.0.4"
+    unbox-primitive "^1.0.0"
+
+es-get-iterator@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7"
+  integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==
+  dependencies:
+    call-bind "^1.0.2"
+    get-intrinsic "^1.1.0"
+    has-symbols "^1.0.1"
+    is-arguments "^1.1.0"
+    is-map "^2.0.2"
+    is-set "^2.0.2"
+    is-string "^1.0.5"
+    isarray "^2.0.5"
+
+es-to-primitive@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
+  integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
+  dependencies:
+    is-callable "^1.1.4"
+    is-date-object "^1.0.1"
+    is-symbol "^1.0.2"
+
+foreach@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+  integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
+
+function-bind@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+  integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
+  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
+  dependencies:
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-symbols "^1.0.1"
+
+has-bigints@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
+  integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
+
+has-symbols@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
+  integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+
+has-symbols@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
+  integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
+
+has@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+  dependencies:
+    function-bind "^1.1.1"
+
[email protected]:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/hterm-umdjs/-/hterm-umdjs-1.4.1.tgz#0cd5352eaf927c70b83c36146cf2c2a281dba957"
+  integrity sha512-r5JOmdDK1bZCmp3cKcuGRLVeum33H+pzD119ZxmQou+QUVe6SAVSz03HvKWVhM2Ao1Biv+fkhFDmnsaRPq0tFg==
+
+is-arguments@^1.0.4, is-arguments@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
+  integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==
+  dependencies:
+    call-bind "^1.0.0"
+
+is-bigint@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a"
+  integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==
+
+is-boolean-object@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8"
+  integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==
+  dependencies:
+    call-bind "^1.0.2"
+
+is-callable@^1.1.4, is-callable@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
+  integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==
+
+is-callable@^1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
+  integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
+
+is-date-object@^1.0.1, is-date-object@^1.0.2:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5"
+  integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==
+
+is-map@^2.0.1, is-map@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
+  integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==
+
+is-negative-zero@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461"
+  integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=
+
+is-negative-zero@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
+  integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
+
+is-number-object@^1.0.4:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb"
+  integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==
+
+is-regex@^1.1.1, is-regex@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
+  integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==
+  dependencies:
+    call-bind "^1.0.2"
+    has-symbols "^1.0.2"
+
+is-set@^2.0.1, is-set@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec"
+  integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==
+
+is-string@^1.0.5:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f"
+  integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==
+
+is-symbol@^1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
+  integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
+  dependencies:
+    has-symbols "^1.0.1"
+
+is-symbol@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
+  integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
+  dependencies:
+    has-symbols "^1.0.2"
+
+is-typed-array@^1.1.3:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e"
+  integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==
+  dependencies:
+    available-typed-arrays "^1.0.2"
+    call-bind "^1.0.2"
+    es-abstract "^1.18.0-next.2"
+    foreach "^2.0.5"
+    has-symbols "^1.0.1"
+
+is-weakmap@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
+  integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
+
+is-weakset@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83"
+  integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==
+
+isarray@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
+  integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
+
+mz@^2.6.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
+  integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
+  dependencies:
+    any-promise "^1.0.0"
+    object-assign "^4.0.1"
+    thenify-all "^1.0.0"
+
+object-assign@^4.0.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+  integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
+object-inspect@^1.8.0:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
+  integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
+
+object-inspect@^1.9.0:
+  version "1.10.3"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
+  integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==
+
+object-is@^1.1.4:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
+  integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.3"
+
+object-keys@^1.0.12, object-keys@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+  integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.1, object.assign@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
+  integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
+  dependencies:
+    call-bind "^1.0.0"
+    define-properties "^1.1.3"
+    has-symbols "^1.0.1"
+    object-keys "^1.1.1"
+
+opentype.js@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/opentype.js/-/opentype.js-1.3.3.tgz#65b8645b090a1ad444065b784d442fa19d1061f6"
+  integrity sha512-/qIY/+WnKGlPIIPhbeNjynfD2PO15G9lA/xqlX2bDH+4lc3Xz5GCQ68mqxj3DdUv6AJqCeaPvuAoH8mVL0zcuA==
+  dependencies:
+    string.prototype.codepointat "^0.2.1"
+    tiny-inflate "^1.0.3"
+
+ps-node@^0.1.6:
+  version "0.1.6"
+  resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3"
+  integrity sha1-mvZ6mdex0BMuUaUDCZ04qNKs4sM=
+  dependencies:
+    table-parser "^0.1.3"
+
+regexp.prototype.flags@^1.3.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26"
+  integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.3"
+
+runes@^0.4.2:
+  version "0.4.3"
+  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"
+  integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+  dependencies:
+    call-bind "^1.0.0"
+    get-intrinsic "^1.0.2"
+    object-inspect "^1.9.0"
+
+slugify@^1.4.0:
+  version "1.4.7"
+  resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.4.7.tgz#e42359d505afd84a44513280868e31202a79a628"
+  integrity sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==
+
+string.prototype.codepointat@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
+  integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
+
+string.prototype.trimend@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz#6ddd9a8796bc714b489a3ae22246a208f37bfa46"
+  integrity sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==
+  dependencies:
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+
+string.prototype.trimend@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
+  integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.3"
+
+string.prototype.trimstart@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz#22d45da81015309cd0cdd79787e8919fc5c613e7"
+  integrity sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==
+  dependencies:
+    define-properties "^1.1.3"
+    es-abstract "^1.18.0-next.1"
+
+string.prototype.trimstart@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
+  integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.3"
+
+table-parser@^0.1.3:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0"
+  integrity sha1-BEHPzhallIFoTCfRtaZ/8VpDx7A=
+  dependencies:
+    connected-domain "^1.0.0"
+
+thenify-all@^1.0.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
+  integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
+  dependencies:
+    thenify ">= 3.1.0 < 4"
+
+"thenify@>= 3.1.0 < 4":
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
+  integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
+  dependencies:
+    any-promise "^1.0.0"
+
+tiny-inflate@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
+  integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
+
+tinyqueue@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
+  integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==
+
+unbox-primitive@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
+  integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==
+  dependencies:
+    function-bind "^1.1.1"
+    has-bigints "^1.0.1"
+    has-symbols "^1.0.2"
+    which-boxed-primitive "^1.0.2"
+
+utils-decorators@^1.8.1:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/utils-decorators/-/utils-decorators-1.8.1.tgz#6e7e2cf46c05a9554c05f004e5235696142bd5f7"
+  integrity sha512-UpqzJj40jdTknZpxdeYL7p+8Ynl3bpHP6yoxoY+RmuDCOaelTiOz4GcDpScPvfZhv/ivHTV1bPJZeQd8tlxczA==
+  dependencies:
+    tinyqueue "^2.0.3"
+
+which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
+  integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
+  dependencies:
+    is-bigint "^1.0.1"
+    is-boolean-object "^1.1.0"
+    is-number-object "^1.0.4"
+    is-string "^1.0.5"
+    is-symbol "^1.0.3"
+
+which-collection@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
+  integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
+  dependencies:
+    is-map "^2.0.1"
+    is-set "^2.0.1"
+    is-weakmap "^2.0.1"
+    is-weakset "^2.0.1"
+
+which-typed-array@^1.1.2:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff"
+  integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==
+  dependencies:
+    available-typed-arrays "^1.0.2"
+    call-bind "^1.0.0"
+    es-abstract "^1.18.0-next.1"
+    foreach "^2.0.5"
+    function-bind "^1.1.1"
+    has-symbols "^1.0.1"
+    is-typed-array "^1.1.3"

+ 1 - 0
terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug

@@ -20,6 +20,7 @@
             div
                 strong {{plugin.name}}
                 small.text-muted.ml-1(*ngIf='!plugin.isBuiltin') {{plugin.version}} / {{plugin.author}}
+                small.text-muted.ml-1(*ngIf='plugin.isBuiltin') Built-in
                 small.text-warning.ml-1(*ngIf='!isPluginEnabled(plugin)') Disabled
             a.text-muted.mb-0((click)='showPluginInfo(plugin)')
                 small {{plugin.description}}

+ 0 - 20
terminus-terminal/README.md

@@ -4,23 +4,3 @@ Terminus Terminal Plugin
 * terminal tabs
 * terminal frontends
 * session management
-* shell detection
-
-Using the API:
-
-```ts
-import { ShellProvider } from 'terminus-terminal'
-```
-
-Exporting your subclasses:
-
-```ts
-@NgModule({
-  ...
-  providers: [
-    ...
-    { provide: ShellProvider, useClass: MyShellPlugin, multi: true },
-    ...
-  ]
-})
-```

+ 2 - 4
terminus-terminal/src/api/baseTerminalTab.component.ts

@@ -6,7 +6,7 @@ import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, Ele
 import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
 import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer } from 'terminus-core'
 
-import { BaseSession, SessionsService } from '../services/sessions.service'
+import { BaseSession } from '../session'
 import { TerminalFrontendService } from '../services/terminalFrontend.service'
 
 import { Frontend } from '../frontends/frontend'
@@ -19,7 +19,7 @@ import { TerminalDecorator } from './decorator'
  */
 export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
     static template: string = require<string>('../components/baseTerminalTab.component.pug')
-    static styles: string[] = [require<string>('../components/terminalTab.component.scss')]
+    static styles: string[] = [require<string>('../components/baseTerminalTab.component.scss')]
     static animations: AnimationTriggerMetadata[] = [trigger('slideInOut', [
         transition(':enter', [
             style({
@@ -83,7 +83,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
     protected app: AppService
     protected hostApp: HostAppService
     protected hotkeys: HotkeysService
-    protected sessions: SessionsService
     protected electron: ElectronService
     protected terminalContainersService: TerminalFrontendService
     protected notifications: NotificationsService
@@ -136,7 +135,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
         this.app = injector.get(AppService)
         this.hostApp = injector.get(HostAppService)
         this.hotkeys = injector.get(HotkeysService)
-        this.sessions = injector.get(SessionsService)
         this.electron = injector.get(ElectronService)
         this.terminalContainersService = injector.get(TerminalFrontendService)
         this.notifications = injector.get(NotificationsService)

+ 0 - 50
terminus-terminal/src/api/interfaces.ts

@@ -3,29 +3,6 @@ export interface ResizeEvent {
     rows: number
 }
 
-export interface SessionOptions {
-    restoreFromPTYID?: string
-    name?: string
-    command: string
-    args?: string[]
-    cwd?: string
-    env?: Record<string, string>
-    width?: number
-    height?: number
-    pauseAfterExit?: boolean
-    runAsAdministrator?: boolean
-}
-
-export interface Profile {
-    name: string
-    color?: string
-    sessionOptions: SessionOptions
-    shell?: string
-    isBuiltin?: boolean
-    icon?: string
-    disableDynamicTitle?: boolean
-}
-
 export interface TerminalColorScheme {
     name: string
     foreground: string
@@ -33,30 +10,3 @@ export interface TerminalColorScheme {
     cursor: string
     colors: string[]
 }
-
-export interface Shell {
-    id: string
-    name?: string
-    command: string
-    args?: string[]
-    env: Record<string, string>
-
-    /**
-     * Base path to which shell's internal FS is relative
-     * Currently used for WSL only
-     */
-    fsBase?: string
-
-    /**
-     * SVG icon
-     */
-    icon?: string
-
-    hidden?: boolean
-}
-
-export interface ChildProcess {
-    pid: number
-    ppid: number
-    command: string
-}

+ 0 - 8
terminus-terminal/src/api/shellProvider.ts

@@ -1,8 +0,0 @@
-import { Shell } from './interfaces'
-
-/**
- * Extend to add support for more shells
- */
-export abstract class ShellProvider {
-    abstract provide (): Promise<Shell[]>
-}

+ 6 - 101
terminus-terminal/src/cli.ts

@@ -1,10 +1,7 @@
-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'
+import { CLIHandler, CLIEvent, HostAppService, AppService } from 'terminus-core'
+import { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
 
 @Injectable()
 export class TerminalCLIHandler extends CLIHandler {
@@ -13,9 +10,7 @@ export class TerminalCLIHandler extends CLIHandler {
 
     constructor (
         private app: AppService,
-        private config: ConfigService,
         private hostApp: HostAppService,
-        private terminal: TerminalService,
     ) {
         super()
     }
@@ -23,113 +18,23 @@ export class TerminalCLIHandler extends CLIHandler {
     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') {
+        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()
-            }
+            return true
         }
-    }
 
-    private handleRunCommand (command: string[]) {
-        this.terminal.openTab({
-            name: '',
-            sessionOptions: {
-                command: command[0],
-                args: command.slice(1),
-            },
-        }, null, true)
-        this.hostApp.bringToFront()
+        return false
     }
 
-    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) {
+        if (this.app.activeTab instanceof BaseTerminalTabComponent && 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
-    }
-}

+ 0 - 0
terminus-terminal/src/components/terminalTab.component.scss → terminus-terminal/src/components/baseTerminalTab.component.scss


+ 2 - 9
terminus-terminal/src/components/terminalSettingsTab.component.ts

@@ -1,6 +1,6 @@
+import { execFile } from 'mz/child_process'
 import { Component } from '@angular/core'
 import { ConfigService, ElectronService } from 'terminus-core'
-import { TerminalService } from '../services/terminal.service'
 
 /** @hidden */
 @Component({
@@ -10,17 +10,10 @@ export class TerminalSettingsTabComponent {
     constructor (
         public config: ConfigService,
         private electron: ElectronService,
-        private terminal: TerminalService,
     ) { }
 
     openWSLVolumeMixer (): void {
         this.electron.shell.openPath('sndvol.exe')
-        this.terminal.openTab({
-            name: '',
-            sessionOptions: {
-                command: 'wsl.exe',
-                args: ['tput', 'bel'],
-            },
-        }, null, true)
+        execFile('wsl.exe', ['tput', 'bel'])
     }
 }

+ 0 - 32
terminus-terminal/src/config.ts

@@ -3,18 +3,8 @@ import { ConfigProvider, Platform } from 'terminus-core'
 /** @hidden */
 export class TerminalConfigProvider extends ConfigProvider {
     defaults = {
-        hotkeys: {
-            'copy-current-path': [],
-            shell: {
-                __nonStructural: true,
-            },
-            profile: {
-                __nonStructural: true,
-            },
-        },
         terminal: {
             frontend: 'xterm',
-            autoOpen: false,
             fontSize: 14,
             fallbackFont: null,
             linePadding: 0,
@@ -26,13 +16,10 @@ export class TerminalConfigProvider extends ConfigProvider {
             cursorBlink: true,
             hideTabIndex: false,
             hideCloseButton: false,
-            customShell: '',
             rightClick: 'menu',
             pasteOnMiddleClick: true,
             copyOnSelect: false,
             scrollOnInput: true,
-            workingDirectory: '',
-            alwaysUseWorkingDirectory: false,
             altIsMeta: false,
             wordSeparator: ' ()[]{}\'"',
             colorScheme: {
@@ -62,12 +49,8 @@ export class TerminalConfigProvider extends ConfigProvider {
                 ],
             },
             customColorSchemes: [],
-            environment: {},
-            profiles: [],
-            useConPTY: true,
             recoverTabs: true,
             warnOnMultilinePaste: true,
-            showDefaultProfiles: true,
             searchRegexAlwaysEnabled: false,
             searchOptions: {
                 regex: false,
@@ -83,8 +66,6 @@ export class TerminalConfigProvider extends ConfigProvider {
         [Platform.macOS]: {
             terminal: {
                 font: 'Menlo',
-                shell: 'default',
-                profile: 'user-default',
             },
             hotkeys: {
                 'ctrl-c': ['Ctrl-C'],
@@ -109,9 +90,6 @@ export class TerminalConfigProvider extends ConfigProvider {
                 'reset-zoom': [
                     '⌘-0',
                 ],
-                'new-tab': [
-                    '⌘-T',
-                ],
                 home: ['⌘-Left', 'Home'],
                 end: ['⌘-Right', 'End'],
                 'previous-word': ['⌥-Left'],
@@ -129,8 +107,6 @@ export class TerminalConfigProvider extends ConfigProvider {
         [Platform.Windows]: {
             terminal: {
                 font: 'Consolas',
-                shell: 'clink',
-                profile: 'cmd-clink',
                 rightClick: 'paste',
                 pasteOnMiddleClick: false,
                 copyOnSelect: true,
@@ -156,9 +132,6 @@ export class TerminalConfigProvider extends ConfigProvider {
                 'reset-zoom': [
                     'Ctrl-0',
                 ],
-                'new-tab': [
-                    'Ctrl-Shift-T',
-                ],
                 home: ['Home'],
                 end: ['End'],
                 'previous-word': ['Ctrl-Left'],
@@ -176,8 +149,6 @@ export class TerminalConfigProvider extends ConfigProvider {
         [Platform.Linux]: {
             terminal: {
                 font: 'Liberation Mono',
-                shell: 'default',
-                profile: 'user-default',
             },
             hotkeys: {
                 'ctrl-c': ['Ctrl-C'],
@@ -200,9 +171,6 @@ export class TerminalConfigProvider extends ConfigProvider {
                 'reset-zoom': [
                     'Ctrl-0',
                 ],
-                'new-tab': [
-                    'Ctrl-Shift-T',
-                ],
                 home: ['Home'],
                 end: ['End'],
                 'previous-word': ['Ctrl-Left'],

+ 8 - 8
terminus-terminal/src/features/debug.ts

@@ -1,7 +1,7 @@
 import * as fs from 'fs'
 import { Injectable } from '@angular/core'
 import { TerminalDecorator } from '../api/decorator'
-import { TerminalTabComponent } from '../components/terminalTab.component'
+import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
 import { ElectronService, HostAppService } from 'terminus-core'
 
 /** @hidden */
@@ -14,7 +14,7 @@ export class DebugDecorator extends TerminalDecorator {
         super()
     }
 
-    attach (terminal: TerminalTabComponent): void {
+    attach (terminal: BaseTerminalTabComponent): void {
         let sessionOutputBuffer = ''
         const bufferLength = 8192
 
@@ -87,23 +87,23 @@ export class DebugDecorator extends TerminalDecorator {
         }
     }
 
-    private doSaveState (terminal: TerminalTabComponent) {
+    private doSaveState (terminal: BaseTerminalTabComponent) {
         this.saveFile(terminal.frontend!.saveState(), 'state.txt')
     }
 
-    private async doCopyState (terminal: TerminalTabComponent) {
+    private async doCopyState (terminal: BaseTerminalTabComponent) {
         const data = '```' + JSON.stringify(terminal.frontend!.saveState()) + '```'
         this.electron.clipboard.writeText(data)
     }
 
-    private async doLoadState (terminal: TerminalTabComponent) {
+    private async doLoadState (terminal: BaseTerminalTabComponent) {
         const data = await this.loadFile()
         if (data) {
             terminal.frontend!.restoreState(data)
         }
     }
 
-    private async doPasteState (terminal: TerminalTabComponent) {
+    private async doPasteState (terminal: BaseTerminalTabComponent) {
         let data = this.electron.clipboard.readText()
         if (data) {
             if (data.startsWith('`')) {
@@ -122,14 +122,14 @@ export class DebugDecorator extends TerminalDecorator {
         this.electron.clipboard.writeText(data)
     }
 
-    private async doLoadOutput (terminal: TerminalTabComponent) {
+    private async doLoadOutput (terminal: BaseTerminalTabComponent) {
         const data = await this.loadFile()
         if (data) {
             terminal.frontend?.write(data)
         }
     }
 
-    private async doPasteOutput (terminal: TerminalTabComponent) {
+    private async doPasteOutput (terminal: BaseTerminalTabComponent) {
         let data = this.electron.clipboard.readText()
         if (data) {
             if (data.startsWith('`')) {

+ 3 - 3
terminus-terminal/src/features/pathDrop.ts

@@ -1,11 +1,11 @@
 import { Injectable } from '@angular/core'
 import { TerminalDecorator } from '../api/decorator'
-import { TerminalTabComponent } from '../components/terminalTab.component'
+import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
 
 /** @hidden */
 @Injectable()
 export class PathDropDecorator extends TerminalDecorator {
-    attach (terminal: TerminalTabComponent): void {
+    attach (terminal: BaseTerminalTabComponent): void {
         setTimeout(() => {
             this.subscribeUntilDetached(terminal, terminal.frontend?.dragOver$.subscribe(event => {
                 event.preventDefault()
@@ -19,7 +19,7 @@ export class PathDropDecorator extends TerminalDecorator {
         })
     }
 
-    private injectPath (terminal: TerminalTabComponent, path: string) {
+    private injectPath (terminal: BaseTerminalTabComponent, path: string) {
         if (path.includes(' ')) {
             path = `"${path}"`
         }

+ 2 - 2
terminus-terminal/src/features/zmodem.ts

@@ -6,7 +6,7 @@ import { Observable } from 'rxjs'
 import { filter } from 'rxjs/operators'
 import { Injectable } from '@angular/core'
 import { TerminalDecorator } from '../api/decorator'
-import { TerminalTabComponent } from '../components/terminalTab.component'
+import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
 import { LogService, Logger, ElectronService, HostAppService, HotkeysService } from 'terminus-core'
 
 const SPACER = '            '
@@ -29,7 +29,7 @@ export class ZModemDecorator extends TerminalDecorator {
         this.cancelEvent = hotkeys.hotkey$.pipe(filter(x => x === 'ctrl-c'))
     }
 
-    attach (terminal: TerminalTabComponent): void {
+    attach (terminal: BaseTerminalTabComponent): void {
         const sentry = new ZModem.Sentry({
             to_terminal: data => {
                 if (!terminal.enablePassthrough) {

+ 1 - 17
terminus-terminal/src/hotkeys.ts

@@ -1,6 +1,5 @@
 import { Injectable } from '@angular/core'
 import { HotkeyDescription, HotkeyProvider } from 'terminus-core'
-import { TerminalService } from './services/terminal.service'
 
 /** @hidden */
 @Injectable()
@@ -54,10 +53,6 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
             id: 'reset-zoom',
             name: 'Reset zoom',
         },
-        {
-            id: 'new-tab',
-            name: 'New tab',
-        },
         {
             id: 'ctrl-c',
             name: 'Intelligent Ctrl-C (copy/abort)',
@@ -76,18 +71,7 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
         },
     ]
 
-    constructor (
-        private terminal: TerminalService,
-    ) { super() }
-
     async provide (): Promise<HotkeyDescription[]> {
-        const profiles = await this.terminal.getProfiles()
-        return [
-            ...this.hotkeys,
-            ...profiles.map(profile => ({
-                id: `profile.${this.terminal.getProfileID(profile)}`,
-                name: `New tab: ${profile.name}`,
-            })),
-        ]
+        return this.hotkeys
     }
 }

+ 6 - 84
terminus-terminal/src/index.ts

@@ -4,58 +4,35 @@ 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, TabContextMenuItemProvider, CLIHandler } from 'terminus-core'
+import TerminusCorePlugin, { ConfigProvider, HotkeysService, HotkeyProvider, TabContextMenuItemProvider, CLIHandler } from 'terminus-core'
 import { SettingsTabProvider } from 'terminus-settings'
 
 import { AppearanceSettingsTabComponent } from './components/appearanceSettingsTab.component'
 import { ColorSchemeSettingsTabComponent } from './components/colorSchemeSettingsTab.component'
-import { TerminalTabComponent } from './components/terminalTab.component'
-import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
 import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
 import { ColorPickerComponent } from './components/colorPicker.component'
 import { ColorSchemePreviewComponent } from './components/colorSchemePreview.component'
-import { EditProfileModalComponent } from './components/editProfileModal.component'
-import { EnvironmentEditorComponent } from './components/environmentEditor.component'
 import { SearchPanelComponent } from './components/searchPanel.component'
 
-import { BaseSession } from './services/sessions.service'
 import { TerminalFrontendService } from './services/terminalFrontend.service'
-import { TerminalService } from './services/terminal.service'
-import { DockMenuService } from './services/dockMenu.service'
 
-import { ButtonProvider } from './buttonProvider'
-import { RecoveryProvider } from './recoveryProvider'
 import { TerminalDecorator } from './api/decorator'
 import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
 import { TerminalColorSchemeProvider } from './api/colorSchemeProvider'
-import { ShellProvider } from './api/shellProvider'
-import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ColorSchemeSettingsTabProvider, ShellSettingsTabProvider } from './settings'
+import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ColorSchemeSettingsTabProvider } from './settings'
 import { DebugDecorator } from './features/debug'
 import { PathDropDecorator } from './features/pathDrop'
 import { ZModemDecorator } from './features/zmodem'
 import { TerminalConfigProvider } from './config'
 import { TerminalHotkeyProvider } from './hotkeys'
 import { HyperColorSchemes } from './colorSchemes'
-import { NewTabContextMenu, CopyPasteContextMenu, SaveAsProfileContextMenu, LegacyContextMenu } from './tabContextMenu'
-
-import { CmderShellProvider } from './shells/cmder'
-import { CustomShellProvider } from './shells/custom'
-import { Cygwin32ShellProvider } from './shells/cygwin32'
-import { Cygwin64ShellProvider } from './shells/cygwin64'
-import { GitBashShellProvider } from './shells/gitBash'
-import { LinuxDefaultShellProvider } from './shells/linuxDefault'
-import { MacOSDefaultShellProvider } from './shells/macDefault'
-import { POSIXShellsProvider } from './shells/posix'
-import { PowerShellCoreShellProvider } from './shells/powershellCore'
-import { WindowsDefaultShellProvider } from './shells/winDefault'
-import { WindowsStockShellsProvider } from './shells/windowsStock'
-import { WSLShellProvider } from './shells/wsl'
+import { CopyPasteContextMenu, LegacyContextMenu } from './tabContextMenu'
 
 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'
+import { TerminalCLIHandler } from './cli'
 
 /** @hidden */
 @NgModule({
@@ -69,11 +46,8 @@ import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from '.
     providers: [
         { provide: SettingsTabProvider, useClass: AppearanceSettingsTabProvider, multi: true },
         { provide: SettingsTabProvider, useClass: ColorSchemeSettingsTabProvider, multi: true },
-        { provide: SettingsTabProvider, useClass: ShellSettingsTabProvider, multi: true },
         { provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
 
-        { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
-        { provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
         { provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
         { provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
         { provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
@@ -81,65 +55,32 @@ import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from '.
         { provide: TerminalDecorator, useClass: ZModemDecorator, multi: true },
         { provide: TerminalDecorator, useClass: DebugDecorator, multi: true },
 
-        { provide: ShellProvider, useClass: WindowsDefaultShellProvider, multi: true },
-        { provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
-        { provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
-        { provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
-        { provide: ShellProvider, useClass: PowerShellCoreShellProvider, multi: true },
-        { provide: ShellProvider, useClass: CmderShellProvider, multi: true },
-        { provide: ShellProvider, useClass: CustomShellProvider, multi: true },
-        { provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true },
-        { provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true },
-        { provide: ShellProvider, useClass: GitBashShellProvider, multi: true },
-        { provide: ShellProvider, useClass: POSIXShellsProvider, multi: true },
-        { provide: ShellProvider, useClass: WSLShellProvider, 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 },
 
         { provide: CLIHandler, useClass: TerminalCLIHandler, multi: true },
-        { provide: CLIHandler, useClass: OpenPathCLIHandler, multi: true },
-        { provide: CLIHandler, useClass: AutoOpenTabCLIHandler, multi: true },
-
-        // For WindowsDefaultShellProvider
-        PowerShellCoreShellProvider,
-        WSLShellProvider,
-        WindowsStockShellsProvider,
     ],
     entryComponents: [
-        TerminalTabComponent,
         AppearanceSettingsTabComponent,
         ColorSchemeSettingsTabComponent,
-        ShellSettingsTabComponent,
         TerminalSettingsTabComponent,
-        EditProfileModalComponent,
     ] as any[],
     declarations: [
         ColorPickerComponent,
         ColorSchemePreviewComponent,
-        TerminalTabComponent,
         AppearanceSettingsTabComponent,
         ColorSchemeSettingsTabComponent,
-        ShellSettingsTabComponent,
         TerminalSettingsTabComponent,
-        EditProfileModalComponent,
-        EnvironmentEditorComponent,
         SearchPanelComponent,
     ] as any[],
     exports: [
         ColorPickerComponent,
-        EnvironmentEditorComponent,
         SearchPanelComponent,
     ],
 })
 export default class TerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
     private constructor (
         hotkeys: HotkeysService,
-        terminal: TerminalService,
-        hostApp: HostAppService,
-        dockMenu: DockMenuService,
     ) {
         const events = [
             {
@@ -165,30 +106,11 @@ export default class TerminalModule { // eslint-disable-line @typescript-eslint/
                 hotkeys.emitKeyEvent(nativeEvent)
             }
         })
-
-        hotkeys.matchedHotkey.subscribe(async (hotkey) => {
-            if (hotkey === 'new-tab') {
-                terminal.openTab()
-            }
-            if (hotkey === 'new-window') {
-                hostApp.newWindow()
-            }
-            if (hotkey.startsWith('profile.')) {
-                const profile = await terminal.getProfileByID(hotkey.split('.')[1])
-                if (profile) {
-                    terminal.openTabWithOptions(profile.sessionOptions)
-                }
-            }
-        })
-
-        dockMenu.update()
     }
 }
 
-export { TerminalService, BaseSession, TerminalTabComponent, TerminalFrontendService, TerminalDecorator, TerminalContextMenuItemProvider, TerminalColorSchemeProvider, ShellProvider }
+export { TerminalFrontendService, TerminalDecorator, TerminalContextMenuItemProvider, TerminalColorSchemeProvider }
 export { Frontend, XTermFrontend, XTermWebGLFrontend, HTermFrontend }
 export { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
 export * from './api/interfaces'
-
-// Deprecations
-export { TerminalColorScheme as ITerminalColorScheme, Shell as IShell } from './api/interfaces'
+export * from './session'

+ 1 - 1
terminus-terminal/src/services/terminalFrontend.service.ts

@@ -3,7 +3,7 @@ import { ConfigService, ThemesService, HotkeysService } from 'terminus-core'
 import { Frontend } from '../frontends/frontend'
 import { HTermFrontend } from '../frontends/htermFrontend'
 import { XTermFrontend, XTermWebGLFrontend } from '../frontends/xtermFrontend'
-import { BaseSession } from '../services/sessions.service'
+import { BaseSession } from '../session'
 
 @Injectable({ providedIn: 'root' })
 export class TerminalFrontendService {

+ 60 - 0
terminus-terminal/src/session.ts

@@ -0,0 +1,60 @@
+import { Observable, Subject } from 'rxjs'
+
+
+/**
+ * A session object for a [[BaseTerminalTabComponent]]
+ * Extend this to implement custom I/O and process management for your terminal tab
+ */
+export abstract class BaseSession {
+    open: boolean
+    name: string
+    truePID: number
+    protected output = new Subject<string>()
+    protected binaryOutput = new Subject<Buffer>()
+    protected closed = new Subject<void>()
+    protected destroyed = new Subject<void>()
+    private initialDataBuffer = Buffer.from('')
+    private initialDataBufferReleased = false
+
+    get output$ (): Observable<string> { return this.output }
+    get binaryOutput$ (): Observable<Buffer> { return this.binaryOutput }
+    get closed$ (): Observable<void> { return this.closed }
+    get destroyed$ (): Observable<void> { return this.destroyed }
+
+    emitOutput (data: Buffer): void {
+        if (!this.initialDataBufferReleased) {
+            this.initialDataBuffer = Buffer.concat([this.initialDataBuffer, data])
+        } else {
+            this.output.next(data.toString())
+            this.binaryOutput.next(data)
+        }
+    }
+
+    releaseInitialDataBuffer (): void {
+        this.initialDataBufferReleased = true
+        this.output.next(this.initialDataBuffer.toString())
+        this.binaryOutput.next(this.initialDataBuffer)
+        this.initialDataBuffer = Buffer.from('')
+    }
+
+    async destroy (): Promise<void> {
+        if (this.open) {
+            this.open = false
+            this.closed.next()
+            this.destroyed.next()
+            this.closed.complete()
+            this.destroyed.complete()
+            this.output.complete()
+            this.binaryOutput.complete()
+            await this.gracefullyKillProcess()
+        }
+    }
+
+    abstract start (options: unknown): void
+    abstract resize (columns: number, rows: number): void
+    abstract write (data: Buffer): void
+    abstract kill (signal?: string): void
+    abstract gracefullyKillProcess (): Promise<void>
+    abstract supportsWorkingDirectory (): boolean
+    abstract getWorkingDirectory (): Promise<string|null>
+}

+ 0 - 13
terminus-terminal/src/settings.ts

@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core'
 import { SettingsTabProvider } from 'terminus-settings'
 
 import { AppearanceSettingsTabComponent } from './components/appearanceSettingsTab.component'
-import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
 import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
 import { ColorSchemeSettingsTabComponent } from './components/colorSchemeSettingsTab.component'
 
@@ -30,18 +29,6 @@ export class ColorSchemeSettingsTabProvider extends SettingsTabProvider {
     }
 }
 
-/** @hidden */
-@Injectable()
-export class ShellSettingsTabProvider extends SettingsTabProvider {
-    id = 'terminal-shell'
-    icon = 'list-ul'
-    title = 'Shell'
-
-    getComponentType (): any {
-        return ShellSettingsTabComponent
-    }
-}
-
 /** @hidden */
 @Injectable()
 export class TerminalSettingsTabProvider extends SettingsTabProvider {

+ 1 - 130
terminus-terminal/src/tabContextMenu.ts

@@ -1,138 +1,9 @@
 import { MenuItemConstructorOptions } from 'electron'
 import { Injectable, NgZone, Optional, Inject } from '@angular/core'
-import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent, NotificationsService } from 'terminus-core'
-import { TerminalTabComponent } from './components/terminalTab.component'
-import { UACService } from './services/uac.service'
-import { TerminalService } from './services/terminal.service'
+import { BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, NotificationsService } from 'terminus-core'
 import { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
 import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
 
-/** @hidden */
-@Injectable()
-export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
-    constructor (
-        private config: ConfigService,
-        private zone: NgZone,
-        private notifications: NotificationsService,
-    ) {
-        super()
-    }
-
-    async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
-        if (!(tab instanceof TerminalTabComponent)) {
-            return []
-        }
-        const items: MenuItemConstructorOptions[] = [
-            {
-                label: 'Save as profile',
-                click: () => this.zone.run(async () => {
-                    const profile = {
-                        sessionOptions: {
-                            ...tab.sessionOptions,
-                            cwd: await tab.session?.getWorkingDirectory() ?? tab.sessionOptions.cwd,
-                        },
-                        name: tab.sessionOptions.command,
-                    }
-                    this.config.store.terminal.profiles = [
-                        ...this.config.store.terminal.profiles,
-                        profile,
-                    ]
-                    this.config.save()
-                    this.notifications.info('Saved')
-                }),
-            },
-        ]
-
-        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<MenuItemConstructorOptions[]> {
-        const profiles = await this.terminalService.getProfiles()
-
-        const items: 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,
-                        runAsAdministrator: true,
-                    })
-                }),
-            })
-        }
-
-        if (tab instanceof BaseTerminalTabComponent && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
-            items.push({
-                label: 'Focus all panes',
-                click: () => this.zone.run(() => {
-                    tab.focusAllPanes()
-                }),
-            })
-        }
-
-        if (tab instanceof TerminalTabComponent && tab.session?.supportsWorkingDirectory()) {
-            items.push({
-                label: 'Copy current path',
-                click: () => this.zone.run(() => tab.copyCurrentPath()),
-            })
-        }
-
-        return items
-    }
-}
-
 /** @hidden */
 @Injectable()
 export class CopyPasteContextMenu extends TabContextMenuItemProvider {

+ 1 - 0
webpack.config.js

@@ -4,6 +4,7 @@ module.exports = [
     require('./terminus-core/webpack.config.js'),
     require('./terminus-settings/webpack.config.js'),
     require('./terminus-terminal/webpack.config.js'),
+    require('./terminus-local/webpack.config.js'),
     require('./terminus-community-color-schemes/webpack.config.js'),
     require('./terminus-plugin-manager/webpack.config.js'),
     require('./terminus-ssh/webpack.config.js'),