|
|
@@ -1,302 +0,0 @@
|
|
|
-import { Injector } from '@angular/core'
|
|
|
-import { ConfigService, getCSSFontFamily, ThemesService } from 'tabby-core'
|
|
|
-import { Frontend, SearchOptions } from './frontend'
|
|
|
-import { hterm, preferenceManager } from './hterm'
|
|
|
-
|
|
|
-/** @hidden */
|
|
|
-export class HTermFrontend extends Frontend {
|
|
|
- term: any
|
|
|
- io: any
|
|
|
- private htermIframe: HTMLElement
|
|
|
- private initialized = false
|
|
|
- private configuredFontSize = 0
|
|
|
- private configuredLinePadding = 0
|
|
|
- private configuredBackgroundColor = 'transparent'
|
|
|
- private zoom = 0
|
|
|
-
|
|
|
- private configService: ConfigService
|
|
|
- private themesService: ThemesService
|
|
|
-
|
|
|
- constructor (injector: Injector) {
|
|
|
- super(injector)
|
|
|
- this.configService = injector.get(ConfigService)
|
|
|
- this.themesService = injector.get(ThemesService)
|
|
|
- }
|
|
|
-
|
|
|
- async attach (host: HTMLElement): Promise<void> {
|
|
|
- if (!this.initialized) {
|
|
|
- this.init()
|
|
|
- this.initialized = true
|
|
|
- preferenceManager.set('background-color', 'transparent')
|
|
|
- this.term.decorate(host)
|
|
|
- this.htermIframe = this.term.scrollPort_.iframe_
|
|
|
- } else {
|
|
|
- host.appendChild(this.htermIframe)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- getSelection (): string {
|
|
|
- return this.term.getSelectionText()
|
|
|
- }
|
|
|
-
|
|
|
- copySelection (): void {
|
|
|
- this.term.copySelectionToClipboard()
|
|
|
- }
|
|
|
-
|
|
|
- selectAll (): void {
|
|
|
- const content = this.term.getDocument().body.children[0]
|
|
|
- const selection = content.ownerDocument.defaultView.getSelection()
|
|
|
- selection.setBaseAndExtent(content, 0, content, 1)
|
|
|
- }
|
|
|
-
|
|
|
- clearSelection (): void {
|
|
|
- this.term.getDocument().getSelection().removeAllRanges()
|
|
|
- }
|
|
|
-
|
|
|
- focus (): void {
|
|
|
- setTimeout(() => {
|
|
|
- this.term.scrollPort_.resize()
|
|
|
- this.term.scrollPort_.focus()
|
|
|
- }, 100)
|
|
|
- }
|
|
|
-
|
|
|
- write (data: string): void {
|
|
|
- this.io.writeUTF8(data)
|
|
|
- }
|
|
|
-
|
|
|
- clear (): void {
|
|
|
- this.term.wipeContents()
|
|
|
- this.term.onVTKeystroke('\f')
|
|
|
- }
|
|
|
-
|
|
|
- configure (): void {
|
|
|
- const config = this.configService.store
|
|
|
-
|
|
|
- this.configuredFontSize = config.terminal.fontSize
|
|
|
- this.configuredLinePadding = config.terminal.linePadding
|
|
|
- this.setFontSize()
|
|
|
-
|
|
|
- preferenceManager.set('font-family', getCSSFontFamily(config))
|
|
|
- preferenceManager.set('enable-bold', true)
|
|
|
- // preferenceManager.set('audible-bell-sound', '')
|
|
|
- preferenceManager.set('desktop-notification-bell', config.terminal.bell === 'notification')
|
|
|
- preferenceManager.set('enable-clipboard-notice', false)
|
|
|
- preferenceManager.set('receive-encoding', 'raw')
|
|
|
- preferenceManager.set('send-encoding', 'raw')
|
|
|
- preferenceManager.set('ctrl-plus-minus-zero-zoom', false)
|
|
|
- preferenceManager.set('scrollbar-visible', process.platform === 'darwin')
|
|
|
- preferenceManager.set('copy-on-select', config.terminal.copyOnSelect)
|
|
|
- preferenceManager.set('pass-meta-v', false)
|
|
|
- preferenceManager.set('alt-is-meta', config.terminal.altIsMeta)
|
|
|
- preferenceManager.set('alt-sends-what', 'browser-key')
|
|
|
- preferenceManager.set('alt-gr-mode', 'ctrl-alt')
|
|
|
- preferenceManager.set('pass-alt-number', true)
|
|
|
- preferenceManager.set('cursor-blink', config.terminal.cursorBlink)
|
|
|
- preferenceManager.set('clear-selection-after-copy', true)
|
|
|
- preferenceManager.set('scroll-on-output', false)
|
|
|
- preferenceManager.set('scroll-on-keystroke', config.terminal.scrollOnInput)
|
|
|
-
|
|
|
- if (config.terminal.colorScheme.foreground) {
|
|
|
- preferenceManager.set('foreground-color', config.terminal.colorScheme.foreground)
|
|
|
- }
|
|
|
-
|
|
|
- if (config.terminal.background === 'colorScheme') {
|
|
|
- if (config.terminal.colorScheme.background) {
|
|
|
- preferenceManager.set('background-color', config.terminal.colorScheme.background)
|
|
|
- }
|
|
|
- } else {
|
|
|
- preferenceManager.set('background-color', config.appearance.vibrancy ? 'transparent' : this.themesService.findCurrentTheme().terminalBackground)
|
|
|
- }
|
|
|
-
|
|
|
- this.configuredBackgroundColor = preferenceManager.get('background-color')
|
|
|
-
|
|
|
- if (!this.term) {
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- let css = require('./hterm.userCSS.scss') // eslint-disable-line
|
|
|
- if (!config.terminal.ligatures) {
|
|
|
- css += `
|
|
|
- * {
|
|
|
- font-feature-settings: "liga" 0;
|
|
|
- font-variant-ligatures: none;
|
|
|
- }
|
|
|
- `
|
|
|
- } else {
|
|
|
- css += `
|
|
|
- * {
|
|
|
- font-feature-settings: "liga" 1;
|
|
|
- font-variant-ligatures: initial;
|
|
|
- }
|
|
|
- `
|
|
|
- }
|
|
|
- css += config.appearance.css
|
|
|
- this.term.setCSS(css)
|
|
|
-
|
|
|
- if (config.terminal.colorScheme.colors) {
|
|
|
- preferenceManager.set(
|
|
|
- 'color-palette-overrides',
|
|
|
- Object.assign([], config.terminal.colorScheme.colors, this.term.colorPaletteOverrides)
|
|
|
- )
|
|
|
- }
|
|
|
- if (config.terminal.colorScheme.cursor) {
|
|
|
- preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
|
|
- }
|
|
|
-
|
|
|
- this.term.setBracketedPaste(config.terminal.bracketedPaste)
|
|
|
- this.term.defaultCursorShape = {
|
|
|
- block: hterm.hterm.Terminal.cursorShape.BLOCK,
|
|
|
- underline: hterm.hterm.Terminal.cursorShape.UNDERLINE,
|
|
|
- beam: hterm.hterm.Terminal.cursorShape.BEAM,
|
|
|
- }[config.terminal.cursor]
|
|
|
- this.term.applyCursorShape()
|
|
|
- this.term.setCursorBlink(config.terminal.cursorBlink)
|
|
|
- if (config.terminal.cursorBlink) {
|
|
|
- this.term.onCursorBlink_()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- setZoom (zoom: number): void {
|
|
|
- this.zoom = zoom
|
|
|
- this.setFontSize()
|
|
|
- }
|
|
|
-
|
|
|
- visualBell (): void {
|
|
|
- preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
|
|
- setTimeout(() => {
|
|
|
- preferenceManager.set('background-color', this.configuredBackgroundColor)
|
|
|
- }, 125)
|
|
|
- }
|
|
|
-
|
|
|
- scrollToBottom (): void {
|
|
|
- this.term.scrollEnd()
|
|
|
- }
|
|
|
-
|
|
|
- findNext (_term: string, _searchOptions?: SearchOptions): boolean {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- findPrevious (_term: string, _searchOptions?: SearchOptions): boolean {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- // eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
|
- saveState (): any { }
|
|
|
-
|
|
|
- // eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
|
- restoreState (_state: string): void { }
|
|
|
-
|
|
|
- private setFontSize () {
|
|
|
- const size = this.configuredFontSize * Math.pow(1.1, this.zoom)
|
|
|
- preferenceManager.set('font-size', size)
|
|
|
- if (this.term) {
|
|
|
- setTimeout(() => {
|
|
|
- this.term.scrollPort_.characterSize = this.term.scrollPort_.measureCharacterSize()
|
|
|
- this.term.setFontSize(size)
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private init () {
|
|
|
- this.term = new hterm.hterm.Terminal()
|
|
|
- this.term.colorPaletteOverrides = []
|
|
|
- this.term.onTerminalReady = () => {
|
|
|
- this.term.installKeyboard()
|
|
|
- this.term.scrollPort_.setCtrlVPaste(true)
|
|
|
- this.io = this.term.io.push()
|
|
|
- this.io.onVTKeystroke = this.io.sendString = data => this.input.next(Buffer.from(data, 'utf-8'))
|
|
|
- this.io.onTerminalResize = (columns, rows) => {
|
|
|
- this.resize.next({ columns, rows })
|
|
|
- }
|
|
|
- this.ready.next()
|
|
|
- this.ready.complete()
|
|
|
-
|
|
|
- this.term.scrollPort_.document_.addEventListener('dragOver', event => {
|
|
|
- this.dragOver.next(event)
|
|
|
- })
|
|
|
-
|
|
|
- this.term.scrollPort_.document_.addEventListener('drop', event => {
|
|
|
- this.drop.next(event)
|
|
|
- })
|
|
|
- }
|
|
|
- this.term.setWindowTitle = title => this.title.next(title)
|
|
|
-
|
|
|
- const _setAlternateMode = this.term.setAlternateMode.bind(this.term)
|
|
|
- this.term.setAlternateMode = (state) => {
|
|
|
- _setAlternateMode(state)
|
|
|
- this.alternateScreenActive.next(state)
|
|
|
- }
|
|
|
-
|
|
|
- this.term.primaryScreen_.syncSelectionCaret = () => null
|
|
|
- this.term.alternateScreen_.syncSelectionCaret = () => null
|
|
|
- this.term.primaryScreen_.terminal = this.term
|
|
|
- this.term.alternateScreen_.terminal = this.term
|
|
|
-
|
|
|
- this.term.scrollPort_.onPaste_ = (event) => {
|
|
|
- event.preventDefault()
|
|
|
- }
|
|
|
-
|
|
|
- const _resize = this.term.scrollPort_.resize.bind(this.term.scrollPort_)
|
|
|
- this.term.scrollPort_.resize = () => {
|
|
|
- if (this.enableResizing) {
|
|
|
- _resize()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const _onMouse = this.term.onMouse_.bind(this.term)
|
|
|
- this.term.onMouse_ = (event) => {
|
|
|
- this.mouseEvent.next(event)
|
|
|
- if (event.type === 'mousedown' && event.which === 3) {
|
|
|
- event.preventDefault()
|
|
|
- event.stopPropagation()
|
|
|
- return
|
|
|
- }
|
|
|
- if (event.type === 'mousewheel' && event.altKey) {
|
|
|
- event.preventDefault()
|
|
|
- }
|
|
|
- _onMouse(event)
|
|
|
- }
|
|
|
-
|
|
|
- this.term.ringBell = () => this.bell.next()
|
|
|
-
|
|
|
- for (const screen of [this.term.primaryScreen_, this.term.alternateScreen_]) {
|
|
|
- const _insertString = screen.insertString.bind(screen)
|
|
|
- screen.insertString = (data) => {
|
|
|
- _insertString(data)
|
|
|
- this.contentUpdated.next()
|
|
|
- }
|
|
|
-
|
|
|
- const _deleteChars = screen.deleteChars.bind(screen)
|
|
|
- screen.deleteChars = (count) => {
|
|
|
- const ret = _deleteChars(count)
|
|
|
- this.contentUpdated.next()
|
|
|
- return ret
|
|
|
- }
|
|
|
-
|
|
|
- const _expandSelection = screen.expandSelection.bind(screen)
|
|
|
- screen.expandSelection = (selection) => {
|
|
|
- // Drop whitespace at the end of selection
|
|
|
- const range = selection.getRangeAt(0)
|
|
|
- if (range.endOffset > 0 && range.endContainer.nodeType === 3 && range.endContainer.textContent !== '') {
|
|
|
- while (/[\s\S]+\s$/.test(range.endContainer.textContent.substr(0, range.endOffset))) {
|
|
|
- range.setEnd(range.endContainer, range.endOffset - 1)
|
|
|
- }
|
|
|
- }
|
|
|
- _expandSelection(selection)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const _measureCharacterSize = this.term.scrollPort_.measureCharacterSize.bind(this.term.scrollPort_)
|
|
|
- this.term.scrollPort_.measureCharacterSize = () => {
|
|
|
- const size = _measureCharacterSize()
|
|
|
- size.height += this.configuredLinePadding
|
|
|
- return size
|
|
|
- }
|
|
|
-
|
|
|
- const _onCursorBlink = this.term.onCursorBlink_.bind(this.term)
|
|
|
- this.term.onCursorBlink_ = () => {
|
|
|
- this.term.cursorNode_.style.opacity = '0'
|
|
|
- _onCursorBlink()
|
|
|
- }
|
|
|
- }
|
|
|
-}
|