mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-23 12:59:54 +00:00
isolated VT implementation into TerminalContainer
This commit is contained in:
parent
cc9c66c4a9
commit
0419900e1d
@ -12,7 +12,9 @@ export interface SSHConnection {
|
|||||||
export class SSHSession extends BaseSession {
|
export class SSHSession extends BaseSession {
|
||||||
constructor (private shell: any) {
|
constructor (private shell: any) {
|
||||||
super()
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
start () {
|
||||||
this.open = true
|
this.open = true
|
||||||
|
|
||||||
this.shell.on('data', data => {
|
this.shell.on('data', data => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Observable, BehaviorSubject, Subject, Subscription } from 'rxjs'
|
import { Subject, Subscription } from 'rxjs'
|
||||||
import { first } from 'rxjs/operators'
|
import { first } from 'rxjs/operators'
|
||||||
import { ToastrService } from 'ngx-toastr'
|
import { ToastrService } from 'ngx-toastr'
|
||||||
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
||||||
@ -7,9 +7,11 @@ import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppSe
|
|||||||
import { IShell } from '../api'
|
import { IShell } from '../api'
|
||||||
import { Session, SessionsService } from '../services/sessions.service'
|
import { Session, SessionsService } from '../services/sessions.service'
|
||||||
import { TerminalService } from '../services/terminal.service'
|
import { TerminalService } from '../services/terminal.service'
|
||||||
|
import { TerminalContainersService } from '../services/terminalContainers.service'
|
||||||
|
|
||||||
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
||||||
import { hterm, preferenceManager } from '../hterm'
|
import { TermContainer } from '../terminalContainers/termContainer'
|
||||||
|
import { hterm } from '../hterm'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'terminalTab',
|
selector: 'terminalTab',
|
||||||
@ -28,24 +30,16 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
@Input() zoom = 0
|
@Input() zoom = 0
|
||||||
@ViewChild('content') content
|
@ViewChild('content') content
|
||||||
@HostBinding('style.background-color') backgroundColor: string
|
@HostBinding('style.background-color') backgroundColor: string
|
||||||
hterm: any
|
termContainer: TermContainer
|
||||||
sessionCloseSubscription: Subscription
|
sessionCloseSubscription: Subscription
|
||||||
hotkeysSubscription: Subscription
|
hotkeysSubscription: Subscription
|
||||||
bell$ = new Subject()
|
|
||||||
size: ResizeEvent
|
size: ResizeEvent
|
||||||
resize$: Observable<ResizeEvent>
|
|
||||||
input$ = new Subject<string>()
|
|
||||||
output$ = new Subject<string>()
|
output$ = new Subject<string>()
|
||||||
contentUpdated$: Observable<void>
|
|
||||||
alternateScreenActive$ = new BehaviorSubject(false)
|
|
||||||
mouseEvent$ = new Subject<Event>()
|
|
||||||
htermVisible = false
|
htermVisible = false
|
||||||
shell: IShell
|
shell: IShell
|
||||||
private resize_ = new Subject<ResizeEvent>()
|
|
||||||
private contentUpdated_ = new Subject<void>()
|
|
||||||
private bellPlayer: HTMLAudioElement
|
private bellPlayer: HTMLAudioElement
|
||||||
private io: any
|
|
||||||
private contextMenu: any
|
private contextMenu: any
|
||||||
|
private termContainerSubscriptions: Subscription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
@ -55,60 +49,40 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
private sessions: SessionsService,
|
private sessions: SessionsService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private terminalService: TerminalService,
|
private terminalService: TerminalService,
|
||||||
|
private terminalContainersService: TerminalContainersService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.resize$ = this.resize_.asObservable()
|
|
||||||
this.decorators = this.decorators || []
|
this.decorators = this.decorators || []
|
||||||
this.setTitle('Terminal')
|
this.setTitle('Terminal')
|
||||||
this.resize$.pipe(first()).subscribe(async resizeEvent => {
|
|
||||||
if (!this.session) {
|
|
||||||
this.session = this.sessions.addSession(
|
|
||||||
Object.assign({}, this.sessionOptions, resizeEvent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
this.session = new Session()
|
||||||
this.session.resize(resizeEvent.width, resizeEvent.height)
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
|
||||||
this.session.output$.subscribe(data => {
|
|
||||||
this.zone.run(() => {
|
|
||||||
this.output$.next(data)
|
|
||||||
this.write(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
|
||||||
this.app.closeTab(this)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.session.releaseInitialDataBuffer()
|
|
||||||
})
|
|
||||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch (hotkey) {
|
switch (hotkey) {
|
||||||
case 'ctrl-c':
|
case 'ctrl-c':
|
||||||
if (this.hterm.getSelectionText()) {
|
if (this.termContainer.getSelection()) {
|
||||||
this.hterm.copySelectionToClipboard()
|
this.termContainer.copySelection()
|
||||||
this.hterm.getDocument().getSelection().removeAllRanges()
|
this.termContainer.clearSelection()
|
||||||
|
this.toastr.info('Copied')
|
||||||
} else {
|
} else {
|
||||||
this.sendInput('\x03')
|
this.sendInput('\x03')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'copy':
|
case 'copy':
|
||||||
this.hterm.copySelectionToClipboard()
|
this.termContainer.copySelection()
|
||||||
|
this.toastr.info('Copied')
|
||||||
break
|
break
|
||||||
case 'paste':
|
case 'paste':
|
||||||
this.paste()
|
this.paste()
|
||||||
break
|
break
|
||||||
case 'clear':
|
case 'clear':
|
||||||
this.clear()
|
this.termContainer.clear()
|
||||||
break
|
break
|
||||||
case 'zoom-in':
|
case 'zoom-in':
|
||||||
this.zoomIn()
|
this.zoomIn()
|
||||||
@ -143,6 +117,29 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
this.bellPlayer.src = require<string>('../bell.ogg')
|
this.bellPlayer.src = require<string>('../bell.ogg')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeSession (columns: number, rows: number) {
|
||||||
|
this.sessions.addSession(
|
||||||
|
this.session,
|
||||||
|
Object.assign({}, this.sessionOptions, {
|
||||||
|
width: columns,
|
||||||
|
height: rows,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
||||||
|
this.session.output$.subscribe(data => {
|
||||||
|
this.zone.run(() => {
|
||||||
|
this.output$.next(data)
|
||||||
|
this.write(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
||||||
|
this.termContainer.destroy()
|
||||||
|
this.app.closeTab(this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
getRecoveryToken (): any {
|
getRecoveryToken (): any {
|
||||||
return {
|
return {
|
||||||
type: 'app:terminal',
|
type: 'app:terminal',
|
||||||
@ -153,46 +150,49 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.configure()
|
this.configure()
|
||||||
setTimeout(() => {
|
this.termContainer.focus()
|
||||||
this.hterm.scrollPort_.resize()
|
|
||||||
this.hterm.scrollPort_.focus()
|
|
||||||
}, 100)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.hterm = new hterm.hterm.Terminal()
|
this.termContainer = this.terminalContainersService.getContainer(this.session)
|
||||||
|
|
||||||
|
this.termContainer.ready$.subscribe(() => {
|
||||||
|
this.htermVisible = true
|
||||||
|
})
|
||||||
|
|
||||||
|
this.termContainer.resize$.pipe(first()).subscribe(async ({columns, rows}) => {
|
||||||
|
if (!this.session.open) {
|
||||||
|
this.initializeSession(columns, rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.session.resize(columns, rows)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
this.session.releaseInitialDataBuffer()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.termContainer.attach(this.content.nativeElement)
|
||||||
|
this.attachTermContainerHandlers()
|
||||||
|
|
||||||
|
this.configure()
|
||||||
|
|
||||||
this.config.enabledServices(this.decorators).forEach((decorator) => {
|
this.config.enabledServices(this.decorators).forEach((decorator) => {
|
||||||
decorator.attach(this)
|
decorator.attach(this)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.attachHTermHandlers(this.hterm)
|
|
||||||
|
|
||||||
this.hterm.onTerminalReady = () => {
|
|
||||||
this.htermVisible = true
|
|
||||||
this.hterm.installKeyboard()
|
|
||||||
this.hterm.scrollPort_.setCtrlVPaste(true)
|
|
||||||
this.io = this.hterm.io.push()
|
|
||||||
this.attachIOHandlers(this.io)
|
|
||||||
}
|
|
||||||
this.hterm.decorate(this.content.nativeElement)
|
|
||||||
this.configure()
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.output$.subscribe(() => {
|
this.output$.subscribe(() => {
|
||||||
this.displayActivity()
|
this.displayActivity()
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
this.bell$.subscribe(() => {
|
this.termContainer.bell$.subscribe(() => {
|
||||||
if (this.config.store.terminal.bell === 'visual') {
|
if (this.config.store.terminal.bell === 'visual') {
|
||||||
preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
this.termContainer.visualBell()
|
||||||
setTimeout(() => {
|
|
||||||
this.configure()
|
|
||||||
}, 125)
|
|
||||||
}
|
}
|
||||||
if (this.config.store.terminal.bell === 'audible') {
|
if (this.config.store.terminal.bell === 'audible') {
|
||||||
this.bellPlayer.play()
|
this.bellPlayer.play()
|
||||||
}
|
}
|
||||||
// TODO audible
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.contextMenu = this.electron.remote.Menu.buildFromTemplate([
|
this.contextMenu = this.electron.remote.Menu.buildFromTemplate([
|
||||||
@ -209,7 +209,8 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
click: () => {
|
click: () => {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.hterm.copySelectionToClipboard()
|
this.termContainer.copySelection()
|
||||||
|
this.toastr.info('Copied')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -225,47 +226,26 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
attachHTermHandlers (hterm: any) {
|
detachTermContainerHandlers () {
|
||||||
hterm.setWindowTitle = title => this.zone.run(() => this.setTitle(title))
|
for (let subscription of this.termContainerSubscriptions) {
|
||||||
|
subscription.unsubscribe()
|
||||||
const _setAlternateMode = hterm.setAlternateMode.bind(hterm)
|
}
|
||||||
hterm.setAlternateMode = (state) => {
|
this.termContainerSubscriptions = []
|
||||||
_setAlternateMode(state)
|
|
||||||
this.alternateScreenActive$.next(state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _copySelectionToClipboard = hterm.copySelectionToClipboard.bind(hterm)
|
attachTermContainerHandlers () {
|
||||||
hterm.copySelectionToClipboard = () => {
|
this.detachTermContainerHandlers()
|
||||||
_copySelectionToClipboard()
|
this.termContainerSubscriptions = [
|
||||||
this.toastr.info('Copied')
|
this.termContainer.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
|
||||||
}
|
|
||||||
|
|
||||||
hterm.primaryScreen_.syncSelectionCaret = () => null
|
this.focused$.subscribe(() => this.termContainer.enableResizing = true),
|
||||||
hterm.alternateScreen_.syncSelectionCaret = () => null
|
this.blurred$.subscribe(() => this.termContainer.enableResizing = false),
|
||||||
hterm.primaryScreen_.terminal = hterm
|
|
||||||
hterm.alternateScreen_.terminal = hterm
|
|
||||||
|
|
||||||
hterm.scrollPort_.onPaste_ = (event) => {
|
this.termContainer.mouseEvent$.subscribe(event => {
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
const _resize = hterm.scrollPort_.resize.bind(hterm.scrollPort_)
|
|
||||||
hterm.scrollPort_.resize = () => {
|
|
||||||
if (!this.hasFocus) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_resize()
|
|
||||||
}
|
|
||||||
|
|
||||||
const _onMouse = hterm.onMouse_.bind(hterm)
|
|
||||||
hterm.onMouse_ = (event) => {
|
|
||||||
this.mouseEvent$.next(event)
|
|
||||||
if (event.type === 'mousedown') {
|
if (event.type === 'mousedown') {
|
||||||
if (event.which === 3) {
|
if (event.which === 3) {
|
||||||
if (this.config.store.terminal.rightClick === 'menu') {
|
if (this.config.store.terminal.rightClick === 'menu') {
|
||||||
this.contextMenu.popup({
|
this.contextMenu.popup({
|
||||||
x: event.pageX + this.content.nativeElement.getBoundingClientRect().left,
|
|
||||||
y: event.pageY + this.content.nativeElement.getBoundingClientRect().top,
|
|
||||||
async: true,
|
async: true,
|
||||||
})
|
})
|
||||||
} else if (this.config.store.terminal.rightClick === 'paste') {
|
} else if (this.config.store.terminal.rightClick === 'paste') {
|
||||||
@ -278,64 +258,33 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
if (event.type === 'mousewheel') {
|
if (event.type === 'mousewheel') {
|
||||||
if (event.ctrlKey || event.metaKey) {
|
if (event.ctrlKey || event.metaKey) {
|
||||||
if (event.wheelDeltaY > 0) {
|
if ((event as MouseWheelEvent).wheelDeltaY > 0) {
|
||||||
this.zoomIn()
|
this.zoomIn()
|
||||||
} else {
|
} else {
|
||||||
this.zoomOut()
|
this.zoomOut()
|
||||||
}
|
}
|
||||||
} else if (event.altKey) {
|
} else if (event.altKey) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
let delta = Math.round(event.wheelDeltaY / 50)
|
let delta = Math.round((event as MouseWheelEvent).wheelDeltaY / 50)
|
||||||
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
this.sendInput(((delta > 0) ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_onMouse(event)
|
}),
|
||||||
}
|
|
||||||
|
|
||||||
hterm.ringBell = () => {
|
this.termContainer.input$.subscribe(data => {
|
||||||
this.bell$.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let screen of [hterm.primaryScreen_, hterm.alternateScreen_]) {
|
|
||||||
const _insertString = screen.insertString.bind(screen)
|
|
||||||
screen.insertString = (data) => {
|
|
||||||
_insertString(data)
|
|
||||||
this.contentUpdated_.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
const _deleteChars = screen.deleteChars.bind(screen)
|
|
||||||
screen.deleteChars = (count) => {
|
|
||||||
let ret = _deleteChars(count)
|
|
||||||
this.contentUpdated_.next()
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _measureCharacterSize = hterm.scrollPort_.measureCharacterSize.bind(hterm.scrollPort_)
|
|
||||||
hterm.scrollPort_.measureCharacterSize = () => {
|
|
||||||
let size = _measureCharacterSize()
|
|
||||||
size.height += this.config.store.terminal.linePadding
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attachIOHandlers (io: any) {
|
|
||||||
io.onVTKeystroke = io.sendString = (data) => {
|
|
||||||
this.sendInput(data)
|
this.sendInput(data)
|
||||||
this.zone.run(() => {
|
}),
|
||||||
this.input$.next(data)
|
|
||||||
})
|
this.termContainer.resize$.subscribe(({columns, rows}) => {
|
||||||
}
|
console.log(`Resizing to ${columns}x${rows}`)
|
||||||
io.onTerminalResize = (columns, rows) => {
|
|
||||||
// console.log(`Resizing to ${columns}x${rows}`)
|
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this.size = { width: columns, height: rows }
|
this.size = { width: columns, height: rows }
|
||||||
if (this.session) {
|
if (this.session.open) {
|
||||||
this.session.resize(columns, rows)
|
this.session.resize(columns, rows)
|
||||||
}
|
}
|
||||||
this.resize_.next(this.size)
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
sendInput (data: string) {
|
sendInput (data: string) {
|
||||||
@ -343,111 +292,48 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
write (data: string) {
|
write (data: string) {
|
||||||
this.io.writeUTF8(data)
|
this.termContainer.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
paste () {
|
paste () {
|
||||||
let data = this.electron.clipboard.readText()
|
let data = this.electron.clipboard.readText()
|
||||||
data = this.hterm.keyboard.encode(data)
|
data = hterm.lib.encodeUTF8(data)
|
||||||
if (this.hterm.options_.bracketedPaste) {
|
if (this.config.store.terminal.bracketedPaste) {
|
||||||
data = '\x1b[200~' + data + '\x1b[201~'
|
data = '\x1b[200~' + data + '\x1b[201~'
|
||||||
}
|
}
|
||||||
data = data.replace(/\r\n/g, '\n')
|
data = data.replace(/\n/g, '\r')
|
||||||
this.sendInput(data)
|
this.sendInput(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
clear () {
|
|
||||||
this.hterm.wipeContents()
|
|
||||||
this.hterm.onVTKeystroke('\f')
|
|
||||||
}
|
|
||||||
|
|
||||||
configure (): void {
|
configure (): void {
|
||||||
let config = this.config.store
|
this.termContainer.configure(this.config.store)
|
||||||
preferenceManager.set('font-family', `"${config.terminal.font}", "monospace-fallback", monospace`)
|
|
||||||
this.setFontSize()
|
|
||||||
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', this.hostApp.platform === Platform.macOS)
|
|
||||||
preferenceManager.set('copy-on-select', config.terminal.copyOnSelect)
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (config.terminal.colorScheme.foreground) {
|
if (this.config.store.terminal.background === 'colorScheme') {
|
||||||
preferenceManager.set('foreground-color', config.terminal.colorScheme.foreground)
|
if (this.config.store.terminal.colorScheme.background) {
|
||||||
}
|
this.backgroundColor = this.config.store.terminal.colorScheme.background
|
||||||
if (config.terminal.background === 'colorScheme') {
|
|
||||||
if (config.terminal.colorScheme.background) {
|
|
||||||
this.backgroundColor = config.terminal.colorScheme.background
|
|
||||||
preferenceManager.set('background-color', config.terminal.colorScheme.background)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.backgroundColor = null
|
this.backgroundColor = null
|
||||||
// hterm can't parse "transparent"
|
|
||||||
preferenceManager.set('background-color', 'transparent')
|
|
||||||
}
|
|
||||||
if (config.terminal.colorScheme.colors) {
|
|
||||||
preferenceManager.set('color-palette-overrides', config.terminal.colorScheme.colors)
|
|
||||||
}
|
|
||||||
if (config.terminal.colorScheme.cursor) {
|
|
||||||
preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
let css = require('../hterm.userCSS.scss')
|
|
||||||
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.hterm.setCSS(css)
|
|
||||||
this.hterm.setBracketedPaste(config.terminal.bracketedPaste)
|
|
||||||
this.hterm.defaultCursorShape = {
|
|
||||||
block: hterm.hterm.Terminal.cursorShape.BLOCK,
|
|
||||||
underline: hterm.hterm.Terminal.cursorShape.UNDERLINE,
|
|
||||||
beam: hterm.hterm.Terminal.cursorShape.BEAM,
|
|
||||||
}[config.terminal.cursor]
|
|
||||||
this.hterm.applyCursorShape()
|
|
||||||
this.hterm.setCursorBlink(config.terminal.cursorBlink)
|
|
||||||
if (config.terminal.cursorBlink) {
|
|
||||||
this.hterm.onCursorBlink_()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomIn () {
|
zoomIn () {
|
||||||
this.zoom++
|
this.zoom++
|
||||||
this.setFontSize()
|
this.termContainer.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut () {
|
zoomOut () {
|
||||||
this.zoom--
|
this.zoom--
|
||||||
this.setFontSize()
|
this.termContainer.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetZoom () {
|
resetZoom () {
|
||||||
this.zoom = 0
|
this.zoom = 0
|
||||||
this.setFontSize()
|
this.termContainer.setZoom(this.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
this.detachTermContainerHandlers()
|
||||||
this.config.enabledServices(this.decorators).forEach(decorator => {
|
this.config.enabledServices(this.decorators).forEach(decorator => {
|
||||||
decorator.detach(this)
|
decorator.detach(this)
|
||||||
})
|
})
|
||||||
@ -455,13 +341,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
if (this.sessionCloseSubscription) {
|
if (this.sessionCloseSubscription) {
|
||||||
this.sessionCloseSubscription.unsubscribe()
|
this.sessionCloseSubscription.unsubscribe()
|
||||||
}
|
}
|
||||||
this.resize_.complete()
|
|
||||||
this.input$.complete()
|
|
||||||
this.output$.complete()
|
this.output$.complete()
|
||||||
this.contentUpdated_.complete()
|
|
||||||
this.alternateScreenActive$.complete()
|
|
||||||
this.mouseEvent$.complete()
|
|
||||||
this.bell$.complete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy () {
|
async destroy () {
|
||||||
@ -481,8 +361,4 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
return confirm(`"${children[0].command}" is still running. Close?`)
|
return confirm(`"${children[0].command}" is still running. Close?`)
|
||||||
}
|
}
|
||||||
|
|
||||||
private setFontSize () {
|
|
||||||
preferenceManager.set('font-size', this.config.store.terminal.fontSize * Math.pow(1.1, this.zoom))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import { ColorPickerComponent } from './components/colorPicker.component'
|
|||||||
|
|
||||||
import { SessionsService, BaseSession } from './services/sessions.service'
|
import { SessionsService, BaseSession } from './services/sessions.service'
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
|
import { TerminalContainersService } from './services/terminalContainers.service'
|
||||||
|
|
||||||
import { ScreenPersistenceProvider } from './persistence/screen'
|
import { ScreenPersistenceProvider } from './persistence/screen'
|
||||||
import { TMuxPersistenceProvider } from './persistence/tmux'
|
import { TMuxPersistenceProvider } from './persistence/tmux'
|
||||||
@ -50,6 +51,7 @@ import { hterm } from './hterm'
|
|||||||
providers: [
|
providers: [
|
||||||
SessionsService,
|
SessionsService,
|
||||||
TerminalService,
|
TerminalService,
|
||||||
|
TerminalContainersService,
|
||||||
|
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
@ -123,4 +125,4 @@ export default class TerminalModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from './api'
|
export * from './api'
|
||||||
export { TerminalService, BaseSession, TerminalTabComponent }
|
export { TerminalService, BaseSession, TerminalTabComponent, TerminalContainersService }
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
|
import { Subscription } from 'rxjs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TerminalDecorator } from './api'
|
import { TerminalDecorator } from './api'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PathDropDecorator extends TerminalDecorator {
|
export class PathDropDecorator extends TerminalDecorator {
|
||||||
|
private subscriptions: Subscription[] = []
|
||||||
|
|
||||||
attach (terminal: TerminalTabComponent): void {
|
attach (terminal: TerminalTabComponent): void {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
terminal.hterm.scrollPort_.document_.addEventListener('dragover', (event) => {
|
this.subscriptions = [
|
||||||
|
terminal.termContainer.dragOver$.subscribe(event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
})
|
}),
|
||||||
terminal.hterm.scrollPort_.document_.addEventListener('drop', (event) => {
|
terminal.termContainer.drop$.subscribe(event => {
|
||||||
for (let file of event.dataTransfer.files) {
|
for (let file of event.dataTransfer.files as any) {
|
||||||
this.injectPath(terminal, file.path)
|
this.injectPath(terminal, file.path)
|
||||||
}
|
}
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
})
|
}),
|
||||||
|
]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,6 +30,9 @@ export class PathDropDecorator extends TerminalDecorator {
|
|||||||
terminal.sendInput(path + ' ')
|
terminal.sendInput(path + ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line no-empty
|
detach (terminal: TerminalTabComponent): void {
|
||||||
detach (terminal: TerminalTabComponent): void { }
|
for (let s of this.subscriptions) {
|
||||||
|
s.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ export abstract class BaseSession {
|
|||||||
this.initialDataBuffer = null
|
this.initialDataBuffer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract start (options: SessionOptions)
|
||||||
abstract resize (columns, rows)
|
abstract resize (columns, rows)
|
||||||
abstract write (data)
|
abstract write (data)
|
||||||
abstract kill (signal?: string)
|
abstract kill (signal?: string)
|
||||||
@ -70,8 +71,7 @@ export abstract class BaseSession {
|
|||||||
export class Session extends BaseSession {
|
export class Session extends BaseSession {
|
||||||
private pty: any
|
private pty: any
|
||||||
|
|
||||||
constructor (options: SessionOptions) {
|
start (options: SessionOptions) {
|
||||||
super()
|
|
||||||
this.name = options.name
|
this.name = options.name
|
||||||
this.recoveryId = options.recoveryId
|
this.recoveryId = options.recoveryId
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ export class Session extends BaseSession {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SessionsService {
|
export class SessionsService {
|
||||||
sessions: {[id: string]: Session} = {}
|
sessions: {[id: string]: BaseSession} = {}
|
||||||
logger: Logger
|
logger: Logger
|
||||||
private lastID = 0
|
private lastID = 0
|
||||||
|
|
||||||
@ -225,10 +225,10 @@ export class SessionsService {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
addSession (options: SessionOptions): Session {
|
addSession (session: BaseSession, options: SessionOptions) {
|
||||||
this.lastID++
|
this.lastID++
|
||||||
options.name = `session-${this.lastID}`
|
options.name = `session-${this.lastID}`
|
||||||
let session = new Session(options)
|
session.start(options)
|
||||||
let persistence = this.getPersistence()
|
let persistence = this.getPersistence()
|
||||||
session.destroyed$.pipe(first()).subscribe(() => {
|
session.destroyed$.pipe(first()).subscribe(() => {
|
||||||
delete this.sessions[session.name]
|
delete this.sessions[session.name]
|
||||||
|
16
terminus-terminal/src/services/terminalContainers.service.ts
Normal file
16
terminus-terminal/src/services/terminalContainers.service.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable } from '@angular/core'
|
||||||
|
import { TermContainer } from '../terminalContainers/termContainer'
|
||||||
|
import { HTermContainer } from '../terminalContainers/htermContainer'
|
||||||
|
import { BaseSession } from '../services/sessions.service'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TerminalContainersService {
|
||||||
|
private containers = new WeakMap<BaseSession, TermContainer>()
|
||||||
|
|
||||||
|
getContainer (session: BaseSession): TermContainer {
|
||||||
|
if (!this.containers.has(session)) {
|
||||||
|
this.containers.set(session, new HTermContainer())
|
||||||
|
}
|
||||||
|
return this.containers.get(session)
|
||||||
|
}
|
||||||
|
}
|
226
terminus-terminal/src/terminalContainers/htermContainer.ts
Normal file
226
terminus-terminal/src/terminalContainers/htermContainer.ts
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
import { TermContainer } from './termContainer'
|
||||||
|
import { hterm, preferenceManager } from '../hterm'
|
||||||
|
|
||||||
|
export class HTermContainer extends TermContainer {
|
||||||
|
term: any
|
||||||
|
io: any
|
||||||
|
private htermIframe: HTMLElement
|
||||||
|
private initialized = false
|
||||||
|
private configuredFontSize = 0
|
||||||
|
private configuredLinePadding = 0
|
||||||
|
private zoom = 0
|
||||||
|
|
||||||
|
attach (host: HTMLElement) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.init()
|
||||||
|
this.initialized = true
|
||||||
|
this.term.decorate(host)
|
||||||
|
this.htermIframe = this.term.scrollPort_.iframe_
|
||||||
|
} else {
|
||||||
|
host.appendChild(this.htermIframe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelection (): string {
|
||||||
|
return this.term.getSelectionText()
|
||||||
|
}
|
||||||
|
|
||||||
|
copySelection () {
|
||||||
|
this.term.copySelectionToClipboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelection () {
|
||||||
|
this.term.getDocument().getSelection().removeAllRanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
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 (config: any): void {
|
||||||
|
this.configuredFontSize = config.terminal.fontSize
|
||||||
|
this.configuredLinePadding = config.terminal.linePadding
|
||||||
|
this.setFontSize()
|
||||||
|
|
||||||
|
preferenceManager.set('font-family', `"${config.terminal.font}", "monospace-fallback", monospace`)
|
||||||
|
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('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)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// hterm can't parse "transparent"
|
||||||
|
preferenceManager.set('background-color', 'transparent')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.terminal.colorScheme.colors) {
|
||||||
|
preferenceManager.set('color-palette-overrides', config.terminal.colorScheme.colors)
|
||||||
|
}
|
||||||
|
if (config.terminal.colorScheme.cursor) {
|
||||||
|
preferenceManager.set('cursor-color', config.terminal.colorScheme.cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let css = require('../hterm.userCSS.scss')
|
||||||
|
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)
|
||||||
|
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 {
|
||||||
|
const color = preferenceManager.get('background-color')
|
||||||
|
preferenceManager.set('background-color', 'rgba(128,128,128,.25)')
|
||||||
|
setTimeout(() => {
|
||||||
|
preferenceManager.set('background-color', color)
|
||||||
|
}, 125)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setFontSize () {
|
||||||
|
preferenceManager.set('font-size', this.configuredFontSize * Math.pow(1.1, this.zoom))
|
||||||
|
}
|
||||||
|
|
||||||
|
private init () {
|
||||||
|
this.term = new hterm.hterm.Terminal()
|
||||||
|
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(data)
|
||||||
|
this.io.onTerminalResize = (columns, rows) => {
|
||||||
|
console.log('hterm resize')
|
||||||
|
this.resize.next({ columns, rows })
|
||||||
|
}
|
||||||
|
this.ready.next(null)
|
||||||
|
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 (let 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) => {
|
||||||
|
let ret = _deleteChars(count)
|
||||||
|
this.contentUpdated.next()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _measureCharacterSize = this.term.scrollPort_.measureCharacterSize.bind(this.term.scrollPort_)
|
||||||
|
this.term.scrollPort_.measureCharacterSize = () => {
|
||||||
|
let size = _measureCharacterSize()
|
||||||
|
size.height += this.configuredLinePadding
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
terminus-terminal/src/terminalContainers/termContainer.ts
Normal file
56
terminus-terminal/src/terminalContainers/termContainer.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Observable, Subject, AsyncSubject, ReplaySubject, BehaviorSubject } from 'rxjs'
|
||||||
|
|
||||||
|
export abstract class TermContainer {
|
||||||
|
enableResizing = true
|
||||||
|
protected ready = new AsyncSubject<void>()
|
||||||
|
protected title = new ReplaySubject<string>(1)
|
||||||
|
protected alternateScreenActive = new BehaviorSubject<boolean>(false)
|
||||||
|
protected mouseEvent = new Subject<MouseEvent>()
|
||||||
|
protected bell = new Subject<void>()
|
||||||
|
protected contentUpdated = new Subject<void>()
|
||||||
|
protected input = new Subject<string>()
|
||||||
|
protected resize = new ReplaySubject<{columns: number, rows: number}>(1)
|
||||||
|
protected dragOver = new Subject<DragEvent>()
|
||||||
|
protected drop = new Subject<DragEvent>()
|
||||||
|
|
||||||
|
get ready$ (): Observable<void> { return this.ready }
|
||||||
|
get title$ (): Observable<string> { return this.title }
|
||||||
|
get alternateScreenActive$ (): Observable<boolean> { return this.alternateScreenActive }
|
||||||
|
get mouseEvent$ (): Observable<MouseEvent> { return this.mouseEvent }
|
||||||
|
get bell$ (): Observable<void> { return this.bell }
|
||||||
|
get contentUpdated$ (): Observable<void> { return this.contentUpdated }
|
||||||
|
get input$ (): Observable<string> { return this.input }
|
||||||
|
get resize$ (): Observable<{columns: number, rows: number}> { return this.resize }
|
||||||
|
get dragOver$ (): Observable<DragEvent> { return this.dragOver }
|
||||||
|
get drop$ (): Observable<DragEvent> { return this.drop }
|
||||||
|
|
||||||
|
abstract attach (host: HTMLElement): void
|
||||||
|
|
||||||
|
destroy (): void {
|
||||||
|
for (let o of [
|
||||||
|
this.ready,
|
||||||
|
this.title,
|
||||||
|
this.alternateScreenActive,
|
||||||
|
this.mouseEvent,
|
||||||
|
this.bell,
|
||||||
|
this.contentUpdated,
|
||||||
|
this.input,
|
||||||
|
this.resize,
|
||||||
|
this.dragOver,
|
||||||
|
this.drop,
|
||||||
|
]) {
|
||||||
|
o.complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getSelection (): string
|
||||||
|
abstract copySelection (): void
|
||||||
|
abstract clearSelection (): void
|
||||||
|
abstract focus (): void
|
||||||
|
abstract write (data: string): void
|
||||||
|
abstract clear (): void
|
||||||
|
abstract visualBell (): void
|
||||||
|
|
||||||
|
abstract configure (configStore: any): void
|
||||||
|
abstract setZoom (zoom: number): void
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user