mirror of
https://github.com/Eugeny/tabby
synced 2024-11-15 01:17:14 +00:00
xterm frontend
This commit is contained in:
parent
8aff33d59c
commit
d3a5c7be8d
20 changed files with 172 additions and 10 deletions
|
@ -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: {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,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'
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 = []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
terminus-terminal/src/terminalContainers/xtermContainer.ts
Normal file
121
terminus-terminal/src/terminalContainers/xtermContainer.ts
Normal file
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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])?$/,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"noUnusedLocals": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es5",
|
||||
|
|
Loading…
Reference in a new issue