Browse Source

xterm frontend

Eugene Pankov 7 years ago
parent
commit
d3a5c7be8d

+ 1 - 0
app/webpack.config.js

@@ -9,6 +9,7 @@ module.exports = {
     'preload': path.resolve(__dirname, 'src/entry.preload.ts'),
     'bundle': path.resolve(__dirname, 'src/entry.ts'),
   },
+  mode: process.env.DEV ? 'development' : 'production',
   context: __dirname,
   devtool: 'source-map',
   output: {

+ 1 - 1
package.json

@@ -115,7 +115,7 @@
   },
   "scripts": {
     "build": "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-settings/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",
-    "watch": "webpack --progress --color --watch",
+    "watch": "DEV=1 webpack --progress --color --watch",
     "start": "cross-env DEV=1 electron app --debug",
     "prod": "cross-env DEV=1 electron app",
     "lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",

+ 1 - 0
terminus-community-color-schemes/webpack.config.js

@@ -13,6 +13,7 @@ module.exports = {
     libraryTarget: 'umd',
     devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
   },
+  mode: process.env.DEV ? 'development' : 'production',
   resolve: {
     modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
     extensions: ['.ts', '.js'],

+ 1 - 0
terminus-core/webpack.config.js

@@ -14,6 +14,7 @@ module.exports = {
     libraryTarget: 'umd',
     devtoolModuleFilenameTemplate: 'webpack-terminus-core:///[resource-path]',
   },
+  mode: process.env.DEV ? 'development' : 'production',
   resolve: {
     modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
     extensions: ['.ts', '.js'],

+ 1 - 0
terminus-plugin-manager/webpack.config.js

@@ -13,6 +13,7 @@ module.exports = {
     libraryTarget: 'umd',
     devtoolModuleFilenameTemplate: 'webpack-terminus-plugin-manager:///[resource-path]',
   },
+  mode: process.env.DEV ? 'development' : 'production',
   resolve: {
     modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
     extensions: ['.ts', '.js'],

+ 1 - 0
terminus-settings/webpack.config.js

@@ -13,6 +13,7 @@ module.exports = {
     libraryTarget: 'umd',
     devtoolModuleFilenameTemplate: 'webpack-terminus-settings:///[resource-path]',
   },
+  mode: process.env.DEV ? 'development' : 'production',
   resolve: {
     modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
     extensions: ['.ts', '.js'],

+ 1 - 0
terminus-ssh/webpack.config.js

@@ -12,6 +12,7 @@ module.exports = {
     libraryTarget: 'umd',
     devtoolModuleFilenameTemplate: 'webpack-terminus-ssh:///[resource-path]',
   },
+  mode: process.env.DEV ? 'development' : 'production',
   resolve: {
     modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
     extensions: ['.ts', '.js'],

+ 2 - 1
terminus-terminal/package.json

@@ -24,7 +24,8 @@
     "@types/winreg": "^1.2.30",
     "dataurl": "0.1.0",
     "deep-equal": "1.0.1",
-    "file-loader": "^0.11.2"
+    "file-loader": "^0.11.2",
+    "xterm": "^3.6.0"
   },
   "peerDependencies": {
     "@angular/common": "4.0.1",

+ 12 - 0
terminus-terminal/src/components/terminalSettingsTab.component.pug

@@ -1,6 +1,18 @@
 h3.mb-3 Appearance
 .row
     .col-md-6
+        .form-line
+            .header
+                .title Frontend
+                .description Switches terminal frontend implementation (experimental)
+
+            select.form-control(
+                [(ngModel)]='config.store.terminal.frontend',
+                (ngModelChange)='config.save()',
+            )
+                option(value='hterm') hterm
+                option(value='xterm') xterm
+                        
         .form-line
             .header
                 .title Font

+ 3 - 3
terminus-terminal/src/components/terminalSettingsTab.component.ts

@@ -1,7 +1,7 @@
 import { Observable } from 'rxjs'
 import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
 import { exec } from 'mz/child_process'
-const equal = require('deep-equal')
+import deepEqual = require('deep-equal')
 const fontManager = require('font-manager')
 
 import { Component, Inject } from '@angular/core'
@@ -17,7 +17,7 @@ export class TerminalSettingsTabComponent {
     shells: IShell[] = []
     persistenceProviders: SessionPersistenceProvider[]
     colorSchemes: ITerminalColorScheme[] = []
-    equalComparator = equal
+    equalComparator = deepEqual
     editingColorScheme: ITerminalColorScheme
     schemeChanged = false
 
@@ -88,7 +88,7 @@ export class TerminalSettingsTabComponent {
     }
 
     isCustomScheme (scheme: ITerminalColorScheme) {
-        return this.config.store.terminal.customColorSchemes.some(x => equal(x, scheme))
+        return this.config.store.terminal.customColorSchemes.some(x => deepEqual(x, scheme))
     }
 
     colorsTrackBy (index) {

+ 2 - 0
terminus-terminal/src/components/terminalTab.component.ts

@@ -174,6 +174,7 @@ export class TerminalTabComponent extends BaseTabComponent {
             this.session.releaseInitialDataBuffer()
         })
 
+        this.termContainer.configure(this.config.store)
         this.termContainer.attach(this.content.nativeElement)
         this.attachTermContainerHandlers()
 
@@ -346,6 +347,7 @@ export class TerminalTabComponent extends BaseTabComponent {
     }
 
     ngOnDestroy () {
+        this.termContainer.detach(this.content.nativeElement)
         this.detachTermContainerHandlers()
         this.config.enabledServices(this.decorators).forEach(decorator => {
             decorator.detach(this)

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

@@ -3,6 +3,7 @@ import { ConfigProvider, Platform } from 'terminus-core'
 export class TerminalConfigProvider extends ConfigProvider {
     defaults = {
         terminal: {
+            frontend: 'hterm',
             autoOpen: false,
             fontSize: 14,
             linePadding: 0,

+ 1 - 1
terminus-terminal/src/persistence/tmux.ts

@@ -1,6 +1,6 @@
 import { Injectable } from '@angular/core'
 import { execFileSync } from 'child_process'
-import * as AsyncLock from 'async-lock'
+import AsyncLock = require('async-lock')
 import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
 import { first, publish } from 'rxjs/operators'
 import * as childProcess from 'child_process'

+ 10 - 1
terminus-terminal/src/services/terminalContainers.service.ts

@@ -1,15 +1,24 @@
 import { Injectable } from '@angular/core'
+import { ConfigService } from 'terminus-core'
 import { TermContainer } from '../terminalContainers/termContainer'
 import { HTermContainer } from '../terminalContainers/htermContainer'
+import { XTermContainer } from '../terminalContainers/xtermContainer'
 import { BaseSession } from '../services/sessions.service'
 
 @Injectable()
 export class TerminalContainersService {
     private containers = new WeakMap<BaseSession, TermContainer>()
 
+    constructor (private config: ConfigService) { }
+
     getContainer (session: BaseSession): TermContainer {
         if (!this.containers.has(session)) {
-            this.containers.set(session, new HTermContainer())
+            this.containers.set(
+                session,
+                (this.config.store.terminal.frontend === 'xterm')
+                    ? new XTermContainer()
+                    : new HTermContainer()
+            )
         }
         return this.containers.get(session)
     }

+ 4 - 2
terminus-terminal/src/terminalContainers/htermContainer.ts

@@ -50,6 +50,9 @@ export class HTermContainer extends TermContainer {
     }
 
     configure (config: any): void {
+        if (!this.term) {
+            return
+        }
         this.configuredFontSize = config.terminal.fontSize
         this.configuredLinePadding = config.terminal.linePadding
         this.setFontSize()
@@ -144,6 +147,7 @@ export class HTermContainer extends TermContainer {
 
     private init () {
         this.term = new hterm.hterm.Terminal()
+        this.term.colorPaletteOverrides = []
         this.term.onTerminalReady = () => {
             this.term.installKeyboard()
             this.term.scrollPort_.setCtrlVPaste(true)
@@ -225,7 +229,5 @@ export class HTermContainer extends TermContainer {
             size.height += this.configuredLinePadding
             return size
         }
-
-        this.term.colorPaletteOverrides = []
     }
 }

+ 1 - 0
terminus-terminal/src/terminalContainers/termContainer.ts

@@ -26,6 +26,7 @@ export abstract class TermContainer {
     get drop$ (): Observable<DragEvent> { return this.drop }
 
     abstract attach (host: HTMLElement): void
+    detach (host: HTMLElement): void { } // tslint:disable-line
 
     destroy (): void {
         for (let o of [

+ 121 - 0
terminus-terminal/src/terminalContainers/xtermContainer.ts

@@ -0,0 +1,121 @@
+import { TermContainer } from './termContainer'
+import { Terminal, ITheme } from 'xterm'
+import * as fit from 'xterm/lib/addons/fit/fit'
+import 'xterm/dist/xterm.css'
+import deepEqual = require('deep-equal')
+
+Terminal.applyAddon(fit)
+
+export class XTermContainer extends TermContainer {
+    enableResizing = true
+    xterm: Terminal
+    private configuredFontSize = 0
+    private zoom = 0
+    private resizeHandler: any
+    private configuredTheme: ITheme = {}
+
+    constructor () {
+        super()
+        this.xterm = new Terminal({
+            allowTransparency: true,
+            enableBold: true,
+        })
+        this.xterm.on('data', data => {
+            this.input.next(data)
+        })
+        this.xterm.on('resize', ({ cols, rows }) => {
+            this.resize.next({ rows, columns: cols })
+        })
+        this.xterm.on('title', title => {
+            this.title.next(title)
+        })
+    }
+
+    attach (host: HTMLElement): void {
+        this.xterm.open(host)
+        this.ready.next(null)
+        this.ready.complete()
+
+        this.resizeHandler = () => (this.xterm as any).fit()
+        window.addEventListener('resize', this.resizeHandler)
+
+        this.resizeHandler()
+
+        host.addEventListener('dragOver', (event: any) => this.dragOver.next(event))
+        host.addEventListener('drop', event => this.drop.next(event))
+    }
+
+    detach (host: HTMLElement): void {
+        window.removeEventListener('resize', this.resizeHandler)
+    }
+
+    getSelection (): string {
+        return this.xterm.getSelection()
+    }
+
+    copySelection (): void {
+        (navigator as any).clipboard.writeText(this.getSelection())
+    }
+
+    clearSelection (): void {
+        this.xterm.clearSelection()
+    }
+
+    focus (): void {
+        setTimeout(() => this.xterm.focus())
+    }
+
+    write (data: string): void {
+        this.xterm.write(data)
+    }
+
+    clear (): void {
+        this.xterm.clear()
+    }
+
+    visualBell (): void {
+        (this.xterm as any).bell()
+    }
+
+    configure (config: any): void {
+        this.xterm.setOption('fontFamily', `"${config.terminal.font}", "monospace-fallback", monospace`)
+        this.xterm.setOption('bellStyle', config.terminal.bell)
+        this.xterm.setOption('cursorStyle', {
+            beam: 'bar'
+        }[config.terminal.cuxrsor] || config.terminal.cursor)
+        this.xterm.setOption('cursorBlink', config.terminal.cursorBlink)
+        this.xterm.setOption('macOptionIsMeta', config.terminal.altIsMeta)
+        // this.xterm.setOption('colors', )
+        this.configuredFontSize = config.terminal.fontSize
+        this.setFontSize()
+
+        let theme: ITheme = {
+            foreground: config.terminal.colorScheme.foreground,
+            background: (config.terminal.background === 'colorScheme') ? config.terminal.colorScheme.background : 'transparent',
+            cursor: config.terminal.colorScheme.cursor,
+        }
+
+        const colorNames = [
+            'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
+            'brightBlack', 'brightRed', 'brightGreen', 'brightYellow', 'brightBlue', 'brightMagenta', 'brightCyan', 'brightWhite'
+        ]
+
+        for (let i = 0; i < colorNames.length; i++) {
+            theme[colorNames[i]] = config.terminal.colorScheme.colors[i]
+        }
+
+        if (!deepEqual(this.configuredTheme, theme)) {
+            this.xterm.setOption('theme', theme)
+            this.configuredTheme = theme
+        }
+    }
+
+    setZoom (zoom: number): void {
+        this.zoom = zoom
+        this.setFontSize()
+    }
+
+    private setFontSize () {
+        this.xterm.setOption('fontSize', this.configuredFontSize * Math.pow(1.1, this.zoom))
+    }
+}

+ 2 - 1
terminus-terminal/webpack.config.js

@@ -13,6 +13,7 @@ module.exports = {
     libraryTarget: 'umd',
     devtoolModuleFilenameTemplate: 'webpack-terminus-terminal:///[resource-path]',
   },
+  mode: process.env.DEV ? 'development' : 'production',
   resolve: {
     modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
     extensions: ['.ts', '.js'],
@@ -35,7 +36,7 @@ module.exports = {
       },
       { test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
       { test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
-      { test: /\.css$/, use: ['to-string-loader', 'css-loader'] },
+      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
       { test: /\.svg/, use: ['svg-inline-loader'] },
       {
         test: /\.(ttf|eot|otf|woff|woff2|ogg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,

+ 4 - 0
terminus-terminal/yarn.lock

@@ -137,3 +137,7 @@ thenify-all@^1.0.0:
 winreg@^1.2.3:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b"
+
+xterm@^3.6.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.6.0.tgz#9b95cd23a338e5842343aec1a104f094c5153e7c"

+ 2 - 0
tsconfig.json

@@ -13,6 +13,8 @@
     "noUnusedLocals": true,
     "declaration": true,
     "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
     "lib": [
       "dom",
       "es5",