mirror of
https://github.com/Eugeny/tabby.git
synced 2024-12-28 13:03:31 +03:00
tmux wip
This commit is contained in:
parent
de29e34363
commit
feb4c5bcb6
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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) { ; }
|
||||
}
|
||||
|
@ -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 },
|
||||
|
190
terminus-terminal/src/tmux.ts
Normal file
190
terminus-terminal/src/tmux.ts
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user