From 7a4806bcc988c0378fa3729aa9f508c6164e9ede Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Wed, 5 Apr 2017 10:50:02 +0200 Subject: [PATCH] . --- app/src/link-highlighter/decorator.ts | 41 ++-------- app/src/terminal/components/terminalTab.ts | 88 +++++++++++++++++----- app/src/terminal/hterm.ts | 12 --- app/src/terminal/index.ts | 1 + app/src/terminal/persistenceProviders.ts | 3 + app/src/terminal/services/sessions.ts | 13 +++- 6 files changed, 87 insertions(+), 71 deletions(-) diff --git a/app/src/link-highlighter/decorator.ts b/app/src/link-highlighter/decorator.ts index a9ad7a4b..f39df36c 100644 --- a/app/src/link-highlighter/decorator.ts +++ b/app/src/link-highlighter/decorator.ts @@ -7,8 +7,6 @@ import { Inject, Injectable } from '@angular/core' import { LinkHandler } from './api' import { TerminalDecorator, TerminalTabComponent } from '../terminal/api' -const debounceDelay = 500 - @Injectable() export class LinkHighlighterDecorator extends TerminalDecorator { @@ -17,40 +15,11 @@ export class LinkHighlighterDecorator extends TerminalDecorator { } attach (terminal: TerminalTabComponent): void { - const Screen = terminal.hterm.screen_.constructor - if (Screen._linkHighlighterInstalled) { - return - } - Screen._linkHighlighterInstalled = true - - const oldInsertString = Screen.prototype.insertString - const oldDeleteChars = Screen.prototype.deleteChars - let self = this - Screen.prototype.insertString = function (content) { - let ret = oldInsertString.bind(this)(content) - self.debounceInsertLinks(this) - return ret - } - Screen.prototype.deleteChars = function (count) { - let ret = oldDeleteChars.bind(this)(count) - self.debounceInsertLinks(this) - return ret - } - } - - debounceInsertLinks (screen) { - if (screen.__insertLinksTimeout) { - screen.__insertLinksRebounce = true - } else { - screen.__insertLinksTimeout = window.setTimeout(() => { - this.insertLinks(screen) - screen.__insertLinksTimeout = null - if (screen.__insertLinksRebounce) { - screen.__insertLinksRebounce = false - this.debounceInsertLinks(screen) - } - }, debounceDelay) - } + terminal.contentUpdated$ + .debounceTime(1000) + .subscribe(() => { + this.insertLinks(terminal.hterm.screen_) + }) } insertLinks (screen) { diff --git a/app/src/terminal/components/terminalTab.ts b/app/src/terminal/components/terminalTab.ts index 87e50eab..1a1a9492 100644 --- a/app/src/terminal/components/terminalTab.ts +++ b/app/src/terminal/components/terminalTab.ts @@ -19,12 +19,14 @@ export class TerminalTabComponent extends BaseTabComponent { hterm: any configSubscription: Subscription focusedSubscription: Subscription - startupTime: number title$ = new BehaviorSubject('') size$ = new ReplaySubject(1) input$ = new Subject() output$ = new Subject() contentUpdated$ = new Subject() + alternateScreenActive$ = new BehaviorSubject(false) + mouseEvent$ = new Subject() + private io: any constructor( private zone: NgZone, @@ -33,7 +35,6 @@ export class TerminalTabComponent extends BaseTabComponent { @Inject(TerminalDecorator) private decorators: TerminalDecorator[], ) { super() - this.startupTime = performance.now() this.configSubscription = config.change.subscribe(() => { this.configure() }) @@ -53,18 +54,13 @@ export class TerminalTabComponent extends BaseTabComponent { this.hterm.onTerminalReady = () => { this.hterm.installKeyboard() - let io = this.hterm.io.push() - this.attachIOHandlers(io) + this.io = this.hterm.io.push() + this.attachIOHandlers(this.io) const dataSubscription = this.model.session.dataAvailable.subscribe((data) => { - if (performance.now() - this.startupTime > 500) { - this.zone.run(() => { - this.model.displayActivity() - }) - } this.zone.run(() => { this.output$.next(data) }) - io.writeUTF8(data) + this.write(data) }) const closedSubscription = this.model.session.closed.subscribe(() => { dataSubscription.unsubscribe() @@ -75,6 +71,12 @@ export class TerminalTabComponent extends BaseTabComponent { } this.hterm.decorate(this.elementRef.nativeElement) this.configure() + + setTimeout(() => { + this.output$.subscribe(() => { + this.model.displayActivity() + }) + }, 1000) } attachHTermHandlers (hterm: any) { @@ -85,23 +87,54 @@ export class TerminalTabComponent extends BaseTabComponent { }) } - const oldInsertString = hterm.screen_.insertString.bind(hterm.screen_) - hterm.screen_.insertString = (data) => { - oldInsertString(data) - this.contentUpdated$.next() + const _decorate = hterm.scrollPort_.decorate.bind(hterm.scrollPort_) + hterm.scrollPort_.decorate = (...args) => { + _decorate(...args) + hterm.scrollPort_.screen_.style.cssText += `; padding-right: ${hterm.scrollPort_.screen_.offsetWidth - hterm.scrollPort_.screen_.clientWidth}px;` } - const oldDeleteChars = hterm.screen_.deleteChars.bind(hterm.screen_) - hterm.screen_.deleteChars = (count) => { - let ret = oldDeleteChars(count) - this.contentUpdated$.next() - return ret + const _setAlternateMode = hterm.setAlternateMode.bind(hterm) + hterm.setAlternateMode = (state) => { + _setAlternateMode(state) + this.alternateScreenActive$.next(state) + } + + const _onPaste_ = hterm.onPaste_.bind(hterm) + hterm.onPaste_ = (event) => { + event.text = event.text.trim() + _onPaste_(event) + } + + const _onMouse_ = hterm.onMouse_.bind(hterm) + hterm.onMouse_ = (event) => { + this.mouseEvent$.next(event) + if ((event.ctrlKey || event.metaKey) && event.type === 'mousewheel') { + event.preventDefault() + let delta = Math.round(event.wheelDeltaY / 50) + this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta))) + } + _onMouse_(event) + } + + for (let screen of [hterm.primaryScreen_, hterm.alternateScreen_]) { + const _insertString = screen.insertString.bind(screen) + screen.insertString = (data) => { + _insertString(data) + this.contentUpdated$.next() + } + + const _deleteChars = screen.deleteChars.bind(screen) + screen.deleteChars = (count) => { + let ret = _deleteChars(count) + this.contentUpdated$.next() + return ret + } } } attachIOHandlers (io: any) { io.onVTKeystroke = io.sendString = (data) => { - this.model.session.write(data) + this.sendInput(data) this.zone.run(() => { this.input$.next(data) }) @@ -115,6 +148,14 @@ export class TerminalTabComponent extends BaseTabComponent { } } + sendInput (data: string) { + this.model.session.write(data) + } + + write (data: string) { + this.io.writeUTF8(data) + } + configure () { let config = this.config.full() preferenceManager.set('font-family', config.terminal.font) @@ -133,5 +174,12 @@ export class TerminalTabComponent extends BaseTabComponent { }) this.focusedSubscription.unsubscribe() this.configSubscription.unsubscribe() + this.title$.complete() + this.size$.complete() + this.input$.complete() + this.output$.complete() + this.contentUpdated$.complete() + this.alternateScreenActive$.complete() + this.mouseEvent$.complete() } } diff --git a/app/src/terminal/hterm.ts b/app/src/terminal/hterm.ts index 038b6560..1a0d4b1f 100644 --- a/app/src/terminal/hterm.ts +++ b/app/src/terminal/hterm.ts @@ -27,16 +27,4 @@ preferenceManager.set('color-palette-overrides', { 0: '#1D272D', }) -const oldDecorate = hterm.hterm.ScrollPort.prototype.decorate -hterm.hterm.ScrollPort.prototype.decorate = function (...args) { - oldDecorate.bind(this)(...args) - this.screen_.style.cssText += `; padding-right: ${this.screen_.offsetWidth - this.screen_.clientWidth}px;` -} - -const oldPaste = hterm.hterm.Terminal.prototype.onPaste_ -hterm.hterm.Terminal.prototype.onPaste_ = function (e) { - e.text = e.text.trim() - oldPaste.bind(this)(e) -} - hterm.hterm.Terminal.prototype.showOverlay = () => null diff --git a/app/src/terminal/index.ts b/app/src/terminal/index.ts index d9eff9bd..a26e9600 100644 --- a/app/src/terminal/index.ts +++ b/app/src/terminal/index.ts @@ -30,6 +30,7 @@ import { hterm } from './hterm' { provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true }, SessionsService, { provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider }, + // { provide: SessionPersistenceProvider, useValue: null }, { provide: SettingsTabProvider, useClass: TerminalSettingsProvider, multi: true }, { provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true }, ], diff --git a/app/src/terminal/persistenceProviders.ts b/app/src/terminal/persistenceProviders.ts index d7daeaf8..d3b2e396 100644 --- a/app/src/terminal/persistenceProviders.ts +++ b/app/src/terminal/persistenceProviders.ts @@ -47,9 +47,12 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider { term xterm-color bindkey "^[OH" beginning-of-line bindkey "^[OF" end-of-line + bindkey "\\027[?1049h" stuff ----alternate enter----- + bindkey "\\027[?1049l" stuff ----alternate leave----- termcapinfo xterm* 'hs:ts=\\E]0;:fs=\\007:ds=\\E]0;\\007' defhstatus "^Et" hardstatus off + altscreen on `, 'utf-8') let recoveryId = `term-tab-${Date.now()}` let args = ['-d', '-m', '-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || []) diff --git a/app/src/terminal/services/sessions.ts b/app/src/terminal/services/sessions.ts index 5cefddc6..a2c469b6 100644 --- a/app/src/terminal/services/sessions.ts +++ b/app/src/terminal/services/sessions.ts @@ -130,8 +130,10 @@ export class SessionsService { } async createNewSession (options: SessionOptions) : Promise { - let recoveryId = await this.persistence.startSession(options) - options = await this.persistence.attachSession(recoveryId) + if (this.persistence) { + let recoveryId = await this.persistence.startSession(options) + options = await this.persistence.attachSession(recoveryId) + } let session = this.addSession(options) return session } @@ -142,7 +144,9 @@ export class SessionsService { let session = new Session(options) const destroySubscription = session.destroyed.subscribe(() => { delete this.sessions[session.name] - this.persistence.terminateSession(session.recoveryId) + if (this.persistence) { + this.persistence.terminateSession(session.recoveryId) + } destroySubscription.unsubscribe() }) this.sessions[session.name] = session @@ -150,6 +154,9 @@ export class SessionsService { } async recover (recoveryId: string) : Promise { + if (!this.persistence) { + return null + } const options = await this.persistence.attachSession(recoveryId) if (!options) { return null