This commit is contained in:
Eugene Pankov 2017-04-04 17:39:36 +02:00
parent 2846637815
commit c894410fdb
8 changed files with 112 additions and 40 deletions

View File

@ -5,7 +5,7 @@
import { Inject, Injectable } from '@angular/core'
import { LinkHandler } from './api'
import { TerminalDecorator } from '../terminal/api'
import { TerminalDecorator, TerminalTabComponent } from '../terminal/api'
const debounceDelay = 500
@ -16,8 +16,8 @@ export class LinkHighlighterDecorator extends TerminalDecorator {
super()
}
decorate (terminal): void {
const Screen = terminal.screen_.constructor
attach (terminal: TerminalTabComponent): void {
const Screen = terminal.hterm.screen_.constructor
if (Screen._linkHighlighterInstalled) {
return
}
@ -26,13 +26,13 @@ export class LinkHighlighterDecorator extends TerminalDecorator {
const oldInsertString = Screen.prototype.insertString
const oldDeleteChars = Screen.prototype.deleteChars
let self = this
Screen.prototype.insertString = function (...args) {
let ret = oldInsertString.bind(this)(...args)
Screen.prototype.insertString = function (content) {
let ret = oldInsertString.bind(this)(content)
self.debounceInsertLinks(this)
return ret
}
Screen.prototype.deleteChars = function (...args) {
let ret = oldDeleteChars.bind(this)(...args)
Screen.prototype.deleteChars = function (count) {
let ret = oldDeleteChars.bind(this)(count)
self.debounceInsertLinks(this)
return ret
}

View File

@ -1,4 +1,5 @@
import * as fs from 'fs'
const untildify = require('untildify')
import { Injectable } from '@angular/core'
import { LinkHandler } from './api'
@ -20,12 +21,16 @@ export class URLHandler extends LinkHandler {
@Injectable()
export class FileHandler extends LinkHandler {
regex = '/[^\\s.,;\'"]+'
regex = '[~/][^\\s.,;\'"]+'
constructor (private electron: ElectronService) {
super()
}
convert (uri: string): string {
return untildify(uri)
}
verify (uri: string) {
return fs.existsSync(uri)
}

View File

@ -1,13 +1,22 @@
import { TerminalTabComponent } from './components/terminalTab'
export { TerminalTabComponent } from './components/terminalTab'
export abstract class TerminalDecorator {
abstract decorate (terminal): void
attach (_terminal: TerminalTabComponent): void { }
detach (_terminal: TerminalTabComponent): void { }
}
export interface ResizeEvent {
width: number
height: number
}
export interface SessionOptions {
name?: string,
command?: string,
args?: string[],
cwd?: string,
env?: any,
name?: string
command?: string
args?: string[]
cwd?: string
env?: any
recoveryId?: string
recoveredTruePID?: number
}

View File

@ -1,11 +1,11 @@
import { BehaviorSubject, Subscription } from 'rxjs'
import { BehaviorSubject, ReplaySubject, Subject, Subscription } from 'rxjs'
import { Component, NgZone, Inject, ElementRef } from '@angular/core'
import { ConfigService } from 'services/config'
import { BaseTabComponent } from 'components/baseTab'
import { TerminalTab } from '../tab'
import { TerminalDecorator } from '../api'
import { TerminalDecorator, ResizeEvent } from '../api'
import { hterm, preferenceManager } from '../hterm'
@ -16,11 +16,15 @@ import { hterm, preferenceManager } from '../hterm'
styles: [require('./terminalTab.scss')],
})
export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
terminal: any
title$ = new BehaviorSubject('')
hterm: any
configSubscription: Subscription
focusedSubscription: Subscription
startupTime: number
title$ = new BehaviorSubject('')
size$ = new ReplaySubject<ResizeEvent>(1)
input$ = new Subject<string>()
output$ = new Subject<string>()
contentUpdated$ = new Subject<void>()
constructor(
private zone: NgZone,
@ -37,27 +41,29 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
initTab () {
this.focusedSubscription = this.model.focused.subscribe(() => {
this.terminal.scrollPort_.focus()
this.hterm.scrollPort_.focus()
})
this.terminal = new hterm.hterm.Terminal()
this.hterm = new hterm.hterm.Terminal()
this.decorators.forEach((decorator) => {
decorator.decorate(this.terminal)
decorator.attach(this)
})
this.terminal.setWindowTitle = (title) => {
this.zone.run(() => {
this.model.title = title
})
}
this.terminal.onTerminalReady = () => {
this.terminal.installKeyboard()
let io = this.terminal.io.push()
this.attachHTermHandlers(this.hterm)
this.hterm.onTerminalReady = () => {
this.hterm.installKeyboard()
let io = this.hterm.io.push()
this.attachIOHandlers(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)
})
const closedSubscription = this.model.session.closed.subscribe(() => {
@ -65,20 +71,50 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
closedSubscription.unsubscribe()
})
io.onVTKeystroke = io.sendString = (str) => {
this.model.session.write(str)
}
io.onTerminalResize = (columns, rows) => {
console.log(`Resizing to ${columns}x${rows}`)
this.model.session.resize(columns, rows)
}
this.model.session.releaseInitialDataBuffer()
}
this.terminal.decorate(this.elementRef.nativeElement)
this.hterm.decorate(this.elementRef.nativeElement)
this.configure()
}
attachHTermHandlers (hterm: any) {
hterm.setWindowTitle = (title) => {
this.zone.run(() => {
this.model.title = title
this.title$.next(title)
})
}
const oldInsertString = hterm.screen_.insertString.bind(hterm.screen_)
hterm.screen_.insertString = (data) => {
oldInsertString(data)
this.contentUpdated$.next()
}
const oldDeleteChars = hterm.screen_.deleteChars.bind(hterm.screen_)
hterm.screen_.deleteChars = (count) => {
let ret = oldDeleteChars(count)
this.contentUpdated$.next()
return ret
}
}
attachIOHandlers (io: any) {
io.onVTKeystroke = io.sendString = (data) => {
this.model.session.write(data)
this.zone.run(() => {
this.input$.next(data)
})
}
io.onTerminalResize = (columns, rows) => {
// console.log(`Resizing to ${columns}x${rows}`)
this.zone.run(() => {
this.model.session.resize(columns, rows)
this.size$.next({ width: columns, height: rows })
})
}
}
configure () {
let config = this.config.full()
preferenceManager.set('font-family', config.terminal.font)
@ -88,9 +124,13 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
preferenceManager.set('enable-clipboard-notice', false)
preferenceManager.set('receive-encoding', 'raw')
preferenceManager.set('send-encoding', 'raw')
this.hterm.setBracketedPaste(config.terminal.bracketedPaste)
}
ngOnDestroy () {
this.decorators.forEach((decorator) => {
decorator.detach(this)
})
this.focusedSubscription.unsubscribe()
this.configSubscription.unsubscribe()
}

View File

@ -7,6 +7,7 @@ export class TerminalConfigProvider extends ConfigProvider {
font: 'monospace',
fontSize: 14,
bell: 'off',
bracketedPaste: true,
},
hotkeys: {
'new-tab': [

View File

@ -33,4 +33,10 @@ hterm.hterm.ScrollPort.prototype.decorate = function (...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

View File

@ -120,11 +120,13 @@ app-root .content {
}
&.has-activity:not(.active) {
/*
.content-wrapper .index {
background: $blue;
color: white;
text-shadow: 0 1px 1px rgba(0,0,0,.95);
}
*/
}
}
}
@ -150,10 +152,14 @@ app-root .content {
}
&.active .content-wrapper {
border-top: 1px solid $blue;
border-top: 1px solid $teal;
border-top-left-radius: $tab-border-radius;
border-top-right-radius: $tab-border-radius;
}
&.has-activity:not(.active) .content-wrapper {
border-top: 1px solid $green;
}
}
}
@ -178,10 +184,14 @@ app-root .content {
}
&.active .content-wrapper {
border-bottom: 1px solid $blue;
border-bottom: 1px solid $teal;
border-bottom-left-radius: $tab-border-radius;
border-bottom-right-radius: $tab-border-radius;
}
&.has-activity:not(.active) .content-wrapper {
border-bottom: 1px solid $green;
}
}
}
}

View File

@ -29,6 +29,7 @@
"tslint": "4.5.0",
"tslint-eslint-rules": "^3.5.1",
"typescript": "2.2.1",
"untildify": "^3.0.2",
"url-loader": "^0.5.7",
"val-loader": "^0.5.0",
"vrsource-tslint-rules": "^4.0.1",