mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-10 02:21:50 +00:00
tmux wip
This commit is contained in:
@@ -37,6 +37,8 @@
|
|||||||
"terminus-settings": "*"
|
"terminus-settings": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/async-lock": "0.0.19",
|
||||||
|
"async-lock": "^1.0.0",
|
||||||
"font-manager": "0.2.2",
|
"font-manager": "0.2.2",
|
||||||
"hterm-umdjs": "1.1.3",
|
"hterm-umdjs": "1.1.3",
|
||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 15px;
|
margin: 15px;
|
||||||
transition: opacity ease-out 0.1s;
|
transition: opacity ease-out 0.25s;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
div[style]:last-child {
|
div[style]:last-child {
|
||||||
|
@@ -66,3 +66,9 @@ hterm.hterm.VT.CSI[' q'] = function (parseState) {
|
|||||||
this.terminal.cursorMode = arg
|
this.terminal.cursorMode = arg
|
||||||
this.terminal.applyCursorShape()
|
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 { ShellsService } from './services/shells.service'
|
||||||
|
|
||||||
import { ScreenPersistenceProvider } from './persistenceProviders'
|
import { ScreenPersistenceProvider } from './persistenceProviders'
|
||||||
|
import { TMuxPersistenceProvider } from './tmux'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { RecoveryProvider } from './recoveryProvider'
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator } from './api'
|
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator } from './api'
|
||||||
@@ -34,18 +35,23 @@ import { hterm } from './hterm'
|
|||||||
SessionsService,
|
SessionsService,
|
||||||
ShellsService,
|
ShellsService,
|
||||||
ScreenPersistenceProvider,
|
ScreenPersistenceProvider,
|
||||||
|
TMuxPersistenceProvider,
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
{
|
{
|
||||||
provide: SessionPersistenceProvider,
|
provide: SessionPersistenceProvider,
|
||||||
useFactory: (hostApp: HostAppService, screen: ScreenPersistenceProvider) => {
|
useFactory: (
|
||||||
|
hostApp: HostAppService,
|
||||||
|
screen: ScreenPersistenceProvider,
|
||||||
|
tmux: TMuxPersistenceProvider,
|
||||||
|
) => {
|
||||||
if (hostApp.platform === Platform.Windows) {
|
if (hostApp.platform === Platform.Windows) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
return screen
|
return tmux
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deps: [HostAppService, ScreenPersistenceProvider],
|
deps: [HostAppService, ScreenPersistenceProvider, TMuxPersistenceProvider],
|
||||||
},
|
},
|
||||||
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: TerminalConfigProvider, 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)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user