1
1
mirror of https://github.com/Eugeny/tabby.git synced 2024-12-28 04:56:16 +03:00
This commit is contained in:
Eugene Pankov 2017-04-16 23:04:29 +02:00
parent 125ec2b81c
commit acc13087bf
18 changed files with 91 additions and 33 deletions

View File

@ -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

View File

@ -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 = ''

View File

@ -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[]> {

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl": "src",
"module": "commonjs",
"target": "es5",
"target": "es2015",
"declaration": false,
"noImplicitAny": false,
"removeComments": false,

View File

@ -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,

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl": "src",
"module": "commonjs",
"target": "es5",
"target": "es2015",
"declaration": false,
"noImplicitAny": false,
"removeComments": false,

View File

@ -40,7 +40,7 @@ import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
})
export class AppRootComponent {
toasterConfig: ToasterConfig
logger: Logger
private logger: Logger
constructor(
private docking: DockingService,

View File

@ -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()
}
}

View File

@ -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)
})
}
}

View File

@ -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()
}
}

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl": "./src",
"module": "commonjs",
"target": "es5",
"target": "es2015",
"declaration": false,
"noImplicitAny": false,
"removeComments": false,

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl": "./src",
"module": "commonjs",
"target": "es5",
"target": "es2015",
"declaration": false,
"noImplicitAny": false,
"removeComments": false,

View File

@ -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())

View File

@ -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()

View File

@ -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,

View File

@ -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`)

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl": "src",
"module": "commonjs",
"target": "es5",
"target": "es2015",
"declaration": false,
"noImplicitAny": false,
"removeComments": false,

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl": "src",
"module": "commonjs",
"target": "es5",
"target": "es2015",
"declaration": false,
"noImplicitAny": false,
"removeComments": false,