Browse Source

Merge pull request #8416 from Clem-Fern/connectable-refactoring

Refactoring connectable tab
Eugene 2 years ago
parent
commit
fac6ec572f

+ 11 - 39
tabby-serial/src/components/serialTab.component.ts

@@ -2,9 +2,8 @@
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
 import colors from 'ansi-colors'
 import { Component, Injector } from '@angular/core'
-import { first } from 'rxjs'
-import { GetRecoveryTokenOptions, Platform, SelectorService } from 'tabby-core'
-import { BaseTerminalTabComponent, Reconnectable } from 'tabby-terminal'
+import { Platform, SelectorService } from 'tabby-core'
+import { BaseTerminalTabComponent, ConnectableTerminalTabComponent } from 'tabby-terminal'
 import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
 
 /** @hidden */
@@ -14,7 +13,7 @@ import { SerialSession, BAUD_RATES, SerialProfile } from '../api'
     styleUrls: ['./serialTab.component.scss', ...BaseTerminalTabComponent.styles],
     animations: BaseTerminalTabComponent.animations,
 })
-export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile> implements Reconnectable {
+export class SerialTabComponent extends ConnectableTerminalTabComponent<SerialProfile> {
     session: SerialSession|null = null
     Platform = Platform
 
@@ -28,8 +27,6 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
     }
 
     ngOnInit () {
-        this.logger = this.log.create('terminalTab')
-
         this.subscribeUntilDestroyed(this.hotkeys.hotkey$, hotkey => {
             if (!this.hasFocus) {
                 return
@@ -54,12 +51,9 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
         })
     }
 
-    protected onFrontendReady (): void {
-        this.initializeSession()
-        super.onFrontendReady()
-    }
-
     async initializeSession () {
+        super.initializeSession()
+
         const session = new SerialSession(this.injector, this.profile)
         this.setSession(session)
 
@@ -82,38 +76,16 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
             this.write(`\r\n${colors.black.bgWhite(' Serial ')} ${msg}\r\n`)
             this.session?.resize(this.size.columns, this.size.rows)
         })
-        this.attachSessionHandler(this.session!.destroyed$, () => {
-            if (this.frontend) {
-                // Session was closed abruptly
-                this.write('\r\n' + colors.black.bgWhite(' SERIAL ') + ` session closed\r\n`)
-
-                if (this.profile.behaviorOnSessionEnd === 'reconnect') {
-                    this.reconnect()
-                } else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
-                    this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
-                    this.input$.pipe(first()).subscribe(() => {
-                        if (!this.session?.open) {
-                            this.reconnect()
-                        }
-                    })
-                }
-            }
-        })
         super.attachSessionHandlers()
     }
 
-    async getRecoveryToken (options?: GetRecoveryTokenOptions): Promise<any> {
-        return {
-            type: 'app:serial-tab',
-            profile: this.profile,
-            savedState: options?.includeState && this.frontend?.saveState(),
-        }
-    }
+    protected onSessionDestroyed (): void {
+        if (this.frontend) {
+            // Session was closed abruptly
+            this.write('\r\n' + colors.black.bgWhite(' SERIAL ') + ` session closed\r\n`)
 
-    async reconnect (): Promise<void> {
-        this.session?.destroy()
-        await this.initializeSession()
-        this.session?.releaseInitialDataBuffer()
+            super.onSessionDestroyed()
+        }
     }
 
     async changeBaudRate () {

+ 10 - 49
tabby-ssh/src/components/sshTab.component.ts

@@ -2,9 +2,8 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
 import colors from 'ansi-colors'
 import { Component, Injector, HostListener } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { first } from 'rxjs'
-import { GetRecoveryTokenOptions, Platform, ProfilesService, RecoveryToken } from 'tabby-core'
-import { BaseTerminalTabComponent, Reconnectable } from 'tabby-terminal'
+import { Platform, ProfilesService } from 'tabby-core'
+import { BaseTerminalTabComponent, ConnectableTerminalTabComponent } from 'tabby-terminal'
 import { SSHService } from '../services/ssh.service'
 import { KeyboardInteractivePrompt, SSHSession } from '../session/ssh'
 import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
@@ -22,7 +21,7 @@ import { SSHMultiplexerService } from '../services/sshMultiplexer.service'
     ],
     animations: BaseTerminalTabComponent.animations,
 })
-export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implements Reconnectable {
+export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile> {
     Platform = Platform
     sshSession: SSHSession|null = null
     session: SSHShellSession|null = null
@@ -30,7 +29,6 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
     sftpPath = '/'
     enableToolbar = true
     activeKIPrompt: KeyboardInteractivePrompt|null = null
-    private reconnectOffered = false
 
     constructor (
         injector: Injector,
@@ -46,8 +44,6 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
     }
 
     ngOnInit (): void {
-        this.logger = this.log.create('terminalTab')
-
         this.subscribeUntilDestroyed(this.hotkeys.hotkey$, hotkey => {
             if (!this.hasFocus) {
                 return
@@ -73,11 +69,6 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
         super.ngOnInit()
     }
 
-    protected onFrontendReady (): void {
-        this.initializeSession()
-        super.onFrontendReady()
-    }
-
     async setupOneSession (injector: Injector, profile: SSHProfile, multiplex = true): Promise<SSHSession> {
         let session = await this.sshMultiplexer.getSession(profile)
         if (!multiplex || !session || !profile.options.reuseSession) {
@@ -150,29 +141,13 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
         return session
     }
 
-    protected attachSessionHandlers (): void {
-        const session = this.session!
-        this.attachSessionHandler(session.destroyed$, () => {
-            if (this.frontend) {
-                // Session was closed abruptly
-                this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` ${this.sshSession?.profile.options.host}: session closed\r\n`)
+    protected onSessionDestroyed (): void {
+        if (this.frontend) {
+            // Session was closed abruptly
+            this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` ${this.sshSession?.profile.options.host}: session closed\r\n`)
 
-                if (this.profile.behaviorOnSessionEnd === 'reconnect') {
-                    this.reconnect()
-                } else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
-                    if (!this.reconnectOffered) {
-                        this.reconnectOffered = true
-                        this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
-                        this.input$.pipe(first()).subscribe(() => {
-                            if (!this.session?.open && this.reconnectOffered) {
-                                this.reconnect()
-                            }
-                        })
-                    }
-                }
-            }
-        })
-        super.attachSessionHandlers()
+            super.onSessionDestroyed()
+        }
     }
 
     private async initializeSessionMaybeMultiplex (multiplex = true): Promise<void> {
@@ -196,7 +171,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
     }
 
     async initializeSession (): Promise<void> {
-        this.reconnectOffered = false
+        await super.initializeSession()
         try {
             await this.initializeSessionMaybeMultiplex(true)
         } catch {
@@ -209,25 +184,11 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
         }
     }
 
-    async getRecoveryToken (options?: GetRecoveryTokenOptions): Promise<RecoveryToken> {
-        return {
-            type: 'app:ssh-tab',
-            profile: this.profile,
-            savedState: options?.includeState && this.frontend?.saveState(),
-        }
-    }
-
     showPortForwarding (): void {
         const modal = this.ngbModal.open(SSHPortForwardingModalComponent).componentInstance as SSHPortForwardingModalComponent
         modal.session = this.sshSession!
     }
 
-    async reconnect (): Promise<void> {
-        this.session?.destroy()
-        await this.initializeSession()
-        this.session?.releaseInitialDataBuffer()
-    }
-
     async canClose (): Promise<boolean> {
         if (!this.session?.open) {
             return true

+ 10 - 49
tabby-telnet/src/components/telnetTab.component.ts

@@ -1,9 +1,8 @@
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
 import colors from 'ansi-colors'
 import { Component, Injector } from '@angular/core'
-import { first } from 'rxjs'
-import { GetRecoveryTokenOptions, Platform, RecoveryToken } from 'tabby-core'
-import { BaseTerminalTabComponent, Reconnectable } from 'tabby-terminal'
+import { Platform } from 'tabby-core'
+import { BaseTerminalTabComponent, ConnectableTerminalTabComponent } from 'tabby-terminal'
 import { TelnetProfile, TelnetSession } from '../session'
 
 
@@ -14,10 +13,9 @@ import { TelnetProfile, TelnetSession } from '../session'
     styleUrls: ['./telnetTab.component.scss', ...BaseTerminalTabComponent.styles],
     animations: BaseTerminalTabComponent.animations,
 })
-export class TelnetTabComponent extends BaseTerminalTabComponent<TelnetProfile> implements Reconnectable {
+export class TelnetTabComponent extends ConnectableTerminalTabComponent<TelnetProfile> {
     Platform = Platform
     session: TelnetSession|null = null
-    private reconnectOffered = false
 
     // eslint-disable-next-line @typescript-eslint/no-useless-constructor
     constructor (
@@ -28,8 +26,6 @@ export class TelnetTabComponent extends BaseTerminalTabComponent<TelnetProfile>
     }
 
     ngOnInit (): void {
-        this.logger = this.log.create('telnetTab')
-
         this.subscribeUntilDestroyed(this.hotkeys.hotkey$, hotkey => {
             if (this.hasFocus && hotkey === 'restart-telnet-session') {
                 this.reconnect()
@@ -39,38 +35,17 @@ export class TelnetTabComponent extends BaseTerminalTabComponent<TelnetProfile>
         super.ngOnInit()
     }
 
-    protected onFrontendReady (): void {
-        this.initializeSession()
-        super.onFrontendReady()
-    }
-
-    protected attachSessionHandlers (): void {
-        const session = this.session!
-        this.attachSessionHandler(session.destroyed$, () => {
-            if (this.frontend) {
-                // Session was closed abruptly
-                this.write('\r\n' + colors.black.bgWhite(' TELNET ') + ` ${this.session?.profile.options.host}: session closed\r\n`)
+    protected onSessionDestroyed (): void {
+        if (this.frontend) {
+            // Session was closed abruptly
+            this.write('\r\n' + colors.black.bgWhite(' TELNET ') + ` ${this.session?.profile.options.host}: session closed\r\n`)
 
-                if (this.profile.behaviorOnSessionEnd === 'reconnect') {
-                    this.reconnect()
-                } else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
-                    if (!this.reconnectOffered) {
-                        this.reconnectOffered = true
-                        this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
-                        this.input$.pipe(first()).subscribe(() => {
-                            if (!this.session?.open && this.reconnectOffered) {
-                                this.reconnect()
-                            }
-                        })
-                    }
-                }
-            }
-        })
-        super.attachSessionHandlers()
+            super.onSessionDestroyed()
+        }
     }
 
     async initializeSession (): Promise<void> {
-        this.reconnectOffered = false
+        await super.initializeSession()
 
         const session = new TelnetSession(this.injector, this.profile)
         this.setSession(session)
@@ -96,20 +71,6 @@ export class TelnetTabComponent extends BaseTerminalTabComponent<TelnetProfile>
         }
     }
 
-    async getRecoveryToken (options?: GetRecoveryTokenOptions): Promise<RecoveryToken> {
-        return {
-            type: 'app:telnet-tab',
-            profile: this.profile,
-            savedState: options?.includeState && this.frontend?.saveState(),
-        }
-    }
-
-    async reconnect (): Promise<void> {
-        this.session?.destroy()
-        await this.initializeSession()
-        this.session?.releaseInitialDataBuffer()
-    }
-
     async canClose (): Promise<boolean> {
         if (!this.session?.open) {
             return true

+ 9 - 7
tabby-terminal/src/api/baseTerminalTab.component.ts

@@ -9,7 +9,7 @@ import { BaseSession } from '../session'
 
 import { Frontend } from '../frontends/frontend'
 import { XTermFrontend, XTermWebGLFrontend } from '../frontends/xtermFrontend'
-import { ResizeEvent, BaseTerminalProfile, isReconnectable } from './interfaces'
+import { ResizeEvent, BaseTerminalProfile } from './interfaces'
 import { TerminalDecorator } from './decorator'
 import { SearchPanelComponent } from '../components/searchPanel.component'
 import { MultifocusService } from '../services/multifocus.service'
@@ -312,11 +312,6 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
                 case 'scroll-to-bottom':
                     this.frontend?.scrollToBottom()
                     break
-                case 'reconnect-tab':
-                    if (isReconnectable(this)) {
-                        this.reconnect()
-                    }
-                    break
             }
         })
 
@@ -784,7 +779,7 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
         })
 
         this.attachSessionHandler(this.session.destroyed$, () => {
-            this.setSession(null)
+            this.onSessionDestroyed()
         })
 
         this.attachSessionHandler(this.session.oscProcessor.copyRequested$, content => {
@@ -793,6 +788,13 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
         })
     }
 
+    /**
+     * Method called when session is destroyed. Set the session to null
+     */
+    protected onSessionDestroyed (): void {
+        this.setSession(null)
+    }
+
     protected detachSessionHandlers (): void {
         this.sessionHandlers.cancelAll()
     }

+ 94 - 0
tabby-terminal/src/api/connectableTerminalTab.component.ts

@@ -0,0 +1,94 @@
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
+
+import { Injector, Component } from '@angular/core'
+
+import { first } from 'rxjs'
+
+import { BaseTerminalProfile } from './interfaces'
+import { BaseTerminalTabComponent } from './baseTerminalTab.component'
+import { GetRecoveryTokenOptions, RecoveryToken } from 'tabby-core'
+
+
+/**
+ * A class to base your custom connectable terminal tabs on
+ */
+@Component({ template: '' })
+export abstract class ConnectableTerminalTabComponent<P extends BaseTerminalProfile> extends BaseTerminalTabComponent<P> {
+
+    protected reconnectOffered = false
+
+    constructor (protected injector: Injector) {
+        super(injector)
+
+        this.subscribeUntilDestroyed(this.hotkeys.hotkey$, hotkey => {
+            if (this.hasFocus && hotkey === 'reconnect-tab') {
+                this.reconnect()
+            }
+        })
+    }
+
+    ngOnInit (): void {
+        this.logger = this.log.create(`${this.profile.type}Tab`)
+
+        super.ngOnInit()
+    }
+
+    protected onFrontendReady (): void {
+        this.initializeSession()
+        super.onFrontendReady()
+    }
+
+    /**
+    * Initialize Connectable Session.
+    * Set reconnectOffered to false
+    */
+    async initializeSession (): Promise<void> {
+        this.reconnectOffered = false
+    }
+
+    /**
+    * Method called when session is destroyed. Handle the tab behavior on session end for connectable tab
+    */
+    protected onSessionDestroyed (): void {
+        super.onSessionDestroyed()
+
+        if (this.frontend) {
+            if (this.profile.behaviorOnSessionEnd === 'reconnect') {
+                this.reconnect()
+            } else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
+                this.offerReconnection()
+            }
+        }
+    }
+
+    /**
+    * Offering reconnection to the user if it hasn't been done yet.
+    * Set reconnectOffered to true
+    */
+    offerReconnection (): void {
+        if (!this.reconnectOffered) {
+            this.reconnectOffered = true
+            this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
+            this.input$.pipe(first()).subscribe(() => {
+                if (!this.session?.open && this.reconnectOffered) {
+                    this.reconnect()
+                }
+            })
+        }
+    }
+
+    async getRecoveryToken (options?: GetRecoveryTokenOptions): Promise<RecoveryToken> {
+        return {
+            type: `app:${this.profile.type}-tab`,
+            profile: this.profile,
+            savedState: options?.includeState && this.frontend?.saveState(),
+        }
+    }
+
+    async reconnect (): Promise<void> {
+        this.session?.destroy()
+        await this.initializeSession()
+        this.session?.releaseInitialDataBuffer()
+    }
+
+}

+ 0 - 9
tabby-terminal/src/api/interfaces.ts

@@ -19,12 +19,3 @@ export interface TerminalColorScheme {
 export interface BaseTerminalProfile extends Profile {
     terminalColorScheme?: TerminalColorScheme
 }
-
-export interface Reconnectable {
-    reconnect: () => Promise<void>;
-}
-
-// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-export function isReconnectable (object: any): object is Reconnectable {
-    return 'reconnect' in object
-}

+ 1 - 0
tabby-terminal/src/index.ts

@@ -90,6 +90,7 @@ export default class TerminalModule { } // eslint-disable-line @typescript-eslin
 export { TerminalDecorator, TerminalContextMenuItemProvider, TerminalColorSchemeProvider }
 export { Frontend, XTermFrontend, XTermWebGLFrontend }
 export { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
+export { ConnectableTerminalTabComponent } from './api/connectableTerminalTab.component'
 export * from './api/interfaces'
 export * from './middleware/streamProcessing'
 export * from './middleware/loginScriptProcessing'

+ 2 - 2
tabby-terminal/src/tabContextMenu.ts

@@ -1,9 +1,9 @@
 import { Injectable, Optional, Inject } from '@angular/core'
 import { BaseTabComponent, TabContextMenuItemProvider, NotificationsService, MenuItemOptions, TranslateService, SplitTabComponent } from 'tabby-core'
 import { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
-import { isReconnectable } from './api/interfaces'
 import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
 import { MultifocusService } from './services/multifocus.service'
+import { ConnectableTerminalTabComponent } from './api/connectableTerminalTab.component'
 
 /** @hidden */
 @Injectable()
@@ -97,7 +97,7 @@ export class ReconnectContextMenu extends TabContextMenuItemProvider {
     ) { super() }
 
     async getItems (tab: BaseTabComponent): Promise<MenuItemOptions[]> {
-        if (isReconnectable(tab)) {
+        if (tab instanceof ConnectableTerminalTabComponent) {
             return [
                 {
                     label: this.translate.instant('Reconnect'),