This commit is contained in:
Eugene Pankov 2017-04-02 17:33:55 +02:00
parent 90d7ee21ae
commit 2846637815
5 changed files with 47 additions and 33 deletions

View File

@ -9,10 +9,11 @@ export interface SessionOptions {
cwd?: string, cwd?: string,
env?: any, env?: any,
recoveryId?: string recoveryId?: string
recoveredTruePID?: number
} }
export abstract class SessionPersistenceProvider { export abstract class SessionPersistenceProvider {
abstract async recoverSession (recoveryId: any): Promise<SessionOptions> abstract async attachSession (recoveryId: any): Promise<SessionOptions>
abstract async createSession (options: SessionOptions): Promise<SessionOptions> abstract async startSession (options: SessionOptions): Promise<any>
abstract async terminateSession (recoveryId: string): Promise<void> abstract async terminateSession (recoveryId: string): Promise<void>
} }

View File

@ -20,7 +20,11 @@ export class ButtonProvider extends ToolbarButtonProvider {
} }
async getNewTab (): Promise<TerminalTab> { async getNewTab (): Promise<TerminalTab> {
return new TerminalTab(await this.sessions.createNewSession({ command: 'zsh' })) let cwd = null
if (this.app.activeTab instanceof TerminalTab) {
cwd = await this.app.activeTab.session.getWorkingDirectory()
}
return new TerminalTab(await this.sessions.createNewSession({ command: 'zsh', cwd }))
} }
provide (): IToolbarButton[] { provide (): IToolbarButton[] {

View File

@ -1,5 +1,5 @@
import { Subscription } from 'rxjs' import { BehaviorSubject, Subscription } from 'rxjs'
import { Component, NgZone, Output, Inject, EventEmitter, ElementRef } from '@angular/core' import { Component, NgZone, Inject, ElementRef } from '@angular/core'
import { ConfigService } from 'services/config' import { ConfigService } from 'services/config'
@ -16,9 +16,8 @@ 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> {
title: string
@Output() titleChange = new EventEmitter()
terminal: any terminal: any
title$ = new BehaviorSubject('')
configSubscription: Subscription configSubscription: Subscription
focusedSubscription: Subscription focusedSubscription: Subscription
startupTime: number startupTime: number
@ -47,8 +46,7 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
}) })
this.terminal.setWindowTitle = (title) => { this.terminal.setWindowTitle = (title) => {
this.zone.run(() => { this.zone.run(() => {
this.title = title this.model.title = title
this.titleChange.emit(title)
}) })
} }
this.terminal.onTerminalReady = () => { this.terminal.onTerminalReady = () => {

View File

@ -1,25 +1,12 @@
import * as fs from 'fs-promise' import * as fs from 'fs-promise'
const exec = require('child-process-promise').exec const exec = require('child-process-promise').exec
const spawn = require('child-process-promise').spawn
import { SessionOptions, SessionPersistenceProvider } from './api' import { SessionOptions, SessionPersistenceProvider } from './api'
export class NullPersistenceProvider extends SessionPersistenceProvider {
async recoverSession (_recoveryId: any): Promise<SessionOptions> {
return null
}
async createSession (_options: SessionOptions): Promise<SessionOptions> {
return null
}
async terminateSession (_recoveryId: string): Promise<void> {
return
}
}
export class ScreenPersistenceProvider extends SessionPersistenceProvider { export class ScreenPersistenceProvider extends SessionPersistenceProvider {
/*
list(): Promise<any[]> { list(): Promise<any[]> {
return exec('screen -list').then((result) => { return exec('screen -list').then((result) => {
return result.stdout.split('\n') return result.stdout.split('\n')
@ -29,17 +16,30 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
return [] return []
}) })
} }
*/
async attachSession (recoveryId: any): Promise<SessionOptions> {
let lines = (await exec('screen -list')).stdout.split('\n')
let screenPID = lines
.filter(line => line.indexOf('.' + recoveryId) !== -1)
.map(line => parseInt(line.trim().split('.')[0]))[0]
if (!screenPID) {
return null
}
lines = (await exec(`ps -o pid --ppid ${screenPID}`)).stdout.split('\n')
let recoveredTruePID = parseInt(lines[1].split(/\s/).filter(x => !!x)[0])
async recoverSession (recoveryId: any): Promise<SessionOptions> {
// TODO check
return { return {
recoveryId, recoveryId,
recoveredTruePID,
command: 'screen', command: 'screen',
args: ['-r', recoveryId], args: ['-r', recoveryId],
} }
} }
async createSession (options: SessionOptions): Promise<SessionOptions> { async startSession (options: SessionOptions): Promise<any> {
let configPath = '/tmp/.termScreenConfig' let configPath = '/tmp/.termScreenConfig'
await fs.writeFile(configPath, ` await fs.writeFile(configPath, `
escape ^^^ escape ^^^
@ -52,10 +52,12 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
hardstatus off hardstatus off
`, 'utf-8') `, 'utf-8')
let recoveryId = `term-tab-${Date.now()}` let recoveryId = `term-tab-${Date.now()}`
options.args = ['-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || []) let args = ['-d', '-m', '-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || [])
options.command = 'screen' await spawn('screen', args, {
options.recoveryId = recoveryId cwd: options.cwd,
return options env: options.env || process.env,
})
return recoveryId
} }
async terminateSession (recoveryId: string): Promise<void> { async terminateSession (recoveryId: string): Promise<void> {

View File

@ -1,4 +1,5 @@
import * as nodePTY from 'node-pty' import * as nodePTY from 'node-pty'
import * as fs from 'fs-promise'
import { Injectable, EventEmitter } from '@angular/core' import { Injectable, EventEmitter } from '@angular/core'
import { Logger, LogService } from 'services/log' import { Logger, LogService } from 'services/log'
@ -12,6 +13,7 @@ export class Session {
closed = new EventEmitter() closed = new EventEmitter()
destroyed = new EventEmitter() destroyed = new EventEmitter()
recoveryId: string recoveryId: string
truePID: number
private pty: any private pty: any
private initialDataBuffer = '' private initialDataBuffer = ''
private initialDataBufferReleased = false private initialDataBufferReleased = false
@ -40,6 +42,8 @@ export class Session {
env: env, env: env,
}) })
this.truePID = options.recoveredTruePID || (<any>this.pty).pid
this.open = true this.open = true
this.pty.on('data', (data) => { this.pty.on('data', (data) => {
@ -105,6 +109,10 @@ export class Session {
this.destroyed.emit() this.destroyed.emit()
this.pty.destroy() this.pty.destroy()
} }
async getWorkingDirectory (): Promise<string> {
return await fs.readlink(`/proc/${this.truePID}/cwd`)
}
} }
@ -122,7 +130,8 @@ export class SessionsService {
} }
async createNewSession (options: SessionOptions) : Promise<Session> { async createNewSession (options: SessionOptions) : Promise<Session> {
options = await this.persistence.createSession(options) let recoveryId = await this.persistence.startSession(options)
options = await this.persistence.attachSession(recoveryId)
let session = this.addSession(options) let session = this.addSession(options)
return session return session
} }
@ -141,7 +150,7 @@ export class SessionsService {
} }
async recover (recoveryId: string) : Promise<Session> { async recover (recoveryId: string) : Promise<Session> {
const options = await this.persistence.recoverSession(recoveryId) const options = await this.persistence.attachSession(recoveryId)
if (!options) { if (!options) {
return null return null
} }