diff --git a/tabby-serial/package.json b/tabby-serial/package.json index a237ae48..219e25c0 100644 --- a/tabby-serial/package.json +++ b/tabby-serial/package.json @@ -19,10 +19,7 @@ "devDependencies": { "@types/node": "14.14.14", "ansi-colors": "^4.1.1", - "binstring": "^0.2.1", - "buffer-replace": "^1.0.0", - "cli-spinner": "^0.2.10", - "hexer": "^1.5.0" + "cli-spinner": "^0.2.10" }, "peerDependencies": { "@angular/animations": "^9.1.9", diff --git a/tabby-serial/src/api.ts b/tabby-serial/src/api.ts index a4ffb506..94f90b72 100644 --- a/tabby-serial/src/api.ts +++ b/tabby-serial/src/api.ts @@ -1,15 +1,8 @@ -import hexdump from 'hexer' -import colors from 'ansi-colors' -import binstring from 'binstring' import stripAnsi from 'strip-ansi' -import bufferReplace from 'buffer-replace' -import { BaseSession } from 'tabby-terminal' import { SerialPort } from 'serialport' import { Logger, Profile } from 'tabby-core' -import { Subject, Observable, interval } from 'rxjs' -import { debounce } from 'rxjs/operators' -import { ReadLine, createInterface as createReadline, clearLine } from 'readline' -import { PassThrough, Readable, Writable } from 'stream' +import { Subject, Observable } from 'rxjs' +import { BaseSession, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal' export interface LoginScript { expect: string @@ -22,7 +15,7 @@ export interface SerialProfile extends Profile { options: SerialProfileOptions } -export interface SerialProfileOptions { +export interface SerialProfileOptions extends StreamProcessingOptions { port: string baudrate?: number databits?: number @@ -34,10 +27,6 @@ export interface SerialProfileOptions { xany?: boolean scripts?: LoginScript[] color?: string - inputMode?: InputMode - inputNewlines?: NewlineMode - outputMode?: OutputMode - outputNewlines?: NewlineMode } export const BAUD_RATES = [ @@ -49,10 +38,6 @@ export interface SerialPortInfo { description?: string } -export type InputMode = null | 'readline' | 'readline-hex' // eslint-disable-line @typescript-eslint/no-type-alias -export type OutputMode = null | 'hex' // eslint-disable-line @typescript-eslint/no-type-alias -export type NewlineMode = null | 'cr' | 'lf' | 'crlf' // eslint-disable-line @typescript-eslint/no-type-alias - export class SerialSession extends BaseSession { scripts?: LoginScript[] serial: SerialPort @@ -60,38 +45,67 @@ export class SerialSession extends BaseSession { get serviceMessage$ (): Observable { return this.serviceMessage } private serviceMessage = new Subject() - private inputReadline: ReadLine - private inputPromptVisible = true - private inputReadlineInStream: Readable & Writable - private inputReadlineOutStream: Readable & Writable + private streamProcessor: TerminalStreamProcessor constructor (public profile: SerialProfile) { super() this.scripts = profile.options.scripts ?? [] + this.streamProcessor = new TerminalStreamProcessor(profile.options) + this.streamProcessor.outputToSession$.subscribe(data => { + this.serial?.write(data.toString()) + }) + this.streamProcessor.outputToTerminal$.subscribe(data => { + this.emitOutput(data) - this.inputReadlineInStream = new PassThrough() - this.inputReadlineOutStream = new PassThrough() - this.inputReadline = createReadline({ - input: this.inputReadlineInStream, - output: this.inputReadlineOutStream, - terminal: true, - prompt: this.profile.options.inputMode === 'readline-hex' ? 'hex> ' : '> ', - } as any) - this.inputReadlineOutStream.on('data', data => { - this.emitOutput(Buffer.from(data)) + const dataString = data.toString() + + if (this.scripts) { + let found = false + for (const script of this.scripts) { + let match = false + let cmd = '' + if (script.isRegex) { + const re = new RegExp(script.expect, 'g') + if (re.test(dataString)) { + cmd = dataString.replace(re, script.send) + match = true + found = true + } + } else { + if (dataString.includes(script.expect)) { + cmd = script.send + match = true + found = true + } + } + + if (match) { + this.logger.info('Executing script: "' + cmd + '"') + this.serial.write(cmd + '\n') + this.scripts = this.scripts.filter(x => x !== script) + } else { + if (script.optional) { + this.logger.debug('Skip optional script: ' + script.expect) + found = true + this.scripts = this.scripts.filter(x => x !== script) + } else { + break + } + } + } + + if (found) { + this.executeUnconditionalScripts() + } + } }) - this.inputReadline.on('line', line => { - this.onInput(Buffer.from(line + '\n')) - this.resetInputPrompt() - }) - this.output$.pipe(debounce(() => interval(500))).subscribe(() => this.onOutputSettled()) } async start (): Promise { this.open = true this.serial.on('readable', () => { - this.onOutput(this.serial.read()) + this.streamProcessor.feedFromSession(this.serial.read()) }) this.serial.on('end', () => { @@ -105,22 +119,18 @@ export class SerialSession extends BaseSession { } write (data: Buffer): void { - if (this.profile.options.inputMode?.startsWith('readline')) { - this.inputReadlineInStream.write(data) - } else { - this.onInput(data) - } + this.streamProcessor.feedFromTerminal(data) } async destroy (): Promise { + this.streamProcessor.close() this.serviceMessage.complete() - this.inputReadline.close() await super.destroy() } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function resize (_, __) { - this.inputReadlineOutStream.emit('resize') + this.streamProcessor.resize() } kill (_?: string): void { @@ -148,118 +158,6 @@ export class SerialSession extends BaseSession { return null } - private replaceNewlines (data: Buffer, mode?: NewlineMode): Buffer { - if (!mode) { - return data - } - data = bufferReplace(data, '\r\n', '\n') - data = bufferReplace(data, '\r', '\n') - const replacement = { - strip: '', - cr: '\r', - lf: '\n', - crlf: '\r\n', - }[mode] - return bufferReplace(data, '\n', replacement) - } - - private onInput (data: Buffer) { - if (this.profile.options.inputMode === 'readline-hex') { - const tokens = data.toString().split(/\s/g) - data = Buffer.concat(tokens.filter(t => !!t).map(t => { - if (t.startsWith('0x')) { - t = t.substring(2) - } - return binstring(t, { 'in': 'hex' }) - })) - } - - data = this.replaceNewlines(data, this.profile.options.inputNewlines) - if (this.serial) { - this.serial.write(data.toString()) - } - } - - private onOutputSettled () { - if (this.profile.options.inputMode?.startsWith('readline') && !this.inputPromptVisible) { - this.resetInputPrompt() - } - } - - private resetInputPrompt () { - this.emitOutput(Buffer.from('\r\n')) - this.inputReadline.prompt(true) - this.inputPromptVisible = true - } - - private onOutput (data: Buffer) { - const dataString = data.toString() - - if (this.profile.options.inputMode?.startsWith('readline')) { - if (this.inputPromptVisible) { - clearLine(this.inputReadlineOutStream, 0) - this.inputPromptVisible = false - } - } - - data = this.replaceNewlines(data, this.profile.options.outputNewlines) - - if (this.profile.options.outputMode === 'hex') { - this.emitOutput(Buffer.concat([ - Buffer.from('\r\n'), - Buffer.from(hexdump(data, { - group: 1, - gutter: 4, - divide: colors.gray(' | '), - emptyHuman: colors.gray('╳'), - }).replace(/\n/g, '\r\n')), - Buffer.from('\r\n\n'), - ])) - } else { - this.emitOutput(data) - } - - if (this.scripts) { - let found = false - for (const script of this.scripts) { - let match = false - let cmd = '' - if (script.isRegex) { - const re = new RegExp(script.expect, 'g') - if (re.test(dataString)) { - cmd = dataString.replace(re, script.send) - match = true - found = true - } - } else { - if (dataString.includes(script.expect)) { - cmd = script.send - match = true - found = true - } - } - - if (match) { - this.logger.info('Executing script: "' + cmd + '"') - this.serial.write(cmd + '\n') - this.scripts = this.scripts.filter(x => x !== script) - } else { - if (script.optional) { - this.logger.debug('Skip optional script: ' + script.expect) - found = true - this.scripts = this.scripts.filter(x => x !== script) - } else { - break - } - } - } - - if (found) { - this.executeUnconditionalScripts() - } - } - } - private executeUnconditionalScripts () { if (this.scripts) { for (const script of this.scripts) { diff --git a/tabby-serial/src/components/serialProfileSettings.component.pug b/tabby-serial/src/components/serialProfileSettings.component.pug index 41b46f56..f569ee4d 100644 --- a/tabby-serial/src/components/serialProfileSettings.component.pug +++ b/tabby-serial/src/components/serialProfileSettings.component.pug @@ -8,7 +8,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav') label Device input.form-control( type='text', - alwaysShowTypeahead, + alwaysVisibleTypeahead, [(ngModel)]='profile.options.port', [ngbTypeahead]='portsAutocomplete', [resultFormatter]='portsFormatter' @@ -19,65 +19,13 @@ ul.nav-tabs(ngbNav, #nav='ngbNav') label Baud Rate input.form-control( type='number', - alwaysShowTypeahead, + alwaysVisibleTypeahead, placeholder='Ask every time', [(ngModel)]='profile.options.baudrate', [ngbTypeahead]='baudratesAutocomplete' ) - .form-line - .header - .title Input mode - - .d-flex(ngbDropdown) - button.btn.btn-secondary.btn-tab-bar( - ngbDropdownToggle, - ) {{getInputModeName(profile.options.inputMode)}} - - div(ngbDropdownMenu) - a.d-flex.flex-column( - *ngFor='let mode of inputModes', - (click)='profile.options.inputMode = mode.key', - ngbDropdownItem - ) - div {{mode.name}} - .text-muted {{mode.description}} - - .form-line - .header - .title Input newlines - - select.form-control( - [(ngModel)]='profile.options.inputNewlines', - ) - option([ngValue]='mode.key', *ngFor='let mode of newlineModes') {{mode.name}} - - .form-line - .header - .title Output mode - - .d-flex(ngbDropdown) - button.btn.btn-secondary.btn-tab-bar( - ngbDropdownToggle, - ) {{getOutputModeName(profile.options.outputMode)}} - - div(ngbDropdownMenu) - a.d-flex.flex-column( - *ngFor='let mode of outputModes', - (click)='profile.options.outputMode = mode.key', - ngbDropdownItem - ) - div {{mode.name}} - .text-muted {{mode.description}} - - .form-line - .header - .title Output newlines - - select.form-control( - [(ngModel)]='profile.options.outputNewlines', - ) - option([ngValue]='mode.key', *ngFor='let mode of newlineModes') {{mode.name}} + stream-processing-settings([options]='profile.options') li(ngbNavItem) a(ngbNavLink) Advanced diff --git a/tabby-serial/src/components/serialProfileSettings.component.ts b/tabby-serial/src/components/serialProfileSettings.component.ts index cae6e8d2..1a6d3f1f 100644 --- a/tabby-serial/src/components/serialProfileSettings.component.ts +++ b/tabby-serial/src/components/serialProfileSettings.component.ts @@ -12,36 +12,12 @@ import { SerialService } from '../services/serial.service' export class SerialProfileSettingsComponent implements ProfileSettingsComponent { profile: SerialProfile foundPorts: SerialPortInfo[] - inputModes = [ - { key: null, name: 'Normal', description: 'Input is sent as you type' }, - { key: 'readline', name: 'Line by line', description: 'Line editor, input is sent after you press Enter' }, - { key: 'readline-hex', name: 'Hexadecimal', description: 'Send bytes by typing in hex values' }, - ] - outputModes = [ - { key: null, name: 'Normal', description: 'Output is shown as it is received' }, - { key: 'hex', name: 'Hexadecimal', description: 'Output is shown as a hexdump' }, - ] - newlineModes = [ - { key: null, name: 'Keep' }, - { key: 'strip', name: 'Strip' }, - { key: 'cr', name: 'Force CR' }, - { key: 'lf', name: 'Force LF' }, - { key: 'crlf', name: 'Force CRLF' }, - ] constructor ( private platform: PlatformService, private serial: SerialService, ) { } - getInputModeName (key) { - return this.inputModes.find(x => x.key === key)?.name - } - - getOutputModeName (key) { - return this.outputModes.find(x => x.key === key)?.name - } - portsAutocomplete = text$ => text$.pipe(map(() => { return this.foundPorts.map(x => x.name) })) diff --git a/tabby-serial/yarn.lock b/tabby-serial/yarn.lock index 2d6dbca0..82fb0408 100644 --- a/tabby-serial/yarn.lock +++ b/tabby-serial/yarn.lock @@ -7,52 +7,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae" integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ== -ansi-color@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-color/-/ansi-color-0.2.1.tgz#3e75c037475217544ed763a8db5709fa9ae5bf9a" - integrity sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o= - ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -binstring@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/binstring/-/binstring-0.2.1.tgz#8a174d301f6d54efda550dd98bb4cb524eacd75d" - integrity sha1-ihdNMB9tVO/aVQ3Zi7TLUk6s110= - -buffer-replace@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-replace/-/buffer-replace-1.0.0.tgz#bc427c40af4c1f06d6933dede57110acba8ade54" - integrity sha1-vEJ8QK9MHwbWkz3t5XEQrLqK3lQ= - cli-spinner@^0.2.10: version "0.2.10" resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47" integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q== - -hexer@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/hexer/-/hexer-1.5.0.tgz#b86ce808598e8a9d1892c571f3cedd86fc9f0653" - integrity sha1-uGzoCFmOip0YksVx887dhvyfBlM= - dependencies: - ansi-color "^0.2.1" - minimist "^1.1.0" - process "^0.10.0" - xtend "^4.0.0" - -minimist@^1.1.0: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -process@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/process/-/process-0.10.1.tgz#842457cc51cfed72dc775afeeafb8c6034372725" - integrity sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU= - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== diff --git a/tabby-terminal/package.json b/tabby-terminal/package.json index 1422018e..96011bb0 100644 --- a/tabby-terminal/package.json +++ b/tabby-terminal/package.json @@ -31,6 +31,9 @@ "shell-escape": "^0.2.0", "utils-decorators": "^1.8.1", "xterm": "^4.9.0-beta.7", + "binstring": "^0.2.1", + "buffer-replace": "^1.0.0", + "hexer": "^1.5.0", "xterm-addon-fit": "^0.5.0", "xterm-addon-ligatures": "^0.5.0", "xterm-addon-search": "^0.8.0", diff --git a/tabby-terminal/src/api/streamProcessing.ts b/tabby-terminal/src/api/streamProcessing.ts new file mode 100644 index 00000000..11860809 --- /dev/null +++ b/tabby-terminal/src/api/streamProcessing.ts @@ -0,0 +1,137 @@ +import hexdump from 'hexer' +import bufferReplace from 'buffer-replace' +import colors from 'ansi-colors' +import binstring from 'binstring' +import { Subject, Observable, interval } from 'rxjs' +import { debounce } from 'rxjs/operators' +import { PassThrough, Readable, Writable } from 'stream' +import { ReadLine, createInterface as createReadline, clearLine } from 'readline' + +export type InputMode = null | 'readline' | 'readline-hex' // eslint-disable-line @typescript-eslint/no-type-alias +export type OutputMode = null | 'hex' // eslint-disable-line @typescript-eslint/no-type-alias +export type NewlineMode = null | 'cr' | 'lf' | 'crlf' // eslint-disable-line @typescript-eslint/no-type-alias + +export interface StreamProcessingOptions { + inputMode?: InputMode + inputNewlines?: NewlineMode + outputMode?: OutputMode + outputNewlines?: NewlineMode +} + +export class TerminalStreamProcessor { + get outputToSession$ (): Observable { return this.outputToSession } + get outputToTerminal$ (): Observable { return this.outputToTerminal } + + protected outputToSession = new Subject() + protected outputToTerminal = new Subject() + + private inputReadline: ReadLine + private inputPromptVisible = true + private inputReadlineInStream: Readable & Writable + private inputReadlineOutStream: Readable & Writable + + constructor (private options: StreamProcessingOptions) { + this.inputReadlineInStream = new PassThrough() + this.inputReadlineOutStream = new PassThrough() + this.inputReadline = createReadline({ + input: this.inputReadlineInStream, + output: this.inputReadlineOutStream, + terminal: true, + prompt: this.options.inputMode === 'readline-hex' ? 'hex> ' : '> ', + } as any) + this.inputReadlineOutStream.on('data', data => { + this.outputToTerminal.next(Buffer.from(data)) + }) + this.inputReadline.on('line', line => { + this.onTerminalInput(Buffer.from(line + '\n')) + this.resetInputPrompt() + }) + this.outputToTerminal$.pipe(debounce(() => interval(500))).subscribe(() => this.onOutputSettled()) + } + + feedFromSession (data: Buffer): void { + if (this.options.inputMode?.startsWith('readline')) { + if (this.inputPromptVisible) { + clearLine(this.inputReadlineOutStream, 0) + this.inputPromptVisible = false + } + } + + data = this.replaceNewlines(data, this.options.outputNewlines) + + if (this.options.outputMode === 'hex') { + this.outputToTerminal.next(Buffer.concat([ + Buffer.from('\r\n'), + Buffer.from(hexdump(data, { + group: 1, + gutter: 4, + divide: colors.gray(' | '), + emptyHuman: colors.gray('╳'), + }).replace(/\n/g, '\r\n')), + Buffer.from('\r\n\n'), + ])) + } else { + this.outputToTerminal.next(data) + } + } + + feedFromTerminal (data: Buffer): void { + if (this.options.inputMode?.startsWith('readline')) { + this.inputReadlineInStream.write(data) + } else { + this.onTerminalInput(data) + } + } + + resize (): void { + this.inputReadlineOutStream.emit('resize') + } + + close (): void { + this.inputReadline.close() + this.outputToSession.complete() + this.outputToTerminal.complete() + } + + private onTerminalInput (data: Buffer) { + if (this.options.inputMode === 'readline-hex') { + const tokens = data.toString().split(/\s/g) + data = Buffer.concat(tokens.filter(t => !!t).map(t => { + if (t.startsWith('0x')) { + t = t.substring(2) + } + return binstring(t, { 'in': 'hex' }) + })) + } + + data = this.replaceNewlines(data, this.options.inputNewlines) + this.outputToSession.next(data) + } + + private onOutputSettled () { + if (this.options.inputMode?.startsWith('readline') && !this.inputPromptVisible) { + this.resetInputPrompt() + } + } + + private resetInputPrompt () { + this.outputToTerminal.next(Buffer.from('\r\n')) + this.inputReadline.prompt(true) + this.inputPromptVisible = true + } + + private replaceNewlines (data: Buffer, mode?: NewlineMode): Buffer { + if (!mode) { + return data + } + data = bufferReplace(data, '\r\n', '\n') + data = bufferReplace(data, '\r', '\n') + const replacement = { + strip: '', + cr: '\r', + lf: '\n', + crlf: '\r\n', + }[mode] + return bufferReplace(data, '\n', replacement) + } +} diff --git a/tabby-terminal/src/components/streamProcessingSettings.component.pug b/tabby-terminal/src/components/streamProcessingSettings.component.pug new file mode 100644 index 00000000..c64c93bb --- /dev/null +++ b/tabby-terminal/src/components/streamProcessingSettings.component.pug @@ -0,0 +1,53 @@ +.form-line + .header + .title Input mode + + .d-flex(ngbDropdown) + button.btn.btn-secondary.btn-tab-bar( + ngbDropdownToggle, + ) {{getInputModeName(options.inputMode)}} + + div(ngbDropdownMenu) + a.d-flex.flex-column( + *ngFor='let mode of inputModes', + (click)='options.inputMode = mode.key', + ngbDropdownItem + ) + div {{mode.name}} + .text-muted {{mode.description}} + +.form-line + .header + .title Input newlines + + select.form-control( + [(ngModel)]='options.inputNewlines', + ) + option([ngValue]='mode.key', *ngFor='let mode of newlineModes') {{mode.name}} + +.form-line + .header + .title Output mode + + .d-flex(ngbDropdown) + button.btn.btn-secondary.btn-tab-bar( + ngbDropdownToggle, + ) {{getOutputModeName(options.outputMode)}} + + div(ngbDropdownMenu) + a.d-flex.flex-column( + *ngFor='let mode of outputModes', + (click)='options.outputMode = mode.key', + ngbDropdownItem + ) + div {{mode.name}} + .text-muted {{mode.description}} + +.form-line + .header + .title Output newlines + + select.form-control( + [(ngModel)]='options.outputNewlines', + ) + option([ngValue]='mode.key', *ngFor='let mode of newlineModes') {{mode.name}} diff --git a/tabby-terminal/src/components/streamProcessingSettings.component.ts b/tabby-terminal/src/components/streamProcessingSettings.component.ts new file mode 100644 index 00000000..e0f5cd29 --- /dev/null +++ b/tabby-terminal/src/components/streamProcessingSettings.component.ts @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { Component, Input } from '@angular/core' +import { StreamProcessingOptions } from '../api/streamProcessing' + +/** @hidden */ +@Component({ + selector: 'stream-processing-settings', + template: require('./streamProcessingSettings.component.pug'), +}) +export class StreamProcessingSettingsComponent { + @Input() options: StreamProcessingOptions + + inputModes = [ + { key: null, name: 'Normal', description: 'Input is sent as you type' }, + { key: 'readline', name: 'Line by line', description: 'Line editor, input is sent after you press Enter' }, + { key: 'readline-hex', name: 'Hexadecimal', description: 'Send bytes by typing in hex values' }, + ] + outputModes = [ + { key: null, name: 'Normal', description: 'Output is shown as it is received' }, + { key: 'hex', name: 'Hexadecimal', description: 'Output is shown as a hexdump' }, + ] + newlineModes = [ + { key: null, name: 'Keep' }, + { key: 'strip', name: 'Strip' }, + { key: 'cr', name: 'Force CR' }, + { key: 'lf', name: 'Force LF' }, + { key: 'crlf', name: 'Force CRLF' }, + ] + + getInputModeName (key) { + return this.inputModes.find(x => x.key === key)?.name + } + + getOutputModeName (key) { + return this.outputModes.find(x => x.key === key)?.name + } +} diff --git a/tabby-terminal/src/index.ts b/tabby-terminal/src/index.ts index 83aa84c1..15a3de9c 100644 --- a/tabby-terminal/src/index.ts +++ b/tabby-terminal/src/index.ts @@ -13,6 +13,7 @@ import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.c import { ColorPickerComponent } from './components/colorPicker.component' import { ColorSchemePreviewComponent } from './components/colorSchemePreview.component' import { SearchPanelComponent } from './components/searchPanel.component' +import { StreamProcessingSettingsComponent } from './components/streamProcessingSettings.component' import { TerminalFrontendService } from './services/terminalFrontend.service' @@ -70,10 +71,12 @@ import { TerminalCLIHandler } from './cli' ColorSchemeSettingsTabComponent, TerminalSettingsTabComponent, SearchPanelComponent, + StreamProcessingSettingsComponent, ] as any[], exports: [ ColorPickerComponent, SearchPanelComponent, + StreamProcessingSettingsComponent, ], }) export default class TerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class @@ -111,4 +114,5 @@ export { TerminalFrontendService, TerminalDecorator, TerminalContextMenuItemProv export { Frontend, XTermFrontend, XTermWebGLFrontend, HTermFrontend } export { BaseTerminalTabComponent } from './api/baseTerminalTab.component' export * from './api/interfaces' +export * from './api/streamProcessing' export * from './session' diff --git a/tabby-terminal/yarn.lock b/tabby-terminal/yarn.lock index 1c667c05..a1216a35 100644 --- a/tabby-terminal/yarn.lock +++ b/tabby-terminal/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.yarnpkg.com/@types/shell-escape/-/shell-escape-0.2.0.tgz#cd2f0df814388599dd07196dcc510de2669d1ed2" integrity sha512-7kUdtJtUylvyISJbe9FMcvMTjRdP0EvNDO1WbT0lT22k/IPBiPRTpmWaKu5HTWLCGLQRWVHrzVHZktTDvvR23g== +ansi-color@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansi-color/-/ansi-color-0.2.1.tgz#3e75c037475217544ed763a8db5709fa9ae5bf9a" + integrity sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o= + ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -29,6 +34,16 @@ available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" +binstring@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/binstring/-/binstring-0.2.1.tgz#8a174d301f6d54efda550dd98bb4cb524eacd75d" + integrity sha1-ihdNMB9tVO/aVQ3Zi7TLUk6s110= + +buffer-replace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-replace/-/buffer-replace-1.0.0.tgz#bc427c40af4c1f06d6933dede57110acba8ade54" + integrity sha1-vEJ8QK9MHwbWkz3t5XEQrLqK3lQ= + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -214,6 +229,16 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hexer@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/hexer/-/hexer-1.5.0.tgz#b86ce808598e8a9d1892c571f3cedd86fc9f0653" + integrity sha1-uGzoCFmOip0YksVx887dhvyfBlM= + dependencies: + ansi-color "^0.2.1" + minimist "^1.1.0" + process "^0.10.0" + xtend "^4.0.0" + hterm-umdjs@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/hterm-umdjs/-/hterm-umdjs-1.4.1.tgz#0cd5352eaf927c70b83c36146cf2c2a281dba957" @@ -338,6 +363,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +minimist@^1.1.0: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + object-inspect@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" @@ -391,6 +421,11 @@ printj@~1.1.0: resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== +process@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/process/-/process-0.10.1.tgz#842457cc51cfed72dc775afeeafb8c6034372725" + integrity sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU= + promise-stream-reader@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649" @@ -535,6 +570,11 @@ which-typed-array@^1.1.2: has-symbols "^1.0.1" is-typed-array "^1.1.3" +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + xterm-addon-fit@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596"