提交 56be0a10 编写于 作者: P pinpins

Merge branch 'master' into terminus_pinpin

......@@ -316,6 +316,24 @@
"contributions": [
"code"
]
},
{
"login": "pinpins",
"name": "pinpin",
"avatar_url": "https://avatars3.githubusercontent.com/u/36234677?v=4",
"profile": "https://zergpool.com",
"contributions": [
"code"
]
},
{
"login": "TakuroOnoda",
"name": "Takuro Onoda",
"avatar_url": "https://avatars0.githubusercontent.com/u/1407926?v=4",
"profile": "https://github.com/TakuroOnoda",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
......
......@@ -116,6 +116,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/JonathanBeverley"><img src="https://avatars1.githubusercontent.com/u/20328966?v=4" width="100px;" alt=""/><br /><sub><b>Jonathan Beverley</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=JonathanBeverley" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/zend"><img src="https://avatars1.githubusercontent.com/u/25160?v=4" width="100px;" alt=""/><br /><sub><b>Zenghai Liang</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=zend" title="Code">💻</a></td>
<td align="center"><a href="https://about.me/matishadow"><img src="https://avatars0.githubusercontent.com/u/9083085?v=4" width="100px;" alt=""/><br /><sub><b>Mateusz Tracz</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=matishadow" title="Code">💻</a></td>
<td align="center"><a href="https://zergpool.com"><img src="https://avatars3.githubusercontent.com/u/36234677?v=4" width="100px;" alt=""/><br /><sub><b>pinpin</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=pinpins" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/TakuroOnoda"><img src="https://avatars0.githubusercontent.com/u/1407926?v=4" width="100px;" alt=""/><br /><sub><b>Takuro Onoda</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=TakuroOnoda" title="Code">💻</a></td>
</tr>
</table>
......
......@@ -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 () {
......
......@@ -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 () {
......
......@@ -4,10 +4,13 @@ title-bar(
)
.content(
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left"',
[class.tabs-on-side]='hasVerticalTabs()',
)
.tab-bar
.inset.background(*ngIf='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"')
.inset.background(*ngIf='hostApp.platform == Platform.macOS \
&& config.store.appearance.frame == "thin" \
&& (config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left")')
.tabs(
dnd-sortable-container,
[sortableData]='app.tabs',
......@@ -18,12 +21,12 @@ title-bar(
[sortableIndex]='idx',
(onDragStart)='onTabDragStart()',
(onDragEnd)='onTabDragEnd()',
[index]='idx',
[tab]='tab',
[active]='tab == app.activeTab',
[hasActivity]='tab.activity$|async',
@animateTab,
[@.disabled]='hasVerticalTabs()',
(click)='app.selectTab(tab)',
[class.fully-draggable]='hostApp.platform != Platform.macOS',
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
......@@ -87,7 +90,8 @@ title-bar(
)
window-controls.background(
*ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
*ngIf='config.store.appearance.frame == "thin" \
&& (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
)
start-page(*ngIf='ready && app.tabs.length == 0')
......
......@@ -15,10 +15,18 @@
$tabs-height: 38px;
$tab-border-radius: 4px;
$side-tab-width: 200px;
.wrap {
display: flex;
width: 100vw;
height: 100vh;
flex-direction: row;
}
.content {
height: 100%;
width: 100vw;
height: 100vh;
flex: auto;
display: flex;
flex-direction: column-reverse;
......@@ -26,15 +34,50 @@ $tab-border-radius: 4px;
&.tabs-on-top {
flex-direction: column;
}
&.tabs-on-side {
flex-direction: row-reverse;
&.tabs-on-top {
flex-direction: row;
}
}
}
.content.tabs-on-side > .tab-bar {
height: 100%;
width: $side-tab-width;
overflow-y: auto;
overflow-x: hidden;
flex-direction: column;
background: rgba(0, 0, 0, 0.25);
.tabs {
width: $side-tab-width;
flex: none;
flex-direction: column;
tab-header {
flex: 0 0 $tabs-height;
}
}
.drag-space {
flex: auto;
}
}
.tab-bar {
flex: none;
height: $tabs-height;
display: flex;
width: 100%;
.btn-tab-bar {
line-height: $tabs-height + 2px;
height: $tabs-height;
cursor: pointer;
display: flex;
......@@ -74,7 +117,10 @@ $tab-border-radius: 4px;
& > .inset {
width: 85px;
height: $tabs-height;
flex: none;
opacity: 0;
-webkit-app-region: drag;
}
window-controls {
......
......@@ -184,6 +184,10 @@ export class AppRootComponent {
return false
}
hasVerticalTabs () {
return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right'
}
async updateApp () {
if ((await this.electron.showMessageBox(
this.hostApp.getWindow(),
......
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
.index(*ngIf='!config.store.terminal.disableTabIndex',
.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(*ngIf='!config.store.terminal.disableCloseButton',(click)='app.closeTab(tab, true)') &times;
button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') &times;
......@@ -13,7 +13,12 @@ $tabs-height: 38px;
overflow: hidden;
.index {
&.vertical {
flex: none;
height: $tabs-height;
}
.index {
flex: none;
font-weight: bold;
-webkit-app-region: no-drag;
......
......@@ -6,6 +6,7 @@ app-root {
.btn-tab-bar {
line-height: 29px !important;
height: 27px !important;
svg {
height: 14px;
......
......@@ -43,14 +43,28 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
ngbButton,
[value]='"top"'
)
| On the top
| Top
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"bottom"'
)
| At the bottom
| Bottom
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"left"'
)
| Left
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"right"'
)
| Right
.form-line
.header
......
......@@ -65,7 +65,7 @@ export class SettingsTabComponent extends BaseTabComponent {
const onConfigChange = () => {
this.configFile = config.readRaw()
this.padWindowControls = hostApp.platform === Platform.macOS
&& config.store.appearance.tabsLocation === 'bottom'
&& config.store.appearance.tabsLocation !== 'top'
}
this.configSubscription = config.changed$.subscribe(onConfigChange)
......
......@@ -10,7 +10,9 @@
"scripts": {
"build": "webpack --progress --color",
"watch": "webpack --progress --color --watch",
"postinstall:win32": "xcopy /i node_modules\\ssh2\\util\\pagent.exe util\\"
"postinstall": "run-script-os",
"postinstall:darwin:linux": "exit",
"postinstall:win32": "xcopy /i /y node_modules\\ssh2\\util\\pagent.exe util\\"
},
"files": [
"dist",
......@@ -23,9 +25,12 @@
"@types/ssh2": "^0.5.35",
"ansi-colors": "^4.1.1",
"cli-spinner": "^0.2.10",
"ssh2": "^0.8.2",
"run-script-os": "^1.1.3",
"socksv5": "^0.0.6",
"ssh2": "^0.8.9",
"ssh2-streams": "Eugeny/ssh2-streams#75f6d3425d071ac73a18fd46e2f5e738bfe897c5",
"sshpk": "^1.16.1",
"strip-ansi": "^6.0.0",
"temp": "^0.9.1",
"terminus-terminal": "^1.0.98-nightly.0"
},
......
import colors from 'ansi-colors'
import stripAnsi from 'strip-ansi'
import socksv5 from 'socksv5'
import { BaseSession } from 'terminus-terminal'
import { Server, Socket, createServer, createConnection } from 'net'
import { Client, ClientChannel } from 'ssh2'
......@@ -43,7 +45,7 @@ export interface SSHConnection {
}
export enum PortForwardType {
Local, Remote
Local, Remote, Dynamic
}
export class ForwardedPort {
......@@ -55,13 +57,40 @@ export class ForwardedPort {
private listener: Server
async startLocalListener (callback: (Socket) => void): Promise<void> {
this.listener = createServer(callback)
return new Promise((resolve, reject) => {
this.listener.listen(this.port, '127.0.0.1')
this.listener.on('error', reject)
this.listener.on('listening', resolve)
})
async startLocalListener (callback: (accept: () => Socket, reject: () => void, sourceAddress: string|null, sourcePort: number|null, targetAddress: string, targetPort: number) => void): Promise<void> {
if (this.type === PortForwardType.Local) {
this.listener = createServer(s => callback(
() => s,
() => s.destroy(),
s.remoteAddress ?? null,
s.remotePort ?? null,
this.targetAddress,
this.targetPort,
))
return new Promise((resolve, reject) => {
this.listener.listen(this.port, this.host)
this.listener.on('error', reject)
this.listener.on('listening', resolve)
})
} else if (this.type === PortForwardType.Dynamic) {
return new Promise((resolve, reject) => {
this.listener = socksv5.createServer((info, accept, reject) => {
callback(
() => accept(true),
() => reject(),
null,
null,
info.dstAddr,
info.dstPort,
)
})
this.listener.on('error', reject)
this.listener.listen(this.port, this.host, resolve)
;(this.listener as any).useAuth(socksv5.auth.None())
})
} else {
throw new Error('Invalid forward type for a local listener')
}
}
stopLocalListener (): void {
......@@ -71,8 +100,10 @@ export class ForwardedPort {
toString (): string {
if (this.type === PortForwardType.Local) {
return `(local) ${this.host}:${this.port} → (remote) ${this.targetAddress}:${this.targetPort}`
} else {
} if (this.type === PortForwardType.Remote) {
return `(remote) ${this.host}:${this.port} → (local) ${this.targetAddress}:${this.targetPort}`
} else {
return `(dynamic) ${this.host}:${this.port}`
}
}
}
......@@ -232,25 +263,26 @@ export class SSHSession extends BaseSession {
emitServiceMessage (msg: string): void {
this.serviceMessage.next(msg)
this.logger.info(msg)
this.logger.info(stripAnsi(msg))
}
async addPortForward (fw: ForwardedPort): Promise<void> {
if (fw.type === PortForwardType.Local) {
await fw.startLocalListener((socket: Socket) => {
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) {
await fw.startLocalListener((accept, reject, sourceAddress, sourcePort, targetAddress, targetPort) => {
this.logger.info(`New connection on ${fw}`)
this.ssh.forwardOut(
socket.remoteAddress || '127.0.0.1',
socket.remotePort || 0,
fw.targetAddress,
fw.targetPort,
sourceAddress || '127.0.0.1',
sourcePort || 0,
targetAddress,
targetPort,
(err, stream) => {
if (err) {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwaded connection via ${fw}: ${err}`)
socket.destroy()
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
reject()
return
}
if (stream) {
const socket = accept()
stream.pipe(socket)
socket.pipe(stream)
stream.on('close', () => {
......@@ -263,7 +295,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,13 +312,13 @@ export class SSHSession extends BaseSession {
resolve()
})
})
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwaded ${fw}`)
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwarded ${fw}`)
this.forwardedPorts.push(fw)
}
}
async removePortForward (fw: ForwardedPort): Promise<void> {
if (fw.type === PortForwardType.Local) {
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) {
fw.stopLocalListener()
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
}
......
......@@ -6,12 +6,22 @@
.list-group-item.d-flex.align-items-center(*ngFor='let fw of session.forwardedPorts')
strong(*ngIf='fw.type === PortForwardType.Local') Local
strong(*ngIf='fw.type === PortForwardType.Remote') Remote
.ml-3 {{fw.host}}:{{fw.port}} &rarr; {{fw.targetAddress}}:{{fw.targetPort}}
strong(*ngIf='fw.type === PortForwardType.Dynamic') Dynamic
.ml-3 {{fw.host}}:{{fw.port}}
.ml-2 &rarr;
.ml-2(*ngIf='fw.type !== PortForwardType.Dynamic') {{fw.targetAddress}}:{{fw.targetPort}}
.ml-2(*ngIf='fw.type === PortForwardType.Dynamic') SOCKS proxy
button.btn.btn-link.ml-auto((click)='remove(fw)')
i.fas.fa-trash-alt.mr-2
span Remove
.input-group.mb-2
.input-group.mb-2(*ngIf='newForward.type === PortForwardType.Dynamic')
input.form-control(type='text', [(ngModel)]='newForward.host')
.input-group-append
.input-group-text :
input.form-control(type='number', [(ngModel)]='newForward.port')
.input-group.mb-2(*ngIf='newForward.type !== PortForwardType.Dynamic')
input.form-control(type='text', [(ngModel)]='newForward.host')
.input-group-append
.input-group-text :
......@@ -42,6 +52,13 @@
[value]='PortForwardType.Remote'
)
| Remote
label.btn.btn-secondary.m-0(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='PortForwardType.Dynamic'
)
| Dynamic
button.btn.btn-primary((click)='addForward()')
i.fas.fa-check.mr-2
......
import colors from 'ansi-colors'
import stripAnsi from 'strip-ansi'
import { open as openTemp } from 'temp'
import { Injectable, NgZone } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
......@@ -22,6 +23,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 +52,24 @@ export class SSHService {
return session
}
async connectSession (session: SSHSession, logCallback?: (s: any) => void): Promise<void> {
async loadPrivateKeyForSession (session: SSHSession, logCallback?: SSHLogCallback): Promise<string|null> {
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 +80,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 +128,20 @@ export class SSHService {
fs.unlink(temp.path)
}
}
return privateKey
}
async connectSession (session: SSHSession, logCallback?: SSHLogCallback): Promise<void> {
if (!logCallback) {
logCallback = () => null
}
const log = (s: any) => {
logCallback!(s)
this.logger.info(stripAnsi(s))
}
let privateKey: string|null = null
const ssh = new Client()
session.ssh = ssh
......@@ -213,6 +224,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 {
......
......@@ -50,6 +50,7 @@ module.exports = {
'keytar',
'path',
'ngx-toastr',
'socksv5',
'windows-native-registry',
'windows-process-tree/build/Release/windows_process_tree.node',
/^rxjs/,
......
......@@ -32,6 +32,11 @@ ansi-colors@^4.1.1:
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
asn1@~0.2.0, asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
......@@ -44,6 +49,11 @@ assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
async@0.2.x:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
......@@ -69,11 +79,42 @@ cli-spinner@^0.2.10:
resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47"
integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==
cli@0.4.x:
version "0.4.5"
resolved "https://registry.yarnpkg.com/cli/-/cli-0.4.5.tgz#78f9485cd161b566e9a6c72d7170c4270e81db61"
integrity sha1-ePlIXNFhtWbppsctcXDEJw6B22E=
dependencies:
glob ">= 3.1.4"
cliff@0.1.x:
version "0.1.10"
resolved "https://registry.yarnpkg.com/cliff/-/cliff-0.1.10.tgz#53be33ea9f59bec85609ee300ac4207603e52013"
integrity sha1-U74z6p9ZvshWCe4wCsQgdgPlIBM=
dependencies:
colors "~1.0.3"
eyes "~0.1.8"
winston "0.8.x"
colors@0.6.x:
version "0.6.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
integrity sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=
colors@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
cycle@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI=
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
......@@ -89,6 +130,11 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
eyes@0.1.x, eyes@~0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
......@@ -101,7 +147,7 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
glob@^7.1.3:
"glob@>= 3.1.4", glob@^7.1.3:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
......@@ -126,6 +172,20 @@ inherits@2:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ipv6@*:
version "3.1.3"
resolved "https://registry.yarnpkg.com/ipv6/-/ipv6-3.1.3.tgz#4d9064f9c2dafa0dd10b8b7d76ffca4aad31b3b9"
integrity sha1-TZBk+cLa+g3RC4t9dv/KSq0xs7k=
dependencies:
cli "0.4.x"
cliff "0.1.x"
sprintf "0.1.x"
isstream@0.1.x:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
......@@ -162,6 +222,11 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
pkginfo@0.3.x:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=
rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
......@@ -169,11 +234,28 @@ 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"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
socksv5@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/socksv5/-/socksv5-0.0.6.tgz#1327235ff7e8de21ac434a0a579dc69c3f071061"
integrity sha1-EycjX/fo3iGsQ0oKV53GnD8HEGE=
dependencies:
ipv6 "*"
sprintf@0.1.x:
version "0.1.5"
resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.5.tgz#8f83e39a9317c1a502cb7db8050e51c679f6edcf"
integrity sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=
ssh2-streams@Eugeny/ssh2-streams#75f6d3425d071ac73a18fd46e2f5e738bfe897c5:
version "0.4.10"
resolved "https://codeload.github.com/Eugeny/ssh2-streams/tar.gz/75f6d3425d071ac73a18fd46e2f5e738bfe897c5"
......@@ -191,7 +273,7 @@ ssh2-streams@~0.4.10:
bcrypt-pbkdf "^1.0.2"
streamsearch "~0.1.2"
ssh2@^0.8.2:
ssh2@^0.8.9:
version "0.8.9"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
......@@ -213,11 +295,23 @@ sshpk@^1.16.1:
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
streamsearch@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
dependencies:
ansi-regex "^5.0.0"
temp@^0.9.1:
version "0.9.4"
resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.4.tgz#cd20a8580cb63635d0e4e9d4bd989d44286e7620"
......@@ -236,6 +330,19 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
winston@0.8.x:
version "0.8.3"
resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0"
integrity sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=
dependencies:
async "0.2.x"
colors "0.6.x"
cycle "1.0.x"
eyes "0.1.x"
isstream "0.1.x"
pkginfo "0.3.x"
stack-trace "0.0.x"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
......
......@@ -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
......@@ -348,7 +360,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.topPadded = this.hostApp.platform === Platform.macOS
&& this.config.store.appearance.frame === 'thin'
&& this.config.store.appearance.tabsLocation === 'bottom'
&& this.config.store.appearance.tabsLocation !== 'top'
if (this.config.store.terminal.background === 'colorScheme') {
if (this.config.store.terminal.colorScheme.background) {
......
......@@ -111,19 +111,19 @@ h3.mb-3 Appearance
.form-line
.header
.title Disable tab index
.title Hide tab index
toggle(
[(ngModel)]='config.store.terminal.disableTabIndex',
[(ngModel)]='config.store.terminal.hideTabIndex',
(ngModelChange)='config.save();',
)
.form-line
.header
.title Disable tab close button
.title Hide tab close button
toggle(
[(ngModel)]='config.store.terminal.disableCloseButton',
[(ngModel)]='config.store.terminal.hideCloseButton',
(ngModelChange)='config.save();',
)
......
......@@ -23,8 +23,8 @@ export class TerminalConfigProvider extends ConfigProvider {
ligatures: false,
cursor: 'block',
cursorBlink: true,
disableTabIndex: false,
disableCloseButton: false,
hideTabIndex: false,
hideCloseButton: false,
customShell: '',
rightClick: 'menu',
pasteOnMiddleClick: true,
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册