mirror of
https://github.com/Eugeny/tabby.git
synced 2024-12-28 04:56:16 +03:00
.
This commit is contained in:
parent
125ec2b81c
commit
acc13087bf
2
Makefile
2
Makefile
@ -6,7 +6,7 @@ SHORT_VERSION=$(shell python -c 'import subprocess; v = subprocess.check_output(
|
||||
all: run
|
||||
|
||||
run:
|
||||
DEV=1 TERMINUS_PLUGINS=`realpath .` ./node_modules/.bin/electron ./app --debug
|
||||
DEV=1 TERMINUS_PLUGINS=$$PWD ./node_modules/.bin/electron ./app --debug
|
||||
|
||||
lint:
|
||||
tslint -c tslint.json app/src/*.ts app/src/**/*.ts
|
||||
|
@ -3,6 +3,7 @@
|
||||
import 'core-js'
|
||||
import 'zone.js/dist/zone.js'
|
||||
import 'core-js/es7/reflect'
|
||||
import 'rxjs'
|
||||
|
||||
// Always land on the start view
|
||||
location.hash = ''
|
||||
|
@ -5,7 +5,7 @@ let nodeRequire = (<any>global).require
|
||||
let module = nodeRequire('module')
|
||||
nodeRequire.main.paths.map(x => module.globalPaths.push(x))
|
||||
if (process.env.TERMINUS_PLUGINS) {
|
||||
process.env.TERMINUS_PLUGINS.split(':').map(x => module.globalPaths.push(x))
|
||||
process.env.TERMINUS_PLUGINS.split(':').map(x => module.globalPaths.unshift(x))
|
||||
}
|
||||
|
||||
export async function loadPlugins (): Promise<any[]> {
|
||||
|
@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": false,
|
||||
|
@ -21,7 +21,7 @@ export class ColorSchemes extends TerminalColorSchemeProvider {
|
||||
})
|
||||
|
||||
schemes.push({
|
||||
name: schemeFile.split('/')[1],
|
||||
name: schemeFile.split('/')[1].trim(),
|
||||
foreground: values.foreground,
|
||||
background: values.background,
|
||||
cursor: values.cursorColor,
|
||||
|
@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": false,
|
||||
|
@ -40,7 +40,7 @@ import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||
})
|
||||
export class AppRootComponent {
|
||||
toasterConfig: ToasterConfig
|
||||
logger: Logger
|
||||
private logger: Logger
|
||||
|
||||
constructor(
|
||||
private docking: DockingService,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { EventEmitter, ViewRef } from '@angular/core'
|
||||
import { Subject, BehaviorSubject } from 'rxjs'
|
||||
import { ViewRef } from '@angular/core'
|
||||
|
||||
|
||||
export abstract class BaseTabComponent {
|
||||
@ -7,13 +7,20 @@ export abstract class BaseTabComponent {
|
||||
title$ = new BehaviorSubject<string>(null)
|
||||
scrollable: boolean
|
||||
hasActivity = false
|
||||
focused = new EventEmitter<any>()
|
||||
blurred = new EventEmitter<any>()
|
||||
focused$ = new Subject<void>()
|
||||
blurred$ = new Subject<void>()
|
||||
hasFocus = false
|
||||
hostView: ViewRef
|
||||
private static lastTabID = 0
|
||||
|
||||
constructor () {
|
||||
this.id = BaseTabComponent.lastTabID++
|
||||
this.focused$.subscribe(() => {
|
||||
this.hasFocus = true
|
||||
})
|
||||
this.blurred$.subscribe(() => {
|
||||
this.hasFocus = false
|
||||
})
|
||||
}
|
||||
|
||||
displayActivity (): void {
|
||||
@ -25,5 +32,8 @@ export abstract class BaseTabComponent {
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
this.focused$.complete()
|
||||
this.blurred$.complete()
|
||||
this.title$.complete()
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,10 @@ import { BaseTabComponent } from '../components/baseTab.component'
|
||||
@Component({
|
||||
selector: 'tab-body',
|
||||
template: `
|
||||
<perfect-scrollbar [config]="{ suppressScrollX: true, suppressScrollY: !scrollable}">
|
||||
<ng-template #placeholder></ng-template>
|
||||
<perfect-scrollbar [config]="{ suppressScrollX: true }" *ngIf="scrollable">
|
||||
<ng-template #scrollablePlaceholder></ng-template>
|
||||
</perfect-scrollbar>
|
||||
<template #nonScrollablePlaceholder [ngIf]="!scrollable"></template>
|
||||
`,
|
||||
styles: [
|
||||
require('./tabBody.component.scss'),
|
||||
@ -17,11 +18,12 @@ export class TabBodyComponent {
|
||||
@Input() @HostBinding('class.active') active: boolean
|
||||
@Input() tab: BaseTabComponent
|
||||
@Input() scrollable: boolean
|
||||
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
||||
@ViewChild('scrollablePlaceholder', {read: ViewContainerRef}) scrollablePlaceholder: ViewContainerRef
|
||||
@ViewChild('nonScrollablePlaceholder', {read: ViewContainerRef}) nonScrollablePlaceholder: ViewContainerRef
|
||||
|
||||
ngAfterViewInit () {
|
||||
setImmediate(() => {
|
||||
this.placeholder.insert(this.tab.hostView)
|
||||
(this.scrollable ? this.scrollablePlaceholder : this.nonScrollablePlaceholder).insert(this.tab.hostView)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -51,11 +51,11 @@ export class AppService {
|
||||
}
|
||||
if (this.activeTab) {
|
||||
this.activeTab.hasActivity = false
|
||||
this.activeTab.blurred.emit()
|
||||
this.activeTab.blurred$.next()
|
||||
}
|
||||
this.activeTab = tab
|
||||
if (this.activeTab) {
|
||||
this.activeTab.focused.emit()
|
||||
this.activeTab.focused$.next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": false,
|
||||
|
@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": false,
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { Observable } from 'rxjs/Observable'
|
||||
import 'rxjs/add/operator/map'
|
||||
import 'rxjs/add/operator/debounceTime'
|
||||
import 'rxjs/add/operator/distinctUntilChanged'
|
||||
import 'rxjs'
|
||||
import { Observable } from 'rxjs'
|
||||
import * as fs from 'fs-promise'
|
||||
const fontManager = require('font-manager')
|
||||
const equal = require('deep-equal')
|
||||
@ -45,7 +43,8 @@ export class TerminalSettingsTabComponent {
|
||||
.map(x => x.split(',')[0].trim())
|
||||
this.fonts.sort()
|
||||
})
|
||||
|
||||
}
|
||||
if (this.hostApp.platform == Platform.Linux || this.hostApp.platform == Platform.macOS) {
|
||||
this.shells = (await fs.readFile('/etc/shells', 'utf-8'))
|
||||
.split('\n')
|
||||
.map(x => x.trim())
|
||||
|
@ -16,7 +16,6 @@ import { hterm, preferenceManager } from '../hterm'
|
||||
export class TerminalTabComponent extends BaseTabComponent {
|
||||
hterm: any
|
||||
configSubscription: Subscription
|
||||
focusedSubscription: Subscription
|
||||
bell$ = new Subject()
|
||||
size$ = new ReplaySubject<ResizeEvent>(1)
|
||||
input$ = new Subject<string>()
|
||||
@ -49,8 +48,9 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.focusedSubscription = this.focused.subscribe(() => {
|
||||
this.focused$.subscribe(() => {
|
||||
this.hterm.scrollPort_.focus()
|
||||
this.hterm.scrollPort_.resize()
|
||||
})
|
||||
|
||||
this.hterm = new hterm.hterm.Terminal()
|
||||
@ -123,6 +123,14 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const _resize = hterm.scrollPort_.resize.bind(hterm.scrollPort_)
|
||||
hterm.scrollPort_.resize = () => {
|
||||
if (!this.hasFocus) {
|
||||
return
|
||||
}
|
||||
_resize()
|
||||
}
|
||||
|
||||
const _onMouse_ = hterm.onMouse_.bind(hterm)
|
||||
hterm.onMouse_ = (event) => {
|
||||
this.mouseEvent$.next(event)
|
||||
@ -216,9 +224,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
this.decorators.forEach((decorator) => {
|
||||
decorator.detach(this)
|
||||
})
|
||||
this.focusedSubscription.unsubscribe()
|
||||
this.configSubscription.unsubscribe()
|
||||
this.title$.complete()
|
||||
this.size$.complete()
|
||||
this.input$.complete()
|
||||
this.output$.complete()
|
||||
|
@ -1,10 +1,40 @@
|
||||
import * as fs from 'fs-promise'
|
||||
const { exec, spawn } = require('child-process-promise')
|
||||
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Logger, LogService } from 'terminus-core'
|
||||
import { SessionOptions, SessionPersistenceProvider } from './api'
|
||||
|
||||
|
||||
interface IChildProcess {
|
||||
pid: number
|
||||
ppid: number
|
||||
command: string
|
||||
}
|
||||
|
||||
async function listProcesses (): Promise<IChildProcess[]> {
|
||||
return (await exec(`ps -A -o pid,ppid,command`)).stdout
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.map(line => line.split(' ').filter(x => x).slice(0, 3))
|
||||
.map(([pid, ppid, command]) => {
|
||||
return {
|
||||
pid: parseInt(pid), ppid: parseInt(ppid), command
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||
private logger: Logger
|
||||
|
||||
constructor (
|
||||
log: LogService,
|
||||
) {
|
||||
super()
|
||||
this.logger = log.create('main')
|
||||
}
|
||||
|
||||
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||
let lines: string[]
|
||||
try {
|
||||
@ -20,8 +50,16 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||
return null
|
||||
}
|
||||
|
||||
lines = (await exec(`pgrep -P ${screenPID}`)).stdout.split('\n')
|
||||
let recoveredTruePID = parseInt(lines[0])
|
||||
let child = (await listProcesses()).find(x => x.ppid === screenPID)
|
||||
|
||||
if (!child) {
|
||||
this.logger.error(`Could not find any children of the screen process (PID ${screenPID})!`)
|
||||
}
|
||||
if (child.command == 'login') {
|
||||
child = (await listProcesses()).find(x => x.ppid === child.pid)
|
||||
}
|
||||
|
||||
let recoveredTruePID = child.pid
|
||||
|
||||
return {
|
||||
recoveryId,
|
||||
@ -36,6 +74,7 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||
await fs.writeFile(configPath, `
|
||||
escape ^^^
|
||||
vbell on
|
||||
deflogin off
|
||||
term xterm-color
|
||||
bindkey "^[OH" beginning-of-line
|
||||
bindkey "^[OF" end-of-line
|
||||
@ -48,6 +87,7 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||
`, 'utf-8')
|
||||
let recoveryId = `term-tab-${Date.now()}`
|
||||
let args = ['-d', '-m', '-c', configPath, '-U', '-S', recoveryId, '-T', 'xterm-256color', '--', options.command].concat(options.args || [])
|
||||
this.logger.debug('Spawning screen with', args.join(' '))
|
||||
await spawn('screen', args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env || process.env,
|
||||
|
@ -113,8 +113,8 @@ export class Session {
|
||||
|
||||
async getWorkingDirectory (): Promise<string> {
|
||||
if (process.platform == 'darwin') {
|
||||
let lines = (await exec(`lsof -p ${this.truePID} -Fn`)).split('\n')
|
||||
return lines[2]
|
||||
let lines = (await exec(`lsof -p ${this.truePID} -Fn`)).stdout.split('\n')
|
||||
return lines[2].substring(1)
|
||||
}
|
||||
if (process.platform == 'linux') {
|
||||
return await fs.readlink(`/proc/${this.truePID}/cwd`)
|
||||
|
@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": false,
|
||||
|
@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": false,
|
||||
|
Loading…
Reference in New Issue
Block a user