Compare commits

...

22 Commits

Author SHA1 Message Date
Eugene Pankov
11f188f1e8 layout fix 2017-11-16 22:54:37 +01:00
Eugene Pankov
9a9db28054 Merge branch 'master' of github.com:Eugeny/terminus 2017-11-16 22:49:23 +01:00
Eugene Pankov
47d57d08ee rearranged terminal settings tab 2017-11-16 22:46:33 +01:00
Eugene Pankov
39e2c386f0 copy-on-select and right click behaviour settings (fixes #201) 2017-11-16 22:37:41 +01:00
Eugene Pankov
c73d39026b API updates 2017-11-04 19:06:58 +01:00
Eugene Pankov
89dff969b1 bumped plugins 2017-10-31 15:27:28 +01:00
Eugene Pankov
e1eb1beb87 log into a file 2017-10-23 22:58:19 +02:00
Eugene Pankov
8d12d6a547 allow specifying a custom startup directory (fixes #223) 2017-10-23 21:54:40 +02:00
Eugene Pankov
33f67503bd pass alt-numbers to the shell (fixes #217) 2017-10-22 22:10:48 +02:00
Eugene Pankov
21e1656780 only show drag space on Win & Linux with thin titlebar (ref #219) 2017-10-22 21:23:53 +02:00
Eugene Pankov
ceacf5c760 added tab context menu (ref #219) 2017-10-22 21:01:31 +02:00
Eugene Pankov
e0c0cd17bd Handle multiple arguments in custom shell 2017-10-21 22:11:27 +02:00
Eugene Pankov
e81e5034b9 explicitly specify --login for POSIX shells 2017-10-20 21:44:34 +02:00
Eugene Pankov
11e0c36ebc properly position context menu (fixes #215) 2017-10-13 20:33:10 +02:00
Eugene Pankov
e52fd0a3dd theming fix 2017-10-08 14:56:51 +02:00
Eugene Pankov
48ccc538e5 fixed button group appearance in settings 2017-10-08 14:48:59 +02:00
Eugene Pankov
53ac39232c a compact theme 2017-10-08 14:47:14 +02:00
Eugene Pankov
f68e06c9ed . 2017-10-07 18:09:22 +02:00
Eugene Pankov
6c884e090c blinking cursor (fixes #191) 2017-10-07 18:07:57 +02:00
Eugene Pankov
38cda117e2 option to auto-start a terminal tab (fixes #107) 2017-10-07 17:47:04 +02:00
Eugene Pankov
fb64ca08d3 custom shells (fixes #50) 2017-10-07 17:27:58 +02:00
Eugene Pankov
0fe7edc5b5 auto reposition window on resolution changes (fixes #150) 2017-10-07 16:47:37 +02:00
40 changed files with 543 additions and 141 deletions

View File

@@ -115,7 +115,7 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
try { try {
let info = JSON.parse(await fs.readFile(infoPath, {encoding: 'utf-8'})) let info = JSON.parse(await fs.readFile(infoPath, {encoding: 'utf-8'}))
if (!info.keywords || info.keywords.indexOf('terminus-plugin') === -1) { if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'))) {
continue continue
} }
let author = info.author let author = info.author

View File

@@ -58,3 +58,7 @@
color: #842fe0; color: #842fe0;
} }
} }
.modal-dialog {
-webkit-app-region: no-drag;
}

View File

@@ -1,9 +1,9 @@
{ {
"name": "terminus-community-color-schemes", "name": "terminus-community-color-schemes",
"version": "1.0.0-alpha.24", "version": "1.0.0-alpha.35",
"description": "Community color schemes for Terminus", "description": "Community color schemes for Terminus",
"keywords": [ "keywords": [
"terminus-plugin" "terminus-builtin-plugin"
], ],
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",

View File

@@ -1,9 +1,9 @@
{ {
"name": "terminus-core", "name": "terminus-core",
"version": "1.0.0-alpha.24", "version": "1.0.0-alpha.35.1",
"description": "Terminus core", "description": "Terminus core",
"keywords": [ "keywords": [
"terminus-plugin" "terminus-builtin-plugin"
], ],
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
@@ -20,6 +20,7 @@
"@types/js-yaml": "^3.9.0", "@types/js-yaml": "^3.9.0",
"@types/node": "^7.0.37", "@types/node": "^7.0.37",
"@types/webpack-env": "^1.13.0", "@types/webpack-env": "^1.13.0",
"@types/winston": "^2.3.6",
"axios": "0.16.2", "axios": "0.16.2",
"bootstrap": "4.0.0-alpha.6", "bootstrap": "4.0.0-alpha.6",
"core-js": "^2.4.1", "core-js": "^2.4.1",
@@ -39,7 +40,8 @@
}, },
"dependencies": { "dependencies": {
"deepmerge": "^1.5.0", "deepmerge": "^1.5.0",
"js-yaml": "^3.9.0" "js-yaml": "^3.9.0",
"winston": "^2.4.0"
}, },
"false": {} "false": {}
} }

View File

@@ -19,7 +19,6 @@ title-bar(
[class.drag-region]='hostApp.platform == Platform.macOS', [class.drag-region]='hostApp.platform == Platform.macOS',
@animateTab, @animateTab,
(click)='app.selectTab(tab)', (click)='app.selectTab(tab)',
(closeClicked)='app.closeTab(tab, true)',
) )
.btn-group .btn-group
@@ -30,7 +29,7 @@ title-bar(
) )
i.fa([class]='"fa fa-" + button.icon') i.fa([class]='"fa fa-" + button.icon')
.drag-space .drag-space(*ngIf='config.store.appearance.frame == "thin" && hostApp.platform != Platform.macOS')
.btn-group .btn-group
button.btn.btn-secondary.btn-tab-bar( button.btn.btn-secondary.btn-tab-bar(

View File

@@ -150,6 +150,8 @@ export class AppRootComponent {
if (this.app.tabs.length === 0) { if (this.app.tabs.length === 0) {
this.app.openDefaultTab() this.app.openDefaultTab()
} }
this.app.emitReady()
} }
@HostListener('dragover') @HostListener('dragover')

View File

@@ -1,3 +1,3 @@
.index {{index + 1}} .index {{index + 1}}
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}} .name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
button((click)='closeClicked.emit()') &times; button((click)='app.closeTab(tab, true)') &times;

View File

@@ -1,7 +1,6 @@
$tabs-height: 36px; $tabs-height: 36px;
:host { :host {
line-height: $tabs-height - 2px;
cursor: pointer; cursor: pointer;
flex: 1000 1 200px; flex: 1000 1 200px;
@@ -24,9 +23,9 @@ $tabs-height: 36px;
margin-left: 10px; margin-left: 10px;
width: 20px; width: 20px;
border-radius: 10px; border-radius: 10px;
line-height: 35px;
text-align: center; text-align: center;
transition: 0.25s all; transition: 0.25s all;
align-self: center;
} }
.name { .name {
@@ -36,6 +35,7 @@ $tabs-height: 36px;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
min-width: 0; min-width: 0;
align-self: center;
} }
button { button {
@@ -49,7 +49,7 @@ $tabs-height: 36px;
height: $button-size; height: $button-size;
border-radius: $button-size / 2; border-radius: $button-size / 2;
line-height: $button-size * 0.87; line-height: $button-size * 0.87;
margin-top: ($tabs-height - $button-size) * 0.5; align-self: center;
margin-right: 10px; margin-right: 10px;
text-align: center; text-align: center;

View File

@@ -1,7 +1,9 @@
import { Component, Input, Output, EventEmitter, HostBinding, HostListener } from '@angular/core' import { Component, Input, HostBinding, HostListener, NgZone } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { BaseTabComponent } from './baseTab.component' import { BaseTabComponent } from './baseTab.component'
import { RenameTabModalComponent } from './renameTabModal.component' import { RenameTabModalComponent } from './renameTabModal.component'
import { ElectronService } from '../services/electron.service'
import { AppService } from '../services/app.service'
@Component({ @Component({
selector: 'tab-header', selector: 'tab-header',
@@ -13,11 +15,55 @@ export class TabHeaderComponent {
@Input() @HostBinding('class.active') active: boolean @Input() @HostBinding('class.active') active: boolean
@Input() @HostBinding('class.has-activity') hasActivity: boolean @Input() @HostBinding('class.has-activity') hasActivity: boolean
@Input() tab: BaseTabComponent @Input() tab: BaseTabComponent
@Output() closeClicked = new EventEmitter() private contextMenu: any
constructor ( constructor (
zone: NgZone,
electron: ElectronService,
public app: AppService,
private ngbModal: NgbModal, private ngbModal: NgbModal,
) { } ) {
this.contextMenu = electron.remote.Menu.buildFromTemplate([
{
label: 'Close',
click: () => {
zone.run(() => {
app.closeTab(this.tab, true)
})
}
},
{
label: 'Close other tabs',
click: () => {
zone.run(() => {
for (let tab of app.tabs.filter(x => x !== this.tab)) {
app.closeTab(tab, true)
}
})
}
},
{
label: 'Close tabs to the right',
click: () => {
zone.run(() => {
for (let tab of app.tabs.slice(app.tabs.indexOf(this.tab) + 1)) {
app.closeTab(tab, true)
}
})
}
},
{
label: 'Close tabs to the left',
click: () => {
zone.run(() => {
for (let tab of app.tabs.slice(0, app.tabs.indexOf(this.tab))) {
app.closeTab(tab, true)
}
})
}
},
])
}
@HostListener('dblclick') onDoubleClick (): void { @HostListener('dblclick') onDoubleClick (): void {
let modal = this.ngbModal.open(RenameTabModalComponent) let modal = this.ngbModal.open(RenameTabModalComponent)
@@ -29,7 +75,15 @@ export class TabHeaderComponent {
@HostListener('auxclick', ['$event']) onAuxClick ($event: MouseEvent): void { @HostListener('auxclick', ['$event']) onAuxClick ($event: MouseEvent): void {
if ($event.which === 2) { if ($event.which === 2) {
this.closeClicked.emit() this.app.closeTab(this.tab, true)
}
if ($event.which === 3) {
this.contextMenu.popup({
x: $event.pageX,
y: $event.pageY,
async: true,
})
event.preventDefault()
} }
} }
} }

View File

@@ -29,7 +29,7 @@ import { HotkeyProvider } from './api/hotkeyProvider'
import { ConfigProvider } from './api/configProvider' import { ConfigProvider } from './api/configProvider'
import { Theme } from './api/theme' import { Theme } from './api/theme'
import { StandardTheme } from './theme' import { StandardTheme, StandardCompactTheme } from './theme'
import { CoreConfigProvider } from './config' import { CoreConfigProvider } from './config'
import 'perfect-scrollbar/dist/css/perfect-scrollbar.css' import 'perfect-scrollbar/dist/css/perfect-scrollbar.css'
@@ -47,6 +47,7 @@ const PROVIDERS = [
UpdaterService, UpdaterService,
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true }, { provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
{ provide: Theme, useClass: StandardTheme, multi: true }, { provide: Theme, useClass: StandardTheme, multi: true },
{ provide: Theme, useClass: StandardCompactTheme, multi: true },
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true }, { provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
] ]

View File

@@ -1,4 +1,4 @@
import { Subject } from 'rxjs' import { Subject, AsyncSubject } from 'rxjs'
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core' import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
import { DefaultTabProvider } from '../api/defaultTabProvider' import { DefaultTabProvider } from '../api/defaultTabProvider'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
@@ -12,7 +12,8 @@ export class AppService {
activeTab: BaseTabComponent activeTab: BaseTabComponent
lastTabIndex = 0 lastTabIndex = 0
logger: Logger logger: Logger
tabsChanged$ = new Subject() tabsChanged$ = new Subject<void>()
ready$ = new AsyncSubject<void>()
constructor ( constructor (
private componentFactoryResolver: ComponentFactoryResolver, private componentFactoryResolver: ComponentFactoryResolver,
@@ -97,4 +98,9 @@ export class AppService {
} }
this.tabsChanged$.next() this.tabsChanged$.next()
} }
emitReady () {
this.ready$.next(null)
this.ready$.complete()
}
} }

View File

@@ -14,7 +14,10 @@ export class DockingService {
private electron: ElectronService, private electron: ElectronService,
private config: ConfigService, private config: ConfigService,
private hostApp: HostAppService, private hostApp: HostAppService,
) {} ) {
electron.screen.on('display-removed', () => this.repositionWindow())
electron.screen.on('display-metrics-changed', () => this.repositionWindow())
}
dock () { dock () {
let display = this.electron.screen.getAllDisplays() let display = this.electron.screen.getAllDisplays()
@@ -71,4 +74,20 @@ export class DockingService {
} }
}) })
} }
getWindow () {
return this.electron.app.window
}
repositionWindow () {
let [x, y] = this.getWindow().getPosition()
for (let screen of this.electron.screen.getAllDisplays()) {
let bounds = screen.bounds
if (x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height) {
return
}
}
let screen = this.electron.screen.getPrimaryDisplay()
this.getWindow().setPosition(screen.bounds.x, screen.bounds.y)
}
} }

View File

@@ -1,12 +1,47 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ElectronService } from './electron.service'
import * as winston from 'winston'
import * as fs from 'fs'
import * as path from 'path'
const initializeWinston = (electron: ElectronService) => {
const logDirectory = electron.app.getPath('userData')
if (!fs.existsSync(logDirectory)) {
fs.mkdirSync(logDirectory)
}
return new winston.Logger({
transports: [
new winston.transports.File({
level: 'info',
filename: path.join(logDirectory, 'log.txt'),
handleExceptions: false,
json: false,
maxsize: 5242880,
maxFiles: 5,
colorize: false
}),
new winston.transports.Console({
level: 'info',
handleExceptions: false,
json: false,
colorize: true
})
],
exitOnError: false
})
}
export class Logger { export class Logger {
constructor ( constructor (
private winstonLogger: any,
private name: string, private name: string,
) {} ) {}
doLog (level: string, ...args: any[]) { doLog (level: string, ...args: any[]) {
console[level](`%c[${this.name}]`, 'color: #aaa', ...args) console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
this.winstonLogger[level](...args)
} }
debug (...args: any[]) { this.doLog('debug', ...args) } debug (...args: any[]) { this.doLog('debug', ...args) }
@@ -18,7 +53,13 @@ export class Logger {
@Injectable() @Injectable()
export class LogService { export class LogService {
private log: any
constructor (electron: ElectronService) {
this.log = initializeWinston(electron)
}
create (name: string): Logger { create (name: string): Logger {
return new Logger(name) return new Logger(this.log, name)
} }
} }

View File

@@ -0,0 +1,15 @@
@import './theme.scss';
app-root {
.tab-bar {
height: 27px !important;
.btn-tab-bar {
line-height: 29px !important;
}
}
terminaltab .content {
margin: 5px !important;
}
}

View File

@@ -131,6 +131,7 @@ app-root {
background: $body-bg2; background: $body-bg2;
border-left: 1px solid transparent; border-left: 1px solid transparent;
border-right: 1px solid transparent; border-right: 1px solid transparent;
border-top: 1px solid transparent;
.index { .index {
color: #555; color: #555;
@@ -159,10 +160,12 @@ app-root {
tab-header { tab-header {
border-top: 1px solid transparent; border-top: 1px solid transparent;
border-bottom: 1px solid $border-color;
margin-bottom: -1px;
&.active { &.active {
border-top: 1px solid $teal; border-top: 1px solid $teal;
margin-bottom: -1px; border-bottom-color: transparent;
} }
&.has-activity:not(.active) { &.has-activity:not(.active) {
@@ -176,6 +179,8 @@ app-root {
tab-header { tab-header {
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
border-top: 1px solid $border-color;
margin-top: -1px;
&.active { &.active {
border-bottom: 1px solid $teal; border-bottom: 1px solid $teal;

View File

@@ -7,3 +7,10 @@ export class StandardTheme extends Theme {
css = require('./theme.scss') css = require('./theme.scss')
terminalBackground = '#1D272D' terminalBackground = '#1D272D'
} }
@Injectable()
export class StandardCompactTheme extends Theme {
name = 'Compact'
css = require('./theme.compact.scss')
terminalBackground = '#1D272D'
}

View File

@@ -45,6 +45,7 @@ module.exports = {
'path', 'path',
'deepmerge', 'deepmerge',
'untildify', 'untildify',
'winston',
'js-yaml', 'js-yaml',
/^rxjs/, /^rxjs/,
/^@angular/, /^@angular/,

View File

@@ -6,7 +6,7 @@
version "3.9.1" version "3.9.1"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.9.1.tgz#2f3c142771bb345829ce690c5838760b6b9ba553" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.9.1.tgz#2f3c142771bb345829ce690c5838760b6b9ba553"
"@types/node@^7.0.37": "@types/node@*", "@types/node@^7.0.37":
version "7.0.43" version "7.0.43"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c"
@@ -14,12 +14,22 @@
version "1.13.1" version "1.13.1"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.1.tgz#b45c222e24301bd006e3edfc762cc6b51bda236a" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.1.tgz#b45c222e24301bd006e3edfc762cc6b51bda236a"
"@types/winston@^2.3.6":
version "2.3.6"
resolved "https://registry.yarnpkg.com/@types/winston/-/winston-2.3.6.tgz#0f0954b9e16abd40598dc6e9cc2ea43044237997"
dependencies:
"@types/node" "*"
argparse@^1.0.7: argparse@^1.0.7:
version "1.0.9" version "1.0.9"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
dependencies: dependencies:
sprintf-js "~1.0.2" sprintf-js "~1.0.2"
async@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
axios@0.16.2: axios@0.16.2:
version "0.16.2" version "0.16.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
@@ -44,10 +54,18 @@ bootstrap@4.0.0-alpha.6:
jquery ">=1.9.1" jquery ">=1.9.1"
tether "^1.4.0" tether "^1.4.0"
colors@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
core-js@^2.4.1: core-js@^2.4.1:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
cycle@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
debug@^2.4.5: debug@^2.4.5:
version "2.6.8" version "2.6.8"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
@@ -97,6 +115,10 @@ esprima@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
eyes@0.1.x:
version "0.1.8"
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
follow-redirects@^1.2.3: follow-redirects@^1.2.3:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea"
@@ -126,6 +148,10 @@ is-buffer@^1.1.5:
version "1.1.5" version "1.1.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
isstream@0.1.x:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
jquery@>=1.9.1: jquery@>=1.9.1:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"
@@ -191,6 +217,10 @@ sprintf-js@~1.0.2:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
tether@^1.4.0: tether@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.0.tgz#0f9fa171f75bf58485d8149e94799d7ae74d1c1a" resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.0.tgz#0f9fa171f75bf58485d8149e94799d7ae74d1c1a"
@@ -209,6 +239,17 @@ uuid-1345@^0.99.6:
dependencies: dependencies:
macaddress "^0.2.7" macaddress "^0.2.7"
winston@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee"
dependencies:
async "~1.0.0"
colors "1.0.x"
cycle "1.0.x"
eyes "0.1.x"
isstream "0.1.x"
stack-trace "0.0.x"
xelement@^1.0.16: xelement@^1.0.16:
version "1.0.16" version "1.0.16"
resolved "https://registry.yarnpkg.com/xelement/-/xelement-1.0.16.tgz#900bb46c20fc2dffadff778a9d2dc36699d0ff7e" resolved "https://registry.yarnpkg.com/xelement/-/xelement-1.0.16.tgz#900bb46c20fc2dffadff778a9d2dc36699d0ff7e"

View File

@@ -1,9 +1,9 @@
{ {
"name": "terminus-plugin-manager", "name": "terminus-plugin-manager",
"version": "1.0.0-alpha.24", "version": "1.0.0-alpha.35.1",
"description": "Terminus' plugin manager", "description": "Terminus' plugin manager",
"keywords": [ "keywords": [
"terminus-plugin" "terminus-builtin-plugin"
], ],
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",

View File

@@ -78,7 +78,8 @@ div(*ngIf='npmInstalled')
.d-flex.w-100 .d-flex.w-100
.mr-auto.d-flex.flex-column .mr-auto.d-flex.flex-column
strong {{plugin.name}} strong {{plugin.name}}
small.text-muted.mb-0 {{plugin.description}} a.text-muted.mb-0((click)='showPluginInfo(plugin)')
small {{plugin.description}}
.d-flex.flex-column.align-items-end.mr-3 .d-flex.flex-column.align-items-end.mr-3
div {{plugin.version}} div {{plugin.version}}
small.text-muted {{plugin.author}} small.text-muted {{plugin.author}}

View File

@@ -5,6 +5,7 @@ import { PluginsSettingsTabComponent } from './components/pluginsSettingsTab.com
@Injectable() @Injectable()
export class PluginsSettingsTabProvider extends SettingsTabProvider { export class PluginsSettingsTabProvider extends SettingsTabProvider {
id = 'plugins'
title = 'Plugins' title = 'Plugins'
getComponentType (): ComponentType { getComponentType (): ComponentType {

View File

@@ -1,9 +1,9 @@
{ {
"name": "terminus-settings", "name": "terminus-settings",
"version": "1.0.0-alpha.24", "version": "1.0.0-alpha.35.3",
"description": "Terminus terminal settings page", "description": "Terminus terminal settings page",
"keywords": [ "keywords": [
"terminus-plugin" "terminus-builtin-plugin"
], ],
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",

View File

@@ -3,6 +3,7 @@ import { Component } from '@angular/core'
export declare type ComponentType = new (...args: any[]) => Component export declare type ComponentType = new (...args: any[]) => Component
export abstract class SettingsTabProvider { export abstract class SettingsTabProvider {
id: string
title: string title: string
getComponentType (): ComponentType { getComponentType (): ComponentType {

View File

@@ -1,7 +1,7 @@
button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
ngb-tabset.vertical(type='tabs') ngb-tabset.vertical(type='tabs', [activeId]='activeTab')
ngb-tab ngb-tab(id='application')
ng-template(ngbTabTitle) ng-template(ngbTabTitle)
| Application | Application
ng-template(ngbTabContent) ng-template(ngbTabContent)
@@ -18,7 +18,7 @@ ngb-tabset.vertical(type='tabs')
.form-group .form-group
label Show tabs label Show tabs
br br
div( .btn-group(
'[(ngModel)]'='config.store.appearance.tabsLocation', '[(ngModel)]'='config.store.appearance.tabsLocation',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
ngbRadioGroup ngbRadioGroup
@@ -41,7 +41,7 @@ ngb-tabset.vertical(type='tabs')
.form-group .form-group
label Window frame label Window frame
br br
div( .btn-group(
'[(ngModel)]'='config.store.appearance.frame' '[(ngModel)]'='config.store.appearance.frame'
'(ngModelChange)'='config.save(); config.requestRestart()' '(ngModelChange)'='config.save(); config.requestRestart()'
ngbRadioGroup ngbRadioGroup
@@ -74,7 +74,7 @@ ngb-tabset.vertical(type='tabs')
.form-group .form-group
label Dock the terminal label Dock the terminal
br br
div( .btn-group(
'[(ngModel)]'='config.store.appearance.dock' '[(ngModel)]'='config.store.appearance.dock'
'(ngModelChange)'='config.save(); docking.dock()' '(ngModelChange)'='config.save(); docking.dock()'
ngbRadioGroup ngbRadioGroup
@@ -164,7 +164,7 @@ ngb-tabset.vertical(type='tabs')
'(ngModelChange)'='config.save()', '(ngModelChange)'='config.save()',
) )
ngb-tab ngb-tab(id='hotkeys')
ng-template(ngbTabTitle) ng-template(ngbTabTitle)
| Hotkeys | Hotkeys
ng-template(ngbTabContent) ng-template(ngbTabContent)
@@ -184,7 +184,7 @@ ngb-tabset.vertical(type='tabs')
'(modelChange)'='config.save(); docking.dock()' '(modelChange)'='config.save(); docking.dock()'
) )
ngb-tab(*ngFor='let provider of settingsProviders') ngb-tab(*ngFor='let provider of settingsProviders', [id]='provider.id')
ng-template(ngbTabTitle) ng-template(ngbTabTitle)
| {{provider.title}} | {{provider.title}}
ng-template(ngbTabContent) ng-template(ngbTabContent)

View File

@@ -1,4 +1,4 @@
import { Component, Inject } from '@angular/core' import { Component, Inject, Input } from '@angular/core'
import { ElectronService, DockingService, ConfigService, IHotkeyDescription, HotkeyProvider, BaseTabComponent, Theme, HostAppService } from 'terminus-core' import { ElectronService, DockingService, ConfigService, IHotkeyDescription, HotkeyProvider, BaseTabComponent, Theme, HostAppService } from 'terminus-core'
import { SettingsTabProvider } from '../api' import { SettingsTabProvider } from '../api'
@@ -12,6 +12,7 @@ import { SettingsTabProvider } from '../api'
], ],
}) })
export class SettingsTabComponent extends BaseTabComponent { export class SettingsTabComponent extends BaseTabComponent {
@Input() activeTab: string
hotkeyFilter = '' hotkeyFilter = ''
private hotkeyDescriptions: IHotkeyDescription[] private hotkeyDescriptions: IHotkeyDescription[]
private screens private screens

View File

@@ -40,3 +40,4 @@ export default class SettingsModule {
} }
export * from './api' export * from './api'
export { SettingsTabComponent }

View File

@@ -1,9 +1,9 @@
{ {
"name": "terminus-terminal", "name": "terminus-terminal",
"version": "1.0.0-alpha.24", "version": "1.0.0-alpha.35.1",
"description": "Terminus' terminal emulation core", "description": "Terminus' terminal emulation core",
"keywords": [ "keywords": [
"terminus-plugin" "terminus-builtin-plugin"
], ],
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",

View File

@@ -1,7 +1,7 @@
h3.mb-2 Appearance
.row .row
.col-md-6 .col-md-6
.form-group .form-group
label Preview
.appearance-preview( .appearance-preview(
[style.font-family]='config.store.terminal.font', [style.font-family]='config.store.terminal.font',
[style.font-size]='config.store.terminal.fontSize + "px"', [style.font-size]='config.store.terminal.fontSize + "px"',
@@ -60,23 +60,6 @@
span([style.color]='config.store.terminal.colorScheme.colors[15]') W span([style.color]='config.store.terminal.colorScheme.colors[15]') W
div div
span &nbsp; span &nbsp;
div
span john@doe-pc
span([style.color]='config.store.terminal.colorScheme.colors[1]') $
span webpack
div
span Asset Size
div
span([style.color]='config.store.terminal.colorScheme.colors[2]') main.js
span 234 kB
span([style.color]='config.store.terminal.colorScheme.colors[2]') [emitted]
div
span([style.color]='config.store.terminal.colorScheme.colors[3]') big.js
span([style.color]='config.store.terminal.colorScheme.colors[3]') 1.2 MB
span([style.color]='config.store.terminal.colorScheme.colors[2]') [emitted]
span([style.color]='config.store.terminal.colorScheme.colors[3]') [big]
div
span &nbsp;
div div
span john@doe-pc span john@doe-pc
span([style.color]='config.store.terminal.colorScheme.colors[1]') $ span([style.color]='config.store.terminal.colorScheme.colors[1]') $
@@ -178,7 +161,7 @@
.form-group.mr-3 .form-group.mr-3
label Terminal background label Terminal background
br br
div( .btn-group(
'[(ngModel)]'='config.store.terminal.background', '[(ngModel)]'='config.store.terminal.background',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
ngbRadioGroup ngbRadioGroup
@@ -197,10 +180,11 @@
[value]='"colorScheme"' [value]='"colorScheme"'
) )
| From colors | From colors
.form-group .form-group
label Cursor shape label Cursor shape
br br
div( .btn-group(
[(ngModel)]='config.store.terminal.cursor', [(ngModel)]='config.store.terminal.cursor',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
ngbRadioGroup ngbRadioGroup
@@ -227,22 +211,56 @@
) )
| ▁ | ▁
.form-group h3.mt-2.mb-2 Behaviour
label Shell
select.form-control( .row
'[(ngModel)]'='config.store.terminal.shell', .col-md-6
.d-flex
.form-group.mr-3
label Shell
select.form-control(
'[(ngModel)]'='config.store.terminal.shell',
(ngModelChange)='config.save()',
)
option(
*ngFor='let shell of shells',
[ngValue]='shell.id'
) {{shell.name}}
.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}}
.form-group(*ngIf='config.store.terminal.shell == "custom"')
label Custom shell
input.form-control(
type='text',
'[(ngModel)]'='config.store.terminal.customShell',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
) )
option(
*ngFor='let shell of shells',
[ngValue]='shell.id'
) {{shell.name}}
.form-group .form-group
label Terminal bell label Working directory
input.form-control(
type='text',
placeholder='Home directory',
'[(ngModel)]'='config.store.terminal.workingDirectory',
(ngModelChange)='config.save()',
)
.form-group
label Auto-open a terminal on app start
br br
div( .btn-group(
'[(ngModel)]'='config.store.terminal.bell', '[(ngModel)]'='config.store.terminal.autoOpen',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
ngbRadioGroup ngbRadioGroup
) )
@@ -250,32 +268,115 @@
input( input(
type='radio', type='radio',
ngbButton, ngbButton,
[value]='"off"' [value]='false'
) )
| Off | Off
label.btn.btn-secondary(ngbButtonLabel) label.btn.btn-secondary(ngbButtonLabel)
input( input(
type='radio', type='radio',
ngbButton, ngbButton,
[value]='"visual"' [value]='true'
) )
| Visual | On
label.btn.btn-secondary(ngbButtonLabel)
input( .col-md-6
type='radio', .d-flex
ngbButton, .form-group.mr-3
[value]='"audible"' label Terminal bell
) br
| Audible .btn-group(
'[(ngModel)]'='config.store.terminal.bell',
(ngModelChange)='config.save()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"off"'
)
| Off
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"visual"'
)
| Visual
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"audible"'
)
| Audible
.form-group .form-group
label Session persistence label Blink cursor
select.form-control( br
'[(ngModel)]'='config.store.terminal.persistence', .btn-group(
(ngModelChange)='config.save()', '[(ngModel)]'='config.store.terminal.cursorBlink',
) (ngModelChange)='config.save()',
option([ngValue]='null') Off ngbRadioGroup
option( )
*ngFor='let provider of persistenceProviders', label.btn.btn-secondary(ngbButtonLabel)
[ngValue]='provider.id' input(
) {{provider.displayName}} type='radio',
ngbButton,
[value]='false'
)
| Off
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='true'
)
| On
.d-flex
.form-group.mr-3
label Copy on select
br
.btn-group(
'[(ngModel)]'='config.store.terminal.copyOnSelect',
(ngModelChange)='config.save()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='false'
)
| Off
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='true'
)
| On
.form-group
label Right click behaviour
br
.btn-group(
'[(ngModel)]'='config.store.terminal.rightClick',
(ngModelChange)='config.save()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
value='menu'
)
| Menu
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
value='paste'
)
| Paste

View File

@@ -60,23 +60,28 @@ export class TerminalTabComponent extends BaseTabComponent {
this.decorators = this.decorators || [] this.decorators = this.decorators || []
this.title = 'Terminal' this.title = 'Terminal'
this.resize$.first().subscribe(async (resizeEvent) => { this.resize$.first().subscribe(async (resizeEvent) => {
this.session = this.sessions.addSession( if (!this.session) {
Object.assign({}, this.sessionOptions, resizeEvent) this.session = this.sessions.addSession(
) Object.assign({}, this.sessionOptions, resizeEvent)
)
}
setTimeout(() => { setTimeout(() => {
this.session.resize(resizeEvent.width, resizeEvent.height) this.session.resize(resizeEvent.width, resizeEvent.height)
}, 1000) }, 1000)
// this.session.output$.bufferTime(10).subscribe((datas) => { // this.session.output$.bufferTime(10).subscribe((datas) => {
this.session.output$.subscribe(data => { this.session.output$.subscribe(data => {
// let data = datas.join('')
this.zone.run(() => { this.zone.run(() => {
this.output$.next(data) this.output$.next(data)
this.write(data)
}) })
this.write(data)
}) })
this.sessionCloseSubscription = this.session.closed$.subscribe(() => { this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
this.app.closeTab(this) this.app.closeTab(this)
}) })
this.session.releaseInitialDataBuffer() this.session.releaseInitialDataBuffer()
}) })
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => { this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
@@ -178,7 +183,7 @@ export class TerminalTabComponent extends BaseTabComponent {
label: 'Paste', label: 'Paste',
click: () => { click: () => {
this.zone.run(() => { this.zone.run(() => {
this.sendInput(this.electron.clipboard.readText()) this.paste()
}) })
} }
}, },
@@ -223,12 +228,17 @@ export class TerminalTabComponent extends BaseTabComponent {
this.mouseEvent$.next(event) this.mouseEvent$.next(event)
if (event.type === 'mousedown') { if (event.type === 'mousedown') {
if (event.which === 3) { if (event.which === 3) {
this.contextMenu.popup({ if (this.config.store.terminal.rightClick === 'menu') {
x: event.pageX, this.contextMenu.popup({
y: event.pageY, x: event.pageX + this.content.nativeElement.getBoundingClientRect().left,
async: true, y: event.pageY + this.content.nativeElement.getBoundingClientRect().top,
}) async: true,
})
} else if (this.config.store.terminal.rightClick === 'paste') {
this.paste()
}
event.preventDefault() event.preventDefault()
event.stopPropagation()
return return
} }
} }
@@ -302,6 +312,10 @@ export class TerminalTabComponent extends BaseTabComponent {
this.io.writeUTF8(data) this.io.writeUTF8(data)
} }
paste () {
this.sendInput(this.electron.clipboard.readText())
}
clear () { clear () {
this.hterm.wipeContents() this.hterm.wipeContents()
this.hterm.onVTKeystroke('\f') this.hterm.onVTKeystroke('\f')
@@ -319,9 +333,11 @@ export class TerminalTabComponent extends BaseTabComponent {
preferenceManager.set('send-encoding', 'raw') preferenceManager.set('send-encoding', 'raw')
preferenceManager.set('ctrl-plus-minus-zero-zoom', false) preferenceManager.set('ctrl-plus-minus-zero-zoom', false)
preferenceManager.set('scrollbar-visible', this.hostApp.platform === Platform.macOS) preferenceManager.set('scrollbar-visible', this.hostApp.platform === Platform.macOS)
preferenceManager.set('copy-on-select', false) preferenceManager.set('copy-on-select', config.terminal.copyOnSelect)
preferenceManager.set('alt-sends-what', 'browser-key') preferenceManager.set('alt-sends-what', 'browser-key')
preferenceManager.set('alt-gr-mode', 'ctrl-alt') preferenceManager.set('alt-gr-mode', 'ctrl-alt')
preferenceManager.set('pass-alt-number', true)
preferenceManager.set('cursor-blink', config.terminal.cursorBlink)
if (config.terminal.colorScheme.foreground) { if (config.terminal.colorScheme.foreground) {
preferenceManager.set('foreground-color', config.terminal.colorScheme.foreground) preferenceManager.set('foreground-color', config.terminal.colorScheme.foreground)
@@ -368,6 +384,10 @@ export class TerminalTabComponent extends BaseTabComponent {
beam: hterm.hterm.Terminal.cursorShape.BEAM, beam: hterm.hterm.Terminal.cursorShape.BEAM,
}[config.terminal.cursor] }[config.terminal.cursor]
this.hterm.applyCursorShape() this.hterm.applyCursorShape()
this.hterm.setCursorBlink(config.terminal.cursorBlink)
if (config.terminal.cursorBlink) {
this.hterm.onCursorBlink_()
}
} }
zoomIn () { zoomIn () {

View File

@@ -3,6 +3,7 @@ import { ConfigProvider, Platform } from 'terminus-core'
export class TerminalConfigProvider extends ConfigProvider { export class TerminalConfigProvider extends ConfigProvider {
defaults = { defaults = {
terminal: { terminal: {
autoOpen: false,
fontSize: 14, fontSize: 14,
linePadding: 0, linePadding: 0,
bell: 'off', bell: 'off',
@@ -10,6 +11,11 @@ export class TerminalConfigProvider extends ConfigProvider {
background: 'theme', background: 'theme',
ligatures: false, ligatures: false,
cursor: 'block', cursor: 'block',
cursorBlink: true,
customShell: '',
rightClick: 'menu',
copyOnSelect: false,
workingDirectory: '',
colorScheme: { colorScheme: {
__nonStructural: true, __nonStructural: true,
name: 'Material', name: 'Material',
@@ -77,6 +83,8 @@ export class TerminalConfigProvider extends ConfigProvider {
font: 'Consolas', font: 'Consolas',
shell: 'clink', shell: 'clink',
persistence: null, persistence: null,
rightClick: 'paste',
copyOnSelect: true,
}, },
hotkeys: { hotkeys: {
'copy': [ 'copy': [

View File

@@ -3,14 +3,14 @@ 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 { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider } from 'terminus-core' import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService } 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'
import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component' import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
import { ColorPickerComponent } from './components/colorPicker.component' import { ColorPickerComponent } from './components/colorPicker.component'
import { SessionsService } from './services/sessions.service' import { SessionsService, BaseSession } from './services/sessions.service'
import { TerminalService } from './services/terminal.service' import { TerminalService } from './services/terminal.service'
import { ScreenPersistenceProvider } from './persistence/screen' import { ScreenPersistenceProvider } from './persistence/screen'
@@ -24,6 +24,7 @@ import { TerminalConfigProvider } from './config'
import { TerminalHotkeyProvider } from './hotkeys' import { TerminalHotkeyProvider } from './hotkeys'
import { HyperColorSchemes } from './colorSchemes' import { HyperColorSchemes } from './colorSchemes'
import { CustomShellProvider } from './shells/custom'
import { Cygwin32ShellProvider } from './shells/cygwin32' import { Cygwin32ShellProvider } from './shells/cygwin32'
import { Cygwin64ShellProvider } from './shells/cygwin64' import { Cygwin64ShellProvider } from './shells/cygwin64'
import { GitBashShellProvider } from './shells/gitBash' import { GitBashShellProvider } from './shells/gitBash'
@@ -59,6 +60,7 @@ import { hterm } from './hterm'
{ 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 },
{ provide: ShellProvider, useClass: CustomShellProvider, multi: true },
{ provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true }, { provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true },
{ provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true }, { provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true },
{ provide: ShellProvider, useClass: GitBashShellProvider, multi: true }, { provide: ShellProvider, useClass: GitBashShellProvider, multi: true },
@@ -76,7 +78,12 @@ import { hterm } from './hterm'
], ],
}) })
export default class TerminalModule { export default class TerminalModule {
constructor (hotkeys: HotkeysService) { constructor (
app: AppService,
config: ConfigService,
hotkeys: HotkeysService,
terminal: TerminalService,
) {
let events = [ let events = [
{ {
name: 'keydown', name: 'keydown',
@@ -101,8 +108,13 @@ export default class TerminalModule {
hotkeys.emitKeyEvent(nativeEvent) hotkeys.emitKeyEvent(nativeEvent)
} }
}) })
if (config.store.terminal.autoOpen) {
app.ready$.subscribe(() => {
terminal.openTab()
})
}
} }
} }
export * from './api' export * from './api'
export { TerminalService } export { TerminalService, BaseSession, TerminalTabComponent }

View File

@@ -14,7 +14,7 @@ export interface IChildProcess {
command: string command: string
} }
export class Session { export abstract class BaseSession {
open: boolean open: boolean
name: string name: string
output$ = new Subject<string>() output$ = new Subject<string>()
@@ -22,11 +22,46 @@ export class Session {
destroyed$ = new Subject<void>() destroyed$ = new Subject<void>()
recoveryId: string recoveryId: string
truePID: number truePID: number
private pty: any
private initialDataBuffer = '' private initialDataBuffer = ''
private initialDataBufferReleased = false private initialDataBufferReleased = false
emitOutput (data: string) {
if (!this.initialDataBufferReleased) {
this.initialDataBuffer += data
} else {
this.output$.next(data)
}
}
releaseInitialDataBuffer () {
this.initialDataBufferReleased = true
this.output$.next(this.initialDataBuffer)
this.initialDataBuffer = null
}
abstract resize (columns, rows)
abstract write (data)
abstract kill (signal?: string)
abstract async getChildProcesses (): Promise<IChildProcess[]>
abstract async gracefullyKillProcess (): Promise<void>
abstract async getWorkingDirectory (): Promise<string>
async destroy (): Promise<void> {
if (this.open) {
this.open = false
this.closed$.next()
this.destroyed$.next()
this.output$.complete()
await this.gracefullyKillProcess()
}
}
}
export class Session extends BaseSession {
private pty: any
constructor (options: SessionOptions) { constructor (options: SessionOptions) {
super()
this.name = options.name this.name = options.name
this.recoveryId = options.recoveryId this.recoveryId = options.recoveryId
@@ -65,12 +100,8 @@ export class Session {
this.open = true this.open = true
this.pty.on('data', (data) => { this.pty.on('data', data => {
if (!this.initialDataBufferReleased) { this.emitOutput(data)
this.initialDataBuffer += data
} else {
this.output$.next(data)
}
}) })
this.pty.on('exit', () => { this.pty.on('exit', () => {
@@ -86,12 +117,6 @@ export class Session {
}) })
} }
releaseInitialDataBuffer () {
this.initialDataBufferReleased = true
this.output$.next(this.initialDataBuffer)
this.initialDataBuffer = null
}
resize (columns, rows) { resize (columns, rows) {
if (this.pty._writable) { if (this.pty._writable) {
this.pty.resize(columns, rows) this.pty.resize(columns, rows)
@@ -144,16 +169,6 @@ export class Session {
} }
} }
async destroy (): Promise<void> {
if (this.open) {
this.open = false
this.closed$.next()
this.destroyed$.next()
this.output$.complete()
await this.gracefullyKillProcess()
}
}
async getWorkingDirectory (): Promise<string> { async getWorkingDirectory (): Promise<string> {
if (!this.truePID) { if (!this.truePID) {
return null return null

View File

@@ -14,19 +14,31 @@ export class TerminalService {
private app: AppService, private app: AppService,
private sessions: SessionsService, private sessions: SessionsService,
private config: ConfigService, private config: ConfigService,
@Inject(ShellProvider) shellProviders: ShellProvider[], @Inject(ShellProvider) private shellProviders: ShellProvider[],
log: LogService, log: LogService,
) { ) {
this.logger = log.create('terminal') this.logger = log.create('terminal')
Promise.all(shellProviders.map(x => x.provide())).then(shellLists => { this.reloadShells()
this.shells$.next(shellLists.reduce((a, b) => a.concat(b)))
this.shells$.complete() config.changed$.subscribe(() => {
this.reloadShells()
}) })
} }
async reloadShells () {
this.shells$ = new AsyncSubject<IShell[]>()
let shellLists = await Promise.all(this.shellProviders.map(x => x.provide()))
this.shells$.next(shellLists.reduce((a, b) => a.concat(b)))
this.shells$.complete()
}
async openTab (shell?: IShell, cwd?: string): Promise<TerminalTabComponent> { async openTab (shell?: IShell, cwd?: string): Promise<TerminalTabComponent> {
if (!cwd && this.app.activeTab instanceof TerminalTabComponent) { if (!cwd) {
cwd = await this.app.activeTab.session.getWorkingDirectory() if (this.app.activeTab instanceof TerminalTabComponent) {
cwd = await this.app.activeTab.session.getWorkingDirectory()
} else {
cwd = this.config.store.terminal.workingDirectory || null
}
} }
if (!shell) { if (!shell) {
let shells = await this.shells$.toPromise() let shells = await this.shells$.toPromise()

View File

@@ -5,6 +5,7 @@ import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.c
@Injectable() @Injectable()
export class TerminalSettingsTabProvider extends SettingsTabProvider { export class TerminalSettingsTabProvider extends SettingsTabProvider {
id = 'terminal'
title = 'Terminal' title = 'Terminal'
getComponentType (): ComponentType { getComponentType (): ComponentType {

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@angular/core'
import { ConfigService } from 'terminus-core'
import { ShellProvider, IShell } from '../api'
@Injectable()
export class CustomShellProvider extends ShellProvider {
constructor (
private config: ConfigService,
) {
super()
}
async provide (): Promise<IShell[]> {
let args = this.config.store.terminal.customShell.split(' ')
return [{
id: 'custom',
name: 'Custom',
command: args[0],
args: args.slice(1),
}]
}
}

View File

@@ -33,7 +33,8 @@ export class LinuxDefaultShellProvider extends ShellProvider {
return [{ return [{
id: 'default', id: 'default',
name: 'User default', name: 'User default',
command: line.split(':')[6] command: line.split(':')[6],
args: ['--login'],
}] }]
} }
} }

View File

@@ -20,7 +20,8 @@ export class MacOSDefaultShellProvider extends ShellProvider {
return [{ return [{
id: 'default', id: 'default',
name: 'User default', name: 'User default',
command: shellEntry.split(' ')[1].trim() command: shellEntry.split(' ')[1].trim(),
args: ['--login'],
}] }]
} }
} }

View File

@@ -24,6 +24,7 @@ export class POSIXShellsProvider extends ShellProvider {
id: x, id: x,
name: x, name: x,
command: x, command: x,
args: ['--login'],
})) }))
} }
} }

View File

@@ -25,7 +25,10 @@ export class WSLShellProvider extends ShellProvider {
return [{ return [{
id: 'wsl', id: 'wsl',
name: 'Bash on Windows', name: 'Bash on Windows',
command: wslPath command: wslPath,
env: {
TERM: 'xterm-color',
}
}] }]
} }
} }

View File

@@ -15,8 +15,10 @@
"skipLibCheck": true, "skipLibCheck": true,
"lib": [ "lib": [
"dom", "dom",
"es2015", "es5",
"es7" "es6",
"es7",
"es2015"
] ]
} }
} }