mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-21 20:09:53 +00:00
Merge branch 'xterm'
This commit is contained in:
commit
5f337e6dbe
@ -9,6 +9,7 @@ module.exports = {
|
|||||||
'preload': path.resolve(__dirname, 'src/entry.preload.ts'),
|
'preload': path.resolve(__dirname, 'src/entry.preload.ts'),
|
||||||
'bundle': path.resolve(__dirname, 'src/entry.ts'),
|
'bundle': path.resolve(__dirname, 'src/entry.ts'),
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
context: __dirname,
|
context: __dirname,
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
output: {
|
output: {
|
||||||
|
@ -115,7 +115,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"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",
|
"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",
|
"start": "cross-env DEV=1 electron app --debug",
|
||||||
"prod": "cross-env DEV=1 electron app",
|
"prod": "cross-env DEV=1 electron app",
|
||||||
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
||||||
|
@ -13,6 +13,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@ -14,6 +14,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-core:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-core:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@ -13,6 +13,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-plugin-manager:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-plugin-manager:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@ -13,6 +13,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-settings:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-settings:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@ -12,6 +12,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-ssh:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-ssh:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
"@types/winreg": "^1.2.30",
|
"@types/winreg": "^1.2.30",
|
||||||
"dataurl": "0.1.0",
|
"dataurl": "0.1.0",
|
||||||
"deep-equal": "1.0.1",
|
"deep-equal": "1.0.1",
|
||||||
"file-loader": "^0.11.2"
|
"file-loader": "^0.11.2",
|
||||||
|
"xterm": "^3.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "4.0.1",
|
"@angular/common": "4.0.1",
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
h3.mb-3 Appearance
|
h3.mb-3 Appearance
|
||||||
.row
|
.row
|
||||||
.col-md-6
|
.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
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Font
|
.title Font
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
const equal = require('deep-equal')
|
import deepEqual = require('deep-equal')
|
||||||
const fontManager = require('font-manager')
|
const fontManager = require('font-manager')
|
||||||
|
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
@ -17,7 +17,7 @@ export class TerminalSettingsTabComponent {
|
|||||||
shells: IShell[] = []
|
shells: IShell[] = []
|
||||||
persistenceProviders: SessionPersistenceProvider[]
|
persistenceProviders: SessionPersistenceProvider[]
|
||||||
colorSchemes: ITerminalColorScheme[] = []
|
colorSchemes: ITerminalColorScheme[] = []
|
||||||
equalComparator = equal
|
equalComparator = deepEqual
|
||||||
editingColorScheme: ITerminalColorScheme
|
editingColorScheme: ITerminalColorScheme
|
||||||
schemeChanged = false
|
schemeChanged = false
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ export class TerminalSettingsTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isCustomScheme (scheme: ITerminalColorScheme) {
|
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) {
|
colorsTrackBy (index) {
|
||||||
|
@ -7,10 +7,10 @@ import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppSe
|
|||||||
import { IShell } from '../api'
|
import { IShell } from '../api'
|
||||||
import { Session, SessionsService } from '../services/sessions.service'
|
import { Session, SessionsService } from '../services/sessions.service'
|
||||||
import { TerminalService } from '../services/terminal.service'
|
import { TerminalService } from '../services/terminal.service'
|
||||||
import { TerminalContainersService } from '../services/terminalContainers.service'
|
import { TerminalFrontendService } from '../services/terminalFrontend.service'
|
||||||
|
|
||||||
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
||||||
import { TermContainer } from '../terminalContainers/termContainer'
|
import { Frontend } from '../frontends/frontend'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'terminalTab',
|
selector: 'terminalTab',
|
||||||
@ -29,7 +29,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
@Input() zoom = 0
|
@Input() zoom = 0
|
||||||
@ViewChild('content') content
|
@ViewChild('content') content
|
||||||
@HostBinding('style.background-color') backgroundColor: string
|
@HostBinding('style.background-color') backgroundColor: string
|
||||||
termContainer: TermContainer
|
frontend: Frontend
|
||||||
sessionCloseSubscription: Subscription
|
sessionCloseSubscription: Subscription
|
||||||
hotkeysSubscription: Subscription
|
hotkeysSubscription: Subscription
|
||||||
htermVisible = false
|
htermVisible = false
|
||||||
@ -39,10 +39,10 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
private contextMenu: any
|
private contextMenu: any
|
||||||
private termContainerSubscriptions: Subscription[] = []
|
private termContainerSubscriptions: Subscription[] = []
|
||||||
|
|
||||||
get input$ (): Observable<string> { return this.termContainer.input$ }
|
get input$ (): Observable<string> { return this.frontend.input$ }
|
||||||
get output$ (): Observable<string> { return this.output }
|
get output$ (): Observable<string> { return this.output }
|
||||||
get resize$ (): Observable<ResizeEvent> { return this.termContainer.resize$ }
|
get resize$ (): Observable<ResizeEvent> { return this.frontend.resize$ }
|
||||||
get alternateScreenActive$ (): Observable<boolean> { return this.termContainer.alternateScreenActive$ }
|
get alternateScreenActive$ (): Observable<boolean> { return this.frontend.alternateScreenActive$ }
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
@ -52,7 +52,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
private sessions: SessionsService,
|
private sessions: SessionsService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private terminalService: TerminalService,
|
private terminalService: TerminalService,
|
||||||
private terminalContainersService: TerminalContainersService,
|
private terminalContainersService: TerminalFrontendService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
||||||
@ -69,23 +69,23 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
switch (hotkey) {
|
switch (hotkey) {
|
||||||
case 'ctrl-c':
|
case 'ctrl-c':
|
||||||
if (this.termContainer.getSelection()) {
|
if (this.frontend.getSelection()) {
|
||||||
this.termContainer.copySelection()
|
this.frontend.copySelection()
|
||||||
this.termContainer.clearSelection()
|
this.frontend.clearSelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
} else {
|
} else {
|
||||||
this.sendInput('\x03')
|
this.sendInput('\x03')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'copy':
|
case 'copy':
|
||||||
this.termContainer.copySelection()
|
this.frontend.copySelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
break
|
break
|
||||||
case 'paste':
|
case 'paste':
|
||||||
this.paste()
|
this.paste()
|
||||||
break
|
break
|
||||||
case 'clear':
|
case 'clear':
|
||||||
this.termContainer.clear()
|
this.frontend.clear()
|
||||||
break
|
break
|
||||||
case 'zoom-in':
|
case 'zoom-in':
|
||||||
this.zoomIn()
|
this.zoomIn()
|
||||||
@ -138,7 +138,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
||||||
this.termContainer.destroy()
|
this.frontend.destroy()
|
||||||
this.app.closeTab(this)
|
this.app.closeTab(this)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -153,16 +153,16 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.configure()
|
this.configure()
|
||||||
this.termContainer.focus()
|
this.frontend.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.termContainer = this.terminalContainersService.getContainer(this.session)
|
this.frontend = this.terminalContainersService.getFrontend(this.session)
|
||||||
|
|
||||||
this.termContainer.ready$.subscribe(() => {
|
this.frontend.ready$.subscribe(() => {
|
||||||
this.htermVisible = true
|
this.htermVisible = true
|
||||||
})
|
})
|
||||||
|
|
||||||
this.termContainer.resize$.pipe(first()).subscribe(async ({columns, rows}) => {
|
this.frontend.resize$.pipe(first()).subscribe(async ({columns, rows}) => {
|
||||||
if (!this.session.open) {
|
if (!this.session.open) {
|
||||||
this.initializeSession(columns, rows)
|
this.initializeSession(columns, rows)
|
||||||
}
|
}
|
||||||
@ -174,7 +174,8 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
this.session.releaseInitialDataBuffer()
|
this.session.releaseInitialDataBuffer()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.termContainer.attach(this.content.nativeElement)
|
this.frontend.configure(this.config.store)
|
||||||
|
this.frontend.attach(this.content.nativeElement)
|
||||||
this.attachTermContainerHandlers()
|
this.attachTermContainerHandlers()
|
||||||
|
|
||||||
this.configure()
|
this.configure()
|
||||||
@ -189,9 +190,9 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
this.termContainer.bell$.subscribe(() => {
|
this.frontend.bell$.subscribe(() => {
|
||||||
if (this.config.store.terminal.bell === 'visual') {
|
if (this.config.store.terminal.bell === 'visual') {
|
||||||
this.termContainer.visualBell()
|
this.frontend.visualBell()
|
||||||
}
|
}
|
||||||
if (this.config.store.terminal.bell === 'audible') {
|
if (this.config.store.terminal.bell === 'audible') {
|
||||||
this.bellPlayer.play()
|
this.bellPlayer.play()
|
||||||
@ -212,7 +213,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.termContainer.copySelection()
|
this.frontend.copySelection()
|
||||||
this.toastr.info('Copied')
|
this.toastr.info('Copied')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -239,12 +240,12 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
attachTermContainerHandlers () {
|
attachTermContainerHandlers () {
|
||||||
this.detachTermContainerHandlers()
|
this.detachTermContainerHandlers()
|
||||||
this.termContainerSubscriptions = [
|
this.termContainerSubscriptions = [
|
||||||
this.termContainer.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
|
this.frontend.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
|
||||||
|
|
||||||
this.focused$.subscribe(() => this.termContainer.enableResizing = true),
|
this.focused$.subscribe(() => this.frontend.enableResizing = true),
|
||||||
this.blurred$.subscribe(() => this.termContainer.enableResizing = false),
|
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
|
||||||
|
|
||||||
this.termContainer.mouseEvent$.subscribe(event => {
|
this.frontend.mouseEvent$.subscribe(event => {
|
||||||
if (event.type === 'mousedown') {
|
if (event.type === 'mousedown') {
|
||||||
if (event.which === 3) {
|
if (event.which === 3) {
|
||||||
if (this.config.store.terminal.rightClick === 'menu') {
|
if (this.config.store.terminal.rightClick === 'menu') {
|
||||||
@ -274,11 +275,11 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
this.termContainer.input$.subscribe(data => {
|
this.frontend.input$.subscribe(data => {
|
||||||
this.sendInput(data)
|
this.sendInput(data)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
this.termContainer.resize$.subscribe(({columns, rows}) => {
|
this.frontend.resize$.subscribe(({columns, rows}) => {
|
||||||
console.log(`Resizing to ${columns}x${rows}`)
|
console.log(`Resizing to ${columns}x${rows}`)
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
if (this.session.open) {
|
if (this.session.open) {
|
||||||
@ -302,7 +303,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
} else {
|
} else {
|
||||||
this.setProgress(null)
|
this.setProgress(null)
|
||||||
}
|
}
|
||||||
this.termContainer.write(data)
|
this.frontend.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
paste () {
|
paste () {
|
||||||
@ -319,7 +320,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configure (): void {
|
configure (): void {
|
||||||
this.termContainer.configure(this.config.store)
|
this.frontend.configure(this.config.store)
|
||||||
|
|
||||||
if (this.config.store.terminal.background === 'colorScheme') {
|
if (this.config.store.terminal.background === 'colorScheme') {
|
||||||
if (this.config.store.terminal.colorScheme.background) {
|
if (this.config.store.terminal.colorScheme.background) {
|
||||||
@ -332,20 +333,21 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
|
|
||||||
zoomIn () {
|
zoomIn () {
|
||||||
this.zoom++
|
this.zoom++
|
||||||
this.termContainer.setZoom(this.zoom)
|
this.frontend.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut () {
|
zoomOut () {
|
||||||
this.zoom--
|
this.zoom--
|
||||||
this.termContainer.setZoom(this.zoom)
|
this.frontend.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetZoom () {
|
resetZoom () {
|
||||||
this.zoom = 0
|
this.zoom = 0
|
||||||
this.termContainer.setZoom(this.zoom)
|
this.frontend.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
this.frontend.detach(this.content.nativeElement)
|
||||||
this.detachTermContainerHandlers()
|
this.detachTermContainerHandlers()
|
||||||
this.config.enabledServices(this.decorators).forEach(decorator => {
|
this.config.enabledServices(this.decorators).forEach(decorator => {
|
||||||
decorator.detach(this)
|
decorator.detach(this)
|
||||||
|
@ -3,6 +3,7 @@ import { ConfigProvider, Platform } from 'terminus-core'
|
|||||||
export class TerminalConfigProvider extends ConfigProvider {
|
export class TerminalConfigProvider extends ConfigProvider {
|
||||||
defaults = {
|
defaults = {
|
||||||
terminal: {
|
terminal: {
|
||||||
|
frontend: 'hterm',
|
||||||
autoOpen: false,
|
autoOpen: false,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
linePadding: 0,
|
linePadding: 0,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs'
|
import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs'
|
||||||
import { ResizeEvent } from '../api'
|
import { ResizeEvent } from '../api'
|
||||||
|
|
||||||
export abstract class TermContainer {
|
export abstract class Frontend {
|
||||||
enableResizing = true
|
enableResizing = true
|
||||||
protected ready = new AsyncSubject<void>()
|
protected ready = new AsyncSubject<void>()
|
||||||
protected title = new ReplaySubject<string>(1)
|
protected title = new ReplaySubject<string>(1)
|
||||||
@ -26,6 +26,7 @@ export abstract class TermContainer {
|
|||||||
get drop$ (): Observable<DragEvent> { return this.drop }
|
get drop$ (): Observable<DragEvent> { return this.drop }
|
||||||
|
|
||||||
abstract attach (host: HTMLElement): void
|
abstract attach (host: HTMLElement): void
|
||||||
|
detach (host: HTMLElement): void { } // tslint:disable-line
|
||||||
|
|
||||||
destroy (): void {
|
destroy (): void {
|
||||||
for (let o of [
|
for (let o of [
|
@ -1,7 +1,7 @@
|
|||||||
import { TermContainer } from './termContainer'
|
import { Frontend } from './frontend'
|
||||||
import { hterm, preferenceManager } from '../hterm'
|
import { hterm, preferenceManager } from '../hterm'
|
||||||
|
|
||||||
export class HTermContainer extends TermContainer {
|
export class HTermFrontend extends Frontend {
|
||||||
term: any
|
term: any
|
||||||
io: any
|
io: any
|
||||||
private htermIframe: HTMLElement
|
private htermIframe: HTMLElement
|
||||||
@ -50,6 +50,9 @@ export class HTermContainer extends TermContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configure (config: any): void {
|
configure (config: any): void {
|
||||||
|
if (!this.term) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.configuredFontSize = config.terminal.fontSize
|
this.configuredFontSize = config.terminal.fontSize
|
||||||
this.configuredLinePadding = config.terminal.linePadding
|
this.configuredLinePadding = config.terminal.linePadding
|
||||||
this.setFontSize()
|
this.setFontSize()
|
||||||
@ -144,6 +147,7 @@ export class HTermContainer extends TermContainer {
|
|||||||
|
|
||||||
private init () {
|
private init () {
|
||||||
this.term = new hterm.hterm.Terminal()
|
this.term = new hterm.hterm.Terminal()
|
||||||
|
this.term.colorPaletteOverrides = []
|
||||||
this.term.onTerminalReady = () => {
|
this.term.onTerminalReady = () => {
|
||||||
this.term.installKeyboard()
|
this.term.installKeyboard()
|
||||||
this.term.scrollPort_.setCtrlVPaste(true)
|
this.term.scrollPort_.setCtrlVPaste(true)
|
||||||
@ -225,7 +229,5 @@ export class HTermContainer extends TermContainer {
|
|||||||
size.height += this.configuredLinePadding
|
size.height += this.configuredLinePadding
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
this.term.colorPaletteOverrides = []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
121
terminus-terminal/src/frontends/xtermFrontend.ts
Normal file
121
terminus-terminal/src/frontends/xtermFrontend.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { Frontend } from './frontend'
|
||||||
|
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 XTermFrontend extends Frontend {
|
||||||
|
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,8 +13,8 @@ import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.c
|
|||||||
import { ColorPickerComponent } from './components/colorPicker.component'
|
import { ColorPickerComponent } from './components/colorPicker.component'
|
||||||
|
|
||||||
import { SessionsService, BaseSession } from './services/sessions.service'
|
import { SessionsService, BaseSession } from './services/sessions.service'
|
||||||
|
import { TerminalFrontendService } from './services/terminalFrontend.service'
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
import { TerminalContainersService } from './services/terminalContainers.service'
|
|
||||||
|
|
||||||
import { ScreenPersistenceProvider } from './persistence/screen'
|
import { ScreenPersistenceProvider } from './persistence/screen'
|
||||||
import { TMuxPersistenceProvider } from './persistence/tmux'
|
import { TMuxPersistenceProvider } from './persistence/tmux'
|
||||||
@ -50,8 +50,8 @@ import { hterm } from './hterm'
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SessionsService,
|
SessionsService,
|
||||||
|
TerminalFrontendService,
|
||||||
TerminalService,
|
TerminalService,
|
||||||
TerminalContainersService,
|
|
||||||
|
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
@ -125,4 +125,4 @@ export default class TerminalModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from './api'
|
export * from './api'
|
||||||
export { TerminalService, BaseSession, TerminalTabComponent, TerminalContainersService }
|
export { TerminalService, BaseSession, TerminalTabComponent, TerminalFrontendService }
|
||||||
|
@ -10,10 +10,10 @@ export class PathDropDecorator extends TerminalDecorator {
|
|||||||
attach (terminal: TerminalTabComponent): void {
|
attach (terminal: TerminalTabComponent): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.subscriptions = [
|
this.subscriptions = [
|
||||||
terminal.termContainer.dragOver$.subscribe(event => {
|
terminal.frontend.dragOver$.subscribe(event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}),
|
}),
|
||||||
terminal.termContainer.drop$.subscribe(event => {
|
terminal.frontend.drop$.subscribe(event => {
|
||||||
for (let file of event.dataTransfer.files as any) {
|
for (let file of event.dataTransfer.files as any) {
|
||||||
this.injectPath(terminal, file.path)
|
this.injectPath(terminal, file.path)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { execFileSync } from 'child_process'
|
import { execFileSync } from 'child_process'
|
||||||
import * as AsyncLock from 'async-lock'
|
import AsyncLock = require('async-lock')
|
||||||
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
||||||
import { first, publish } from 'rxjs/operators'
|
import { first, publish } from 'rxjs/operators'
|
||||||
import * as childProcess from 'child_process'
|
import * as childProcess from 'child_process'
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
const psNode = require('ps-node')
|
import psNode = require('ps-node')
|
||||||
let nodePTY
|
let nodePTY
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { first } from 'rxjs/operators'
|
import { first } from 'rxjs/operators'
|
||||||
import { Injectable, Inject } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { Logger, LogService, ElectronService, ConfigService } from 'terminus-core'
|
import { Logger, LogService, ConfigService } from 'terminus-core'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
|
|
||||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
@ -202,7 +202,6 @@ export class SessionsService {
|
|||||||
constructor (
|
constructor (
|
||||||
@Inject(SessionPersistenceProvider) private persistenceProviders: SessionPersistenceProvider[],
|
@Inject(SessionPersistenceProvider) private persistenceProviders: SessionPersistenceProvider[],
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
electron: ElectronService,
|
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
nodePTY = require('node-pty-tmp')
|
nodePTY = require('node-pty-tmp')
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core'
|
|
||||||
import { TermContainer } from '../terminalContainers/termContainer'
|
|
||||||
import { HTermContainer } from '../terminalContainers/htermContainer'
|
|
||||||
import { BaseSession } from '../services/sessions.service'
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class TerminalContainersService {
|
|
||||||
private containers = new WeakMap<BaseSession, TermContainer>()
|
|
||||||
|
|
||||||
getContainer (session: BaseSession): TermContainer {
|
|
||||||
if (!this.containers.has(session)) {
|
|
||||||
this.containers.set(session, new HTermContainer())
|
|
||||||
}
|
|
||||||
return this.containers.get(session)
|
|
||||||
}
|
|
||||||
}
|
|
25
terminus-terminal/src/services/terminalFrontend.service.ts
Normal file
25
terminus-terminal/src/services/terminalFrontend.service.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { ConfigService } from 'terminus-core'
|
||||||
|
import { Frontend } from '../frontends/frontend'
|
||||||
|
import { HTermFrontend } from '../frontends/htermFrontend'
|
||||||
|
import { XTermFrontend } from '../frontends/xtermFrontend'
|
||||||
|
import { BaseSession } from '../services/sessions.service'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TerminalFrontendService {
|
||||||
|
private containers = new WeakMap<BaseSession, Frontend>()
|
||||||
|
|
||||||
|
constructor (private config: ConfigService) { }
|
||||||
|
|
||||||
|
getFrontend (session: BaseSession): Frontend {
|
||||||
|
if (!this.containers.has(session)) {
|
||||||
|
this.containers.set(
|
||||||
|
session,
|
||||||
|
(this.config.store.terminal.frontend === 'xterm')
|
||||||
|
? new XTermFrontend()
|
||||||
|
: new HTermFrontend()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.containers.get(session)
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ module.exports = {
|
|||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
devtoolModuleFilenameTemplate: 'webpack-terminus-terminal:///[resource-path]',
|
devtoolModuleFilenameTemplate: 'webpack-terminus-terminal:///[resource-path]',
|
||||||
},
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
@ -35,7 +36,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-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: /\.svg/, use: ['svg-inline-loader'] },
|
||||||
{
|
{
|
||||||
test: /\.(ttf|eot|otf|woff|woff2|ogg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
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:
|
winreg@^1.2.3:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b"
|
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,
|
"noUnusedLocals": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"es5",
|
"es5",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user