mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-23 12:59:54 +00:00
.
This commit is contained in:
parent
2846637815
commit
c894410fdb
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { Inject, Injectable } from '@angular/core'
|
import { Inject, Injectable } from '@angular/core'
|
||||||
import { LinkHandler } from './api'
|
import { LinkHandler } from './api'
|
||||||
import { TerminalDecorator } from '../terminal/api'
|
import { TerminalDecorator, TerminalTabComponent } from '../terminal/api'
|
||||||
|
|
||||||
const debounceDelay = 500
|
const debounceDelay = 500
|
||||||
|
|
||||||
@ -16,8 +16,8 @@ export class LinkHighlighterDecorator extends TerminalDecorator {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate (terminal): void {
|
attach (terminal: TerminalTabComponent): void {
|
||||||
const Screen = terminal.screen_.constructor
|
const Screen = terminal.hterm.screen_.constructor
|
||||||
if (Screen._linkHighlighterInstalled) {
|
if (Screen._linkHighlighterInstalled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -26,13 +26,13 @@ export class LinkHighlighterDecorator extends TerminalDecorator {
|
|||||||
const oldInsertString = Screen.prototype.insertString
|
const oldInsertString = Screen.prototype.insertString
|
||||||
const oldDeleteChars = Screen.prototype.deleteChars
|
const oldDeleteChars = Screen.prototype.deleteChars
|
||||||
let self = this
|
let self = this
|
||||||
Screen.prototype.insertString = function (...args) {
|
Screen.prototype.insertString = function (content) {
|
||||||
let ret = oldInsertString.bind(this)(...args)
|
let ret = oldInsertString.bind(this)(content)
|
||||||
self.debounceInsertLinks(this)
|
self.debounceInsertLinks(this)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
Screen.prototype.deleteChars = function (...args) {
|
Screen.prototype.deleteChars = function (count) {
|
||||||
let ret = oldDeleteChars.bind(this)(...args)
|
let ret = oldDeleteChars.bind(this)(count)
|
||||||
self.debounceInsertLinks(this)
|
self.debounceInsertLinks(this)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
const untildify = require('untildify')
|
||||||
|
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { LinkHandler } from './api'
|
import { LinkHandler } from './api'
|
||||||
@ -20,12 +21,16 @@ export class URLHandler extends LinkHandler {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileHandler extends LinkHandler {
|
export class FileHandler extends LinkHandler {
|
||||||
regex = '/[^\\s.,;\'"]+'
|
regex = '[~/][^\\s.,;\'"]+'
|
||||||
|
|
||||||
constructor (private electron: ElectronService) {
|
constructor (private electron: ElectronService) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convert (uri: string): string {
|
||||||
|
return untildify(uri)
|
||||||
|
}
|
||||||
|
|
||||||
verify (uri: string) {
|
verify (uri: string) {
|
||||||
return fs.existsSync(uri)
|
return fs.existsSync(uri)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
|
import { TerminalTabComponent } from './components/terminalTab'
|
||||||
|
export { TerminalTabComponent } from './components/terminalTab'
|
||||||
|
|
||||||
export abstract class TerminalDecorator {
|
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 {
|
export interface SessionOptions {
|
||||||
name?: string,
|
name?: string
|
||||||
command?: string,
|
command?: string
|
||||||
args?: string[],
|
args?: string[]
|
||||||
cwd?: string,
|
cwd?: string
|
||||||
env?: any,
|
env?: any
|
||||||
recoveryId?: string
|
recoveryId?: string
|
||||||
recoveredTruePID?: number
|
recoveredTruePID?: number
|
||||||
}
|
}
|
||||||
|
@ -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 { Component, NgZone, Inject, ElementRef } from '@angular/core'
|
||||||
|
|
||||||
import { ConfigService } from 'services/config'
|
import { ConfigService } from 'services/config'
|
||||||
|
|
||||||
import { BaseTabComponent } from 'components/baseTab'
|
import { BaseTabComponent } from 'components/baseTab'
|
||||||
import { TerminalTab } from '../tab'
|
import { TerminalTab } from '../tab'
|
||||||
import { TerminalDecorator } from '../api'
|
import { TerminalDecorator, ResizeEvent } from '../api'
|
||||||
|
|
||||||
import { hterm, preferenceManager } from '../hterm'
|
import { hterm, preferenceManager } from '../hterm'
|
||||||
|
|
||||||
@ -16,11 +16,15 @@ import { hterm, preferenceManager } from '../hterm'
|
|||||||
styles: [require('./terminalTab.scss')],
|
styles: [require('./terminalTab.scss')],
|
||||||
})
|
})
|
||||||
export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
||||||
terminal: any
|
hterm: any
|
||||||
title$ = new BehaviorSubject('')
|
|
||||||
configSubscription: Subscription
|
configSubscription: Subscription
|
||||||
focusedSubscription: Subscription
|
focusedSubscription: Subscription
|
||||||
startupTime: number
|
startupTime: number
|
||||||
|
title$ = new BehaviorSubject('')
|
||||||
|
size$ = new ReplaySubject<ResizeEvent>(1)
|
||||||
|
input$ = new Subject<string>()
|
||||||
|
output$ = new Subject<string>()
|
||||||
|
contentUpdated$ = new Subject<void>()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
@ -37,27 +41,29 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
|||||||
|
|
||||||
initTab () {
|
initTab () {
|
||||||
this.focusedSubscription = this.model.focused.subscribe(() => {
|
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) => {
|
this.decorators.forEach((decorator) => {
|
||||||
decorator.decorate(this.terminal)
|
decorator.attach(this)
|
||||||
})
|
})
|
||||||
this.terminal.setWindowTitle = (title) => {
|
|
||||||
this.zone.run(() => {
|
this.attachHTermHandlers(this.hterm)
|
||||||
this.model.title = title
|
|
||||||
})
|
this.hterm.onTerminalReady = () => {
|
||||||
}
|
this.hterm.installKeyboard()
|
||||||
this.terminal.onTerminalReady = () => {
|
let io = this.hterm.io.push()
|
||||||
this.terminal.installKeyboard()
|
this.attachIOHandlers(io)
|
||||||
let io = this.terminal.io.push()
|
|
||||||
const dataSubscription = this.model.session.dataAvailable.subscribe((data) => {
|
const dataSubscription = this.model.session.dataAvailable.subscribe((data) => {
|
||||||
if (performance.now() - this.startupTime > 500) {
|
if (performance.now() - this.startupTime > 500) {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this.model.displayActivity()
|
this.model.displayActivity()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
this.zone.run(() => {
|
||||||
|
this.output$.next(data)
|
||||||
|
})
|
||||||
io.writeUTF8(data)
|
io.writeUTF8(data)
|
||||||
})
|
})
|
||||||
const closedSubscription = this.model.session.closed.subscribe(() => {
|
const closedSubscription = this.model.session.closed.subscribe(() => {
|
||||||
@ -65,20 +71,50 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
|||||||
closedSubscription.unsubscribe()
|
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.model.session.releaseInitialDataBuffer()
|
||||||
}
|
}
|
||||||
this.terminal.decorate(this.elementRef.nativeElement)
|
this.hterm.decorate(this.elementRef.nativeElement)
|
||||||
this.configure()
|
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 () {
|
configure () {
|
||||||
let config = this.config.full()
|
let config = this.config.full()
|
||||||
preferenceManager.set('font-family', config.terminal.font)
|
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('enable-clipboard-notice', false)
|
||||||
preferenceManager.set('receive-encoding', 'raw')
|
preferenceManager.set('receive-encoding', 'raw')
|
||||||
preferenceManager.set('send-encoding', 'raw')
|
preferenceManager.set('send-encoding', 'raw')
|
||||||
|
this.hterm.setBracketedPaste(config.terminal.bracketedPaste)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
this.decorators.forEach((decorator) => {
|
||||||
|
decorator.detach(this)
|
||||||
|
})
|
||||||
this.focusedSubscription.unsubscribe()
|
this.focusedSubscription.unsubscribe()
|
||||||
this.configSubscription.unsubscribe()
|
this.configSubscription.unsubscribe()
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
font: 'monospace',
|
font: 'monospace',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
bell: 'off',
|
bell: 'off',
|
||||||
|
bracketedPaste: true,
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
'new-tab': [
|
'new-tab': [
|
||||||
|
@ -33,4 +33,10 @@ hterm.hterm.ScrollPort.prototype.decorate = function (...args) {
|
|||||||
this.screen_.style.cssText += `; padding-right: ${this.screen_.offsetWidth - this.screen_.clientWidth}px;`
|
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
|
hterm.hterm.Terminal.prototype.showOverlay = () => null
|
||||||
|
@ -120,11 +120,13 @@ app-root .content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.has-activity:not(.active) {
|
&.has-activity:not(.active) {
|
||||||
|
/*
|
||||||
.content-wrapper .index {
|
.content-wrapper .index {
|
||||||
background: $blue;
|
background: $blue;
|
||||||
color: white;
|
color: white;
|
||||||
text-shadow: 0 1px 1px rgba(0,0,0,.95);
|
text-shadow: 0 1px 1px rgba(0,0,0,.95);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,10 +152,14 @@ app-root .content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.active .content-wrapper {
|
&.active .content-wrapper {
|
||||||
border-top: 1px solid $blue;
|
border-top: 1px solid $teal;
|
||||||
border-top-left-radius: $tab-border-radius;
|
border-top-left-radius: $tab-border-radius;
|
||||||
border-top-right-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 {
|
&.active .content-wrapper {
|
||||||
border-bottom: 1px solid $blue;
|
border-bottom: 1px solid $teal;
|
||||||
border-bottom-left-radius: $tab-border-radius;
|
border-bottom-left-radius: $tab-border-radius;
|
||||||
border-bottom-right-radius: $tab-border-radius;
|
border-bottom-right-radius: $tab-border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-activity:not(.active) .content-wrapper {
|
||||||
|
border-bottom: 1px solid $green;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"tslint": "4.5.0",
|
"tslint": "4.5.0",
|
||||||
"tslint-eslint-rules": "^3.5.1",
|
"tslint-eslint-rules": "^3.5.1",
|
||||||
"typescript": "2.2.1",
|
"typescript": "2.2.1",
|
||||||
|
"untildify": "^3.0.2",
|
||||||
"url-loader": "^0.5.7",
|
"url-loader": "^0.5.7",
|
||||||
"val-loader": "^0.5.0",
|
"val-loader": "^0.5.0",
|
||||||
"vrsource-tslint-rules": "^4.0.1",
|
"vrsource-tslint-rules": "^4.0.1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user