mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-20 11:29:56 +00:00
selectable persistence providers
This commit is contained in:
parent
514fdbfb6a
commit
1c62f3074c
@ -28,6 +28,10 @@ export interface SessionOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class SessionPersistenceProvider {
|
export abstract class SessionPersistenceProvider {
|
||||||
|
abstract id: string
|
||||||
|
abstract displayName: string
|
||||||
|
|
||||||
|
abstract isAvailable (): boolean
|
||||||
abstract async attachSession (recoveryId: any): Promise<SessionOptions>
|
abstract async attachSession (recoveryId: any): Promise<SessionOptions>
|
||||||
abstract async startSession (options: SessionOptions): Promise<any>
|
abstract async startSession (options: SessionOptions): Promise<any>
|
||||||
abstract async terminateSession (recoveryId: string): Promise<void>
|
abstract async terminateSession (recoveryId: string): Promise<void>
|
||||||
|
@ -259,3 +259,15 @@
|
|||||||
[value]='"audible"'
|
[value]='"audible"'
|
||||||
)
|
)
|
||||||
| Audible
|
| Audible
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Session persistence
|
||||||
|
select.form-control(
|
||||||
|
'[(ngModel)]'='config.store.terminal.persistence',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
option([ngValue]='null') Off
|
||||||
|
option(
|
||||||
|
*ngFor='let provider of persistenceProviders',
|
||||||
|
[ngValue]='provider.id'
|
||||||
|
) {{provider.displayName}}
|
||||||
|
@ -5,7 +5,7 @@ const fontManager = require('font-manager')
|
|||||||
|
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
import { ConfigService, HostAppService, Platform } from 'terminus-core'
|
import { ConfigService, HostAppService, Platform } from 'terminus-core'
|
||||||
import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvider } from '../api'
|
import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvider, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: require('./terminalSettingsTab.component.pug'),
|
template: require('./terminalSettingsTab.component.pug'),
|
||||||
@ -14,6 +14,7 @@ import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvide
|
|||||||
export class TerminalSettingsTabComponent {
|
export class TerminalSettingsTabComponent {
|
||||||
fonts: string[] = []
|
fonts: string[] = []
|
||||||
shells: IShell[] = []
|
shells: IShell[] = []
|
||||||
|
persistenceProviders: SessionPersistenceProvider[]
|
||||||
colorSchemes: ITerminalColorScheme[] = []
|
colorSchemes: ITerminalColorScheme[] = []
|
||||||
equalComparator = equal
|
equalComparator = equal
|
||||||
editingColorScheme: ITerminalColorScheme
|
editingColorScheme: ITerminalColorScheme
|
||||||
@ -24,7 +25,10 @@ export class TerminalSettingsTabComponent {
|
|||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
@Inject(ShellProvider) private shellProviders: ShellProvider[],
|
@Inject(ShellProvider) private shellProviders: ShellProvider[],
|
||||||
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
||||||
) { }
|
@Inject(SessionPersistenceProvider) persistenceProviders: SessionPersistenceProvider[],
|
||||||
|
) {
|
||||||
|
this.persistenceProviders = persistenceProviders.filter(x => x.isAvailable())
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) {
|
if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) {
|
||||||
|
@ -43,6 +43,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
terminal: {
|
terminal: {
|
||||||
font: 'Menlo',
|
font: 'Menlo',
|
||||||
shell: '~default-shell~',
|
shell: '~default-shell~',
|
||||||
|
persistence: 'screen',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
'copy': [
|
'copy': [
|
||||||
@ -74,6 +75,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
terminal: {
|
terminal: {
|
||||||
font: 'Consolas',
|
font: 'Consolas',
|
||||||
shell: '~clink~',
|
shell: '~clink~',
|
||||||
|
persistence: null,
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
'copy': [
|
'copy': [
|
||||||
@ -104,6 +106,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
terminal: {
|
terminal: {
|
||||||
font: 'Liberation Mono',
|
font: 'Liberation Mono',
|
||||||
shell: '~default-shell~',
|
shell: '~default-shell~',
|
||||||
|
persistence: 'screen',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
'copy': [
|
'copy': [
|
||||||
|
@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser'
|
|||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
import { HostAppService, Platform, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider } from 'terminus-core'
|
import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider } from 'terminus-core'
|
||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
@ -12,8 +12,8 @@ import { ColorPickerComponent } from './components/colorPicker.component'
|
|||||||
|
|
||||||
import { SessionsService } from './services/sessions.service'
|
import { SessionsService } from './services/sessions.service'
|
||||||
|
|
||||||
import { ScreenPersistenceProvider } from './persistenceProviders'
|
import { ScreenPersistenceProvider } from './persistence/screen'
|
||||||
import { TMuxPersistenceProvider } from './tmux'
|
import { TMuxPersistenceProvider } from './persistence/tmux'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { RecoveryProvider } from './recoveryProvider'
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator, ShellProvider } from './api'
|
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator, ShellProvider } from './api'
|
||||||
@ -42,36 +42,17 @@ import { hterm } from './hterm'
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SessionsService,
|
SessionsService,
|
||||||
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,
|
|
||||||
useFactory: (
|
|
||||||
hostApp: HostAppService,
|
|
||||||
screen: ScreenPersistenceProvider,
|
|
||||||
tmux: TMuxPersistenceProvider,
|
|
||||||
) => {
|
|
||||||
if (hostApp.platform === Platform.Windows) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
if (tmux.isAvailable()) {
|
|
||||||
tmux.init()
|
|
||||||
return tmux
|
|
||||||
} else {
|
|
||||||
return screen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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 },
|
||||||
{ provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
|
{ provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
|
||||||
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
|
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
|
||||||
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
|
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
|
||||||
|
|
||||||
|
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider, multi: true },
|
||||||
|
{ provide: SessionPersistenceProvider, useClass: TMuxPersistenceProvider, multi: true },
|
||||||
|
|
||||||
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
|
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
|
||||||
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
|
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
|
||||||
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
|
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { exec, spawn } from 'mz/child_process'
|
import { exec, spawn } from 'mz/child_process'
|
||||||
import { exec as execCallback } from 'child_process'
|
import { exec as execAsync, execFileSync } from 'child_process'
|
||||||
|
|
||||||
import { AsyncSubject } from 'rxjs'
|
import { AsyncSubject } from 'rxjs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Logger, LogService } from 'terminus-core'
|
import { Logger, LogService } from 'terminus-core'
|
||||||
import { SessionOptions, SessionPersistenceProvider } from './api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
declare function delay (ms: number): Promise<void>
|
declare function delay (ms: number): Promise<void>
|
||||||
|
|
||||||
@ -29,6 +29,8 @@ async function listProcesses (): Promise<IChildProcess[]> {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||||
|
id = 'screen'
|
||||||
|
displayName = 'GNU Screen'
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@ -38,9 +40,18 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
this.logger = log.create('main')
|
this.logger = log.create('main')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAvailable () {
|
||||||
|
try {
|
||||||
|
execFileSync('sh', ['-c', 'which screen'])
|
||||||
|
return true
|
||||||
|
} catch (_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||||
let lines = await new Promise<string[]>(resolve => {
|
let lines = await new Promise<string[]>(resolve => {
|
||||||
execCallback('screen -list', (_err, stdout) => {
|
execAsync('screen -list', (_err, stdout) => {
|
||||||
// returns an error code on macOS
|
// returns an error code on macOS
|
||||||
resolve(stdout.split('\n'))
|
resolve(stdout.split('\n'))
|
||||||
})
|
})
|
@ -3,7 +3,7 @@ import { execFileSync } from 'child_process'
|
|||||||
import * as AsyncLock from 'async-lock'
|
import * as AsyncLock from 'async-lock'
|
||||||
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
||||||
import * as childProcess from 'child_process'
|
import * as childProcess from 'child_process'
|
||||||
import { SessionOptions, SessionPersistenceProvider } from './api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
const TMUX_CONFIG = `
|
const TMUX_CONFIG = `
|
||||||
set -g status off
|
set -g status off
|
||||||
@ -174,10 +174,15 @@ export class TMux {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TMuxPersistenceProvider extends SessionPersistenceProvider {
|
export class TMuxPersistenceProvider extends SessionPersistenceProvider {
|
||||||
|
id = 'tmux'
|
||||||
|
displayName = 'Tmux'
|
||||||
private tmux: TMux
|
private tmux: TMux
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
super()
|
super()
|
||||||
|
if (this.isAvailable()) {
|
||||||
|
this.tmux = new TMux()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isAvailable (): boolean {
|
isAvailable (): boolean {
|
||||||
@ -189,10 +194,6 @@ export class TMuxPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init () {
|
|
||||||
this.tmux = new TMux()
|
|
||||||
}
|
|
||||||
|
|
||||||
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||||
let sessions = await this.tmux.list()
|
let sessions = await this.tmux.list()
|
||||||
if (!sessions.includes(recoveryId)) {
|
if (!sessions.includes(recoveryId)) {
|
@ -3,8 +3,8 @@ const psNode = require('ps-node')
|
|||||||
let nodePTY
|
let nodePTY
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable, Inject } from '@angular/core'
|
||||||
import { Logger, LogService, ElectronService } from 'terminus-core'
|
import { Logger, LogService, ElectronService, ConfigService } from 'terminus-core'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
|
|
||||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
@ -178,7 +178,8 @@ export class SessionsService {
|
|||||||
private lastID = 0
|
private lastID = 0
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private persistence: SessionPersistenceProvider,
|
@Inject(SessionPersistenceProvider) private persistenceProviders: SessionPersistenceProvider[],
|
||||||
|
private config: ConfigService,
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
@ -187,9 +188,10 @@ export class SessionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async prepareNewSession (options: SessionOptions): Promise<SessionOptions> {
|
async prepareNewSession (options: SessionOptions): Promise<SessionOptions> {
|
||||||
if (this.persistence) {
|
let persistence = this.getPersistence()
|
||||||
let recoveryId = await this.persistence.startSession(options)
|
if (persistence) {
|
||||||
options = await this.persistence.attachSession(recoveryId)
|
let recoveryId = await persistence.startSession(options)
|
||||||
|
options = await persistence.attachSession(recoveryId)
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
@ -198,10 +200,11 @@ export class SessionsService {
|
|||||||
this.lastID++
|
this.lastID++
|
||||||
options.name = `session-${this.lastID}`
|
options.name = `session-${this.lastID}`
|
||||||
let session = new Session(options)
|
let session = new Session(options)
|
||||||
|
let persistence = this.getPersistence()
|
||||||
session.destroyed$.first().subscribe(() => {
|
session.destroyed$.first().subscribe(() => {
|
||||||
delete this.sessions[session.name]
|
delete this.sessions[session.name]
|
||||||
if (this.persistence) {
|
if (persistence) {
|
||||||
this.persistence.terminateSession(session.recoveryId)
|
persistence.terminateSession(session.recoveryId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.sessions[session.name] = session
|
this.sessions[session.name] = session
|
||||||
@ -209,9 +212,14 @@ export class SessionsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async recover (recoveryId: string): Promise<SessionOptions> {
|
async recover (recoveryId: string): Promise<SessionOptions> {
|
||||||
if (!this.persistence) {
|
let persistence = this.getPersistence()
|
||||||
|
if (persistence) {
|
||||||
|
return await persistence.attachSession(recoveryId)
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return await this.persistence.attachSession(recoveryId)
|
|
||||||
|
private getPersistence (): SessionPersistenceProvider {
|
||||||
|
return this.persistenceProviders.find(x => x.id === this.config.store.terminal.persistence) || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user