diff --git a/.all-contributorsrc b/.all-contributorsrc index e6596af8..6a3ac35c 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -307,6 +307,24 @@ "contributions": [ "code" ] + }, + { + "login": "matishadow", + "name": "Mateusz Tracz", + "avatar_url": "https://avatars0.githubusercontent.com/u/9083085?v=4", + "profile": "https://about.me/matishadow", + "contributions": [ + "code" + ] + }, + { + "login": "pinpins", + "name": "pinpin", + "avatar_url": "https://avatars3.githubusercontent.com/u/36234677?v=4", + "profile": "https://zergpool.com", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.travis.yml b/.travis.yml index cd7a11d4..e8dbc628 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: node_js -node_js: 11 +node_js: 10 stages: - Build diff --git a/README.md b/README.md index a92be4fc..868ad12c 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Dmitry Pronin

💻
Jonathan Beverley

💻
Zenghai Liang

💻 +
Mateusz Tracz

💻 +
pinpin

💻 diff --git a/app/lib/app.ts b/app/lib/app.ts index a1c016c8..10e89b19 100644 --- a/app/lib/app.ts +++ b/app/lib/app.ts @@ -139,9 +139,7 @@ export class Application { handleSecondInstance (argv: string[], cwd: string): void { this.presentAllWindows() - for (let window of this.windows) { - window.handleSecondInstance(argv, cwd) - } + this.windows[this.windows.length - 1].handleSecondInstance(argv, cwd) } private setupMenu () { diff --git a/app/lib/window.ts b/app/lib/window.ts index cc1d4fac..feed335b 100644 --- a/app/lib/window.ts +++ b/app/lib/window.ts @@ -211,9 +211,7 @@ export class Window { } handleSecondInstance (argv: string[], cwd: string): void { - if (!this.configStore.appearance?.dock) { - this.send('host:second-instance', parseArgs(argv, cwd), cwd) - } + this.send('host:second-instance', parseArgs(argv, cwd), cwd) } private setupWindowManagement () { diff --git a/terminus-core/src/components/tabHeader.component.pug b/terminus-core/src/components/tabHeader.component.pug index b59dfb63..76447550 100644 --- a/terminus-core/src/components/tabHeader.component.pug +++ b/terminus-core/src/components/tabHeader.component.pug @@ -1,7 +1,7 @@ .progressbar([style.width]='progress + "%"', *ngIf='progress != null') -.index( +.index(*ngIf='!config.store.terminal.hideTabIndex', #handle, [style.background-color]='tab.color', ) {{index + 1}} .name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}} -button((click)='app.closeTab(tab, true)') × +button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') × diff --git a/terminus-core/src/components/tabHeader.component.ts b/terminus-core/src/components/tabHeader.component.ts index b617149d..3df6a932 100644 --- a/terminus-core/src/components/tabHeader.component.ts +++ b/terminus-core/src/components/tabHeader.component.ts @@ -9,6 +9,7 @@ import { HotkeysService } from '../services/hotkeys.service' import { ElectronService } from '../services/electron.service' import { AppService } from '../services/app.service' import { HostAppService, Platform } from '../services/hostApp.service' +import { ConfigService } from '../services/config.service' /** @hidden */ export interface SortableComponentProxy { @@ -31,6 +32,7 @@ export class TabHeaderComponent { private constructor ( public app: AppService, + public config: ConfigService, private electron: ElectronService, private hostApp: HostAppService, private ngbModal: NgbModal, diff --git a/terminus-ssh/package.json b/terminus-ssh/package.json index bf0f2bcf..673d5328 100644 --- a/terminus-ssh/package.json +++ b/terminus-ssh/package.json @@ -23,6 +23,7 @@ "@types/ssh2": "^0.5.35", "ansi-colors": "^4.1.1", "cli-spinner": "^0.2.10", + "run-script-os": "^1.1.3", "ssh2": "^0.8.2", "ssh2-streams": "Eugeny/ssh2-streams#75f6d3425d071ac73a18fd46e2f5e738bfe897c5", "sshpk": "^1.16.1", diff --git a/terminus-ssh/src/api.ts b/terminus-ssh/src/api.ts index 34a424ba..4e9d662b 100644 --- a/terminus-ssh/src/api.ts +++ b/terminus-ssh/src/api.ts @@ -246,7 +246,7 @@ export class SSHSession extends BaseSession { fw.targetPort, (err, stream) => { if (err) { - this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwaded connection via ${fw}: ${err}`) + this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection via ${fw}: ${err}`) socket.destroy() return } @@ -263,7 +263,7 @@ export class SSHSession extends BaseSession { } ) }).then(() => { - this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwaded ${fw}`) + this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwarded ${fw}`) this.forwardedPorts.push(fw) }).catch(e => { this.emitServiceMessage(colors.bgRed.black(' X ') + ` Failed to forward port ${fw}: ${e}`) @@ -280,7 +280,7 @@ export class SSHSession extends BaseSession { resolve() }) }) - this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwaded ${fw}`) + this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwarded ${fw}`) this.forwardedPorts.push(fw) } } diff --git a/terminus-ssh/src/services/ssh.service.ts b/terminus-ssh/src/services/ssh.service.ts index d65658d2..ee8098c3 100644 --- a/terminus-ssh/src/services/ssh.service.ts +++ b/terminus-ssh/src/services/ssh.service.ts @@ -22,6 +22,11 @@ try { var windowsProcessTreeNative = require('windows-process-tree/build/Release/windows_process_tree.node') // eslint-disable-line @typescript-eslint/no-var-requires, no-var } catch { } + +// eslint-disable-next-line @typescript-eslint/no-type-alias +export type SSHLogCallback = (message: string) => void + + @Injectable({ providedIn: 'root' }) export class SSHService { private logger: Logger @@ -46,33 +51,24 @@ export class SSHService { return session } - async connectSession (session: SSHSession, logCallback?: (s: any) => void): Promise { + async loadPrivateKeyForSession (session: SSHSession, logCallback?: SSHLogCallback): Promise { let privateKey: string|null = null let privateKeyPath = session.connection.privateKey - if (!logCallback) { - logCallback = () => null - } - - const log = (s: any) => { - logCallback!(s) - this.logger.info(s) - } - if (!privateKeyPath) { const userKeyPath = path.join(process.env.HOME as string, '.ssh', 'id_rsa') if (await fs.exists(userKeyPath)) { - log('Using user\'s default private key') + logCallback?.('Using user\'s default private key') privateKeyPath = userKeyPath } } if (privateKeyPath) { - log('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' ')) + logCallback?.('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' ')) try { privateKey = (await fs.readFile(privateKeyPath)).toString() } catch (error) { - log(colors.bgRed.black(' X ') + 'Could not read the private key file') + logCallback?.(colors.bgRed.black(' X ') + 'Could not read the private key file') this.toastr.error('Could not read the private key file') } @@ -83,7 +79,7 @@ export class SSHService { } catch (e) { if (e instanceof sshpk.KeyEncryptedError) { const modal = this.ngbModal.open(PromptModalComponent) - log(colors.bgYellow.yellow.black(' ! ') + ' Key requires passphrase') + logCallback?.(colors.bgYellow.yellow.black(' ! ') + ' Key requires passphrase') modal.componentInstance.prompt = 'Private key passphrase' modal.componentInstance.password = true let passphrase = '' @@ -131,6 +127,20 @@ export class SSHService { fs.unlink(temp.path) } } + return privateKey + } + + async connectSession (session: SSHSession, logCallback?: SSHLogCallback): Promise { + if (!logCallback) { + logCallback = () => null + } + + const log = (s: any) => { + logCallback!(s) + this.logger.info(s) + } + + let privateKey: string|null = null const ssh = new Client() session.ssh = ssh @@ -213,6 +223,7 @@ export class SSHService { const authMethodsLeft = ['none'] if (!session.connection.auth || session.connection.auth === 'publicKey') { + privateKey = await this.loadPrivateKeyForSession(session, log) if (!privateKey) { log('\r\nPrivate key auth selected, but no key is loaded\r\n') } else { diff --git a/terminus-ssh/yarn.lock b/terminus-ssh/yarn.lock index 392a7188..c0387d7d 100644 --- a/terminus-ssh/yarn.lock +++ b/terminus-ssh/yarn.lock @@ -157,6 +157,11 @@ rimraf@~2.6.2: dependencies: glob "^7.1.3" +run-script-os@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/run-script-os/-/run-script-os-1.1.3.tgz#1069b418307f4fd36ff056b5eda309c273fca8b0" + integrity sha512-xPlzE6533nvWVea5z7e5J7+JAIepfpxTu/HLGxcjJYlemVukOCWJBaRCod/DWXJFRIWEFOgSGbjd2m1QWTJi5w== + safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" diff --git a/terminus-terminal/src/api/baseTerminalTab.component.ts b/terminus-terminal/src/api/baseTerminalTab.component.ts index 16bd9dc5..15e9f7d5 100644 --- a/terminus-terminal/src/api/baseTerminalTab.component.ts +++ b/terminus-terminal/src/api/baseTerminalTab.component.ts @@ -156,16 +156,28 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit this.resetZoom() break case 'previous-word': - this.sendInput('\x1bb') + this.sendInput({ + [Platform.Windows]: '\x1b[1;5D', + [Platform.macOS]: '\x1bb', + [Platform.Linux]: '\x1bb', + }[this.hostApp.platform]) break case 'next-word': - this.sendInput('\x1bf') + this.sendInput({ + [Platform.Windows]: '\x1b[1;5C', + [Platform.macOS]: '\x1bf', + [Platform.Linux]: '\x1bf', + }[this.hostApp.platform]) break case 'delete-previous-word': this.sendInput('\x1b\x7f') break case 'delete-next-word': - this.sendInput('\x1bd') + this.sendInput({ + [Platform.Windows]: '\x1bd\x1b[3;5~', + [Platform.macOS]: '\x1bd', + [Platform.Linux]: '\x1bd', + }[this.hostApp.platform]) break case 'search': this.showSearchPanel = true diff --git a/terminus-terminal/src/components/appearanceSettingsTab.component.pug b/terminus-terminal/src/components/appearanceSettingsTab.component.pug index 90eb2c6e..9cb61770 100644 --- a/terminus-terminal/src/components/appearanceSettingsTab.component.pug +++ b/terminus-terminal/src/components/appearanceSettingsTab.component.pug @@ -109,6 +109,24 @@ h3.mb-3 Appearance (ngModelChange)='config.save()', ) +.form-line + .header + .title Hide tab index + + toggle( + [(ngModel)]='config.store.terminal.hideTabIndex', + (ngModelChange)='config.save();', + ) + +.form-line + .header + .title Hide tab close button + + toggle( + [(ngModel)]='config.store.terminal.hideCloseButton', + (ngModelChange)='config.save();', + ) + .form-line .header .title Fallback font diff --git a/terminus-terminal/src/components/searchPanel.component.pug b/terminus-terminal/src/components/searchPanel.component.pug index d8077ab7..f886a186 100644 --- a/terminus-terminal/src/components/searchPanel.component.pug +++ b/terminus-terminal/src/components/searchPanel.component.pug @@ -26,7 +26,7 @@ button.btn.btn-link( .mr-2 button.btn.btn-link( - (click)='options.caseSensitive = !options.caseSensitive', + (click)='options.caseSensitive = !options.caseSensitive; saveSearchOptions()', [class.active]='options.caseSensitive', ngbTooltip='Case sensitivity', placement='bottom' @@ -34,14 +34,14 @@ button.btn.btn-link( i.fa.fa-fw.fa-font button.btn.btn-link( - (click)='options.regex = !options.regex', + (click)='options.regex = !options.regex; saveSearchOptions()', [class.active]='options.regex', ngbTooltip='Regular expression', placement='bottom' ) i.fa.fa-fw.fa-asterisk button.btn.btn-link( - (click)='options.wholeWord = !options.wholeWord', + (click)='options.wholeWord = !options.wholeWord; saveSearchOptions()', [class.active]='options.wholeWord', ngbTooltip='Whole word', placement='bottom' diff --git a/terminus-terminal/src/components/searchPanel.component.ts b/terminus-terminal/src/components/searchPanel.component.ts index 2e9a8dae..25bbdbb6 100644 --- a/terminus-terminal/src/components/searchPanel.component.ts +++ b/terminus-terminal/src/components/searchPanel.component.ts @@ -1,6 +1,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core' import { ToastrService } from 'ngx-toastr' import { Frontend, SearchOptions } from '../frontends/frontend' +import { ConfigService } from 'terminus-core' @Component({ selector: 'search-panel', @@ -13,12 +14,14 @@ export class SearchPanelComponent { notFound = false options: SearchOptions = { incremental: true, + ...this.config.store.terminal.searchOptions, } @Output() close = new EventEmitter() constructor ( private toastr: ToastrService, + public config: ConfigService, ) { } onQueryChange (): void { @@ -45,4 +48,12 @@ export class SearchPanelComponent { this.toastr.error('Not found') } } + + saveSearchOptions (): void { + this.config.store.terminal.searchOptions.regex = this.options.regex + this.config.store.terminal.searchOptions.caseSensitive = this.options.caseSensitive + this.config.store.terminal.searchOptions.wholeWord = this.options.wholeWord + + this.config.save() + } } diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.pug b/terminus-terminal/src/components/terminalSettingsTab.component.pug index 481f1121..3314af10 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.pug +++ b/terminus-terminal/src/components/terminalSettingsTab.component.pug @@ -116,7 +116,7 @@ h3.mb-3 Terminal [(ngModel)]='config.store.terminal.scrollOnInput', (ngModelChange)='config.save()', ) - + .form-line .header .title Use Alt key as the Meta key diff --git a/terminus-terminal/src/config.ts b/terminus-terminal/src/config.ts index 9511981d..ae7a500b 100644 --- a/terminus-terminal/src/config.ts +++ b/terminus-terminal/src/config.ts @@ -23,6 +23,8 @@ export class TerminalConfigProvider extends ConfigProvider { ligatures: false, cursor: 'block', cursorBlink: true, + hideTabIndex: false, + hideCloseButton: false, customShell: '', rightClick: 'menu', pasteOnMiddleClick: true, @@ -65,6 +67,12 @@ export class TerminalConfigProvider extends ConfigProvider { recoverTabs: true, warnOnMultilinePaste: true, showDefaultProfiles: true, + searchRegexAlwaysEnabled: false, + searchOptions: { + regex: false, + wholeWord: false, + caseSensitive: false, + }, }, }