@typescript-eslint linter

This commit is contained in:
Eugene Pankov
2019-06-14 23:47:48 +02:00
parent a5ecdeb5ea
commit c008a3478e
96 changed files with 1334 additions and 810 deletions

82
.eslintrc.yml Normal file
View File

@@ -0,0 +1,82 @@
parser: '@typescript-eslint/parser'
parserOptions:
project: tsconfig.json
extends:
- 'plugin:@typescript-eslint/all'
plugins:
- '@typescript-eslint'
env:
browser: true
es6: true
node: true
commonjs: true
rules:
'@typescript-eslint/semi':
- error
- never
'@typescript-eslint/indent':
- error
- 4
'@typescript-eslint/explicit-member-accessibility':
- error
- accessibility: no-public
overrides:
parameterProperties: explicit
'@typescript-eslint/no-require-imports': off
'@typescript-eslint/no-parameter-properties': off
'@typescript-eslint/explicit-function-return-type': off
'@typescript-eslint/no-explicit-any': off
'@typescript-eslint/no-magic-numbers': off
'@typescript-eslint/member-delimiter-style': off
'@typescript-eslint/promise-function-async': off
'@typescript-eslint/no-unnecessary-type-assertion': off
'@typescript-eslint/require-array-sort-compare': off
'@typescript-eslint/no-use-before-define':
- error
- classes: false
no-duplicate-imports: error
array-bracket-spacing:
- error
- never
block-scoped-var: error
brace-style:
- error
- 1tbs
- allowSingleLine: true
computed-property-spacing:
- error
- never
comma-dangle:
- error
- always-multiline
curly: error
eol-last: error
eqeqeq:
- error
- smart
linebreak-style:
- error
- unix
max-depth:
- 1
- 5
max-statements:
- 1
- 80
no-multiple-empty-lines: error
no-mixed-spaces-and-tabs: error
no-trailing-spaces: error
'@typescript-eslint/no-unused-vars':
- error
- vars: all
args: after-used
argsIgnorePattern: ^_
no-undef: error
object-curly-spacing:
- error
- always
quote-props:
- warn
- as-needed
- keywords: true
numbers: true

View File

@@ -92,11 +92,11 @@ Plugins provide functionality by exporting singular or multi providers:
```javascript ```javascript
import { NgModule, Injectable } from '@angular/core' import { NgModule, Injectable } from '@angular/core'
import { ToolbarButtonProvider, IToolbarButton } from 'terminus-core' import { ToolbarButtonProvider, ToolbarButton } from 'terminus-core'
@Injectable() @Injectable()
export class MyButtonProvider extends ToolbarButtonProvider { export class MyButtonProvider extends ToolbarButtonProvider {
provide (): IToolbarButton[] { provide (): ToolbarButton[] {
return [{ return [{
icon: 'star', icon: 'star',
title: 'Foobar', title: 'Foobar',

View File

@@ -16,7 +16,7 @@ export function getRootModule (plugins: any[]) {
}), }),
] ]
const bootstrap = [ const bootstrap = [
...(plugins.filter(x => x.bootstrap).map(x => x.bootstrap)), ...plugins.filter(x => x.bootstrap).map(x => x.bootstrap),
] ]
if (bootstrap.length === 0) { if (bootstrap.length === 0) {
@@ -26,7 +26,7 @@ export function getRootModule (plugins: any[]) {
@NgModule({ @NgModule({
imports, imports,
bootstrap, bootstrap,
}) class RootModule { } }) class RootModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
return RootModule return RootModule
} }

View File

@@ -21,15 +21,15 @@ Raven.config(
return splitArray[splitArray.length - 1] return splitArray[splitArray.length - 1]
} }
data.exception.values[0].stacktrace.frames.forEach(frame => { data.exception.values[0].stacktrace.frames.forEach((frame: any) => {
frame.filename = normalize(frame.filename) frame.filename = normalize(frame.filename)
}) })
data.culprit = data.exception.values[0].stacktrace.frames[0].filename data.culprit = data.exception.values[0].stacktrace.frames[0].filename
return data return data
} },
} },
) )
process.on('uncaughtException' as any, (err) => { process.on('uncaughtException' as any, (err) => {

View File

@@ -2,19 +2,19 @@ import 'zone.js'
import 'core-js/proposals/reflect-metadata' import 'core-js/proposals/reflect-metadata'
import 'rxjs' import 'rxjs'
import isDev = require('electron-is-dev') import * as isDev from 'electron-is-dev'
import './global.scss' import './global.scss'
import './toastr.scss' import './toastr.scss'
// Always land on the start view
location.hash = ''
import { enableProdMode, NgModuleRef } from '@angular/core' import { enableProdMode, NgModuleRef } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { getRootModule } from './app.module' import { getRootModule } from './app.module'
import { findPlugins, loadPlugins, IPluginInfo } from './plugins' import { findPlugins, loadPlugins, PluginInfo } from './plugins'
// Always land on the start view
location.hash = ''
;(process as any).enablePromiseAPI = true ;(process as any).enablePromiseAPI = true
@@ -28,12 +28,12 @@ if (isDev) {
enableProdMode() enableProdMode()
} }
async function bootstrap (plugins: IPluginInfo[], safeMode = false): Promise<NgModuleRef<any>> { async function bootstrap (plugins: PluginInfo[], safeMode = false): Promise<NgModuleRef<any>> {
if (safeMode) { if (safeMode) {
plugins = plugins.filter(x => x.isBuiltin) plugins = plugins.filter(x => x.isBuiltin)
} }
const pluginsModules = await loadPlugins(plugins, (current, total) => { const pluginsModules = await loadPlugins(plugins, (current, total) => {
(document.querySelector('.progress .bar') as HTMLElement).style.width = 100 * current / total + '%' (document.querySelector('.progress .bar') as HTMLElement).style.width = `${100 * current / total}%` // eslint-disable-line
}) })
const module = getRootModule(pluginsModules) const module = getRootModule(pluginsModules)
window['rootModule'] = module window['rootModule'] = module

View File

@@ -1,6 +1,6 @@
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import * as path from 'path' import * as path from 'path'
const nodeModule = require('module') const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
const nodeRequire = (global as any).require const nodeRequire = (global as any).require
function normalizePath (path: string): string { function normalizePath (path: string): string {
@@ -38,9 +38,9 @@ if (process.env.TERMINUS_PLUGINS) {
process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.push(normalizePath(x))) process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.push(normalizePath(x)))
} }
export declare type ProgressCallback = (current: number, total: number) => void export type ProgressCallback = (current: number, total: number) => void // eslint-disable-line @typescript-eslint/no-type-alias
export interface IPluginInfo { export interface PluginInfo {
name: string name: string
description: string description: string
packageName: string packageName: string
@@ -87,9 +87,9 @@ const originalRequire = (global as any).require
return originalRequire.apply(this, arguments) return originalRequire.apply(this, arguments)
} }
export async function findPlugins (): Promise<IPluginInfo[]> { export async function findPlugins (): Promise<PluginInfo[]> {
const paths = nodeModule.globalPaths const paths = nodeModule.globalPaths
let foundPlugins: IPluginInfo[] = [] let foundPlugins: PluginInfo[] = []
const candidateLocations: { pluginDir: string, packageName: string }[] = [] const candidateLocations: { pluginDir: string, packageName: string }[] = []
const PREFIX = 'terminus-' const PREFIX = 'terminus-'
@@ -102,7 +102,7 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
if (await fs.exists(path.join(pluginDir, 'package.json'))) { if (await fs.exists(path.join(pluginDir, 'package.json'))) {
candidateLocations.push({ candidateLocations.push({
pluginDir: path.dirname(pluginDir), pluginDir: path.dirname(pluginDir),
packageName: path.basename(pluginDir) packageName: path.basename(pluginDir),
}) })
} }
for (const packageName of pluginNames) { for (const packageName of pluginNames) {
@@ -152,7 +152,7 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
return foundPlugins return foundPlugins
} }
export async function loadPlugins (foundPlugins: IPluginInfo[], progress: ProgressCallback): Promise<any[]> { export async function loadPlugins (foundPlugins: PluginInfo[], progress: ProgressCallback): Promise<any[]> {
const plugins: any[] = [] const plugins: any[] = []
progress(0, 1) progress(0, 1)
let index = 0 let index = 0

View File

@@ -1,6 +1,6 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
@Component({ @Component({
template: '<app-root></app-root>' template: '<app-root></app-root>',
}) })
export class RootComponent { } export class RootComponent { } // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -630,7 +630,7 @@ debug@^4.1.1:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debuglog@*, debuglog@^1.0.1: debuglog@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
@@ -1220,7 +1220,7 @@ import-lazy@^2.1.0:
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
imurmurhash@*, imurmurhash@^0.1.4: imurmurhash@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
@@ -1516,7 +1516,7 @@ libnpm@^2.0.1:
read-package-json "^2.0.13" read-package-json "^2.0.13"
stringify-package "^1.0.0" stringify-package "^1.0.0"
libnpmaccess@*, libnpmaccess@^3.0.1: libnpmaccess@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-3.0.1.tgz#5b3a9de621f293d425191aa2e779102f84167fa8" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-3.0.1.tgz#5b3a9de621f293d425191aa2e779102f84167fa8"
integrity sha512-RlZ7PNarCBt+XbnP7R6PoVgOq9t+kou5rvhaInoNibhPO7eMlRfS0B8yjatgn2yaHIwWNyoJDolC/6Lc5L/IQA== integrity sha512-RlZ7PNarCBt+XbnP7R6PoVgOq9t+kou5rvhaInoNibhPO7eMlRfS0B8yjatgn2yaHIwWNyoJDolC/6Lc5L/IQA==
@@ -1545,7 +1545,7 @@ libnpmhook@^5.0.2:
get-stream "^4.0.0" get-stream "^4.0.0"
npm-registry-fetch "^3.8.0" npm-registry-fetch "^3.8.0"
libnpmorg@*, libnpmorg@^1.0.0: libnpmorg@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-1.0.0.tgz#979b868c48ba28c5820e3bb9d9e73c883c16a232" resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-1.0.0.tgz#979b868c48ba28c5820e3bb9d9e73c883c16a232"
integrity sha512-o+4eVJBoDGMgRwh2lJY0a8pRV2c/tQM/SxlqXezjcAg26Qe9jigYVs+Xk0vvlYDWCDhP0g74J8UwWeAgsB7gGw== integrity sha512-o+4eVJBoDGMgRwh2lJY0a8pRV2c/tQM/SxlqXezjcAg26Qe9jigYVs+Xk0vvlYDWCDhP0g74J8UwWeAgsB7gGw==
@@ -1570,7 +1570,7 @@ libnpmpublish@^1.1.0:
semver "^5.5.1" semver "^5.5.1"
ssri "^6.0.1" ssri "^6.0.1"
libnpmsearch@*, libnpmsearch@^2.0.0: libnpmsearch@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-2.0.0.tgz#de05af47ada81554a5f64276a69599070d4a5685" resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-2.0.0.tgz#de05af47ada81554a5f64276a69599070d4a5685"
integrity sha512-vd+JWbTGzOSfiOc+72MU6y7WqmBXn49egCCrIXp27iE/88bX8EpG64ST1blWQI1bSMUr9l1AKPMVsqa2tS5KWA== integrity sha512-vd+JWbTGzOSfiOc+72MU6y7WqmBXn49egCCrIXp27iE/88bX8EpG64ST1blWQI1bSMUr9l1AKPMVsqa2tS5KWA==
@@ -1579,7 +1579,7 @@ libnpmsearch@*, libnpmsearch@^2.0.0:
get-stream "^4.0.0" get-stream "^4.0.0"
npm-registry-fetch "^3.8.0" npm-registry-fetch "^3.8.0"
libnpmteam@*, libnpmteam@^1.0.1: libnpmteam@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-1.0.1.tgz#ff704b1b6c06ea674b3b1101ac3e305f5114f213" resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-1.0.1.tgz#ff704b1b6c06ea674b3b1101ac3e305f5114f213"
integrity sha512-gDdrflKFCX7TNwOMX1snWojCoDE5LoRWcfOC0C/fqF7mBq8Uz9zWAX4B2RllYETNO7pBupBaSyBDkTAC15cAMg== integrity sha512-gDdrflKFCX7TNwOMX1snWojCoDE5LoRWcfOC0C/fqF7mBq8Uz9zWAX4B2RllYETNO7pBupBaSyBDkTAC15cAMg==
@@ -1634,11 +1634,6 @@ lockfile@^1.0.4:
dependencies: dependencies:
signal-exit "^3.0.2" signal-exit "^3.0.2"
lodash._baseindexof@*:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=
lodash._baseuniq@~4.6.0: lodash._baseuniq@~4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
@@ -1647,33 +1642,11 @@ lodash._baseuniq@~4.6.0:
lodash._createset "~4.0.0" lodash._createset "~4.0.0"
lodash._root "~3.0.0" lodash._root "~3.0.0"
lodash._bindcallback@*:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
lodash._cacheindexof@*:
version "3.0.2"
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=
lodash._createcache@*:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
dependencies:
lodash._getnative "^3.0.0"
lodash._createset@~4.0.0: lodash._createset@~4.0.0:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
lodash._getnative@*, lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
lodash._root@~3.0.0: lodash._root@~3.0.0:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
@@ -1689,11 +1662,6 @@ lodash.isequal@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
lodash.restparam@*:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
lodash.union@~4.6.0: lodash.union@~4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
@@ -2051,7 +2019,7 @@ npm-pick-manifest@^2.2.3:
npm-package-arg "^6.0.0" npm-package-arg "^6.0.0"
semver "^5.4.1" semver "^5.4.1"
npm-profile@*, npm-profile@^4.0.1: npm-profile@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-4.0.1.tgz#d350f7a5e6b60691c7168fbb8392c3603583f5aa" resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-4.0.1.tgz#d350f7a5e6b60691c7168fbb8392c3603583f5aa"
integrity sha512-NQ1I/1Q7YRtHZXkcuU1/IyHeLy6pd+ScKg4+DQHdfsm769TGq6HPrkbuNJVJS4zwE+0mvvmeULzQdWn2L2EsVA== integrity sha512-NQ1I/1Q7YRtHZXkcuU1/IyHeLy6pd+ScKg4+DQHdfsm769TGq6HPrkbuNJVJS4zwE+0mvvmeULzQdWn2L2EsVA==
@@ -2645,7 +2613,7 @@ readable-stream@~1.1.10:
isarray "0.0.1" isarray "0.0.1"
string_decoder "~0.10.x" string_decoder "~0.10.x"
readdir-scoped-modules@*, readdir-scoped-modules@^1.0.0: readdir-scoped-modules@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747"
integrity sha1-n6+jfShr5dksuuve4DDcm19AZ0c= integrity sha1-n6+jfShr5dksuuve4DDcm19AZ0c=

View File

@@ -7,6 +7,8 @@
"@types/js-yaml": "^3.12.1", "@types/js-yaml": "^3.12.1",
"@types/node": "^12.0.8", "@types/node": "^12.0.8",
"@types/webpack-env": "1.13.9", "@types/webpack-env": "1.13.9",
"@typescript-eslint/eslint-plugin": "^1.10.2",
"@typescript-eslint/parser": "^1.10.2",
"app-builder-lib": "^20.43.0", "app-builder-lib": "^20.43.0",
"apply-loader": "2.0.0", "apply-loader": "2.0.0",
"awesome-typescript-loader": "^5.0.0", "awesome-typescript-loader": "^5.0.0",
@@ -17,6 +19,7 @@
"electron-builder": "^20.43.0", "electron-builder": "^20.43.0",
"electron-installer-snap": "^3.2.0", "electron-installer-snap": "^3.2.0",
"electron-rebuild": "^1.8.5", "electron-rebuild": "^1.8.5",
"eslint": "^5.16.0",
"file-loader": "^4.0.0", "file-loader": "^4.0.0",
"graceful-fs": "^4.1.15", "graceful-fs": "^4.1.15",
"html-loader": "0.5.5", "html-loader": "0.5.5",
@@ -40,9 +43,6 @@
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"svg-inline-loader": "^0.8.0", "svg-inline-loader": "^0.8.0",
"to-string-loader": "1.1.5", "to-string-loader": "1.1.5",
"tslint": "^5.17.0",
"tslint-config-standard": "^8.0.1",
"tslint-eslint-rules": "^5.4.0",
"typedoc": "^0.14.2", "typedoc": "^0.14.2",
"typescript": "^3.5.2", "typescript": "^3.5.2",
"url-loader": "^2.0.0", "url-loader": "^2.0.0",
@@ -131,8 +131,11 @@
"start": "cross-env TERMINUS_DEV=1 electron app --debug", "start": "cross-env TERMINUS_DEV=1 electron app --debug",
"prod": "cross-env TERMINUS_DEV=1 electron app", "prod": "cross-env TERMINUS_DEV=1 electron app",
"docs": "typedoc --out docs/api terminus-core/src && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src", "docs": "typedoc --out docs/api terminus-core/src && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src",
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts", "lint": "eslint --ext ts */src",
"postinstall": "node ./scripts/install-deps.js" "postinstall": "node ./scripts/install-deps.js"
}, },
"repository": "eugeny/terminus" "repository": "eugeny/terminus",
"dependencies": {
"eslint-plugin-import": "^2.17.3"
}
} }

View File

@@ -1,12 +1,12 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { TerminalColorSchemeProvider, ITerminalColorScheme } from 'terminus-terminal' import { TerminalColorSchemeProvider, TerminalColorScheme } from 'terminus-terminal'
const schemeContents = require.context('../schemes/', true, /.*/) const schemeContents = require.context('../schemes/', true, /.*/)
@Injectable() @Injectable()
export class ColorSchemes extends TerminalColorSchemeProvider { export class ColorSchemes extends TerminalColorSchemeProvider {
async getSchemes (): Promise<ITerminalColorScheme[]> { async getSchemes (): Promise<TerminalColorScheme[]> {
const schemes: ITerminalColorScheme[] = [] const schemes: TerminalColorScheme[] = []
schemeContents.keys().forEach(schemeFile => { schemeContents.keys().forEach(schemeFile => {
const lines = (schemeContents(schemeFile).default as string).split('\n') const lines = (schemeContents(schemeFile).default as string).split('\n')
@@ -16,7 +16,7 @@ export class ColorSchemes extends TerminalColorSchemeProvider {
lines lines
.filter(x => x.startsWith('#define')) .filter(x => x.startsWith('#define'))
.map(x => x.split(' ').map(v => v.trim())) .map(x => x.split(' ').map(v => v.trim()))
.forEach(([ignore, variableName, variableValue]) => { .forEach(([_, variableName, variableValue]) => {
variables[variableName] = variableValue variables[variableName] = variableValue
}) })

View File

@@ -8,4 +8,4 @@ import { ColorSchemes } from './colorSchemes'
{ provide: TerminalColorSchemeProvider, useClass: ColorSchemes, multi: true }, { provide: TerminalColorSchemeProvider, useClass: ColorSchemes, multi: true },
], ],
}) })
export default class PopularThemesModule { } export default class PopularThemesModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -1,4 +1,4 @@
export interface IHotkeyDescription { export interface HotkeyDescription {
id: string id: string
name: string name: string
} }
@@ -8,7 +8,7 @@ export interface IHotkeyDescription {
* must also provide the `hotkeys.foo` config options with the default values * must also provide the `hotkeys.foo` config options with the default values
*/ */
export abstract class HotkeyProvider { export abstract class HotkeyProvider {
hotkeys: IHotkeyDescription[] = [] hotkeys: HotkeyDescription[] = []
abstract provide (): Promise<IHotkeyDescription[]> abstract provide (): Promise<HotkeyDescription[]>
} }

View File

@@ -1,9 +1,9 @@
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component' export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component' export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
export { TabRecoveryProvider, RecoveredTab } from './tabRecovery' export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider' export { ToolbarButtonProvider, ToolbarButton } from './toolbarButtonProvider'
export { ConfigProvider } from './configProvider' export { ConfigProvider } from './configProvider'
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider' export { HotkeyProvider, HotkeyDescription } from './hotkeyProvider'
export { Theme } from './theme' export { Theme } from './theme'
export { TabContextMenuItemProvider } from './tabContextMenuProvider' export { TabContextMenuItemProvider } from './tabContextMenuProvider'

View File

@@ -3,7 +3,7 @@ import { SafeHtml } from '@angular/platform-browser'
/** /**
* See [[ToolbarButtonProvider]] * See [[ToolbarButtonProvider]]
*/ */
export interface IToolbarButton { export interface ToolbarButton {
/** /**
* Raw SVG icon code * Raw SVG icon code
*/ */
@@ -25,15 +25,15 @@ export interface IToolbarButton {
click?: () => void click?: () => void
submenu?: () => Promise<IToolbarButton[]> submenu?: () => Promise<ToolbarButton[]>
/** @hidden */ /** @hidden */
submenuItems?: IToolbarButton[] submenuItems?: ToolbarButton[]
} }
/** /**
* Extend to add buttons to the toolbar * Extend to add buttons to the toolbar
*/ */
export abstract class ToolbarButtonProvider { export abstract class ToolbarButtonProvider {
abstract provide (): IToolbarButton[] abstract provide (): ToolbarButton[]
} }

View File

@@ -15,7 +15,7 @@ import { TouchbarService } from '../services/touchbar.service'
import { BaseTabComponent } from './baseTab.component' import { BaseTabComponent } from './baseTab.component'
import { SafeModeModalComponent } from './safeModeModal.component' import { SafeModeModalComponent } from './safeModeModal.component'
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api' import { AppService, ToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */ /** @hidden */
@Component({ @Component({
@@ -26,36 +26,36 @@ import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
trigger('animateTab', [ trigger('animateTab', [
state('in', style({ state('in', style({
'flex-basis': '200px', 'flex-basis': '200px',
'width': '200px', width: '200px',
})), })),
transition(':enter', [ transition(':enter', [
style({ style({
'flex-basis': '1px', 'flex-basis': '1px',
'width': '1px', width: '1px',
}), }),
animate('250ms ease-in-out', style({ animate('250ms ease-in-out', style({
'flex-basis': '200px', 'flex-basis': '200px',
'width': '200px', width: '200px',
})) })),
]), ]),
transition(':leave', [ transition(':leave', [
style({ style({
'flex-basis': '200px', 'flex-basis': '200px',
'width': '200px', width: '200px',
}), }),
animate('250ms ease-in-out', style({ animate('250ms ease-in-out', style({
'flex-basis': '1px', 'flex-basis': '1px',
'width': '1px', width: '1px',
})) })),
]) ]),
]) ]),
] ],
}) })
export class AppRootComponent { export class AppRootComponent {
Platform = Platform Platform = Platform
@Input() ready = false @Input() ready = false
@Input() leftToolbarButtons: IToolbarButton[] @Input() leftToolbarButtons: ToolbarButton[]
@Input() rightToolbarButtons: IToolbarButton[] @Input() rightToolbarButtons: ToolbarButton[]
@HostBinding('class.platform-win32') platformClassWindows = process.platform === 'win32' @HostBinding('class.platform-win32') platformClassWindows = process.platform === 'win32'
@HostBinding('class.platform-darwin') platformClassMacOS = process.platform === 'darwin' @HostBinding('class.platform-darwin') platformClassMacOS = process.platform === 'darwin'
@HostBinding('class.platform-linux') platformClassLinux = process.platform === 'linux' @HostBinding('class.platform-linux') platformClassLinux = process.platform === 'linux'
@@ -89,7 +89,7 @@ export class AppRootComponent {
this.updateIcon = domSanitizer.bypassSecurityTrustHtml(require('../icons/gift.svg')), this.updateIcon = domSanitizer.bypassSecurityTrustHtml(require('../icons/gift.svg')),
this.hotkeys.matchedHotkey.subscribe((hotkey) => { this.hotkeys.matchedHotkey.subscribe((hotkey: string) => {
if (hotkey.startsWith('tab-')) { if (hotkey.startsWith('tab-')) {
const index = parseInt(hotkey.split('-')[1]) const index = parseInt(hotkey.split('-')[1])
if (index <= this.app.tabs.length) { if (index <= this.app.tabs.length) {
@@ -233,20 +233,20 @@ export class AppRootComponent {
}) })
} }
async generateButtonSubmenu (button: IToolbarButton) { async generateButtonSubmenu (button: ToolbarButton) {
if (button.submenu) { if (button.submenu) {
button.submenuItems = await button.submenu() button.submenuItems = await button.submenu()
} }
} }
private getToolbarButtons (aboveZero: boolean): IToolbarButton[] { private getToolbarButtons (aboveZero: boolean): ToolbarButton[] {
let buttons: IToolbarButton[] = [] let buttons: ToolbarButton[] = []
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => { this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
buttons = buttons.concat(provider.provide()) buttons = buttons.concat(provider.provide())
}) })
return buttons return buttons
.filter((button) => (button.weight > 0) === aboveZero) .filter(button => button.weight > 0 === aboveZero)
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0)) .sort((a: ToolbarButton, b: ToolbarButton) => (a.weight || 0) - (b.weight || 0))
} }
private updateVibrancy () { private updateVibrancy () {

View File

@@ -8,7 +8,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
styles: [require('./checkbox.component.scss')], styles: [require('./checkbox.component.scss')],
providers: [ providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: CheckboxComponent, multi: true }, { provide: NG_VALUE_ACCESSOR, useExisting: CheckboxComponent, multi: true },
] ],
}) })
export class CheckboxComponent implements ControlValueAccessor { export class CheckboxComponent implements ControlValueAccessor {
@HostBinding('class.active') @Input() model: boolean @HostBinding('class.active') @Input() model: boolean

View File

@@ -6,8 +6,8 @@ import { TabsService } from '../services/tabs.service'
import { HotkeysService } from '../services/hotkeys.service' import { HotkeysService } from '../services/hotkeys.service'
import { TabRecoveryService } from '../services/tabRecovery.service' import { TabRecoveryService } from '../services/tabRecovery.service'
export declare type SplitOrientation = 'v' | 'h' export type SplitOrientation = 'v' | 'h' // eslint-disable-line @typescript-eslint/no-type-alias
export declare type SplitDirection = 'r' | 't' | 'b' | 'l' export type SplitDirection = 'r' | 't' | 'b' | 'l' // eslint-disable-line @typescript-eslint/no-type-alias
/** /**
* Describes a horizontal or vertical split row or column * Describes a horizontal or vertical split row or column
@@ -198,33 +198,33 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
return return
} }
switch (hotkey) { switch (hotkey) {
case 'split-right': case 'split-right':
this.splitTab(this.focusedTab, 'r') this.splitTab(this.focusedTab, 'r')
break break
case 'split-bottom': case 'split-bottom':
this.splitTab(this.focusedTab, 'b') this.splitTab(this.focusedTab, 'b')
break break
case 'split-top': case 'split-top':
this.splitTab(this.focusedTab, 't') this.splitTab(this.focusedTab, 't')
break break
case 'split-left': case 'split-left':
this.splitTab(this.focusedTab, 'l') this.splitTab(this.focusedTab, 'l')
break break
case 'pane-nav-left': case 'pane-nav-left':
this.navigate('l') this.navigate('l')
break break
case 'pane-nav-right': case 'pane-nav-right':
this.navigate('r') this.navigate('r')
break break
case 'pane-nav-up': case 'pane-nav-up':
this.navigate('t') this.navigate('t')
break break
case 'pane-nav-down': case 'pane-nav-down':
this.navigate('b') this.navigate('b')
break break
case 'close-pane': case 'close-pane':
this.removeTab(this.focusedTab) this.removeTab(this.focusedTab)
break break
} }
}) })
} }
@@ -291,11 +291,11 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
let insertIndex = target.children.indexOf(relative) let insertIndex = target.children.indexOf(relative)
if ( if (
(target.orientation === 'v' && ['l', 'r'].includes(side)) || target.orientation === 'v' && ['l', 'r'].includes(side) ||
(target.orientation === 'h' && ['t', 'b'].includes(side)) target.orientation === 'h' && ['t', 'b'].includes(side)
) { ) {
const newContainer = new SplitContainer() const newContainer = new SplitContainer()
newContainer.orientation = (target.orientation === 'v') ? 'h' : 'v' newContainer.orientation = target.orientation === 'v' ? 'h' : 'v'
newContainer.children = [relative] newContainer.children = [relative]
newContainer.ratios = [1] newContainer.ratios = [1]
target.children[insertIndex] = newContainer target.children[insertIndex] = newContainer
@@ -306,7 +306,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
if (insertIndex === -1) { if (insertIndex === -1) {
insertIndex = 0 insertIndex = 0
} else { } else {
insertIndex += (side === 'l' || side === 't') ? 0 : 1 insertIndex += side === 'l' || side === 't' ? 0 : 1
} }
for (let i = 0; i < target.children.length; i++) { for (let i = 0; i < target.children.length; i++) {
@@ -419,7 +419,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
} }
private attachTabView (tab: BaseTabComponent) { private attachTabView (tab: BaseTabComponent) {
const ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any> const ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any> // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
this.viewRefs.set(tab, ref) this.viewRefs.set(tab, ref)
ref.rootNodes[0].addEventListener('click', () => this.focus(tab)) ref.rootNodes[0].addEventListener('click', () => this.focus(tab))
@@ -448,7 +448,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
} }
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) { private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
const size = (root.orientation === 'v') ? h : w const size = root.orientation === 'v' ? h : w
const sizes = root.ratios.map(x => x * size) const sizes = root.ratios.map(x => x * size)
root.x = x root.x = x
@@ -458,10 +458,10 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
let offset = 0 let offset = 0
root.children.forEach((child, i) => { root.children.forEach((child, i) => {
const childX = (root.orientation === 'v') ? x : (x + offset) const childX = root.orientation === 'v' ? x : x + offset
const childY = (root.orientation === 'v') ? (y + offset) : y const childY = root.orientation === 'v' ? y + offset : y
const childW = (root.orientation === 'v') ? w : sizes[i] const childW = root.orientation === 'v' ? w : sizes[i]
const childH = (root.orientation === 'v') ? sizes[i] : h const childH = root.orientation === 'v' ? sizes[i] : h
if (child instanceof SplitContainer) { if (child instanceof SplitContainer) {
this.layoutInternal(child, childX, childY, childW, childH) this.layoutInternal(child, childX, childY, childW, childH)
} else { } else {
@@ -472,7 +472,7 @@ export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDes
element.style.width = `${childW}%` element.style.width = `${childW}%`
element.style.height = `${childH}%` element.style.height = `${childH}%`
element.style.opacity = (child === this.focusedTab) ? 1 : 0.75 element.style.opacity = child === this.focusedTab ? 1 : 0.75
} }
offset += sizes[i] offset += sizes[i]

View File

@@ -23,13 +23,13 @@ export class SplitTabSpannerComponent {
constructor (private element: ElementRef) { } constructor (private element: ElementRef) { }
ngAfterViewInit () { ngAfterViewInit () {
this.element.nativeElement.addEventListener('mousedown', e => { this.element.nativeElement.addEventListener('mousedown', (e: MouseEvent) => {
this.isActive = true this.isActive = true
const start = this.isVertical ? e.pageY : e.pageX const start = this.isVertical ? e.pageY : e.pageX
let current = start let current = start
const oldPosition = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft const oldPosition: number = this.isVertical ? this.element.nativeElement.offsetTop : this.element.nativeElement.offsetLeft
const dragHandler = e => { const dragHandler = (e: MouseEvent) => {
current = this.isVertical ? e.pageY : e.pageX current = this.isVertical ? e.pageY : e.pageX
const newPosition = oldPosition + (current - start) const newPosition = oldPosition + (current - start)
if (this.isVertical) { if (this.isVertical) {

View File

@@ -1,7 +1,7 @@
import { Component, Inject } from '@angular/core' import { Component, Inject } from '@angular/core'
import { ConfigService } from '../services/config.service' import { ConfigService } from '../services/config.service'
import { HomeBaseService } from '../services/homeBase.service' import { HomeBaseService } from '../services/homeBase.service'
import { IToolbarButton, ToolbarButtonProvider } from '../api' import { ToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */ /** @hidden */
@Component({ @Component({
@@ -19,11 +19,11 @@ export class StartPageComponent {
) { ) {
} }
getButtons (): IToolbarButton[] { getButtons (): ToolbarButton[] {
return this.config.enabledServices(this.toolbarButtonProviders) return this.config.enabledServices(this.toolbarButtonProviders)
.map(provider => provider.provide()) .map(provider => provider.provide())
.reduce((a, b) => a.concat(b)) .reduce((a, b) => a.concat(b))
.filter(x => !!x.click) .filter(x => !!x.click)
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0)) .sort((a: ToolbarButton, b: ToolbarButton) => (a.weight || 0) - (b.weight || 0))
} }
} }

View File

@@ -85,7 +85,6 @@ export class TabHeaderComponent {
contextMenu.popup({ contextMenu.popup({
x: $event.pageX, x: $event.pageX,
y: $event.pageY, y: $event.pageY,
async: true,
}) })
} }
} }

View File

@@ -6,4 +6,4 @@ import { Component } from '@angular/core'
template: require('./titleBar.component.pug'), template: require('./titleBar.component.pug'),
styles: [require('./titleBar.component.scss')], styles: [require('./titleBar.component.scss')],
}) })
export class TitleBarComponent { } export class TitleBarComponent { } // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -17,7 +17,7 @@ import { CheckboxComponent } from './checkbox.component'
styles: [require('./toggle.component.scss')], styles: [require('./toggle.component.scss')],
providers: [ providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: ToggleComponent, multi: true }, { provide: NG_VALUE_ACCESSOR, useExisting: ToggleComponent, multi: true },
] ],
}) })
export class ToggleComponent extends CheckboxComponent { export class ToggleComponent extends CheckboxComponent {
} }

View File

@@ -2,7 +2,7 @@ import { Directive, AfterViewInit, ElementRef } from '@angular/core'
/** @hidden */ /** @hidden */
@Directive({ @Directive({
selector: '[autofocus]' selector: '[autofocus]',
}) })
export class AutofocusDirective implements AfterViewInit { export class AutofocusDirective implements AfterViewInit {
constructor (private el: ElementRef) { } constructor (private el: ElementRef) { }

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { IHotkeyDescription, HotkeyProvider } from './api/hotkeyProvider' import { HotkeyDescription, HotkeyProvider } from './api/hotkeyProvider'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
export class AppHotkeyProvider extends HotkeyProvider { export class AppHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [ hotkeys: HotkeyDescription[] = [
{ {
id: 'new-window', id: 'new-window',
name: 'New window', name: 'New window',
@@ -115,7 +115,7 @@ export class AppHotkeyProvider extends HotkeyProvider {
}, },
] ]
async provide (): Promise<IHotkeyDescription[]> { async provide (): Promise<HotkeyDescription[]> {
return this.hotkeys return this.hotkeys
} }
} }

View File

@@ -39,6 +39,12 @@ import { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu }
import 'perfect-scrollbar/css/perfect-scrollbar.css' import 'perfect-scrollbar/css/perfect-scrollbar.css'
import 'ng2-dnd/bundles/style.css' import 'ng2-dnd/bundles/style.css'
// PerfectScrollbar fix
import { fromEvent } from 'rxjs/internal/observable/fromEvent'
import { merge } from 'rxjs/internal/observable/merge'
require('rxjs').fromEvent = fromEvent
require('rxjs').merge = merge
const PROVIDERS = [ const PROVIDERS = [
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true }, { provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
{ provide: Theme, useClass: StandardTheme, multi: true }, { provide: Theme, useClass: StandardTheme, multi: true },
@@ -49,7 +55,7 @@ const PROVIDERS = [
{ provide: TabContextMenuItemProvider, useClass: CloseContextMenu, multi: true }, { provide: TabContextMenuItemProvider, useClass: CloseContextMenu, multi: true },
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true }, { provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true }, { provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } } { provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
] ]
/** @hidden */ /** @hidden */
@@ -88,9 +94,9 @@ const PROVIDERS = [
CheckboxComponent, CheckboxComponent,
ToggleComponent, ToggleComponent,
AutofocusDirective, AutofocusDirective,
] ],
}) })
export default class AppModule { export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
constructor (app: AppService, config: ConfigService) { constructor (app: AppService, config: ConfigService) {
app.ready$.subscribe(() => { app.ready$.subscribe(() => {
if (config.store.enableWelcomeTab) { if (config.store.enableWelcomeTab) {
@@ -107,11 +113,9 @@ export default class AppModule {
} }
} }
// PerfectScrollbar fix
import { fromEvent } from 'rxjs/internal/observable/fromEvent'
import { merge } from 'rxjs/internal/observable/merge'
require('rxjs').fromEvent = fromEvent
require('rxjs').merge = merge
export { AppRootComponent as bootstrap } export { AppRootComponent as bootstrap }
export * from './api' export * from './api'
// Deprecations
export { ToolbarButton as IToolbarButton } from './api'
export { HotkeyDescription as IHotkeyDescription } from './api'

View File

@@ -21,7 +21,7 @@ class CompletionObserver {
} }
async tick () { async tick () {
if (!(await this.tab.getCurrentProcess())) { if (!await this.tab.getCurrentProcess()) {
this.done.next(null) this.done.next(null)
this.stop() this.stop()
} }
@@ -81,7 +81,7 @@ export class AppService {
}) })
} }
private addTabRaw (tab: BaseTabComponent) { addTabRaw (tab: BaseTabComponent) {
this.tabs.push(tab) this.tabs.push(tab)
this.selectTab(tab) this.selectTab(tab)
this.tabsChanged.next() this.tabsChanged.next()

View File

@@ -14,7 +14,7 @@ function isStructuralMember (v) {
Object.keys(v).length > 0 && !v.__nonStructural Object.keys(v).length > 0 && !v.__nonStructural
} }
function isNonStructuralObjectMember (v) { function isNonStructuralObjectMember (v): boolean {
return v instanceof Object && !(v instanceof Array) && v.__nonStructural return v instanceof Object && !(v instanceof Array) && v.__nonStructural
} }
@@ -46,13 +46,13 @@ export class ConfigProxy {
get: () => this.getValue(key), get: () => this.getValue(key),
set: (value) => { set: (value) => {
this.setValue(key, value) this.setValue(key, value)
} },
} }
) )
} }
} }
this.getValue = (key: string) => { this.getValue = (key: string) => { // eslint-disable-line @typescript-eslint/unbound-method
if (real[key] !== undefined) { if (real[key] !== undefined) {
return real[key] return real[key]
} else { } else {
@@ -66,13 +66,13 @@ export class ConfigProxy {
} }
} }
this.setValue = (key: string, value: any) => { this.setValue = (key: string, value: any) => { // eslint-disable-line @typescript-eslint/unbound-method
real[key] = value real[key] = value
} }
} }
getValue (key: string): any { } // tslint:disable-line getValue (_key: string): any { }
setValue (key: string, value: any) { } // tslint:disable-line setValue (_key: string, _value: any) { }
} }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@@ -160,10 +160,6 @@ export class ConfigService {
this.emitChange() this.emitChange()
} }
private emitChange (): void {
this.changed.next()
}
requestRestart (): void { requestRestart (): void {
this.restartRequested = true this.restartRequested = true
} }
@@ -179,7 +175,7 @@ export class ConfigService {
this.servicesCache = {} this.servicesCache = {}
const ngModule = window['rootModule'].ngInjectorDef const ngModule = window['rootModule'].ngInjectorDef
for (const imp of ngModule.imports) { for (const imp of ngModule.imports) {
const module = (imp['ngModule'] || imp) const module = imp['ngModule'] || imp
if (module.ngInjectorDef && module.ngInjectorDef.providers) { if (module.ngInjectorDef && module.ngInjectorDef.providers) {
this.servicesCache[module['pluginName']] = module.ngInjectorDef.providers.map(provider => { this.servicesCache[module['pluginName']] = module.ngInjectorDef.providers.map(provider => {
return provider['useClass'] || provider return provider['useClass'] || provider
@@ -196,4 +192,8 @@ export class ConfigService {
return true return true
}) })
} }
private emitChange (): void {
this.changed.next()
}
} }

View File

@@ -3,11 +3,6 @@ import { ConfigService } from '../services/config.service'
import { ElectronService } from '../services/electron.service' import { ElectronService } from '../services/electron.service'
import { HostAppService, Bounds } from '../services/hostApp.service' import { HostAppService, Bounds } from '../services/hostApp.service'
export interface IScreen {
id: string
name: string
}
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class DockingService { export class DockingService {
/** @hidden */ /** @hidden */
@@ -29,7 +24,7 @@ export class DockingService {
} }
let display = this.electron.screen.getAllDisplays() let display = this.electron.screen.getAllDisplays()
.filter((x) => x.id === this.config.store.appearance.dockScreen)[0] .filter(x => x.id === this.config.store.appearance.dockScreen)[0]
if (!display) { if (!display) {
display = this.getCurrentScreen() display = this.getCurrentScreen()
} }
@@ -71,10 +66,10 @@ export class DockingService {
return this.electron.screen.getAllDisplays().map((display, index) => { return this.electron.screen.getAllDisplays().map((display, index) => {
return { return {
id: display.id, id: display.id,
name: { name: [
0: 'Primary display', 'Primary display',
1: 'Secondary display', 'Secondary display',
}[index] || `Display ${index + 1}` ][index] || `Display ${index + 1}`,
} }
}) })
} }

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { TouchBar, BrowserWindow, Menu, MenuItem } from 'electron' import { TouchBar, BrowserWindow, Menu, MenuItem, NativeImage } from 'electron'
export interface MessageBoxResponse { export interface MessageBoxResponse {
response: number response: number
@@ -8,16 +8,16 @@ export interface MessageBoxResponse {
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ElectronService { export class ElectronService {
app: any app: Electron.App
ipcRenderer: any ipcRenderer: Electron.IpcRenderer
shell: any shell: Electron.Shell
dialog: any dialog: Electron.Dialog
clipboard: any clipboard: Electron.Clipboard
globalShortcut: any globalShortcut: Electron.GlobalShortcut
nativeImage: any nativeImage: typeof NativeImage
screen: any screen: Electron.Screen
remote: any remote: Electron.Remote
autoUpdater: any autoUpdater: Electron.AutoUpdater
TouchBar: typeof TouchBar TouchBar: typeof TouchBar
BrowserWindow: typeof BrowserWindow BrowserWindow: typeof BrowserWindow
Menu: typeof Menu Menu: typeof Menu
@@ -52,7 +52,7 @@ export class ElectronService {
} }
} }
showMessageBox ( async showMessageBox (
browserWindow: Electron.BrowserWindow, browserWindow: Electron.BrowserWindow,
options: Electron.MessageBoxOptions options: Electron.MessageBoxOptions
): Promise<MessageBoxResponse> { ): Promise<MessageBoxResponse> {

View File

@@ -2,8 +2,8 @@ import * as os from 'os'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ElectronService } from './electron.service' import { ElectronService } from './electron.service'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import mixpanel = require('mixpanel') import * as mixpanel from 'mixpanel'
import uuidv4 = require('uuid/v4') import * as uuidv4 from 'uuid/v4'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class HomeBaseService { export class HomeBaseService {
@@ -53,7 +53,7 @@ export class HomeBaseService {
getAnalyticsProperties () { getAnalyticsProperties () {
return { return {
distinct_id: window.localStorage.analyticsUserID, distinct_id: window.localStorage.analyticsUserID, // eslint-disable-line @typescript-eslint/camelcase
platform: process.platform, platform: process.platform,
os: os.release(), os: os.release(),
version: this.appVersion, version: this.appVersion,

View File

@@ -1,5 +1,5 @@
import * as path from 'path' import * as path from 'path'
import shellEscape = require('shell-escape') import * as shellEscape from 'shell-escape'
import { Observable, Subject } from 'rxjs' import { Observable, Subject } from 'rxjs'
import { Injectable, NgZone, EventEmitter } from '@angular/core' import { Injectable, NgZone, EventEmitter } from '@angular/core'
import { ElectronService } from './electron.service' import { ElectronService } from './electron.service'
@@ -97,7 +97,7 @@ export class HostAppService {
this.platform = { this.platform = {
win32: Platform.Windows, win32: Platform.Windows,
darwin: Platform.macOS, darwin: Platform.macOS,
linux: Platform.Linux linux: Platform.Linux,
}[process.platform] }[process.platform]
this.windowId = parseInt(location.search.substring(1)) this.windowId = parseInt(location.search.substring(1))

View File

@@ -1,5 +1,5 @@
import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core' import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
import { IHotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider' import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
import { stringifyKeySequence } from './hotkeys.util' import { stringifyKeySequence } from './hotkeys.util'
import { ConfigService } from '../services/config.service' import { ConfigService } from '../services/config.service'
import { ElectronService } from '../services/electron.service' import { ElectronService } from '../services/electron.service'
@@ -24,7 +24,7 @@ export class HotkeysService {
globalHotkey = new EventEmitter() globalHotkey = new EventEmitter()
private currentKeystrokes: EventBufferEntry[] = [] private currentKeystrokes: EventBufferEntry[] = []
private disabledLevel = 0 private disabledLevel = 0
private hotkeyDescriptions: IHotkeyDescription[] = [] private hotkeyDescriptions: HotkeyDescription[] = []
/** @hidden */ /** @hidden */
constructor ( constructor (
@@ -94,56 +94,7 @@ export class HotkeysService {
return stringifyKeySequence(this.currentKeystrokes.map(x => x.event)) return stringifyKeySequence(this.currentKeystrokes.map(x => x.event))
} }
private registerGlobalHotkey () { getCurrentFullyMatchedHotkey (): string {
this.electron.globalShortcut.unregisterAll()
let value = this.config.store.hotkeys['toggle-window'] || []
if (typeof value === 'string') {
value = [value]
}
value.forEach((item: string | string[]) => {
item = (typeof item === 'string') ? [item] : item
try {
let electronKeySpec = item[0]
electronKeySpec = electronKeySpec.replace('⌘', 'Command')
electronKeySpec = electronKeySpec.replace('⌥', 'Alt')
electronKeySpec = electronKeySpec.replace(/-/g, '+')
this.electron.globalShortcut.register(electronKeySpec, () => {
this.globalHotkey.emit()
})
} catch (err) {
console.error('Could not register the global hotkey:', err)
}
})
}
private getHotkeysConfig () {
return this.getHotkeysConfigRecursive(this.config.store.hotkeys)
}
private getHotkeysConfigRecursive (branch: any) {
const keys = {}
for (const key in branch) {
let value = branch[key]
if (value instanceof Object && !(value instanceof Array)) {
const subkeys = this.getHotkeysConfigRecursive(value)
for (const subkey in subkeys) {
keys[key + '.' + subkey] = subkeys[subkey]
}
} else {
if (typeof value === 'string') {
value = [value]
}
if (value) {
value = value.map((item: string | string[]) => (typeof item === 'string') ? [item] : item)
keys[key] = value
}
}
}
return keys
}
private getCurrentFullyMatchedHotkey (): string {
const currentStrokes = this.getCurrentKeystrokes() const currentStrokes = this.getCurrentKeystrokes()
const config = this.getHotkeysConfig() const config = this.getHotkeysConfig()
for (const id in config) { for (const id in config) {
@@ -178,7 +129,7 @@ export class HotkeysService {
result.push({ result.push({
matchedLength: matchLength, matchedLength: matchLength,
id, id,
strokes: sequence strokes: sequence,
}) })
break break
} }
@@ -188,7 +139,7 @@ export class HotkeysService {
return result return result
} }
getHotkeyDescription (id: string): IHotkeyDescription { getHotkeyDescription (id: string): HotkeyDescription {
return this.hotkeyDescriptions.filter((x) => x.id === id)[0] return this.hotkeyDescriptions.filter((x) => x.id === id)[0]
} }
@@ -204,7 +155,7 @@ export class HotkeysService {
return this.disabledLevel === 0 return this.disabledLevel === 0
} }
async getHotkeyDescriptions (): Promise<IHotkeyDescription[]> { async getHotkeyDescriptions (): Promise<HotkeyDescription[]> {
return ( return (
await Promise.all( await Promise.all(
this.config.enabledServices(this.hotkeyProviders) this.config.enabledServices(this.hotkeyProviders)
@@ -212,4 +163,53 @@ export class HotkeysService {
) )
).reduce((a, b) => a.concat(b)) ).reduce((a, b) => a.concat(b))
} }
private registerGlobalHotkey () {
this.electron.globalShortcut.unregisterAll()
let value = this.config.store.hotkeys['toggle-window'] || []
if (typeof value === 'string') {
value = [value]
}
value.forEach((item: string | string[]) => {
item = typeof item === 'string' ? [item] : item
try {
let electronKeySpec = item[0]
electronKeySpec = electronKeySpec.replace('⌘', 'Command')
electronKeySpec = electronKeySpec.replace('⌥', 'Alt')
electronKeySpec = electronKeySpec.replace(/-/g, '+')
this.electron.globalShortcut.register(electronKeySpec, () => {
this.globalHotkey.emit()
})
} catch (err) {
console.error('Could not register the global hotkey:', err)
}
})
}
private getHotkeysConfig () {
return this.getHotkeysConfigRecursive(this.config.store.hotkeys)
}
private getHotkeysConfigRecursive (branch: any) {
const keys = {}
for (const key in branch) {
let value = branch[key]
if (value instanceof Object && !(value instanceof Array)) {
const subkeys = this.getHotkeysConfigRecursive(value)
for (const subkey in subkeys) {
keys[key + '.' + subkey] = subkeys[subkey]
}
} else {
if (typeof value === 'string') {
value = [value]
}
if (value) {
value = value.map((item: string | string[]) => typeof item === 'string' ? [item] : item)
keys[key] = value
}
}
}
return keys
}
} }

View File

@@ -20,9 +20,9 @@ const initializeWinston = (electron: ElectronService) => {
handleExceptions: false, handleExceptions: false,
maxsize: 5242880, maxsize: 5242880,
maxFiles: 5, maxFiles: 5,
}) }),
], ],
exitOnError: false exitOnError: false,
}) })
} }
@@ -32,18 +32,32 @@ export class Logger {
private name: string, private name: string,
) {} ) {}
debug (...args: any[]) {
this.doLog('debug', ...args)
}
info (...args: any[]) {
this.doLog('info', ...args)
}
warn (...args: any[]) {
this.doLog('warn', ...args)
}
error (...args: any[]) {
this.doLog('error', ...args)
}
log (...args: any[]) {
this.doLog('log', ...args)
}
private doLog (level: string, ...args: any[]) { private doLog (level: string, ...args: any[]) {
console[level](`%c[${this.name}]`, 'color: #aaa', ...args) console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
if (this.winstonLogger) { if (this.winstonLogger) {
this.winstonLogger[level](...args) this.winstonLogger[level](...args)
} }
} }
debug (...args: any[]) { this.doLog('debug', ...args) }
info (...args: any[]) { this.doLog('info', ...args) }
warn (...args: any[]) { this.doLog('warn', ...args) }
error (...args: any[]) { this.doLog('error', ...args) }
log (...args: any[]) { this.doLog('log', ...args) }
} }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })

View File

@@ -5,9 +5,11 @@ import { Injectable } from '@angular/core'
import { ElectronService } from './electron.service' import { ElectronService } from './electron.service'
import { HostAppService, Platform } from './hostApp.service' import { HostAppService, Platform } from './hostApp.service'
/* eslint-disable block-scoped-var */
try { try {
var wnr = require('windows-native-registry') // tslint:disable-line var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
} catch (_) { } // tslint:disable-line } catch (_) { }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ShellIntegrationService { export class ShellIntegrationService {
@@ -17,11 +19,11 @@ export class ShellIntegrationService {
private registryKeys = [ private registryKeys = [
{ {
path: 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here', path: 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here',
command: 'open "%V"' command: 'open "%V"',
}, },
{ {
path: 'Software\\Classes\\*\\shell\\Paste path into Terminus', path: 'Software\\Classes\\*\\shell\\Paste path into Terminus',
command: 'paste "%V"' command: 'paste "%V"',
}, },
] ]
constructor ( constructor (
@@ -40,15 +42,6 @@ export class ShellIntegrationService {
this.updatePaths() this.updatePaths()
} }
private async updatePaths (): Promise<void> {
// Update paths in case of an update
if (this.hostApp.platform === Platform.Windows) {
if (await this.isInstalled()) {
await this.install()
}
}
}
async isInstalled (): Promise<boolean> { async isInstalled (): Promise<boolean> {
if (this.hostApp.platform === Platform.macOS) { if (this.hostApp.platform === Platform.macOS) {
return fs.exists(path.join(this.automatorWorkflowsDestination, this.automatorWorkflows[0])) return fs.exists(path.join(this.automatorWorkflowsDestination, this.automatorWorkflows[0]))
@@ -59,7 +52,7 @@ export class ShellIntegrationService {
} }
async install () { async install () {
const exe = process.env.PORTABLE_EXECUTABLE_FILE || this.electron.app.getPath('exe') const exe: string = process.env.PORTABLE_EXECUTABLE_FILE || this.electron.app.getPath('exe')
if (this.hostApp.platform === Platform.macOS) { if (this.hostApp.platform === Platform.macOS) {
for (const wf of this.automatorWorkflows) { for (const wf of this.automatorWorkflows) {
await exec(`cp -r "${this.automatorWorkflowsLocation}/${wf}" "${this.automatorWorkflowsDestination}"`) await exec(`cp -r "${this.automatorWorkflowsLocation}/${wf}" "${this.automatorWorkflowsDestination}"`)
@@ -85,4 +78,13 @@ export class ShellIntegrationService {
} }
} }
} }
private async updatePaths (): Promise<void> {
// Update paths in case of an update
if (this.hostApp.platform === Platform.Windows) {
if (await this.isInstalled()) {
await this.install()
}
}
}
} }

View File

@@ -54,5 +54,4 @@ export class TabRecoveryService {
} }
return [] return []
} }
} }

View File

@@ -2,7 +2,8 @@ import { Injectable, ComponentFactoryResolver, Injector } from '@angular/core'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { TabRecoveryService } from './tabRecovery.service' import { TabRecoveryService } from './tabRecovery.service'
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent // eslint-disable-next-line @typescript-eslint/no-type-alias
export type TabComponentType = new (...args: any[]) => BaseTabComponent
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class TabsService { export class TabsService {

View File

@@ -4,7 +4,7 @@ import { AppService } from './app.service'
import { ConfigService } from './config.service' import { ConfigService } from './config.service'
import { ElectronService } from './electron.service' import { ElectronService } from './electron.service'
import { HostAppService, Platform } from './hostApp.service' import { HostAppService, Platform } from './hostApp.service'
import { IToolbarButton, ToolbarButtonProvider } from '../api' import { ToolbarButton, ToolbarButtonProvider } from '../api'
/** @hidden */ /** @hidden */
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@@ -61,7 +61,7 @@ export class TouchbarService {
return return
} }
let buttons: IToolbarButton[] = [] let buttons: ToolbarButton[] = []
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => { this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
buttons = buttons.concat(provider.provide()) buttons = buttons.concat(provider.provide())
}) })
@@ -76,7 +76,7 @@ export class TouchbarService {
selectedIndex: this.app.tabs.indexOf(this.app.activeTab), selectedIndex: this.app.tabs.indexOf(this.app.activeTab),
change: (selectedIndex) => this.zone.run(() => { change: (selectedIndex) => this.zone.run(() => {
this.app.selectTab(this.app.tabs[selectedIndex]) this.app.selectTab(this.app.tabs[selectedIndex])
}) }),
}) })
this.buttonsSegmentedControl = new this.electron.TouchBar.TouchBarSegmentedControl({ this.buttonsSegmentedControl = new this.electron.TouchBar.TouchBarSegmentedControl({
@@ -84,7 +84,7 @@ export class TouchbarService {
mode: 'buttons', mode: 'buttons',
change: (selectedIndex) => this.zone.run(() => { change: (selectedIndex) => this.zone.run(() => {
buttons[selectedIndex].click() buttons[selectedIndex].click()
}) }),
}) })
const touchBar = new this.electron.TouchBar({ const touchBar = new this.electron.TouchBar({
@@ -93,12 +93,12 @@ export class TouchbarService {
new this.electron.TouchBar.TouchBarSpacer({ size: 'flexible' }), new this.electron.TouchBar.TouchBarSpacer({ size: 'flexible' }),
new this.electron.TouchBar.TouchBarSpacer({ size: 'small' }), new this.electron.TouchBar.TouchBarSpacer({ size: 'small' }),
this.buttonsSegmentedControl, this.buttonsSegmentedControl,
] ],
}) })
this.hostApp.setTouchBar(touchBar) this.hostApp.setTouchBar(touchBar)
} }
private getButton (button: IToolbarButton): Electron.SegmentedControlSegment { private getButton (button: ToolbarButton): Electron.SegmentedControlSegment {
return { return {
label: button.touchBarNSImage ? null : this.shortenTitle(button.touchBarTitle || button.title), label: button.touchBarNSImage ? null : this.shortenTitle(button.touchBarTitle || button.title),
icon: button.touchBarNSImage ? this.getCachedNSImage(button.touchBarNSImage) : null, icon: button.touchBarNSImage ? this.getCachedNSImage(button.touchBarNSImage) : null,

View File

@@ -22,7 +22,7 @@ export class CloseContextMenu extends TabContextMenuItemProvider {
label: 'Close', label: 'Close',
click: () => this.zone.run(() => { click: () => this.zone.run(() => {
this.app.closeTab(tab, true) this.app.closeTab(tab, true)
}) }),
}, },
{ {
label: 'Close other tabs', label: 'Close other tabs',
@@ -30,7 +30,7 @@ export class CloseContextMenu extends TabContextMenuItemProvider {
for (const t of this.app.tabs.filter(x => x !== tab)) { for (const t of this.app.tabs.filter(x => x !== tab)) {
this.app.closeTab(t, true) this.app.closeTab(t, true)
} }
}) }),
}, },
{ {
label: 'Close tabs to the right', label: 'Close tabs to the right',
@@ -38,7 +38,7 @@ export class CloseContextMenu extends TabContextMenuItemProvider {
for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) { for (const t of this.app.tabs.slice(this.app.tabs.indexOf(tab) + 1)) {
this.app.closeTab(t, true) this.app.closeTab(t, true)
} }
}) }),
}, },
{ {
label: 'Close tabs to the left', label: 'Close tabs to the left',
@@ -46,7 +46,7 @@ export class CloseContextMenu extends TabContextMenuItemProvider {
for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) { for (const t of this.app.tabs.slice(0, this.app.tabs.indexOf(tab))) {
this.app.closeTab(t, true) this.app.closeTab(t, true)
} }
}) }),
}, },
] ]
} }
@@ -78,11 +78,11 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
return [ return [
{ {
label: 'Rename', label: 'Rename',
click: () => this.zone.run(() => tabHeader.showRenameTabModal()) click: () => this.zone.run(() => tabHeader.showRenameTabModal()),
}, },
{ {
label: 'Duplicate', label: 'Duplicate',
click: () => this.zone.run(() => this.app.duplicateTab(tab)) click: () => this.zone.run(() => this.app.duplicateTab(tab)),
}, },
{ {
label: 'Color', label: 'Color',
@@ -95,7 +95,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
tab.color = color.value tab.color = color.value
}), }),
})) as Electron.MenuItemConstructorOptions[], })) as Electron.MenuItemConstructorOptions[],
} },
] ]
} }
} }
@@ -138,7 +138,7 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
} else { } else {
this.app.stopObservingTabCompletion(tab) this.app.stopObservingTabCompletion(tab)
} }
}) }),
}, },
] ]
} }

View File

@@ -4,7 +4,7 @@ import * as semver from 'semver'
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { ConfigService, ElectronService } from 'terminus-core' import { ConfigService, ElectronService } from 'terminus-core'
import { IPluginInfo, PluginManagerService } from '../services/pluginManager.service' import { PluginInfo, PluginManagerService } from '../services/pluginManager.service'
enum BusyState { Installing, Uninstalling } enum BusyState { Installing, Uninstalling }
@@ -15,10 +15,10 @@ enum BusyState { Installing, Uninstalling }
}) })
export class PluginsSettingsTabComponent { export class PluginsSettingsTabComponent {
BusyState = BusyState BusyState = BusyState
@Input() availablePlugins$: Observable<IPluginInfo[]> @Input() availablePlugins$: Observable<PluginInfo[]>
@Input() availablePluginsQuery$ = new BehaviorSubject<string>('') @Input() availablePluginsQuery$ = new BehaviorSubject<string>('')
@Input() availablePluginsReady = false @Input() availablePluginsReady = false
@Input() knownUpgrades: {[id: string]: IPluginInfo} = {} @Input() knownUpgrades: {[id: string]: PluginInfo} = {}
@Input() busy: {[id: string]: BusyState} = {} @Input() busy: {[id: string]: BusyState} = {}
@Input() erroredPlugin: string @Input() erroredPlugin: string
@Input() errorMessage: string @Input() errorMessage: string
@@ -58,11 +58,11 @@ export class PluginsSettingsTabComponent {
this.availablePluginsQuery$.next(query) this.availablePluginsQuery$.next(query)
} }
isAlreadyInstalled (plugin: IPluginInfo): boolean { isAlreadyInstalled (plugin: PluginInfo): boolean {
return this.pluginManager.installedPlugins.some(x => x.name === plugin.name) return this.pluginManager.installedPlugins.some(x => x.name === plugin.name)
} }
async installPlugin (plugin: IPluginInfo): Promise<void> { async installPlugin (plugin: PluginInfo): Promise<void> {
this.busy[plugin.name] = BusyState.Installing this.busy[plugin.name] = BusyState.Installing
try { try {
await this.pluginManager.installPlugin(plugin) await this.pluginManager.installPlugin(plugin)
@@ -76,7 +76,7 @@ export class PluginsSettingsTabComponent {
} }
} }
async uninstallPlugin (plugin: IPluginInfo): Promise<void> { async uninstallPlugin (plugin: PluginInfo): Promise<void> {
this.busy[plugin.name] = BusyState.Uninstalling this.busy[plugin.name] = BusyState.Uninstalling
try { try {
await this.pluginManager.uninstallPlugin(plugin) await this.pluginManager.uninstallPlugin(plugin)
@@ -90,21 +90,21 @@ export class PluginsSettingsTabComponent {
} }
} }
async upgradePlugin (plugin: IPluginInfo): Promise<void> { async upgradePlugin (plugin: PluginInfo): Promise<void> {
return this.installPlugin(this.knownUpgrades[plugin.name]) return this.installPlugin(this.knownUpgrades[plugin.name])
} }
showPluginInfo (plugin: IPluginInfo) { showPluginInfo (plugin: PluginInfo) {
this.electron.shell.openExternal('https://www.npmjs.com/package/' + plugin.packageName) this.electron.shell.openExternal('https://www.npmjs.com/package/' + plugin.packageName)
} }
enablePlugin (plugin: IPluginInfo) { enablePlugin (plugin: PluginInfo) {
this.config.store.pluginBlacklist = this.config.store.pluginBlacklist.filter(x => x !== plugin.name) this.config.store.pluginBlacklist = this.config.store.pluginBlacklist.filter(x => x !== plugin.name)
this.config.save() this.config.save()
this.config.requestRestart() this.config.requestRestart()
} }
disablePlugin (plugin: IPluginInfo) { disablePlugin (plugin: PluginInfo) {
this.config.store.pluginBlacklist = [...this.config.store.pluginBlacklist, plugin.name] this.config.store.pluginBlacklist = [...this.config.store.pluginBlacklist, plugin.name]
this.config.save() this.config.save()
this.config.requestRestart() this.config.requestRestart()

View File

@@ -27,6 +27,6 @@ import { PluginsSettingsTabProvider } from './settings'
PluginsSettingsTabComponent, PluginsSettingsTabComponent,
], ],
}) })
export default class PluginManagerModule { } export default class PluginManagerModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
export { PluginManagerService } export { PluginManagerService }

View File

@@ -8,7 +8,7 @@ const NAME_PREFIX = 'terminus-'
const KEYWORD = 'terminus-plugin' const KEYWORD = 'terminus-plugin'
const OFFICIAL_NPM_ACCOUNT = 'eugenepankov' const OFFICIAL_NPM_ACCOUNT = 'eugenepankov'
export interface IPluginInfo { export interface PluginInfo {
name: string name: string
description: string description: string
packageName: string packageName: string
@@ -25,7 +25,7 @@ export class PluginManagerService {
logger: Logger logger: Logger
builtinPluginsPath: string = (window as any).builtinPluginsPath builtinPluginsPath: string = (window as any).builtinPluginsPath
userPluginsPath: string = (window as any).userPluginsPath userPluginsPath: string = (window as any).userPluginsPath
installedPlugins: IPluginInfo[] = (window as any).installedPlugins installedPlugins: PluginInfo[] = (window as any).installedPlugins
private npmReady: Promise<void> private npmReady: Promise<void>
private npm: any private npm: any
@@ -57,12 +57,12 @@ export class PluginManagerService {
return this.npm return this.npm
} }
listAvailable (query?: string): Observable<IPluginInfo[]> { listAvailable (query?: string): Observable<PluginInfo[]> {
return from( return from(
axios.get(`https://www.npmjs.com/search?q=keywords%3A${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=1000`, { axios.get(`https://www.npmjs.com/search?q=keywords%3A${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=1000`, {
headers: { headers: {
'x-spiferack': '1', 'x-spiferack': '1',
} },
}) })
).pipe( ).pipe(
map(response => response.data.objects.map(item => ({ map(response => response.data.objects.map(item => ({
@@ -78,7 +78,7 @@ export class PluginManagerService {
) )
} }
async installPlugin (plugin: IPluginInfo) { async installPlugin (plugin: PluginInfo) {
(await this.getNPM()).commands.install([`${plugin.packageName}@${plugin.version}`], err => { (await this.getNPM()).commands.install([`${plugin.packageName}@${plugin.version}`], err => {
if (err) { if (err) {
this.logger.error(err) this.logger.error(err)
@@ -88,7 +88,7 @@ export class PluginManagerService {
}) })
} }
async uninstallPlugin (plugin: IPluginInfo) { async uninstallPlugin (plugin: PluginInfo) {
(await this.getNPM()).commands.remove([plugin.packageName], err => { (await this.getNPM()).commands.remove([plugin.packageName], err => {
if (err) { if (err) {
this.logger.error(err) this.logger.error(err)

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser' import { DomSanitizer } from '@angular/platform-browser'
import { ToolbarButtonProvider, IToolbarButton, AppService, HostAppService, HotkeysService } from 'terminus-core' import { ToolbarButtonProvider, ToolbarButton, AppService, HostAppService, HotkeysService } from 'terminus-core'
import { SettingsTabComponent } from './components/settingsTab.component' import { SettingsTabComponent } from './components/settingsTab.component'
@@ -23,7 +23,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
}) })
} }
provide (): IToolbarButton[] { provide (): ToolbarButton[] {
return [{ return [{
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/cog.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/cog.svg')),
title: 'Settings', title: 'Settings',

View File

@@ -21,7 +21,7 @@ const INPUT_TIMEOUT = 1000
animate('250ms ease-out', style({ animate('250ms ease-out', style({
transform: 'translateX(0)', transform: 'translateX(0)',
opacity: '1', opacity: '1',
})) })),
]), ]),
transition(':leave', [ transition(':leave', [
style({ style({
@@ -31,10 +31,10 @@ const INPUT_TIMEOUT = 1000
animate('250ms ease-in', style({ animate('250ms ease-in', style({
transform: 'translateX(25px)', transform: 'translateX(25px)',
opacity: '0', opacity: '0',
})) })),
]) ]),
]) ]),
] ],
}) })
export class HotkeyInputModalComponent { export class HotkeyInputModalComponent {
@Input() value: string[] = [] @Input() value: string[] = []

View File

@@ -24,7 +24,7 @@ export class MultiHotkeyInputComponent {
if (typeof this.model === 'string') { if (typeof this.model === 'string') {
this.model = [this.model] this.model = [this.model]
} }
this.model = this.model.map(item => (typeof item === 'string') ? [item] : item) this.model = this.model.map(item => typeof item === 'string' ? [item] : item)
} }
editItem (item) { editItem (item) {

View File

@@ -6,14 +6,14 @@ import {
ElectronService, ElectronService,
DockingService, DockingService,
ConfigService, ConfigService,
IHotkeyDescription, HotkeyDescription,
HotkeysService, HotkeysService,
BaseTabComponent, BaseTabComponent,
Theme, Theme,
HostAppService, HostAppService,
Platform, Platform,
HomeBaseService, HomeBaseService,
ShellIntegrationService ShellIntegrationService,
} from 'terminus-core' } from 'terminus-core'
import { SettingsTabProvider } from '../api' import { SettingsTabProvider } from '../api'
@@ -30,7 +30,7 @@ import { SettingsTabProvider } from '../api'
export class SettingsTabComponent extends BaseTabComponent { export class SettingsTabComponent extends BaseTabComponent {
@Input() activeTab: string @Input() activeTab: string
hotkeyFilter = '' hotkeyFilter = ''
hotkeyDescriptions: IHotkeyDescription[] hotkeyDescriptions: HotkeyDescription[]
screens: any[] screens: any[]
Platform = Platform Platform = Platform
configDefaults: any configDefaults: any

View File

@@ -7,17 +7,17 @@ export class SettingsConfigProvider extends ConfigProvider {
[Platform.macOS]: { [Platform.macOS]: {
hotkeys: { hotkeys: {
settings: ['⌘-,'], settings: ['⌘-,'],
} },
}, },
[Platform.Windows]: { [Platform.Windows]: {
hotkeys: { hotkeys: {
settings: ['Ctrl-,'] settings: ['Ctrl-,'],
} },
}, },
[Platform.Linux]: { [Platform.Linux]: {
hotkeys: { hotkeys: {
settings: ['Ctrl-,'] settings: ['Ctrl-,'],
} },
}, },
} }
} }

View File

@@ -1,17 +1,17 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { IHotkeyDescription, HotkeyProvider } from 'terminus-core' import { HotkeyDescription, HotkeyProvider } from 'terminus-core'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
export class SettingsHotkeyProvider extends HotkeyProvider { export class SettingsHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [ hotkeys: HotkeyDescription[] = [
{ {
id: 'settings', id: 'settings',
name: 'Open Settings', name: 'Open Settings',
}, },
] ]
async provide (): Promise<IHotkeyDescription[]> { async provide (): Promise<HotkeyDescription[]> {
return this.hotkeys return this.hotkeys
} }
} }

View File

@@ -42,7 +42,7 @@ import { SettingsConfigProvider } from './config'
SettingsTabBodyComponent, SettingsTabBodyComponent,
], ],
}) })
export default class SettingsModule { } export default class SettingsModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
export * from './api' export * from './api'
export { SettingsTabComponent } export { SettingsTabComponent }

View File

@@ -141,7 +141,7 @@ export class SSHSession extends BaseSession {
} }
} }
export interface ISSHConnectionGroup { export interface SSHConnectionGroup {
name: string name: string
connections: SSHConnection[] connections: SSHConnection[]
} }

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser' import { DomSanitizer } from '@angular/platform-browser'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { HotkeysService, ToolbarButtonProvider, IToolbarButton } from 'terminus-core' import { HotkeysService, ToolbarButtonProvider, ToolbarButton } from 'terminus-core'
import { SSHModalComponent } from './components/sshModal.component' import { SSHModalComponent } from './components/sshModal.component'
/** @hidden */ /** @hidden */
@@ -13,7 +13,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
hotkeys: HotkeysService, hotkeys: HotkeysService,
) { ) {
super() super()
hotkeys.matchedHotkey.subscribe(async (hotkey) => { hotkeys.matchedHotkey.subscribe(async (hotkey: string) => {
if (hotkey === 'ssh') { if (hotkey === 'ssh') {
this.activate() this.activate()
} }
@@ -24,7 +24,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
this.ngbModal.open(SSHModalComponent) this.ngbModal.open(SSHModalComponent)
} }
provide (): IToolbarButton[] { provide (): ToolbarButton[] {
return [{ return [{
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/globe.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/globe.svg')),
weight: 5, weight: 5,
@@ -32,7 +32,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
touchBarNSImage: 'NSTouchBarOpenInBrowserTemplate', touchBarNSImage: 'NSTouchBarOpenInBrowserTemplate',
click: async () => { click: async () => {
this.activate() this.activate()
} },
}] }]
} }
} }

View File

@@ -27,23 +27,25 @@ export class EditConnectionModalComponent {
this.newScript = { expect: '', send: '' } this.newScript = { expect: '', send: '' }
for (const k of Object.values(SSHAlgorithmType)) { for (const k of Object.values(SSHAlgorithmType)) {
this.supportedAlgorithms[k] = ALGORITHMS[{ const supportedAlg = {
[SSHAlgorithmType.KEX]: 'SUPPORTED_KEX', [SSHAlgorithmType.KEX]: 'SUPPORTED_KEX',
[SSHAlgorithmType.HOSTKEY]: 'SUPPORTED_SERVER_HOST_KEY', [SSHAlgorithmType.HOSTKEY]: 'SUPPORTED_SERVER_HOST_KEY',
[SSHAlgorithmType.CIPHER]: 'SUPPORTED_CIPHER', [SSHAlgorithmType.CIPHER]: 'SUPPORTED_CIPHER',
[SSHAlgorithmType.HMAC]: 'SUPPORTED_HMAC', [SSHAlgorithmType.HMAC]: 'SUPPORTED_HMAC',
}[k]] }[k]
this.defaultAlgorithms[k] = ALGORITHMS[{ const defaultAlg = {
[SSHAlgorithmType.KEX]: 'KEX', [SSHAlgorithmType.KEX]: 'KEX',
[SSHAlgorithmType.HOSTKEY]: 'SERVER_HOST_KEY', [SSHAlgorithmType.HOSTKEY]: 'SERVER_HOST_KEY',
[SSHAlgorithmType.CIPHER]: 'CIPHER', [SSHAlgorithmType.CIPHER]: 'CIPHER',
[SSHAlgorithmType.HMAC]: 'HMAC', [SSHAlgorithmType.HMAC]: 'HMAC',
}[k]] }[k]
this.supportedAlgorithms[k] = ALGORITHMS[supportedAlg]
this.defaultAlgorithms[k] = ALGORITHMS[defaultAlg]
} }
} }
async ngOnInit () { async ngOnInit () {
this.hasSavedPassword = !!(await this.passwordStorage.loadPassword(this.connection)) this.hasSavedPassword = !!await this.passwordStorage.loadPassword(this.connection)
this.connection.algorithms = this.connection.algorithms || {} this.connection.algorithms = this.connection.algorithms || {}
for (const k of Object.values(SSHAlgorithmType)) { for (const k of Object.values(SSHAlgorithmType)) {
if (!this.connection.algorithms[k]) { if (!this.connection.algorithms[k]) {
@@ -77,8 +79,8 @@ export class EditConnectionModalComponent {
save () { save () {
for (const k of Object.values(SSHAlgorithmType)) { for (const k of Object.values(SSHAlgorithmType)) {
this.connection.algorithms[k] = Object.entries(this.algorithms[k]) this.connection.algorithms[k] = Object.entries(this.algorithms[k])
.filter(([k, v]) => !!v) .filter(([_k, v]) => !!v)
.map(([k, v]) => k) .map(([k, _v]) => k)
} }
this.modalInstance.close(this.connection) this.modalInstance.close(this.connection)
} }

View File

@@ -4,7 +4,7 @@ import { ToastrService } from 'ngx-toastr'
import { ConfigService, AppService } from 'terminus-core' import { ConfigService, AppService } from 'terminus-core'
import { SettingsTabComponent } from 'terminus-settings' import { SettingsTabComponent } from 'terminus-settings'
import { SSHService } from '../services/ssh.service' import { SSHService } from '../services/ssh.service'
import { SSHConnection, ISSHConnectionGroup } from '../api' import { SSHConnection, SSHConnectionGroup } from '../api'
/** @hidden */ /** @hidden */
@Component({ @Component({
@@ -13,10 +13,10 @@ import { SSHConnection, ISSHConnectionGroup } from '../api'
}) })
export class SSHModalComponent { export class SSHModalComponent {
connections: SSHConnection[] connections: SSHConnection[]
childFolders: ISSHConnectionGroup[] childFolders: SSHConnectionGroup[]
quickTarget: string quickTarget: string
lastConnection: SSHConnection lastConnection: SSHConnection
childGroups: ISSHConnectionGroup[] childGroups: SSHConnectionGroup[]
groupCollapsed: {[id: string]: boolean} = {} groupCollapsed: {[id: string]: boolean} = {}
constructor ( constructor (
@@ -49,7 +49,9 @@ export class SSHModalComponent {
const connection: SSHConnection = { const connection: SSHConnection = {
name: this.quickTarget, name: this.quickTarget,
host, user, port host,
user,
port,
} }
window.localStorage.lastConnection = JSON.stringify(connection) window.localStorage.lastConnection = JSON.stringify(connection)
this.connect(connection) this.connect(connection)

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, ElectronService, HostAppService } from 'terminus-core' import { ConfigService, ElectronService, HostAppService } from 'terminus-core'
import { SSHConnection, ISSHConnectionGroup } from '../api' import { SSHConnection, SSHConnectionGroup } from '../api'
import { EditConnectionModalComponent } from './editConnectionModal.component' import { EditConnectionModalComponent } from './editConnectionModal.component'
import { PromptModalComponent } from './promptModal.component' import { PromptModalComponent } from './promptModal.component'
@@ -11,7 +11,7 @@ import { PromptModalComponent } from './promptModal.component'
}) })
export class SSHSettingsTabComponent { export class SSHSettingsTabComponent {
connections: SSHConnection[] connections: SSHConnection[]
childGroups: ISSHConnectionGroup[] childGroups: SSHConnectionGroup[]
groupCollapsed: {[id: string]: boolean} = {} groupCollapsed: {[id: string]: boolean} = {}
constructor ( constructor (
@@ -70,7 +70,7 @@ export class SSHSettingsTabComponent {
} }
} }
editGroup (group: ISSHConnectionGroup) { editGroup (group: SSHConnectionGroup) {
const modal = this.ngbModal.open(PromptModalComponent) const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = 'New group name' modal.componentInstance.prompt = 'New group name'
modal.componentInstance.value = group.name modal.componentInstance.value = group.name
@@ -86,7 +86,7 @@ export class SSHSettingsTabComponent {
}) })
} }
async deleteGroup (group: ISSHConnectionGroup) { async deleteGroup (group: SSHConnectionGroup) {
if ((await this.electron.showMessageBox( if ((await this.electron.showMessageBox(
this.hostApp.getWindow(), this.hostApp.getWindow(),
{ {

View File

@@ -6,10 +6,10 @@ export class SSHConfigProvider extends ConfigProvider {
ssh: { ssh: {
connections: [], connections: [],
options: { options: {
} },
}, },
hotkeys: { hotkeys: {
'ssh': [ ssh: [
'Alt-S', 'Alt-S',
], ],
}, },

View File

@@ -47,4 +47,4 @@ import { RecoveryProvider } from './recoveryProvider'
SSHTabComponent, SSHTabComponent,
], ],
}) })
export default class SSHModule { } export default class SSHModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@@ -9,13 +9,12 @@ import { SSHConnection, SSHSession } from '../api'
import { PromptModalComponent } from '../components/promptModal.component' import { PromptModalComponent } from '../components/promptModal.component'
import { SSHTabComponent } from '../components/sshTab.component' import { SSHTabComponent } from '../components/sshTab.component'
import { PasswordStorageService } from './passwordStorage.service' import { PasswordStorageService } from './passwordStorage.service'
const { SSH2Stream } = require('ssh2-streams') import { SSH2Stream } from 'ssh2-streams'
let windowsProcessTree /* eslint-disable block-scoped-var */
try { try {
windowsProcessTree = require('windows-process-tree/build/Release/windows_process_tree.node') var windowsProcessTree = require('windows-process-tree/build/Release/windows_process_tree.node') // eslint-disable-line @typescript-eslint/no-var-requires
} catch (e) { } catch (_) { }
} // tslint:disable-line
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class SSHService { export class SSHService {
@@ -46,10 +45,10 @@ export class SSHService {
let privateKeyPath = session.connection.privateKey let privateKeyPath = session.connection.privateKey
if (!logCallback) { if (!logCallback) {
logCallback = (s) => null logCallback = () => null
} }
const log = s => { const log = (s: any) => {
logCallback(s) logCallback(s)
this.logger.info(s) this.logger.info(s)
} }
@@ -84,7 +83,7 @@ export class SSHService {
modal.componentInstance.password = true modal.componentInstance.password = true
try { try {
privateKeyPassphrase = await modal.result privateKeyPassphrase = await modal.result
} catch (_err) { } // tslint:disable-line } catch (e) { }
} }
} }
} }
@@ -214,11 +213,11 @@ export class SSHService {
session.shell = shell session.shell = shell
shell.on('greeting', greeting => { shell.on('greeting', greeting => {
log('Shell Greeting: ' + greeting) log(`Shell Greeting: ${greeting}`)
}) })
shell.on('banner', banner => { shell.on('banner', banner => {
log('Shell Banner: ' + banner) log(`Shell Banner: ${banner}`)
}) })
} catch (error) { } catch (error) {
this.toastr.error(error.message) this.toastr.error(error.message)
@@ -227,7 +226,8 @@ export class SSHService {
} }
} }
/* eslint-disable */
const _authPassword = SSH2Stream.prototype.authPassword const _authPassword = SSH2Stream.prototype.authPassword
SSH2Stream.prototype.authPassword = async function (username, passwordFn) { SSH2Stream.prototype.authPassword = async function (username, passwordFn: any) {
_authPassword.bind(this)(username, await passwordFn()) _authPassword.bind(this)(username, await passwordFn())
} } as any

View File

@@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"exclude": ["node_modules", "dist", "typings"],
"compilerOptions": {
"baseUrl": "src",
"emitDeclarationOnly": true,
"declaration": true,
"declarationDir": "./typings",
"paths": {
"terminus-*": ["../../terminus-*"],
"*": ["../../app/node_modules/*"]
}
}
}

View File

@@ -22,11 +22,11 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
static animations: AnimationTriggerMetadata[] = [trigger('slideInOut', [ static animations: AnimationTriggerMetadata[] = [trigger('slideInOut', [
transition(':enter', [ transition(':enter', [
style({ transform: 'translateY(-25%)' }), style({ transform: 'translateY(-25%)' }),
animate('100ms ease-in-out', style({ transform: 'translateY(0%)' })) animate('100ms ease-in-out', style({ transform: 'translateY(0%)' })),
]), ]),
transition(':leave', [ transition(':leave', [
animate('100ms ease-in-out', style({ transform: 'translateY(-25%)' })) animate('100ms ease-in-out', style({ transform: 'translateY(-25%)' })),
]) ]),
])] ])]
session: BaseSession session: BaseSession
@@ -90,53 +90,53 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
return return
} }
switch (hotkey) { switch (hotkey) {
case 'ctrl-c': case 'ctrl-c':
if (this.frontend.getSelection()) { if (this.frontend.getSelection()) {
this.frontend.copySelection()
this.frontend.clearSelection()
this.toastr.info('Copied')
} else {
this.sendInput('\x03')
}
break
case 'copy':
this.frontend.copySelection() this.frontend.copySelection()
this.frontend.clearSelection() this.frontend.clearSelection()
this.toastr.info('Copied') this.toastr.info('Copied')
} else { break
this.sendInput('\x03') case 'paste':
} this.paste()
break break
case 'copy': case 'clear':
this.frontend.copySelection() this.frontend.clear()
this.frontend.clearSelection() break
this.toastr.info('Copied') case 'zoom-in':
break this.zoomIn()
case 'paste': break
this.paste() case 'zoom-out':
break this.zoomOut()
case 'clear': break
this.frontend.clear() case 'reset-zoom':
break this.resetZoom()
case 'zoom-in': break
this.zoomIn() case 'previous-word':
break this.sendInput('\x1bb')
case 'zoom-out': break
this.zoomOut() case 'next-word':
break this.sendInput('\x1bf')
case 'reset-zoom': break
this.resetZoom() case 'delete-previous-word':
break this.sendInput('\x1b\x7f')
case 'previous-word': break
this.sendInput('\x1bb') case 'delete-next-word':
break this.sendInput('\x1bd')
case 'next-word': break
this.sendInput('\x1bf') case 'search':
break this.showSearchPanel = true
case 'delete-previous-word': setImmediate(() => {
this.sendInput('\x1b\x7f') this.element.nativeElement.querySelector('.search-input').focus()
break })
case 'delete-next-word': break
this.sendInput('\x1bd')
break
case 'search':
this.showSearchPanel = true
setImmediate(() => {
this.element.nativeElement.querySelector('.search-input').focus()
})
break
} }
}) })
this.bellPlayer = document.createElement('audio') this.bellPlayer = document.createElement('audio')
@@ -219,89 +219,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
return items return items
} }
protected detachTermContainerHandlers () {
for (const subscription of this.termContainerSubscriptions) {
subscription.unsubscribe()
}
this.termContainerSubscriptions = []
}
protected attachTermContainerHandlers () {
this.detachTermContainerHandlers()
const maybeConfigure = () => {
if (this.hasFocus) {
setTimeout(() => this.configure(), 250)
}
}
this.termContainerSubscriptions = [
this.frontend.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
this.focused$.subscribe(() => this.frontend.enableResizing = true),
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
this.frontend.mouseEvent$.subscribe(async event => {
if (event.type === 'mousedown') {
if (event.which === 2) {
this.paste()
event.preventDefault()
event.stopPropagation()
return
}
if (event.which === 3) {
if (this.config.store.terminal.rightClick === 'menu') {
this.hostApp.popupContextMenu(await this.buildContextMenu())
} else if (this.config.store.terminal.rightClick === 'paste') {
this.paste()
}
event.preventDefault()
event.stopPropagation()
return
}
}
if (event.type === 'mousewheel') {
let wheelDeltaY = 0
if ('wheelDeltaY' in event) {
wheelDeltaY = (event as MouseWheelEvent)['wheelDeltaY']
} else {
wheelDeltaY = (event as MouseWheelEvent)['deltaY']
}
if (event.ctrlKey || event.metaKey) {
if (wheelDeltaY > 0) {
this.zoomIn()
} else {
this.zoomOut()
}
} else if (event.altKey) {
event.preventDefault()
const delta = Math.round(wheelDeltaY / 50)
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
}
}
}),
this.frontend.input$.subscribe(data => {
this.sendInput(data)
}),
this.frontend.resize$.subscribe(({ columns, rows }) => {
this.logger.debug(`Resizing to ${columns}x${rows}`)
this.size = { columns, rows }
this.zone.run(() => {
if (this.session && this.session.open) {
this.session.resize(columns, rows)
}
})
}),
this.hostApp.displayMetricsChanged$.subscribe(maybeConfigure),
this.hostApp.windowMoved$.subscribe(maybeConfigure),
]
}
/** /**
* Feeds input into the active session * Feeds input into the active session
*/ */
@@ -330,7 +247,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
} }
paste () { paste () {
let data = this.electron.clipboard.readText() let data = this.electron.clipboard.readText() as string
if (this.config.store.terminal.bracketedPaste) { if (this.config.store.terminal.bracketedPaste) {
data = '\x1b[200~' + data + '\x1b[201~' data = '\x1b[200~' + data + '\x1b[201~'
} }
@@ -401,6 +318,89 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
} }
} }
protected detachTermContainerHandlers () {
for (const subscription of this.termContainerSubscriptions) {
subscription.unsubscribe()
}
this.termContainerSubscriptions = []
}
protected attachTermContainerHandlers () {
this.detachTermContainerHandlers()
const maybeConfigure = () => {
if (this.hasFocus) {
setTimeout(() => this.configure(), 250)
}
}
this.termContainerSubscriptions = [
this.frontend.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
this.focused$.subscribe(() => this.frontend.enableResizing = true),
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
this.frontend.mouseEvent$.subscribe(async event => {
if (event.type === 'mousedown') {
if (event.which === 2) {
this.paste()
event.preventDefault()
event.stopPropagation()
return
}
if (event.which === 3) {
if (this.config.store.terminal.rightClick === 'menu') {
this.hostApp.popupContextMenu(await this.buildContextMenu())
} else if (this.config.store.terminal.rightClick === 'paste') {
this.paste()
}
event.preventDefault()
event.stopPropagation()
return
}
}
if (event.type === 'mousewheel') {
let wheelDeltaY = 0
if ('wheelDeltaY' in event) {
wheelDeltaY = (event as MouseWheelEvent)['wheelDeltaY']
} else {
wheelDeltaY = (event as MouseWheelEvent)['deltaY']
}
if (event.ctrlKey || event.metaKey) {
if (wheelDeltaY > 0) {
this.zoomIn()
} else {
this.zoomOut()
}
} else if (event.altKey) {
event.preventDefault()
const delta = Math.round(wheelDeltaY / 50)
this.sendInput((delta > 0 ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
}
}
}),
this.frontend.input$.subscribe(data => {
this.sendInput(data)
}),
this.frontend.resize$.subscribe(({ columns, rows }) => {
this.logger.debug(`Resizing to ${columns}x${rows}`)
this.size = { columns, rows }
this.zone.run(() => {
if (this.session && this.session.open) {
this.session.resize(columns, rows)
}
})
}),
this.hostApp.displayMetricsChanged$.subscribe(maybeConfigure),
this.hostApp.windowMoved$.subscribe(maybeConfigure),
]
}
protected attachSessionHandlers () { protected attachSessionHandlers () {
// this.session.output$.bufferTime(10).subscribe((datas) => { // this.session.output$.bufferTime(10).subscribe((datas) => {
this.session.output$.subscribe(data => { this.session.output$.subscribe(data => {

View File

@@ -1,8 +1,8 @@
import { ITerminalColorScheme } from './interfaces' import { TerminalColorScheme } from './interfaces'
/** /**
* Extend to add more terminal color schemes * Extend to add more terminal color schemes
*/ */
export abstract class TerminalColorSchemeProvider { export abstract class TerminalColorSchemeProvider {
abstract async getSchemes (): Promise<ITerminalColorScheme[]> abstract async getSchemes (): Promise<TerminalColorScheme[]>
} }

View File

@@ -7,10 +7,10 @@ export abstract class TerminalDecorator {
/** /**
* Called when a new terminal tab starts * Called when a new terminal tab starts
*/ */
attach (terminal: BaseTerminalTabComponent): void { } // tslint:disable-line no-empty attach (terminal: BaseTerminalTabComponent): void { } // eslint-disable-line
/** /**
* Called before a terminal tab is destroyed * Called before a terminal tab is destroyed
*/ */
detach (terminal: BaseTerminalTabComponent): void { } // tslint:disable-line no-empty detach (terminal: BaseTerminalTabComponent): void { } // eslint-disable-line
} }

View File

@@ -24,7 +24,7 @@ export interface Profile {
icon?: SafeHtml icon?: SafeHtml
} }
export interface ITerminalColorScheme { export interface TerminalColorScheme {
name: string name: string
foreground: string foreground: string
background: string background: string
@@ -32,7 +32,7 @@ export interface ITerminalColorScheme {
colors: string[] colors: string[]
} }
export interface IShell { export interface Shell {
id: string id: string
name?: string name?: string
command: string command: string

View File

@@ -1,8 +1,8 @@
import { IShell } from './interfaces' import { Shell } from './interfaces'
/** /**
* Extend to add support for more shells * Extend to add support for more shells
*/ */
export abstract class ShellProvider { export abstract class ShellProvider {
abstract async provide (): Promise<IShell[]> abstract async provide (): Promise<Shell[]>
} }

View File

@@ -1,7 +1,7 @@
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser' import { DomSanitizer } from '@angular/platform-browser'
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, HostAppService, ElectronService } from 'terminus-core' import { ToolbarButtonProvider, ToolbarButton, ElectronService } from 'terminus-core'
import { TerminalService } from './services/terminal.service' import { TerminalService } from './services/terminal.service'
@@ -9,11 +9,9 @@ import { TerminalService } from './services/terminal.service'
@Injectable() @Injectable()
export class ButtonProvider extends ToolbarButtonProvider { export class ButtonProvider extends ToolbarButtonProvider {
constructor ( constructor (
electron: ElectronService,
private terminal: TerminalService, private terminal: TerminalService,
private domSanitizer: DomSanitizer, private domSanitizer: DomSanitizer,
hostApp: HostAppService,
electron: ElectronService,
hotkeys: HotkeysService,
) { ) {
super() super()
if (!electron.remote.process.env.TERMINUS_DEV) { if (!electron.remote.process.env.TERMINUS_DEV) {
@@ -30,7 +28,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
} }
} }
provide (): IToolbarButton[] { provide (): ToolbarButton[] {
return [ return [
{ {
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/plus.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/plus.svg')),
@@ -38,7 +36,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
touchBarNSImage: 'NSTouchBarAddDetailTemplate', touchBarNSImage: 'NSTouchBarAddDetailTemplate',
click: async () => { click: async () => {
this.terminal.openTab() this.terminal.openTab()
} },
}, },
{ {
icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/profiles.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('./icons/profiles.svg')),
@@ -50,7 +48,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
title: profile.name, title: profile.name,
click: () => this.terminal.openTab(profile), click: () => this.terminal.openTab(profile),
})) }))
} },
}, },
] ]
} }

View File

@@ -2,17 +2,19 @@ import * as fs from 'mz/fs'
import * as path from 'path' import * as path from 'path'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { TerminalColorSchemeProvider } from './api/colorSchemeProvider' import { TerminalColorSchemeProvider } from './api/colorSchemeProvider'
import { ITerminalColorScheme } from './api/interfaces' import { TerminalColorScheme } from './api/interfaces'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
export class HyperColorSchemes extends TerminalColorSchemeProvider { export class HyperColorSchemes extends TerminalColorSchemeProvider {
async getSchemes (): Promise<ITerminalColorScheme[]> { async getSchemes (): Promise<TerminalColorScheme[]> {
const pluginsPath = path.join(process.env.HOME, '.hyper_plugins', 'node_modules') const pluginsPath = path.join(process.env.HOME, '.hyper_plugins', 'node_modules')
if (!(await fs.exists(pluginsPath))) return [] if (!await fs.exists(pluginsPath)) {
return []
}
const plugins = await fs.readdir(pluginsPath) const plugins = await fs.readdir(pluginsPath)
const themes: ITerminalColorScheme[] = [] const themes: TerminalColorScheme[] = []
plugins.forEach(plugin => { plugins.forEach(plugin => {
try { try {

View File

@@ -1,13 +1,13 @@
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators' import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
import { exec } from 'mz/child_process' import { exec } from 'mz/child_process'
import deepEqual = require('deep-equal') import deepEqual from 'deep-equal'
const fontManager = require('fontmanager-redux') const fontManager = require('fontmanager-redux') // eslint-disable-line
import { Component, Inject } from '@angular/core' import { Component, Inject } from '@angular/core'
import { ConfigService, HostAppService, Platform, ElectronService } from 'terminus-core' import { ConfigService, HostAppService, Platform, ElectronService } from 'terminus-core'
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider' import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
import { ITerminalColorScheme } from '../api/interfaces' import { TerminalColorScheme } from '../api/interfaces'
import { getCSSFontFamily } from '../utils' import { getCSSFontFamily } from '../utils'
/** @hidden */ /** @hidden */
@@ -17,9 +17,9 @@ import { getCSSFontFamily } from '../utils'
}) })
export class AppearanceSettingsTabComponent { export class AppearanceSettingsTabComponent {
fonts: string[] = [] fonts: string[] = []
colorSchemes: ITerminalColorScheme[] = [] colorSchemes: TerminalColorScheme[] = []
equalComparator = deepEqual equalComparator = deepEqual
editingColorScheme: ITerminalColorScheme editingColorScheme: TerminalColorScheme
schemeChanged = false schemeChanged = false
constructor ( constructor (
@@ -32,7 +32,7 @@ export class AppearanceSettingsTabComponent {
async ngOnInit () { async ngOnInit () {
if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) { if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) {
const fonts = await new Promise<any[]>((resolve) => fontManager.findFonts({ monospace: true }, resolve)) const fonts = await new Promise<any[]>((resolve) => fontManager.findFonts({ monospace: true }, resolve))
this.fonts = fonts.map(x => (x.family + ' ' + x.style).trim()) this.fonts = fonts.map(x => `${x.family} ${x.style}`.trim())
this.fonts.sort() this.fonts.sort()
} }
if (this.hostApp.platform === Platform.Linux) { if (this.hostApp.platform === Platform.Linux) {
@@ -50,14 +50,14 @@ export class AppearanceSettingsTabComponent {
fontAutocomplete = (text$: Observable<string>) => { fontAutocomplete = (text$: Observable<string>) => {
return text$.pipe( return text$.pipe(
debounceTime(200), debounceTime(200),
distinctUntilChanged(), distinctUntilChanged(),
map(query => this.fonts.filter(v => new RegExp(query, 'gi').test(v))), map(query => this.fonts.filter(v => new RegExp(query, 'gi').test(v))),
map(list => Array.from(new Set(list))), map(list => Array.from(new Set(list))),
) )
} }
editScheme (scheme: ITerminalColorScheme) { editScheme (scheme: TerminalColorScheme) {
this.editingColorScheme = scheme this.editingColorScheme = scheme
this.schemeChanged = false this.schemeChanged = false
} }
@@ -75,7 +75,7 @@ export class AppearanceSettingsTabComponent {
this.editingColorScheme = null this.editingColorScheme = null
} }
async deleteScheme (scheme: ITerminalColorScheme) { async deleteScheme (scheme: TerminalColorScheme) {
if ((await this.electron.showMessageBox( if ((await this.electron.showMessageBox(
this.hostApp.getWindow(), this.hostApp.getWindow(),
{ {
@@ -92,7 +92,7 @@ export class AppearanceSettingsTabComponent {
} }
} }
isCustomScheme (scheme: ITerminalColorScheme) { isCustomScheme (scheme: TerminalColorScheme) {
return this.config.store.terminal.customColorSchemes.some(x => deepEqual(x, scheme)) return this.config.store.terminal.customColorSchemes.some(x => deepEqual(x, scheme))
} }

View File

@@ -1,6 +1,6 @@
import { Component, Input, Output, EventEmitter } from '@angular/core' import { Component, Input, Output, EventEmitter } from '@angular/core'
import { ToastrService } from 'ngx-toastr' import { ToastrService } from 'ngx-toastr'
import { Frontend, ISearchOptions } from '../frontends/frontend' import { Frontend, SearchOptions } from '../frontends/frontend'
@Component({ @Component({
selector: 'search-panel', selector: 'search-panel',
@@ -8,11 +8,11 @@ import { Frontend, ISearchOptions } from '../frontends/frontend'
styles: [require('./searchPanel.component.scss')], styles: [require('./searchPanel.component.scss')],
}) })
export class SearchPanelComponent { export class SearchPanelComponent {
static globalOptions: SearchOptions = {}
@Input() query: string @Input() query: string
@Input() frontend: Frontend @Input() frontend: Frontend
notFound = false notFound = false
static globalOptions: ISearchOptions = {} options: SearchOptions = SearchPanelComponent.globalOptions
options: ISearchOptions = SearchPanelComponent.globalOptions
@Output() close = new EventEmitter() @Output() close = new EventEmitter()

View File

@@ -4,7 +4,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { ConfigService, ElectronService, HostAppService, Platform } from 'terminus-core' import { ConfigService, ElectronService, HostAppService, Platform } from 'terminus-core'
import { EditProfileModalComponent } from './editProfileModal.component' import { EditProfileModalComponent } from './editProfileModal.component'
import { IShell, Profile } from '../api/interfaces' import { Shell, Profile } from '../api/interfaces'
import { TerminalService } from '../services/terminal.service' import { TerminalService } from '../services/terminal.service'
import { WIN_BUILD_CONPTY_SUPPORTED, WIN_BUILD_CONPTY_STABLE, isWindowsBuild } from '../utils' import { WIN_BUILD_CONPTY_SUPPORTED, WIN_BUILD_CONPTY_STABLE, isWindowsBuild } from '../utils'
@@ -13,7 +13,7 @@ import { WIN_BUILD_CONPTY_SUPPORTED, WIN_BUILD_CONPTY_STABLE, isWindowsBuild } f
template: require('./shellSettingsTab.component.pug'), template: require('./shellSettingsTab.component.pug'),
}) })
export class ShellSettingsTabComponent { export class ShellSettingsTabComponent {
shells: IShell[] = [] shells: Shell[] = []
profiles: Profile[] = [] profiles: Profile[] = []
Platform = Platform Platform = Platform
isConPTYAvailable: boolean isConPTYAvailable: boolean
@@ -64,7 +64,7 @@ export class ShellSettingsTabComponent {
} }
} }
newProfile (shell: IShell) { newProfile (shell: Shell) {
const profile: Profile = { const profile: Profile = {
name: shell.name, name: shell.name,
sessionOptions: this.terminalService.optionsFromShell(shell), sessionOptions: this.terminalService.optionsFromShell(shell),

View File

@@ -29,12 +29,12 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
return return
} }
switch (hotkey) { switch (hotkey) {
case 'home': case 'home':
this.sendInput(isConPTY ? '\x1b[H' : '\x1bOH') this.sendInput(isConPTY ? '\x1b[H' : '\x1bOH')
break break
case 'end': case 'end':
this.sendInput(isConPTY ? '\x1b[F' : '\x1bOF') this.sendInput(isConPTY ? '\x1b[F' : '\x1bOF')
break break
} }
}) })
@@ -74,7 +74,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
return null return null
} }
return { return {
name: children[0].command name: children[0].command,
} }
} }

View File

@@ -51,7 +51,7 @@ export class TerminalConfigProvider extends ConfigProvider {
'#C792EA', '#C792EA',
'#89DDFF', '#89DDFF',
'#ffffff', '#ffffff',
] ],
}, },
customColorSchemes: [], customColorSchemes: [],
environment: {}, environment: {},
@@ -69,12 +69,12 @@ export class TerminalConfigProvider extends ConfigProvider {
}, },
hotkeys: { hotkeys: {
'ctrl-c': ['Ctrl-C'], 'ctrl-c': ['Ctrl-C'],
'copy': [ copy: [
'⌘-C', '⌘-C',
], ],
'paste': [ paste: [
], ],
'clear': [ clear: [
'⌘-K', '⌘-K',
], ],
'zoom-in': [ 'zoom-in': [
@@ -92,13 +92,13 @@ export class TerminalConfigProvider extends ConfigProvider {
'⌘-T', '⌘-T',
'⌘-N', '⌘-N',
], ],
'home': ['⌘-Left', 'Home'], home: ['⌘-Left', 'Home'],
'end': ['⌘-Right', 'End'], end: ['⌘-Right', 'End'],
'previous-word': ['⌥-Left'], 'previous-word': ['⌥-Left'],
'next-word': ['⌥-Right'], 'next-word': ['⌥-Right'],
'delete-previous-word': ['⌥-Backspace'], 'delete-previous-word': ['⌥-Backspace'],
'delete-next-word': ['⌥-Delete'], 'delete-next-word': ['⌥-Delete'],
'search': [ search: [
'⌘-F', '⌘-F',
], ],
}, },
@@ -113,13 +113,13 @@ export class TerminalConfigProvider extends ConfigProvider {
}, },
hotkeys: { hotkeys: {
'ctrl-c': ['Ctrl-C'], 'ctrl-c': ['Ctrl-C'],
'copy': [ copy: [
'Ctrl-Shift-C', 'Ctrl-Shift-C',
], ],
'paste': [ paste: [
'Ctrl-Shift-V', 'Ctrl-Shift-V',
], ],
'clear': [ clear: [
'Ctrl-L', 'Ctrl-L',
], ],
'zoom-in': [ 'zoom-in': [
@@ -136,13 +136,13 @@ export class TerminalConfigProvider extends ConfigProvider {
'new-tab': [ 'new-tab': [
'Ctrl-Shift-T', 'Ctrl-Shift-T',
], ],
'home': ['Home'], home: ['Home'],
'end': ['End'], end: ['End'],
'previous-word': ['Ctrl-Left'], 'previous-word': ['Ctrl-Left'],
'next-word': ['Ctrl-Right'], 'next-word': ['Ctrl-Right'],
'delete-previous-word': ['Ctrl-Backspace'], 'delete-previous-word': ['Ctrl-Backspace'],
'delete-next-word': ['Ctrl-Delete'], 'delete-next-word': ['Ctrl-Delete'],
'search': [ search: [
'Ctrl-Shift-F', 'Ctrl-Shift-F',
], ],
}, },
@@ -155,13 +155,13 @@ export class TerminalConfigProvider extends ConfigProvider {
}, },
hotkeys: { hotkeys: {
'ctrl-c': ['Ctrl-C'], 'ctrl-c': ['Ctrl-C'],
'copy': [ copy: [
'Ctrl-Shift-C', 'Ctrl-Shift-C',
], ],
'paste': [ paste: [
'Ctrl-Shift-V', 'Ctrl-Shift-V',
], ],
'clear': [ clear: [
'Ctrl-L', 'Ctrl-L',
], ],
'zoom-in': [ 'zoom-in': [
@@ -178,13 +178,13 @@ export class TerminalConfigProvider extends ConfigProvider {
'new-tab': [ 'new-tab': [
'Ctrl-Shift-T', 'Ctrl-Shift-T',
], ],
'home': ['Home'], home: ['Home'],
'end': ['End'], end: ['End'],
'previous-word': ['Ctrl-Left'], 'previous-word': ['Ctrl-Left'],
'next-word': ['Ctrl-Right'], 'next-word': ['Ctrl-Right'],
'delete-previous-word': ['Ctrl-Backspace'], 'delete-previous-word': ['Ctrl-Backspace'],
'delete-next-word': ['Ctrl-Delete'], 'delete-next-word': ['Ctrl-Delete'],
'search': [ search: [
'Ctrl-Shift-F', 'Ctrl-Shift-F',
], ],
}, },

View File

@@ -28,7 +28,7 @@ export class NewTabContextMenu extends TerminalContextMenuItemProvider {
label: 'New terminal', label: 'New terminal',
click: () => this.zone.run(() => { click: () => this.zone.run(() => {
this.terminalService.openTabWithOptions((tab as any).sessionOptions) this.terminalService.openTabWithOptions((tab as any).sessionOptions)
}) }),
}, },
{ {
label: 'New with profile', label: 'New with profile',
@@ -37,7 +37,7 @@ export class NewTabContextMenu extends TerminalContextMenuItemProvider {
click: () => this.zone.run(async () => { click: () => this.zone.run(async () => {
this.terminalService.openTab(profile, await tab.session.getWorkingDirectory()) this.terminalService.openTab(profile, await tab.session.getWorkingDirectory())
}), }),
})) })),
}, },
] ]
@@ -49,7 +49,7 @@ export class NewTabContextMenu extends TerminalContextMenuItemProvider {
click: () => this.zone.run(async () => { click: () => this.zone.run(async () => {
this.terminalService.openTabWithOptions({ this.terminalService.openTabWithOptions({
...profile.sessionOptions, ...profile.sessionOptions,
runAsAdministrator: true runAsAdministrator: true,
}) })
}), }),
})), })),
@@ -83,13 +83,13 @@ export class CopyPasteContextMenu extends TerminalContextMenuItemProvider {
this.toastr.info('Copied') this.toastr.info('Copied')
}) })
}) })
} },
}, },
{ {
label: 'Paste', label: 'Paste',
click: () => { click: () => {
this.zone.run(() => tab.paste()) this.zone.run(() => tab.paste())
} },
}, },
] ]
} }

View File

@@ -2,7 +2,7 @@ import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } fro
import { ResizeEvent } from '../api/interfaces' import { ResizeEvent } from '../api/interfaces'
import { ConfigService, ThemesService, HotkeysService } from 'terminus-core' import { ConfigService, ThemesService, HotkeysService } from 'terminus-core'
export interface ISearchOptions { export interface SearchOptions {
regex?: boolean regex?: boolean
wholeWord?: boolean wholeWord?: boolean
caseSensitive?: boolean caseSensitive?: boolean
@@ -39,9 +39,6 @@ export abstract class Frontend {
get dragOver$ (): Observable<DragEvent> { return this.dragOver } get dragOver$ (): Observable<DragEvent> { return this.dragOver }
get drop$ (): Observable<DragEvent> { return this.drop } get drop$ (): Observable<DragEvent> { return this.drop }
abstract attach (host: HTMLElement): void
detach (host: HTMLElement): void { } // tslint:disable-line
destroy (): void { destroy (): void {
for (const o of [ for (const o of [
this.ready, this.ready,
@@ -59,6 +56,9 @@ export abstract class Frontend {
} }
} }
abstract attach (host: HTMLElement): void
detach (host: HTMLElement): void { } // eslint-disable-line
abstract getSelection (): string abstract getSelection (): string
abstract copySelection (): void abstract copySelection (): void
abstract clearSelection (): void abstract clearSelection (): void
@@ -71,6 +71,6 @@ export abstract class Frontend {
abstract configure (): void abstract configure (): void
abstract setZoom (zoom: number): void abstract setZoom (zoom: number): void
abstract findNext (term: string, searchOptions?: ISearchOptions): boolean abstract findNext (term: string, searchOptions?: SearchOptions): boolean
abstract findPrevious (term: string, searchOptions?: ISearchOptions): boolean abstract findPrevious (term: string, searchOptions?: SearchOptions): boolean
} }

View File

@@ -1,3 +1,5 @@
/* eslint-disable */
/** @hidden */ /** @hidden */
export const hterm = require('hterm-umdjs') export const hterm = require('hterm-umdjs')
@@ -114,7 +116,5 @@ const _collapseToEnd = Selection.prototype.collapseToEnd
Selection.prototype.collapseToEnd = function () { Selection.prototype.collapseToEnd = function () {
try { try {
_collapseToEnd.apply(this) _collapseToEnd.apply(this)
} catch (err) { } catch (e) { }
// tslint-disable-line
}
} }

View File

@@ -1,4 +1,4 @@
import { Frontend, ISearchOptions } from './frontend' import { Frontend, SearchOptions } from './frontend'
import { hterm, preferenceManager } from './hterm' import { hterm, preferenceManager } from './hterm'
import { getCSSFontFamily } from '../utils' import { getCSSFontFamily } from '../utils'
@@ -98,7 +98,7 @@ export class HTermFrontend extends Frontend {
return return
} }
let css = require('./hterm.userCSS.scss') let css = require('./hterm.userCSS.scss') // eslint-disable-line
if (!config.terminal.ligatures) { if (!config.terminal.ligatures) {
css += ` css += `
* { * {
@@ -156,6 +156,14 @@ export class HTermFrontend extends Frontend {
this.term.scrollEnd() this.term.scrollEnd()
} }
findNext (_term: string, _searchOptions?: SearchOptions): boolean {
return false
}
findPrevious (_term: string, _searchOptions?: SearchOptions): boolean {
return false
}
private setFontSize () { private setFontSize () {
const size = this.configuredFontSize * Math.pow(1.1, this.zoom) const size = this.configuredFontSize * Math.pow(1.1, this.zoom)
preferenceManager.set('font-size', size) preferenceManager.set('font-size', size)
@@ -269,12 +277,4 @@ export class HTermFrontend extends Frontend {
_onCursorBlink() _onCursorBlink()
} }
} }
findNext (term: string, searchOptions?: ISearchOptions): boolean {
return false
}
findPrevious (term: string, searchOptions?: ISearchOptions): boolean {
return false
}
} }

View File

@@ -1,16 +1,16 @@
import { Frontend } from './frontend' import { Frontend, SearchOptions } from './frontend'
import { Terminal, ITheme } from 'xterm' import { Terminal, ITheme } from 'xterm'
import { getCSSFontFamily } from '../utils' import { getCSSFontFamily } from '../utils'
import { FitAddon } from 'xterm-addon-fit' import { FitAddon } from 'xterm-addon-fit'
import { enableLigatures } from 'xterm-addon-ligatures' import { enableLigatures } from 'xterm-addon-ligatures'
import { SearchAddon, ISearchOptions } from 'xterm-addon-search' import { SearchAddon } from 'xterm-addon-search'
import './xterm.css' import './xterm.css'
import deepEqual = require('deep-equal') import deepEqual from 'deep-equal'
import { Attributes, AttributeData, CellData } from 'xterm/src/common/buffer/BufferLine' import { Attributes, AttributeData, CellData } from 'xterm/src/common/buffer/BufferLine'
const COLOR_NAMES = [ const COLOR_NAMES = [
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
'brightBlack', 'brightRed', 'brightGreen', 'brightYellow', 'brightBlue', 'brightMagenta', 'brightCyan', 'brightWhite' 'brightBlack', 'brightRed', 'brightGreen', 'brightYellow', 'brightBlue', 'brightMagenta', 'brightCyan', 'brightWhite',
] ]
/** @hidden */ /** @hidden */
@@ -127,7 +127,7 @@ export class XTermFrontend extends Frontend {
ro.observe(host) ro.observe(host)
} }
detach (host: HTMLElement): void { detach (_host: HTMLElement): void {
window.removeEventListener('resize', this.resizeHandler) window.removeEventListener('resize', this.resizeHandler)
} }
@@ -138,7 +138,7 @@ export class XTermFrontend extends Frontend {
copySelection (): void { copySelection (): void {
require('electron').remote.clipboard.write({ require('electron').remote.clipboard.write({
text: this.getSelection(), text: this.getSelection(),
html: this.getSelectionAsHTML() html: this.getSelectionAsHTML(),
}) })
} }
@@ -184,7 +184,7 @@ export class XTermFrontend extends Frontend {
this.xterm.setOption('fontFamily', getCSSFontFamily(config.terminal.font)) this.xterm.setOption('fontFamily', getCSSFontFamily(config.terminal.font))
this.xterm.setOption('bellStyle', config.terminal.bell) this.xterm.setOption('bellStyle', config.terminal.bell)
this.xterm.setOption('cursorStyle', { this.xterm.setOption('cursorStyle', {
beam: 'bar' beam: 'bar',
}[config.terminal.cursor] || config.terminal.cursor) }[config.terminal.cursor] || config.terminal.cursor)
this.xterm.setOption('cursorBlink', config.terminal.cursorBlink) this.xterm.setOption('cursorBlink', config.terminal.cursorBlink)
this.xterm.setOption('macOptionIsMeta', config.terminal.altIsMeta) this.xterm.setOption('macOptionIsMeta', config.terminal.altIsMeta)
@@ -196,7 +196,7 @@ export class XTermFrontend extends Frontend {
const theme: ITheme = { const theme: ITheme = {
foreground: config.terminal.colorScheme.foreground, foreground: config.terminal.colorScheme.foreground,
background: (config.terminal.background === 'colorScheme') ? config.terminal.colorScheme.background : (config.appearance.vibrancy ? 'transparent' : this.themesService.findCurrentTheme().terminalBackground), background: config.terminal.background === 'colorScheme' ? config.terminal.colorScheme.background : config.appearance.vibrancy ? 'transparent' : this.themesService.findCurrentTheme().terminalBackground,
cursor: config.terminal.colorScheme.cursor, cursor: config.terminal.colorScheme.cursor,
} }
@@ -219,11 +219,11 @@ export class XTermFrontend extends Frontend {
this.setFontSize() this.setFontSize()
} }
findNext (term: string, searchOptions?: ISearchOptions): boolean { findNext (term: string, searchOptions?: SearchOptions): boolean {
return this.search.findNext(term, searchOptions) return this.search.findNext(term, searchOptions)
} }
findPrevious (term: string, searchOptions?: ISearchOptions): boolean { findPrevious (term: string, searchOptions?: SearchOptions): boolean {
return this.search.findPrevious(term, searchOptions) return this.search.findPrevious(term, searchOptions)
} }

View File

@@ -1,12 +1,12 @@
import slug from 'slug' import slug from 'slug'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { IHotkeyDescription, HotkeyProvider } from 'terminus-core' import { HotkeyDescription, HotkeyProvider } from 'terminus-core'
import { TerminalService } from './services/terminal.service' import { TerminalService } from './services/terminal.service'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
export class TerminalHotkeyProvider extends HotkeyProvider { export class TerminalHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [ hotkeys: HotkeyDescription[] = [
{ {
id: 'copy', id: 'copy',
name: 'Copy to clipboard', name: 'Copy to clipboard',
@@ -73,13 +73,13 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
private terminal: TerminalService, private terminal: TerminalService,
) { super() } ) { super() }
async provide (): Promise<IHotkeyDescription[]> { async provide (): Promise<HotkeyDescription[]> {
const profiles = await this.terminal.getProfiles() const profiles = await this.terminal.getProfiles()
return [ return [
...this.hotkeys, ...this.hotkeys,
...profiles.map(profile => ({ ...profiles.map(profile => ({
id: `profile.${slug(profile.name).toLowerCase()}`, id: `profile.${slug(profile.name).toLowerCase()}`,
name: `New tab: ${profile.name}` name: `New tab: ${profile.name}`,
})), })),
] ]
} }

View File

@@ -98,7 +98,7 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
// For WindowsDefaultShellProvider // For WindowsDefaultShellProvider
PowerShellCoreShellProvider, PowerShellCoreShellProvider,
WSLShellProvider, WSLShellProvider,
WindowsStockShellsProvider WindowsStockShellsProvider,
], ],
entryComponents: [ entryComponents: [
TerminalTabComponent, TerminalTabComponent,
@@ -122,7 +122,7 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
EnvironmentEditorComponent, EnvironmentEditorComponent,
], ],
}) })
export default class TerminalModule { export default class TerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
constructor ( constructor (
app: AppService, app: AppService,
config: ConfigService, config: ConfigService,
@@ -199,7 +199,7 @@ export default class TerminalModule {
hostApp.cliPaste$.subscribe(text => { hostApp.cliPaste$.subscribe(text => {
if (app.activeTab instanceof TerminalTabComponent && app.activeTab.session) { if (app.activeTab instanceof TerminalTabComponent && app.activeTab.session) {
(app.activeTab as TerminalTabComponent).sendInput(text) app.activeTab.sendInput(text)
hostApp.bringToFront() hostApp.bringToFront()
} }
}) })
@@ -222,3 +222,6 @@ export { TerminalService, BaseSession, TerminalTabComponent, TerminalFrontendSer
export { Frontend, XTermFrontend, XTermWebGLFrontend, HTermFrontend } export { Frontend, XTermFrontend, XTermWebGLFrontend, HTermFrontend }
export { BaseTerminalTabComponent } from './api/baseTerminalTab.component' export { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
export * from './api/interfaces' export * from './api/interfaces'
// Deprecations
export { TerminalColorScheme as ITerminalColorScheme, Shell as IShell } from './api/interfaces'

View File

@@ -25,7 +25,7 @@ export class PathDropDecorator extends TerminalDecorator {
} }
injectPath (terminal: TerminalTabComponent, path: string) { injectPath (terminal: TerminalTabComponent, path: string) {
if (path.indexOf(' ') >= 0) { if (path.includes(' ')) {
path = `"${path}"` path = `"${path}"`
} }
path = path.replace(/\\/g, '\\\\') path = path.replace(/\\/g, '\\\\')

View File

@@ -29,7 +29,7 @@ export class DockMenuService {
title: profile.name, title: profile.name,
iconPath: process.execPath, iconPath: process.execPath,
iconIndex: 0, iconIndex: 0,
})) })),
}] : null) }] : null)
} }
if (this.hostApp.platform === Platform.macOS) { if (this.hostApp.platform === Platform.macOS) {

View File

@@ -1,4 +1,4 @@
import psNode = require('ps-node') import * as psNode from 'ps-node'
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import * as os from 'os' import * as os from 'os'
import * as nodePTY from 'node-pty' import * as nodePTY from 'node-pty'
@@ -11,21 +11,23 @@ import { exec } from 'mz/child_process'
import { SessionOptions } from '../api/interfaces' import { SessionOptions } from '../api/interfaces'
import { WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from '../utils' import { WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from '../utils'
try { /* eslint-disable block-scoped-var */
var macOSNativeProcessList = require('macos-native-processlist') // tslint:disable-line
} catch { } // tslint:disable-line
try { try {
var windowsProcessTree = require('@terminus-term/windows-process-tree') // tslint:disable-line var macOSNativeProcessList = require('macos-native-processlist') // eslint-disable-line @typescript-eslint/no-var-requires
} catch { } // tslint:disable-line } catch { }
export interface IChildProcess { try {
var windowsProcessTree = require('@terminus-term/windows-process-tree') // eslint-disable-line @typescript-eslint/no-var-requires
} catch { }
export interface ChildProcess {
pid: number pid: number
ppid: number ppid: number
command: string command: string
} }
const windowsDirectoryRegex = /([a-zA-Z]:[^\:\[\]\?\"\<\>\|]+)/mi // tslint:disable-line const windowsDirectoryRegex = /([a-zA-Z]:[^\:\[\]\?\"\<\>\|]+)/mi
const OSC1337Prefix = '\x1b]1337;' const OSC1337Prefix = '\x1b]1337;'
const OSC1337Suffix = '\x07' const OSC1337Suffix = '\x07'
@@ -61,14 +63,6 @@ export abstract class BaseSession {
this.initialDataBuffer = null this.initialDataBuffer = null
} }
abstract start (options: SessionOptions): void
abstract resize (columns: number, rows: number): void
abstract write (data: string): void
abstract kill (signal?: string): void
abstract async getChildProcesses (): Promise<IChildProcess[]>
abstract async gracefullyKillProcess (): Promise<void>
abstract async getWorkingDirectory (): Promise<string>
async destroy (): Promise<void> { async destroy (): Promise<void> {
if (this.open) { if (this.open) {
this.open = false this.open = false
@@ -78,6 +72,14 @@ export abstract class BaseSession {
await this.gracefullyKillProcess() await this.gracefullyKillProcess()
} }
} }
abstract start (options: SessionOptions): void
abstract resize (columns: number, rows: number): void
abstract write (data: string): void
abstract kill (signal?: string): void
abstract async getChildProcesses (): Promise<ChildProcess[]>
abstract async gracefullyKillProcess (): Promise<void>
abstract async getWorkingDirectory (): Promise<string>
} }
/** @hidden */ /** @hidden */
@@ -128,12 +130,12 @@ export class Session extends BaseSession {
cwd, cwd,
env: env, env: env,
// `1` instead of `true` forces ConPTY even if unstable // `1` instead of `true` forces ConPTY even if unstable
experimentalUseConpty: ((isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY) ? 1 : false) as any, experimentalUseConpty: (isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY ? 1 : false) as any,
}) })
this.guessedCWD = cwd this.guessedCWD = cwd
this.truePID = (this.pty as any).pid this.truePID = this.pty['pid']
setTimeout(async () => { setTimeout(async () => {
// Retrieve any possible single children now that shell has fully started // Retrieve any possible single children now that shell has fully started
@@ -173,7 +175,7 @@ export class Session extends BaseSession {
this.pauseAfterExit = options.pauseAfterExit this.pauseAfterExit = options.pauseAfterExit
} }
processOSC1337 (data) { processOSC1337 (data: string) {
if (data.includes(OSC1337Prefix)) { if (data.includes(OSC1337Prefix)) {
const preData = data.substring(0, data.indexOf(OSC1337Prefix)) const preData = data.substring(0, data.indexOf(OSC1337Prefix))
let params = data.substring(data.indexOf(OSC1337Prefix) + OSC1337Prefix.length) let params = data.substring(data.indexOf(OSC1337Prefix) + OSC1337Prefix.length)
@@ -183,7 +185,7 @@ export class Session extends BaseSession {
if (params.startsWith('CurrentDir=')) { if (params.startsWith('CurrentDir=')) {
this.reportedCWD = params.split('=')[1] this.reportedCWD = params.split('=')[1]
if (this.reportedCWD.startsWith('~')) { if (this.reportedCWD.startsWith('~')) {
this.reportedCWD = os.homedir + this.reportedCWD.substring(1) this.reportedCWD = os.homedir() + this.reportedCWD.substring(1)
} }
data = preData + postData data = preData + postData
} }
@@ -211,7 +213,7 @@ export class Session extends BaseSession {
this.pty.kill(signal) this.pty.kill(signal)
} }
async getChildProcesses (): Promise<IChildProcess[]> { async getChildProcesses (): Promise<ChildProcess[]> {
if (!this.truePID) { if (!this.truePID) {
return [] return []
} }
@@ -224,7 +226,7 @@ export class Session extends BaseSession {
})) }))
} }
if (process.platform === 'win32') { if (process.platform === 'win32') {
return new Promise<IChildProcess[]>(resolve => { return new Promise<ChildProcess[]>(resolve => {
windowsProcessTree.getProcessTree(this.truePID, tree => { windowsProcessTree.getProcessTree(this.truePID, tree => {
resolve(tree ? tree.children.map(child => ({ resolve(tree ? tree.children.map(child => ({
pid: child.pid, pid: child.pid,
@@ -234,12 +236,12 @@ export class Session extends BaseSession {
}) })
}) })
} }
return new Promise<IChildProcess[]>((resolve, reject) => { return new Promise<ChildProcess[]>((resolve, reject) => {
psNode.lookup({ ppid: this.truePID }, (err, processes) => { psNode.lookup({ ppid: this.truePID }, (err, processes) => {
if (err) { if (err) {
return reject(err) return reject(err)
} }
resolve(processes as IChildProcess[]) resolve(processes as ChildProcess[])
}) })
}) })
} }

View File

@@ -4,19 +4,19 @@ import { Observable, AsyncSubject } from 'rxjs'
import { Injectable, Inject } from '@angular/core' import { Injectable, Inject } from '@angular/core'
import { AppService, Logger, LogService, ConfigService, SplitTabComponent } from 'terminus-core' import { AppService, Logger, LogService, ConfigService, SplitTabComponent } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell, SessionOptions, Profile } from '../api/interfaces' import { Shell, SessionOptions, Profile } from '../api/interfaces'
import { TerminalTabComponent } from '../components/terminalTab.component' import { TerminalTabComponent } from '../components/terminalTab.component'
import { UACService } from './uac.service' import { UACService } from './uac.service'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class TerminalService { export class TerminalService {
private shells = new AsyncSubject<IShell[]>() private shells = new AsyncSubject<Shell[]>()
private logger: Logger private logger: Logger
/** /**
* A fresh list of all available shells * A fresh list of all available shells
*/ */
get shells$ (): Observable<IShell[]> { return this.shells } get shells$ (): Observable<Shell[]> { return this.shells }
/** @hidden */ /** @hidden */
constructor ( constructor (
@@ -34,11 +34,6 @@ export class TerminalService {
}) })
} }
private async getShells (): Promise<IShell[]> {
const shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
return shellLists.reduce((a, b) => a.concat(b), [])
}
async getProfiles (includeHidden?: boolean): Promise<Profile[]> { async getProfiles (includeHidden?: boolean): Promise<Profile[]> {
const shells = await this.shells$.toPromise() const shells = await this.shells$.toPromise()
return [ return [
@@ -47,19 +42,11 @@ export class TerminalService {
name: shell.name, name: shell.name,
icon: shell.icon, icon: shell.icon,
sessionOptions: this.optionsFromShell(shell), sessionOptions: this.optionsFromShell(shell),
isBuiltin: true isBuiltin: true,
})) })),
] ]
} }
private async reloadShells () {
this.shells = new AsyncSubject<IShell[]>()
const shells = await this.getShells()
this.logger.debug('Shells list:', shells)
this.shells.next(shells)
this.shells.complete()
}
/** /**
* Launches a new terminal with a specific shell and CWD * Launches a new terminal with a specific shell and CWD
* @param pause Wait for a keypress when the shell exits * @param pause Wait for a keypress when the shell exits
@@ -102,7 +89,7 @@ export class TerminalService {
return this.openTabWithOptions(sessionOptions) return this.openTabWithOptions(sessionOptions)
} }
optionsFromShell (shell: IShell): SessionOptions { optionsFromShell (shell: Shell): SessionOptions {
return { return {
command: shell.command, command: shell.command,
args: shell.args || [], args: shell.args || [],
@@ -124,4 +111,17 @@ export class TerminalService {
{ sessionOptions } { sessionOptions }
) as TerminalTabComponent ) as TerminalTabComponent
} }
private async getShells (): Promise<Shell[]> {
const shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
return shellLists.reduce((a, b) => a.concat(b), [])
}
private async reloadShells () {
this.shells = new AsyncSubject<Shell[]>()
const shells = await this.getShells()
this.logger.debug('Shells list:', shells)
this.shells.next(shells)
this.shells.complete()
}
} }

View File

@@ -18,11 +18,11 @@ export class TerminalFrontendService {
getFrontend (session?: BaseSession): Frontend { getFrontend (session?: BaseSession): Frontend {
if (!session) { if (!session) {
const frontend: Frontend = new ({ const frontend: Frontend = new {
'xterm': XTermFrontend, xterm: XTermFrontend,
'xterm-webgl': XTermWebGLFrontend, 'xterm-webgl': XTermWebGLFrontend,
'hterm': HTermFrontend, hterm: HTermFrontend,
}[this.config.store.terminal.frontend])() }[this.config.store.terminal.frontend]()
frontend.configService = this.config frontend.configService = this.config
frontend.themesService = this.themes frontend.themesService = this.themes
frontend.hotkeysService = this.hotkeys frontend.hotkeysService = this.hotkeys

View File

@@ -4,7 +4,7 @@ import { DomSanitizer } from '@angular/platform-browser'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -16,7 +16,7 @@ export class CmderShellProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Windows) { if (this.hostApp.platform !== Platform.Windows) {
return [] return []
} }
@@ -37,7 +37,7 @@ export class CmderShellProvider extends ShellProvider {
icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/cmder.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/cmder.svg')),
env: { env: {
TERM: 'cygwin', TERM: 'cygwin',
} },
}, },
{ {
id: 'cmderps', id: 'cmderps',
@@ -50,7 +50,7 @@ export class CmderShellProvider extends ShellProvider {
'-noprofile', '-noprofile',
'-noexit', '-noexit',
'-command', '-command',
`Invoke-Expression '. ''${path.join(process.env.CMDER_ROOT, 'vendor', 'profile.ps1')}'''` `Invoke-Expression '. ''${path.join(process.env.CMDER_ROOT, 'vendor', 'profile.ps1')}'''`,
], ],
icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/cmder-powershell.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/cmder-powershell.svg')),
env: {}, env: {},

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'
import { ConfigService } from 'terminus-core' import { ConfigService } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -13,7 +13,7 @@ export class CustomShellProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
const args = this.config.store.terminal.customShell.split(' ') const args = this.config.store.terminal.customShell.split(' ')
return [{ return [{
id: 'custom', id: 'custom',

View File

@@ -4,11 +4,13 @@ import { DomSanitizer } from '@angular/platform-browser'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/* eslint-disable block-scoped-var */
try { try {
var wnr = require('windows-native-registry') // tslint:disable-line var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
} catch { } // tslint:disable-line } catch { }
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -20,7 +22,7 @@ export class Cygwin32ShellProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Windows) { if (this.hostApp.platform !== Platform.Windows) {
return [] return []
} }
@@ -38,7 +40,7 @@ export class Cygwin32ShellProvider extends ShellProvider {
icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/cygwin.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/cygwin.svg')),
env: { env: {
TERM: 'cygwin', TERM: 'cygwin',
} },
}] }]
} }
} }

View File

@@ -4,11 +4,13 @@ import { DomSanitizer } from '@angular/platform-browser'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/* eslint-disable block-scoped-var */
try { try {
var wnr = require('windows-native-registry') // tslint:disable-line var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
} catch { } // tslint:disable-line } catch { }
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -20,7 +22,7 @@ export class Cygwin64ShellProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Windows) { if (this.hostApp.platform !== Platform.Windows) {
return [] return []
} }
@@ -38,7 +40,7 @@ export class Cygwin64ShellProvider extends ShellProvider {
icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/cygwin.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/cygwin.svg')),
env: { env: {
TERM: 'cygwin', TERM: 'cygwin',
} },
}] }]
} }
} }

View File

@@ -4,11 +4,13 @@ import { DomSanitizer } from '@angular/platform-browser'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/* eslint-disable block-scoped-var */
try { try {
var wnr = require('windows-native-registry') // tslint:disable-line var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
} catch { } // tslint:disable-line } catch { }
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -20,7 +22,7 @@ export class GitBashShellProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Windows) { if (this.hostApp.platform !== Platform.Windows) {
return [] return []
} }
@@ -39,11 +41,11 @@ export class GitBashShellProvider extends ShellProvider {
id: 'git-bash', id: 'git-bash',
name: 'Git-Bash', name: 'Git-Bash',
command: path.join(gitBashPath, 'bin', 'bash.exe'), command: path.join(gitBashPath, 'bin', 'bash.exe'),
args: [ '--login', '-i' ], args: ['--login', '-i'],
icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/git-bash.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/git-bash.svg')),
env: { env: {
TERM: 'cygwin', TERM: 'cygwin',
} },
}] }]
} }
} }

View File

@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'
import { HostAppService, Platform, LogService, Logger } from 'terminus-core' import { HostAppService, Platform, LogService, Logger } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -18,7 +18,7 @@ export class LinuxDefaultShellProvider extends ShellProvider {
this.logger = log.create('linuxDefaultShell') this.logger = log.create('linuxDefaultShell')
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Linux) { if (this.hostApp.platform !== Platform.Linux) {
return [] return []
} }

View File

@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -14,7 +14,7 @@ export class MacOSDefaultShellProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.macOS) { if (this.hostApp.platform !== Platform.macOS) {
return [] return []
} }

View File

@@ -4,7 +4,7 @@ import { Injectable } from '@angular/core'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -15,7 +15,7 @@ export class POSIXShellsProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform === Platform.Windows) { if (this.hostApp.platform === Platform.Windows) {
return [] return []
} }

View File

@@ -2,11 +2,13 @@ import { Injectable } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser' import { DomSanitizer } from '@angular/platform-browser'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/* eslint-disable block-scoped-var */
try { try {
var wnr = require('windows-native-registry') // tslint:disable-line var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
} catch { } // tslint:disable-line } catch { }
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -18,7 +20,7 @@ export class PowerShellCoreShellProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Windows) { if (this.hostApp.platform !== Platform.Windows) {
return [] return []
} }
@@ -37,7 +39,7 @@ export class PowerShellCoreShellProvider extends ShellProvider {
icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/powershell-core.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/powershell-core.svg')),
env: { env: {
TERM: 'cygwin', TERM: 'cygwin',
} },
}] }]
} }
} }

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
import { WSLShellProvider } from './wsl' import { WSLShellProvider } from './wsl'
import { PowerShellCoreShellProvider } from './powershellCore' import { PowerShellCoreShellProvider } from './powershellCore'
@@ -27,7 +27,7 @@ export class WindowsDefaultShellProvider extends ShellProvider {
] ]
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Windows) { if (this.hostApp.platform !== Platform.Windows) {
return [] return []
} }

View File

@@ -4,7 +4,7 @@ import { DomSanitizer } from '@angular/platform-browser'
import { HostAppService, Platform, ElectronService } from 'terminus-core' import { HostAppService, Platform, ElectronService } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -17,7 +17,7 @@ export class WindowsStockShellsProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Windows) { if (this.hostApp.platform !== Platform.Windows) {
return [] return []
} }
@@ -55,7 +55,7 @@ export class WindowsStockShellsProvider extends ShellProvider {
icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/powershell.svg')), icon: this.domSanitizer.bypassSecurityTrustHtml(require('../icons/powershell.svg')),
env: { env: {
TERM: 'cygwin', TERM: 'cygwin',
} },
}, },
] ]
} }

View File

@@ -5,12 +5,14 @@ import { Injectable } from '@angular/core'
import { HostAppService, Platform } from 'terminus-core' import { HostAppService, Platform } from 'terminus-core'
import { ShellProvider } from '../api/shellProvider' import { ShellProvider } from '../api/shellProvider'
import { IShell } from '../api/interfaces' import { Shell } from '../api/interfaces'
import { isWindowsBuild, WIN_BUILD_WSL_EXE_DISTRO_FLAG } from '../utils' import { isWindowsBuild, WIN_BUILD_WSL_EXE_DISTRO_FLAG } from '../utils'
/* eslint-disable block-scoped-var */
try { try {
var wnr = require('windows-native-registry') // tslint:disable-line var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
} catch { } // tslint:disable-line } catch { }
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
@@ -21,7 +23,7 @@ export class WSLShellProvider extends ShellProvider {
super() super()
} }
async provide (): Promise<IShell[]> { async provide (): Promise<Shell[]> {
if (this.hostApp.platform !== Platform.Windows) { if (this.hostApp.platform !== Platform.Windows) {
return [] return []
} }
@@ -29,14 +31,14 @@ export class WSLShellProvider extends ShellProvider {
const bashPath = `${process.env.windir}\\system32\\bash.exe` const bashPath = `${process.env.windir}\\system32\\bash.exe`
const wslPath = `${process.env.windir}\\system32\\wsl.exe` const wslPath = `${process.env.windir}\\system32\\wsl.exe`
const shells: IShell[] = [{ const shells: Shell[] = [{
id: 'wsl', id: 'wsl',
name: 'WSL / Default distro', name: 'WSL / Default distro',
command: wslPath, command: wslPath,
env: { env: {
TERM: 'xterm-color', TERM: 'xterm-color',
COLORTERM: 'truecolor', COLORTERM: 'truecolor',
} },
}] }]
const lxssPath = 'Software\\Microsoft\\Windows\\CurrentVersion\\Lxss' const lxssPath = 'Software\\Microsoft\\Windows\\CurrentVersion\\Lxss'
@@ -50,13 +52,13 @@ export class WSLShellProvider extends ShellProvider {
env: { env: {
TERM: 'xterm-color', TERM: 'xterm-color',
COLORTERM: 'truecolor', COLORTERM: 'truecolor',
} },
}] }]
} else { } else {
return [] return []
} }
} }
for (const child of wnr.listRegistrySubkeys(wnr.HK.CU, lxssPath)) { for (const child of wnr.listRegistrySubkeys(wnr.HK.CU, lxssPath) as string[]) {
const childKey = wnr.getRegistryKey(wnr.HK.CU, lxssPath + '\\' + child) const childKey = wnr.getRegistryKey(wnr.HK.CU, lxssPath + '\\' + child)
if (!childKey.DistributionName) { if (!childKey.DistributionName) {
continue continue
@@ -67,11 +69,11 @@ export class WSLShellProvider extends ShellProvider {
name: `WSL / ${name}`, name: `WSL / ${name}`,
command: wslPath, command: wslPath,
args: ['-d', name], args: ['-d', name],
fsBase: childKey.BasePath.value + '\\rootfs', fsBase: childKey.BasePath.value as string + '\\rootfs',
env: { env: {
TERM: 'xterm-color', TERM: 'xterm-color',
COLORTERM: 'truecolor', COLORTERM: 'truecolor',
} },
}) })
} }

View File

@@ -25,7 +25,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
const profile = { const profile = {
sessionOptions: { sessionOptions: {
...tab.sessionOptions, ...tab.sessionOptions,
cwd: (await tab.session.getWorkingDirectory()) || tab.sessionOptions.cwd, cwd: await tab.session.getWorkingDirectory() || tab.sessionOptions.cwd,
}, },
name: tab.sessionOptions.command, name: tab.sessionOptions.command,
} }
@@ -35,8 +35,8 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
] ]
this.config.save() this.config.save()
this.toastr.info('Saved') this.toastr.info('Saved')
}) }),
} },
] ]
} }
} }

View File

@@ -1,21 +0,0 @@
{
"extends": [
"tslint-eslint-rules",
"tslint-config-standard"
],
"rules": {
"radix": false,
"indent": [
true,
"spaces"
],
"ter-indent": [true, 4],
"prefer-const": true,
"trailing-comma": [
true,
{
"singleline": "never"
}
]
}
}

611
yarn.lock

File diff suppressed because it is too large Load Diff