1
1
mirror of https://github.com/Eugeny/tabby.git synced 2024-12-28 13:03:31 +03:00
This commit is contained in:
Eugene Pankov 2017-07-26 16:04:55 +02:00
parent de29e34363
commit feb4c5bcb6
5 changed files with 208 additions and 4 deletions

View File

@ -37,6 +37,8 @@
"terminus-settings": "*"
},
"dependencies": {
"@types/async-lock": "0.0.19",
"async-lock": "^1.0.0",
"font-manager": "0.2.2",
"hterm-umdjs": "1.1.3",
"mz": "^2.6.0",

View File

@ -9,7 +9,7 @@
display: block;
overflow: hidden;
margin: 15px;
transition: opacity ease-out 0.1s;
transition: opacity ease-out 0.25s;
opacity: 0;
div[style]:last-child {

View File

@ -66,3 +66,9 @@ hterm.hterm.VT.CSI[' q'] = function (parseState) {
this.terminal.cursorMode = arg
this.terminal.applyCursorShape()
}
Selection.prototype.collapseToEnd = function () {
try {
this.collapseToEnd()
} catch (err) { ; }
}

View File

@ -14,6 +14,7 @@ import { SessionsService } from './services/sessions.service'
import { ShellsService } from './services/shells.service'
import { ScreenPersistenceProvider } from './persistenceProviders'
import { TMuxPersistenceProvider } from './tmux'
import { ButtonProvider } from './buttonProvider'
import { RecoveryProvider } from './recoveryProvider'
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator } from './api'
@ -34,18 +35,23 @@ import { hterm } from './hterm'
SessionsService,
ShellsService,
ScreenPersistenceProvider,
TMuxPersistenceProvider,
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
{
provide: SessionPersistenceProvider,
useFactory: (hostApp: HostAppService, screen: ScreenPersistenceProvider) => {
useFactory: (
hostApp: HostAppService,
screen: ScreenPersistenceProvider,
tmux: TMuxPersistenceProvider,
) => {
if (hostApp.platform === Platform.Windows) {
return null
} else {
return screen
return tmux
}
},
deps: [HostAppService, ScreenPersistenceProvider],
deps: [HostAppService, ScreenPersistenceProvider, TMuxPersistenceProvider],
},
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },

View File

@ -0,0 +1,190 @@
import { Injectable } from '@angular/core'
import * as AsyncLock from 'async-lock'
import { Observable, Subject } from 'rxjs'
import * as childProcess from 'child_process'
import { SessionOptions, SessionPersistenceProvider } from './api'
const TMUX_CONFIG = `
set -g status off
`
export class TMuxBlock {
time: number
number: number
error: boolean
lines: string[]
constructor (line: string) {
this.time = parseInt(line.split(' ')[1])
this.number = parseInt(line.split(' ')[2])
this.lines = []
}
}
export class TMuxMessage {
type: string
content: string
constructor (line: string) {
this.type = line.substring(0, line.indexOf(' '))
this.content = line.substring(line.indexOf(' ') + 1)
}
}
export class TMuxCommandProcess {
private process: childProcess.ChildProcess
private rawOutput$ = new Subject<string>()
private line$ = new Subject<string>()
private message$ = new Subject<string>()
private block$ = new Subject<TMuxBlock>()
private response$: Observable<TMuxBlock>
private lock = new AsyncLock({ timeout: 1000 })
constructor () {
this.process = childProcess.spawn('tmux', ['-C', '-L', 'terminus', 'new-session', '-A', '-D', '-s', 'control'])
console.log('[tmux] started')
this.process.stdout.on('data', data => {
console.debug('tmux says:', data.toString())
this.rawOutput$.next(data.toString())
})
let rawBuffer = ''
this.rawOutput$.subscribe(raw => {
rawBuffer += raw
if (rawBuffer.includes('\n')) {
let lines = rawBuffer.split('\n')
rawBuffer = lines.pop()
lines.forEach(line => this.line$.next(line))
}
})
let currentBlock = null
this.line$.subscribe(line => {
if (currentBlock) {
if (line.startsWith('%end ')) {
let block = currentBlock
currentBlock = null
setImmediate(() => {
this.block$.next(block)
})
} else if (line.startsWith('%error ')) {
let block = currentBlock
block.error = true
currentBlock = null
setImmediate(() => {
this.block$.next(block)
})
} else {
currentBlock.lines.push(line)
}
} else {
if (line.startsWith('%begin ')) {
currentBlock = new TMuxBlock(line)
} else {
this.message$.next(line)
}
}
})
this.response$ = this.block$.skip(1).share()
this.block$.subscribe(block => {
console.debug('[tmux] block:', block)
})
this.response$.subscribe(response => {
console.debug('[tmux] response:', response)
})
this.message$.subscribe(message => {
console.debug('[tmux] message:', message)
})
}
command (command: string): Promise<TMuxBlock> {
return this.lock.acquire('key', () => {
let p = this.response$.take(1).toPromise()
console.debug('[tmux] command:', command)
this.process.stdin.write(command + '\n')
p.then(x => console.log('promise then', x))
p.catch(x => console.log('promise catch', x))
return p
}).then(response => {
if (response.error) {
throw response
}
return response
}) as Promise<TMuxBlock>
}
destroy () {
this.rawOutput$.complete()
this.line$.complete()
this.block$.complete()
this.message$.complete()
this.process.kill('SIGTERM')
}
}
export class TMux {
private process: TMuxCommandProcess
constructor () {
this.process = new TMuxCommandProcess()
TMUX_CONFIG.split('\n').filter(x => x).forEach(async (line) => {
await this.process.command(line)
})
}
async create (id: string, options: SessionOptions): Promise<void> {
let args = [options.command].concat(options.args)
let cmd = args.map(x => `"${x.replace('"', '\\"')}"`)
await this.process.command(
`new-session -s "${id}" -d`
+ (options.cwd ? ` -c '${options.cwd.replace("'", "\\'")}'` : '')
+ ` '${cmd}'`
)
}
async list (): Promise<string[]> {
let block = await this.process.command('list-sessions -F "#{session_name}"')
return block.lines
}
async terminate (id: string): Promise<void> {
await this.process.command(`kill-session -t ${id}`)
}
}
@Injectable()
export class TMuxPersistenceProvider extends SessionPersistenceProvider {
private tmux: TMux
constructor () {
super()
this.tmux = new TMux()
}
async attachSession (recoveryId: any): Promise<SessionOptions> {
let sessions = await this.tmux.list()
if (!sessions.includes(recoveryId)) {
return null
}
return {
command: 'tmux',
args: ['-L', 'terminus', 'attach-session', '-d', '-t', recoveryId],
recoveryId,
}
}
async startSession (options: SessionOptions): Promise<any> {
// TODO env
let recoveryId = Date.now().toString()
await this.tmux.create(recoveryId, options)
return recoveryId
}
async terminateSession (recoveryId: string): Promise<void> {
await this.tmux.terminate(recoveryId)
}
}