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