mirror of
https://github.com/Eugeny/tabby.git
synced 2025-02-05 14:29:47 +08:00
plugged memory leaks
This commit is contained in:
parent
c98fd2042d
commit
5c22e22caa
@ -21,7 +21,7 @@ if (process.platform === 'win32' && !('HOME' in process.env)) {
|
|||||||
process.env.HOME = `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`
|
process.env.HOME = `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.TERMINUS_DEV) {
|
if (process.env.TERMINUS_DEV && !process.env.TERMINUS_FORCE_ANGULAR_PROD) {
|
||||||
console.warn('Running in debug mode')
|
console.warn('Running in debug mode')
|
||||||
} else {
|
} else {
|
||||||
enableProdMode()
|
enableProdMode()
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
export { BaseComponent, SubscriptionContainer } from '../components/base.component'
|
||||||
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
||||||
export { TabHeaderComponent } from '../components/tabHeader.component'
|
export { TabHeaderComponent } from '../components/tabHeader.component'
|
||||||
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
|
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
|
||||||
|
54
terminus-core/src/components/base.component.ts
Normal file
54
terminus-core/src/components/base.component.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Observable, Subscription } from 'rxjs'
|
||||||
|
|
||||||
|
interface CancellableEvent {
|
||||||
|
element: HTMLElement
|
||||||
|
event: string
|
||||||
|
handler: EventListenerOrEventListenerObject
|
||||||
|
options?: boolean|AddEventListenerOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SubscriptionContainer {
|
||||||
|
private subscriptions: Subscription[] = []
|
||||||
|
private events: CancellableEvent[] = []
|
||||||
|
|
||||||
|
addEventListener (element: HTMLElement, event: string, handler: EventListenerOrEventListenerObject, options?: boolean|AddEventListenerOptions): void {
|
||||||
|
element.addEventListener(event, handler, options)
|
||||||
|
this.events.push({
|
||||||
|
element,
|
||||||
|
event,
|
||||||
|
handler,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe <T> (observable: Observable<T>, handler: (v: T) => void): void {
|
||||||
|
this.subscriptions.push(observable.subscribe(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelAll (): void {
|
||||||
|
for (const s of this.subscriptions) {
|
||||||
|
s.unsubscribe()
|
||||||
|
}
|
||||||
|
for (const e of this.events) {
|
||||||
|
e.element.removeEventListener(e.event, e.handler, e.options)
|
||||||
|
}
|
||||||
|
this.subscriptions = []
|
||||||
|
this.events = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BaseComponent {
|
||||||
|
private subscriptionContainer = new SubscriptionContainer()
|
||||||
|
|
||||||
|
addEventListenerUntilDestroyed (element: HTMLElement, event: string, handler: EventListenerOrEventListenerObject, options?: boolean|AddEventListenerOptions): void {
|
||||||
|
this.subscriptionContainer.addEventListener(element, event, handler, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeUntilDestroyed <T> (observable: Observable<T>, handler: (v: T) => void): void {
|
||||||
|
this.subscriptionContainer.subscribe(observable, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy (): void {
|
||||||
|
this.subscriptionContainer.cancelAll()
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { ViewRef } from '@angular/core'
|
import { ViewRef } from '@angular/core'
|
||||||
import { RecoveryToken } from '../api/tabRecovery'
|
import { RecoveryToken } from '../api/tabRecovery'
|
||||||
|
import { BaseComponent } from './base.component'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an active "process" inside a tab,
|
* Represents an active "process" inside a tab,
|
||||||
@ -13,7 +14,7 @@ export interface BaseTabProcess {
|
|||||||
/**
|
/**
|
||||||
* Abstract base class for custom tab components
|
* Abstract base class for custom tab components
|
||||||
*/
|
*/
|
||||||
export abstract class BaseTabComponent {
|
export abstract class BaseTabComponent extends BaseComponent {
|
||||||
/**
|
/**
|
||||||
* Parent tab (usually a SplitTabComponent)
|
* Parent tab (usually a SplitTabComponent)
|
||||||
*/
|
*/
|
||||||
@ -69,6 +70,7 @@ export abstract class BaseTabComponent {
|
|||||||
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
|
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
|
||||||
|
|
||||||
protected constructor () {
|
protected constructor () {
|
||||||
|
super()
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.hasFocus = true
|
this.hasFocus = true
|
||||||
})
|
})
|
||||||
@ -158,10 +160,17 @@ export abstract class BaseTabComponent {
|
|||||||
this.blurred.complete()
|
this.blurred.complete()
|
||||||
this.titleChange.complete()
|
this.titleChange.complete()
|
||||||
this.progress.complete()
|
this.progress.complete()
|
||||||
|
this.activity.complete()
|
||||||
this.recoveryStateChangedHint.complete()
|
this.recoveryStateChangedHint.complete()
|
||||||
if (!skipDestroyedEvent) {
|
if (!skipDestroyedEvent) {
|
||||||
this.destroyed.next()
|
this.destroyed.next()
|
||||||
}
|
}
|
||||||
this.destroyed.complete()
|
this.destroyed.complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
ngOnDestroy (): void {
|
||||||
|
this.destroy()
|
||||||
|
super.ngOnDestroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Observable, Subject, Subscription } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, AfterViewInit, OnDestroy } from '@angular/core'
|
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, AfterViewInit, OnDestroy } from '@angular/core'
|
||||||
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
||||||
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from '../api/tabRecovery'
|
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from '../api/tabRecovery'
|
||||||
@ -163,7 +163,6 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
/** @hidden */
|
/** @hidden */
|
||||||
private focusedTab: BaseTabComponent|null = null
|
private focusedTab: BaseTabComponent|null = null
|
||||||
private maximizedTab: BaseTabComponent|null = null
|
private maximizedTab: BaseTabComponent|null = null
|
||||||
private hotkeysSubscription: Subscription
|
|
||||||
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
||||||
|
|
||||||
private tabAdded = new Subject<BaseTabComponent>()
|
private tabAdded = new Subject<BaseTabComponent>()
|
||||||
@ -210,7 +209,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
})
|
})
|
||||||
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
this.blurred$.subscribe(() => this.getAllTabs().forEach(x => x.emitBlurred()))
|
||||||
|
|
||||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, hotkey => {
|
||||||
if (!this.hasFocus || !this.focusedTab) {
|
if (!this.hasFocus || !this.focusedTab) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -272,7 +271,9 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
ngOnDestroy (): void {
|
ngOnDestroy (): void {
|
||||||
this.hotkeysSubscription.unsubscribe()
|
this.tabAdded.complete()
|
||||||
|
this.tabRemoved.complete()
|
||||||
|
super.ngOnDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns Flat list of all sub-tabs */
|
/** @returns Flat list of all sub-tabs */
|
||||||
@ -497,18 +498,18 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
const ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any> // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
const ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any> // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
this.viewRefs.set(tab, ref)
|
this.viewRefs.set(tab, ref)
|
||||||
|
|
||||||
ref.rootNodes[0].addEventListener('click', () => this.focus(tab))
|
tab.addEventListenerUntilDestroyed(ref.rootNodes[0], 'click', () => this.focus(tab))
|
||||||
|
|
||||||
tab.titleChange$.subscribe(t => this.setTitle(t))
|
tab.subscribeUntilDestroyed(tab.titleChange$, t => this.setTitle(t))
|
||||||
tab.activity$.subscribe(a => a ? this.displayActivity() : this.clearActivity())
|
tab.subscribeUntilDestroyed(tab.activity$, a => a ? this.displayActivity() : this.clearActivity())
|
||||||
tab.progress$.subscribe(p => this.setProgress(p))
|
tab.subscribeUntilDestroyed(tab.progress$, p => this.setProgress(p))
|
||||||
if (tab.title) {
|
if (tab.title) {
|
||||||
this.setTitle(tab.title)
|
this.setTitle(tab.title)
|
||||||
}
|
}
|
||||||
tab.recoveryStateChangedHint$.subscribe(() => {
|
tab.subscribeUntilDestroyed(tab.recoveryStateChangedHint$, () => {
|
||||||
this.recoveryStateChangedHint.next()
|
this.recoveryStateChangedHint.next()
|
||||||
})
|
})
|
||||||
tab.destroyed$.subscribe(() => {
|
tab.subscribeUntilDestroyed(tab.destroyed$, () => {
|
||||||
this.removeTab(tab)
|
this.removeTab(tab)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { ElectronService } from '../services/electron.service'
|
|||||||
import { AppService } from '../services/app.service'
|
import { AppService } from '../services/app.service'
|
||||||
import { HostAppService, Platform } from '../services/hostApp.service'
|
import { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from '../services/config.service'
|
||||||
|
import { BaseComponent } from './base.component'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
export interface SortableComponentProxy {
|
export interface SortableComponentProxy {
|
||||||
@ -23,7 +24,7 @@ export interface SortableComponentProxy {
|
|||||||
template: require('./tabHeader.component.pug'),
|
template: require('./tabHeader.component.pug'),
|
||||||
styles: [require('./tabHeader.component.scss')],
|
styles: [require('./tabHeader.component.scss')],
|
||||||
})
|
})
|
||||||
export class TabHeaderComponent {
|
export class TabHeaderComponent extends BaseComponent {
|
||||||
@Input() index: number
|
@Input() index: number
|
||||||
@Input() @HostBinding('class.active') active: boolean
|
@Input() @HostBinding('class.active') active: boolean
|
||||||
@Input() tab: BaseTabComponent
|
@Input() tab: BaseTabComponent
|
||||||
@ -41,7 +42,8 @@ export class TabHeaderComponent {
|
|||||||
@Inject(SortableComponent) private parentDraggable: SortableComponentProxy,
|
@Inject(SortableComponent) private parentDraggable: SortableComponentProxy,
|
||||||
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
|
@Optional() @Inject(TabContextMenuItemProvider) protected contextMenuProviders: TabContextMenuItemProvider[],
|
||||||
) {
|
) {
|
||||||
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
super()
|
||||||
|
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, (hotkey) => {
|
||||||
if (this.app.activeTab === this.tab) {
|
if (this.app.activeTab === this.tab) {
|
||||||
if (hotkey === 'rename-tab') {
|
if (hotkey === 'rename-tab') {
|
||||||
this.showRenameTabModal()
|
this.showRenameTabModal()
|
||||||
@ -52,7 +54,7 @@ export class TabHeaderComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.tab.progress$.subscribe(progress => {
|
this.subscribeUntilDestroyed(this.tab.progress$, progress => {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this.progress = progress
|
this.progress = progress
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
|
import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
|
||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
||||||
import { stringifyKeySequence } from './hotkeys.util'
|
import { stringifyKeySequence, EventData } from './hotkeys.util'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { HostAppService } from './hostApp.service'
|
import { HostAppService } from './hostApp.service'
|
||||||
@ -14,10 +14,6 @@ export interface PartialHotkeyMatch {
|
|||||||
|
|
||||||
const KEY_TIMEOUT = 2000
|
const KEY_TIMEOUT = 2000
|
||||||
|
|
||||||
interface EventBufferEntry {
|
|
||||||
event: KeyboardEvent
|
|
||||||
time: number
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HotkeysService {
|
export class HotkeysService {
|
||||||
@ -32,7 +28,7 @@ export class HotkeysService {
|
|||||||
get hotkey$ (): Observable<string> { return this._hotkey }
|
get hotkey$ (): Observable<string> { return this._hotkey }
|
||||||
|
|
||||||
private _hotkey = new Subject<string>()
|
private _hotkey = new Subject<string>()
|
||||||
private currentKeystrokes: EventBufferEntry[] = []
|
private currentKeystrokes: EventData[] = []
|
||||||
private disabledLevel = 0
|
private disabledLevel = 0
|
||||||
private hotkeyDescriptions: HotkeyDescription[] = []
|
private hotkeyDescriptions: HotkeyDescription[] = []
|
||||||
|
|
||||||
@ -73,7 +69,16 @@ export class HotkeysService {
|
|||||||
*/
|
*/
|
||||||
pushKeystroke (name: string, nativeEvent: KeyboardEvent): void {
|
pushKeystroke (name: string, nativeEvent: KeyboardEvent): void {
|
||||||
(nativeEvent as any).event = name
|
(nativeEvent as any).event = name
|
||||||
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
|
this.currentKeystrokes.push({
|
||||||
|
ctrlKey: nativeEvent.ctrlKey,
|
||||||
|
metaKey: nativeEvent.metaKey,
|
||||||
|
altKey: nativeEvent.altKey,
|
||||||
|
shiftKey: nativeEvent.shiftKey,
|
||||||
|
code: nativeEvent.code,
|
||||||
|
key: nativeEvent.key,
|
||||||
|
eventName: name,
|
||||||
|
time: performance.now(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,7 +109,7 @@ export class HotkeysService {
|
|||||||
|
|
||||||
getCurrentKeystrokes (): string[] {
|
getCurrentKeystrokes (): string[] {
|
||||||
this.currentKeystrokes = this.currentKeystrokes.filter(x => performance.now() - x.time < KEY_TIMEOUT)
|
this.currentKeystrokes = this.currentKeystrokes.filter(x => performance.now() - x.time < KEY_TIMEOUT)
|
||||||
return stringifyKeySequence(this.currentKeystrokes.map(x => x.event))
|
return stringifyKeySequence(this.currentKeystrokes)
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFullyMatchedHotkey (): string|null {
|
getCurrentFullyMatchedHotkey (): string|null {
|
||||||
|
@ -10,15 +10,26 @@ export const altKeyName = {
|
|||||||
linux: 'Alt',
|
linux: 'Alt',
|
||||||
}[process.platform]
|
}[process.platform]
|
||||||
|
|
||||||
|
export interface EventData {
|
||||||
|
ctrlKey: boolean
|
||||||
|
metaKey: boolean
|
||||||
|
altKey: boolean
|
||||||
|
shiftKey: boolean
|
||||||
|
key: string
|
||||||
|
code: string
|
||||||
|
eventName: string
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
const REGEX_LATIN_KEYNAME = /^[A-Za-z]$/
|
const REGEX_LATIN_KEYNAME = /^[A-Za-z]$/
|
||||||
|
|
||||||
export function stringifyKeySequence (events: KeyboardEvent[]): string[] {
|
export function stringifyKeySequence (events: EventData[]): string[] {
|
||||||
const items: string[] = []
|
const items: string[] = []
|
||||||
events = events.slice()
|
events = events.slice()
|
||||||
|
|
||||||
while (events.length > 0) {
|
while (events.length > 0) {
|
||||||
const event = events.shift()!
|
const event = events.shift()!
|
||||||
if ((event as any).event === 'keydown') {
|
if (event.eventName === 'keydown') {
|
||||||
const itemKeys: string[] = []
|
const itemKeys: string[] = []
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
itemKeys.push('Ctrl')
|
itemKeys.push('Ctrl')
|
||||||
|
@ -6,7 +6,6 @@ import { first } from 'rxjs/operators'
|
|||||||
import { BaseTerminalTabComponent } from 'terminus-terminal'
|
import { BaseTerminalTabComponent } from 'terminus-terminal'
|
||||||
import { SerialService } from '../services/serial.service'
|
import { SerialService } from '../services/serial.service'
|
||||||
import { SerialConnection, SerialSession, BAUD_RATES } from '../api'
|
import { SerialConnection, SerialSession, BAUD_RATES } from '../api'
|
||||||
import { Subscription } from 'rxjs'
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
@ -20,7 +19,6 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
|||||||
session: SerialSession|null = null
|
session: SerialSession|null = null
|
||||||
serialPort: any
|
serialPort: any
|
||||||
private serialService: SerialService
|
private serialService: SerialService
|
||||||
private homeEndSubscription: Subscription
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||||
constructor (
|
constructor (
|
||||||
@ -33,7 +31,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
|||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this.logger = this.log.create('terminalTab')
|
this.logger = this.log.create('terminalTab')
|
||||||
|
|
||||||
this.homeEndSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -130,9 +128,4 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
|||||||
this.serialPort.update({ baudRate: rate })
|
this.serialPort.update({ baudRate: rate })
|
||||||
this.connection!.baudrate = rate
|
this.connection!.baudrate = rate
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
|
||||||
this.homeEndSubscription.unsubscribe()
|
|
||||||
super.ngOnDestroy()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { trigger, transition, style, animate } from '@angular/animations'
|
import { trigger, transition, style, animate } from '@angular/animations'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { Subscription } from 'rxjs'
|
import { HotkeysService, BaseComponent } from 'terminus-core'
|
||||||
import { HotkeysService } from 'terminus-core'
|
|
||||||
|
|
||||||
const INPUT_TIMEOUT = 1000
|
const INPUT_TIMEOUT = 1000
|
||||||
|
|
||||||
@ -36,11 +35,10 @@ const INPUT_TIMEOUT = 1000
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class HotkeyInputModalComponent {
|
export class HotkeyInputModalComponent extends BaseComponent {
|
||||||
@Input() value: string[] = []
|
@Input() value: string[] = []
|
||||||
@Input() timeoutProgress = 0
|
@Input() timeoutProgress = 0
|
||||||
|
|
||||||
private keySubscription: Subscription
|
|
||||||
private lastKeyEvent: number|null = null
|
private lastKeyEvent: number|null = null
|
||||||
private keyTimeoutInterval: number|null = null
|
private keyTimeoutInterval: number|null = null
|
||||||
|
|
||||||
@ -48,8 +46,9 @@ export class HotkeyInputModalComponent {
|
|||||||
private modalInstance: NgbActiveModal,
|
private modalInstance: NgbActiveModal,
|
||||||
public hotkeys: HotkeysService,
|
public hotkeys: HotkeysService,
|
||||||
) {
|
) {
|
||||||
|
super()
|
||||||
this.hotkeys.clearCurrentKeystrokes()
|
this.hotkeys.clearCurrentKeystrokes()
|
||||||
this.keySubscription = hotkeys.key.subscribe((event) => {
|
this.subscribeUntilDestroyed(hotkeys.key, (event) => {
|
||||||
this.lastKeyEvent = performance.now()
|
this.lastKeyEvent = performance.now()
|
||||||
this.value = this.hotkeys.getCurrentKeystrokes()
|
this.value = this.hotkeys.getCurrentKeystrokes()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@ -75,10 +74,10 @@ export class HotkeyInputModalComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy (): void {
|
ngOnDestroy (): void {
|
||||||
this.keySubscription.unsubscribe()
|
|
||||||
this.hotkeys.clearCurrentKeystrokes()
|
this.hotkeys.clearCurrentKeystrokes()
|
||||||
this.hotkeys.enable()
|
this.hotkeys.enable()
|
||||||
clearInterval(this.keyTimeoutInterval!)
|
clearInterval(this.keyTimeoutInterval!)
|
||||||
|
super.ngOnDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
close (): void {
|
close (): void {
|
||||||
|
@ -58,7 +58,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
&& config.store.appearance.tabsLocation !== 'top'
|
&& config.store.appearance.tabsLocation !== 'top'
|
||||||
}
|
}
|
||||||
|
|
||||||
this.configSubscription = config.changed$.subscribe(onConfigChange)
|
this.configSubscription = this.subscribeUntilDestroyed(config.changed$, onConfigChange)
|
||||||
onConfigChange()
|
onConfigChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
isWindowsBuild,
|
isWindowsBuild,
|
||||||
WIN_BUILD_FLUENT_BG_SUPPORTED,
|
WIN_BUILD_FLUENT_BG_SUPPORTED,
|
||||||
|
BaseComponent,
|
||||||
} from 'terminus-core'
|
} from 'terminus-core'
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ import {
|
|||||||
selector: 'window-settings-tab',
|
selector: 'window-settings-tab',
|
||||||
template: require('./windowSettingsTab.component.pug'),
|
template: require('./windowSettingsTab.component.pug'),
|
||||||
})
|
})
|
||||||
export class WindowSettingsTabComponent {
|
export class WindowSettingsTabComponent extends BaseComponent {
|
||||||
screens: any[]
|
screens: any[]
|
||||||
Platform = Platform
|
Platform = Platform
|
||||||
isFluentVibrancySupported = false
|
isFluentVibrancySupported = false
|
||||||
@ -29,10 +30,11 @@ export class WindowSettingsTabComponent {
|
|||||||
public zone: NgZone,
|
public zone: NgZone,
|
||||||
@Inject(Theme) public themes: Theme[],
|
@Inject(Theme) public themes: Theme[],
|
||||||
) {
|
) {
|
||||||
|
super()
|
||||||
this.screens = this.docking.getScreens()
|
this.screens = this.docking.getScreens()
|
||||||
this.themes = config.enabledServices(this.themes)
|
this.themes = config.enabledServices(this.themes)
|
||||||
|
|
||||||
hostApp.displaysChanged$.subscribe(() => {
|
this.subscribeUntilDestroyed(hostApp.displaysChanged$, () => {
|
||||||
this.zone.run(() => this.screens = this.docking.getScreens())
|
this.zone.run(() => this.screens = this.docking.getScreens())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import { BaseTerminalTabComponent } from 'terminus-terminal'
|
|||||||
import { SSHService } from '../services/ssh.service'
|
import { SSHService } from '../services/ssh.service'
|
||||||
import { SSHConnection, SSHSession } from '../api'
|
import { SSHConnection, SSHSession } from '../api'
|
||||||
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
||||||
import { Subscription } from 'rxjs'
|
|
||||||
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@ -22,7 +21,6 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
connection?: SSHConnection
|
connection?: SSHConnection
|
||||||
session: SSHSession|null = null
|
session: SSHSession|null = null
|
||||||
private sessionStack: SSHSession[] = []
|
private sessionStack: SSHSession[] = []
|
||||||
private homeEndSubscription: Subscription
|
|
||||||
private recentInputs = ''
|
private recentInputs = ''
|
||||||
private reconnectOffered = false
|
private reconnectOffered = false
|
||||||
private spinner = new Spinner({
|
private spinner = new Spinner({
|
||||||
@ -50,7 +48,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
|
|
||||||
this.enableDynamicTitle = !this.connection.disableDynamicTitle
|
this.enableDynamicTitle = !this.connection.disableDynamicTitle
|
||||||
|
|
||||||
this.homeEndSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -225,11 +223,6 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
)).response === 1
|
)).response === 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy (): void {
|
|
||||||
this.homeEndSubscription.unsubscribe()
|
|
||||||
super.ngOnDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private startSpinner () {
|
private startSpinner () {
|
||||||
this.spinner.setSpinnerString(6)
|
this.spinner.setSpinnerString(6)
|
||||||
this.spinner.start()
|
this.spinner.start()
|
||||||
|
@ -4,7 +4,7 @@ import { first } from 'rxjs/operators'
|
|||||||
import colors from 'ansi-colors'
|
import colors from 'ansi-colors'
|
||||||
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
|
import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
|
||||||
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
|
import { trigger, transition, style, animate, AnimationTriggerMetadata } from '@angular/animations'
|
||||||
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent } from 'terminus-core'
|
import { AppService, ConfigService, BaseTabComponent, ElectronService, HostAppService, HotkeysService, NotificationsService, Platform, LogService, Logger, TabContextMenuItemProvider, SplitTabComponent, SubscriptionContainer } from 'terminus-core'
|
||||||
|
|
||||||
import { BaseSession, SessionsService } from '../services/sessions.service'
|
import { BaseSession, SessionsService } from '../services/sessions.service'
|
||||||
import { TerminalFrontendService } from '../services/terminalFrontend.service'
|
import { TerminalFrontendService } from '../services/terminalFrontend.service'
|
||||||
@ -95,12 +95,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
protected logger: Logger
|
protected logger: Logger
|
||||||
protected output = new Subject<string>()
|
protected output = new Subject<string>()
|
||||||
protected sessionChanged = new Subject<BaseSession|null>()
|
protected sessionChanged = new Subject<BaseSession|null>()
|
||||||
private sessionCloseSubscription: Subscription
|
|
||||||
private hotkeysSubscription: Subscription
|
|
||||||
private bellPlayer: HTMLAudioElement
|
private bellPlayer: HTMLAudioElement
|
||||||
private termContainerSubscriptions: Subscription[] = []
|
private termContainerSubscriptions = new SubscriptionContainer()
|
||||||
private allFocusModeSubscription: Subscription|null = null
|
private allFocusModeSubscription: Subscription|null = null
|
||||||
private sessionHandlers: Subscription[] = []
|
private sessionHandlers = new SubscriptionContainer()
|
||||||
|
|
||||||
get input$ (): Observable<Buffer> {
|
get input$ (): Observable<Buffer> {
|
||||||
if (!this.frontend) {
|
if (!this.frontend) {
|
||||||
@ -149,7 +147,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.logger = this.log.create('baseTerminalTab')
|
this.logger = this.log.create('baseTerminalTab')
|
||||||
this.setTitle('Terminal')
|
this.setTitle('Terminal')
|
||||||
|
|
||||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(async hotkey => {
|
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, async hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -475,7 +473,13 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
ngOnDestroy (): void {
|
ngOnDestroy (): void {
|
||||||
|
super.ngOnDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy (): Promise<void> {
|
||||||
this.frontend?.detach(this.content.nativeElement)
|
this.frontend?.detach(this.content.nativeElement)
|
||||||
|
this.frontend = undefined
|
||||||
|
this.content.nativeElement.remove()
|
||||||
this.detachTermContainerHandlers()
|
this.detachTermContainerHandlers()
|
||||||
this.config.enabledServices(this.decorators).forEach(decorator => {
|
this.config.enabledServices(this.decorators).forEach(decorator => {
|
||||||
try {
|
try {
|
||||||
@ -484,14 +488,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.logger.warn('Decorator attach() throws', e)
|
this.logger.warn('Decorator attach() throws', e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.hotkeysSubscription.unsubscribe()
|
|
||||||
if (this.sessionCloseSubscription) {
|
|
||||||
this.sessionCloseSubscription.unsubscribe()
|
|
||||||
}
|
|
||||||
this.output.complete()
|
this.output.complete()
|
||||||
}
|
|
||||||
|
|
||||||
async destroy (): Promise<void> {
|
|
||||||
super.destroy()
|
super.destroy()
|
||||||
if (this.session?.open) {
|
if (this.session?.open) {
|
||||||
await this.session.destroy()
|
await this.session.destroy()
|
||||||
@ -499,10 +497,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected detachTermContainerHandlers (): void {
|
protected detachTermContainerHandlers (): void {
|
||||||
for (const subscription of this.termContainerSubscriptions) {
|
this.termContainerSubscriptions.cancelAll()
|
||||||
subscription.unsubscribe()
|
|
||||||
}
|
|
||||||
this.termContainerSubscriptions = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected attachTermContainerHandlers (): void {
|
protected attachTermContainerHandlers (): void {
|
||||||
@ -518,71 +513,69 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.termContainerSubscriptions = [
|
this.termContainerSubscriptions.subscribe(this.frontend.title$, title => this.zone.run(() => {
|
||||||
this.frontend.title$.subscribe(title => this.zone.run(() => {
|
if (this.enableDynamicTitle) {
|
||||||
if (this.enableDynamicTitle) {
|
this.setTitle(title)
|
||||||
this.setTitle(title)
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.termContainerSubscriptions.subscribe(this.focused$, () => this.frontend && (this.frontend.enableResizing = true))
|
||||||
|
this.termContainerSubscriptions.subscribe(this.blurred$, () => this.frontend && (this.frontend.enableResizing = false))
|
||||||
|
|
||||||
|
this.termContainerSubscriptions.subscribe(this.frontend.mouseEvent$, async event => {
|
||||||
|
if (event.type === 'mousedown') {
|
||||||
|
if (event.which === 2) {
|
||||||
|
if (this.config.store.terminal.pasteOnMiddleClick) {
|
||||||
|
this.paste()
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
})),
|
if (event.which === 3 || event.which === 1 && event.ctrlKey) {
|
||||||
|
if (this.config.store.terminal.rightClick === 'menu') {
|
||||||
this.focused$.subscribe(() => this.frontend && (this.frontend.enableResizing = true)),
|
this.hostApp.popupContextMenu(await this.buildContextMenu())
|
||||||
this.blurred$.subscribe(() => this.frontend && (this.frontend.enableResizing = false)),
|
} else if (this.config.store.terminal.rightClick === 'paste') {
|
||||||
|
this.paste()
|
||||||
this.frontend.mouseEvent$.subscribe(async event => {
|
|
||||||
if (event.type === 'mousedown') {
|
|
||||||
if (event.which === 2) {
|
|
||||||
if (this.config.store.terminal.pasteOnMiddleClick) {
|
|
||||||
this.paste()
|
|
||||||
}
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.which === 3 || event.which === 1 && event.ctrlKey) {
|
|
||||||
if (this.config.store.terminal.rightClick === 'menu') {
|
|
||||||
this.hostApp.popupContextMenu(await this.buildContextMenu())
|
|
||||||
} else if (this.config.store.terminal.rightClick === 'paste') {
|
|
||||||
this.paste()
|
|
||||||
}
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (event.type === 'mousewheel') {
|
}
|
||||||
let wheelDeltaY = 0
|
if (event.type === 'mousewheel') {
|
||||||
|
let wheelDeltaY = 0
|
||||||
|
|
||||||
if ('wheelDeltaY' in event) {
|
if ('wheelDeltaY' in event) {
|
||||||
wheelDeltaY = (event as MouseWheelEvent)['wheelDeltaY']
|
wheelDeltaY = (event as MouseWheelEvent)['wheelDeltaY']
|
||||||
} else {
|
} else {
|
||||||
wheelDeltaY = (event as MouseWheelEvent)['deltaY']
|
wheelDeltaY = (event as MouseWheelEvent)['deltaY']
|
||||||
}
|
|
||||||
|
|
||||||
if (event.altKey) {
|
|
||||||
event.preventDefault()
|
|
||||||
const delta = Math.round(wheelDeltaY / 50)
|
|
||||||
this.sendInput((delta > 0 ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
|
|
||||||
this.frontend.input$.subscribe(data => {
|
if (event.altKey) {
|
||||||
this.sendInput(data)
|
event.preventDefault()
|
||||||
}),
|
const delta = Math.round(wheelDeltaY / 50)
|
||||||
|
this.sendInput((delta > 0 ? '\u001bOA' : '\u001bOB').repeat(Math.abs(delta)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.frontend.resize$.subscribe(({ columns, rows }) => {
|
this.termContainerSubscriptions.subscribe(this.frontend.input$, data => {
|
||||||
this.logger.debug(`Resizing to ${columns}x${rows}`)
|
this.sendInput(data)
|
||||||
this.size = { columns, rows }
|
})
|
||||||
this.zone.run(() => {
|
|
||||||
if (this.session?.open) {
|
|
||||||
this.session.resize(columns, rows)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
|
|
||||||
this.hostApp.displayMetricsChanged$.subscribe(maybeConfigure),
|
this.termContainerSubscriptions.subscribe(this.frontend.resize$, ({ columns, rows }) => {
|
||||||
this.hostApp.windowMoved$.subscribe(maybeConfigure),
|
this.logger.debug(`Resizing to ${columns}x${rows}`)
|
||||||
]
|
this.size = { columns, rows }
|
||||||
|
this.zone.run(() => {
|
||||||
|
if (this.session?.open) {
|
||||||
|
this.session.resize(columns, rows)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.termContainerSubscriptions.subscribe(this.hostApp.displayMetricsChanged$, maybeConfigure)
|
||||||
|
this.termContainerSubscriptions.subscribe(this.hostApp.windowMoved$, maybeConfigure)
|
||||||
}
|
}
|
||||||
|
|
||||||
setSession (session: BaseSession|null, destroyOnSessionClose = false): void {
|
setSession (session: BaseSession|null, destroyOnSessionClose = false): void {
|
||||||
@ -600,8 +593,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.sessionChanged.next(session)
|
this.sessionChanged.next(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected attachSessionHandler (subscription: Subscription): void {
|
protected attachSessionHandler <T> (observable: Observable<T>, handler: (v: T) => void): void {
|
||||||
this.sessionHandlers.push(subscription)
|
this.sessionHandlers.subscribe(observable, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected attachSessionHandlers (destroyOnSessionClose = false): void {
|
protected attachSessionHandlers (destroyOnSessionClose = false): void {
|
||||||
@ -610,29 +603,26 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
// this.session.output$.bufferTime(10).subscribe((datas) => {
|
||||||
this.attachSessionHandler(this.session.output$.subscribe(data => {
|
this.attachSessionHandler(this.session.output$, data => {
|
||||||
if (this.enablePassthrough) {
|
if (this.enablePassthrough) {
|
||||||
this.output.next(data)
|
this.output.next(data)
|
||||||
this.write(data)
|
this.write(data)
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
|
||||||
if (destroyOnSessionClose) {
|
if (destroyOnSessionClose) {
|
||||||
this.attachSessionHandler(this.sessionCloseSubscription = this.session.closed$.subscribe(() => {
|
this.attachSessionHandler(this.session.closed$, () => {
|
||||||
this.frontend?.destroy()
|
this.frontend?.destroy()
|
||||||
this.destroy()
|
this.destroy()
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.attachSessionHandler(this.session.destroyed$.subscribe(() => {
|
this.attachSessionHandler(this.session.destroyed$, () => {
|
||||||
this.setSession(null)
|
this.setSession(null)
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected detachSessionHandlers (): void {
|
protected detachSessionHandlers (): void {
|
||||||
for (const s of this.sessionHandlers) {
|
this.sessionHandlers.cancelAll()
|
||||||
s.unsubscribe()
|
|
||||||
}
|
|
||||||
this.sessionHandlers = []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Component, Input, Injector } from '@angular/core'
|
import { Component, Input, Injector } from '@angular/core'
|
||||||
import { Subscription } from 'rxjs'
|
|
||||||
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'terminus-core'
|
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'terminus-core'
|
||||||
import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
|
import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
|
||||||
import { SessionOptions } from '../api/interfaces'
|
import { SessionOptions } from '../api/interfaces'
|
||||||
@ -14,7 +13,6 @@ import { Session } from '../services/sessions.service'
|
|||||||
})
|
})
|
||||||
export class TerminalTabComponent extends BaseTerminalTabComponent {
|
export class TerminalTabComponent extends BaseTerminalTabComponent {
|
||||||
@Input() sessionOptions: SessionOptions
|
@Input() sessionOptions: SessionOptions
|
||||||
private homeEndSubscription: Subscription
|
|
||||||
session: Session|null = null
|
session: Session|null = null
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||||
@ -30,7 +28,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
|
|
||||||
const isConPTY = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY
|
const isConPTY = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY
|
||||||
|
|
||||||
this.homeEndSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.subscribeUntilDestroyed(this.hotkeys.matchedHotkey, hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -106,7 +104,6 @@ export class TerminalTabComponent extends BaseTerminalTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy (): void {
|
ngOnDestroy (): void {
|
||||||
this.homeEndSubscription.unsubscribe()
|
|
||||||
super.ngOnDestroy()
|
super.ngOnDestroy()
|
||||||
this.session?.destroy()
|
this.session?.destroy()
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export class DebugDecorator extends TerminalDecorator {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
terminal.content.nativeElement.addEventListener('keyup', e => {
|
terminal.addEventListenerUntilDestroyed(terminal.content.nativeElement, 'keyup', (e: KeyboardEvent) => {
|
||||||
// Ctrl-Shift-Alt-1
|
// Ctrl-Shift-Alt-1
|
||||||
if (e.which === 49 && e.ctrlKey && e.shiftKey && e.altKey) {
|
if (e.which === 49 && e.ctrlKey && e.shiftKey && e.altKey) {
|
||||||
this.doSaveState(terminal)
|
this.doSaveState(terminal)
|
||||||
|
@ -34,7 +34,9 @@ export class XTermFrontend extends Frontend {
|
|||||||
private fitAddon = new FitAddon()
|
private fitAddon = new FitAddon()
|
||||||
private serializeAddon = new SerializeAddon()
|
private serializeAddon = new SerializeAddon()
|
||||||
private ligaturesAddon?: LigaturesAddon
|
private ligaturesAddon?: LigaturesAddon
|
||||||
|
private webGLAddon?: WebglAddon
|
||||||
private opened = false
|
private opened = false
|
||||||
|
private resizeObserver?: any
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
super()
|
super()
|
||||||
@ -141,7 +143,8 @@ export class XTermFrontend extends Frontend {
|
|||||||
await new Promise(resolve => setTimeout(resolve, process.env.XWEB ? 1000 : 0))
|
await new Promise(resolve => setTimeout(resolve, process.env.XWEB ? 1000 : 0))
|
||||||
|
|
||||||
if (this.enableWebGL) {
|
if (this.enableWebGL) {
|
||||||
this.xterm.loadAddon(new WebglAddon())
|
this.webGLAddon = new WebglAddon()
|
||||||
|
this.xterm.loadAddon(this.webGLAddon)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ready.next()
|
this.ready.next()
|
||||||
@ -160,12 +163,19 @@ export class XTermFrontend extends Frontend {
|
|||||||
host.addEventListener('mouseup', event => this.mouseEvent.next(event))
|
host.addEventListener('mouseup', event => this.mouseEvent.next(event))
|
||||||
host.addEventListener('mousewheel', event => this.mouseEvent.next(event as MouseEvent))
|
host.addEventListener('mousewheel', event => this.mouseEvent.next(event as MouseEvent))
|
||||||
|
|
||||||
const ro = new window['ResizeObserver'](() => setTimeout(() => this.resizeHandler()))
|
this.resizeObserver = new window['ResizeObserver'](() => setTimeout(() => this.resizeHandler()))
|
||||||
ro.observe(host)
|
this.resizeObserver.observe(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
detach (_host: HTMLElement): void {
|
detach (_host: HTMLElement): void {
|
||||||
window.removeEventListener('resize', this.resizeHandler)
|
window.removeEventListener('resize', this.resizeHandler)
|
||||||
|
this.resizeObserver?.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy (): void {
|
||||||
|
super.destroy()
|
||||||
|
this.webGLAddon?.dispose()
|
||||||
|
this.xterm.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelection (): string {
|
getSelection (): string {
|
||||||
|
@ -7,7 +7,7 @@ const bundleAnalyzer = new BundleAnalyzerPlugin({
|
|||||||
|
|
||||||
module.exports = options => {
|
module.exports = options => {
|
||||||
const isDev = !!process.env.TERMINUS_DEV
|
const isDev = !!process.env.TERMINUS_DEV
|
||||||
const devtool = isDev && process.platform === 'win32' ? 'eval-cheap-module-source-map' : 'cheap-module-source-map'
|
const devtool = process.env.WEBPACK_DEVTOOL ?? (isDev && process.platform === 'win32' ? 'eval-cheap-module-source-map' : 'cheap-module-source-map')
|
||||||
const config = {
|
const config = {
|
||||||
target: 'node',
|
target: 'node',
|
||||||
entry: 'src/index.ts',
|
entry: 'src/index.ts',
|
||||||
|
Loading…
Reference in New Issue
Block a user