Преглед изворни кода

fixed #5193 - all-tabs broadcast mode

Eugene Pankov пре 3 година
родитељ
комит
1f5ed218f9

+ 15 - 0
tabby-core/src/components/appRoot.component.scss

@@ -185,3 +185,18 @@ hotkey-hint {
 ::ng-deep .btn-update svg {
     fill: cyan;
 }
+
+::ng-deep .broadcast-status-warning {
+    background: red;
+    position: absolute;
+    top: 0;
+    left: 50%;
+    padding: 5px 10px;
+    color: black;
+    border-radius: 0 0 5px 5px;
+
+    width: 300px;
+    margin-left: -150px;
+    text-align: center;
+    font-weight: bold;
+}

+ 1 - 1
tabby-core/src/configDefaults.macos.yaml

@@ -17,7 +17,7 @@ hotkeys:
   move-tab-right:
     - '⌘-Shift-Right'
   rearrange-panes:
-    - '-Shift'
+    - 'Ctrl-Shift'
   tab-1:
     - '⌘-1'
   tab-2:

+ 13 - 3
tabby-local/src/tabContextMenu.ts

@@ -1,6 +1,7 @@
 import { Injectable } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, SplitTabComponent, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent, TranslateService } from 'tabby-core'
+import { MultifocusService } from 'tabby-terminal'
 import { TerminalTabComponent } from './components/terminalTab.component'
 import { UACService } from './services/uac.service'
 import { TerminalService } from './services/terminal.service'
@@ -65,6 +66,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
         private terminalService: TerminalService,
         private uac: UACService,
         private translate: TranslateService,
+        private multifocus: MultifocusService,
     ) {
         super()
     }
@@ -131,13 +133,21 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
             })
         }
 
-        if (tab instanceof TerminalTabComponent && tab.parent instanceof SplitTabComponent && tab.parent.getAllTabs().length > 1) {
+        if (tab instanceof TerminalTabComponent && tab.parent instanceof SplitTabComponent) {
             items.push({
-                label: this.translate.instant('Focus all panes'),
+                label: this.translate.instant('Focus all tabs'),
                 click: () => {
-                    tab.focusAllPanes()
+                    this.multifocus.focusAllTabs()
                 },
             })
+            if (tab.parent.getAllTabs().length > 1) {
+                items.push({
+                    label: this.translate.instant('Focus all panes'),
+                    click: () => {
+                        this.multifocus.focusAllPanes()
+                    },
+                })
+            }
         }
 
         return items

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

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
-import { BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, HostAppService, Platform, MenuItemOptions, TranslateService } from 'tabby-core'
+import { BaseTabComponent, TabContextMenuItemProvider, HostAppService, Platform, MenuItemOptions, TranslateService } from 'tabby-core'
 import { SSHTabComponent } from './components/sshTab.component'
 import { SSHService } from './services/ssh.service'
 
@@ -17,7 +17,7 @@ export class SFTPContextMenu extends TabContextMenuItemProvider {
         super()
     }
 
-    async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
+    async getItems (tab: BaseTabComponent): Promise<MenuItemOptions[]> {
         if (!(tab instanceof SSHTabComponent) || !tab.profile) {
             return []
         }

+ 6 - 37
tabby-terminal/src/api/baseTerminalTab.component.ts

@@ -1,4 +1,4 @@
-import { Observable, Subject, Subscription, first, auditTime } from 'rxjs'
+import { Observable, Subject, first, auditTime } from 'rxjs'
 import { Spinner } from 'cli-spinner'
 import colors from 'ansi-colors'
 import { NgZone, OnInit, OnDestroy, Injector, ViewChild, HostBinding, Input, ElementRef, InjectFlags } from '@angular/core'
@@ -12,6 +12,7 @@ import { XTermFrontend, XTermWebGLFrontend } from '../frontends/xtermFrontend'
 import { ResizeEvent } from './interfaces'
 import { TerminalDecorator } from './decorator'
 import { SearchPanelComponent } from '../components/searchPanel.component'
+import { MultifocusService } from '../services/multifocus.service'
 
 /**
  * A class to base your custom terminal tabs on
@@ -117,6 +118,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
     protected contextMenuProviders: TabContextMenuItemProvider[]
     protected hostWindow: HostWindowService
     protected translate: TranslateService
+    protected multifocus: MultifocusService
     // Deps end
 
     protected logger: Logger
@@ -124,7 +126,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
     protected sessionChanged = new Subject<BaseSession|null>()
     private bellPlayer: HTMLAudioElement
     private termContainerSubscriptions = new SubscriptionContainer()
-    private allFocusModeSubscription: Subscription|null = null
     private sessionHandlers = new SubscriptionContainer()
     private spinner = new Spinner({
         stream: {
@@ -187,6 +188,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
         this.contextMenuProviders = injector.get<any>(TabContextMenuItemProvider, null, InjectFlags.Optional) as TabContextMenuItemProvider[]
         this.hostWindow = injector.get(HostWindowService)
         this.translate = injector.get(TranslateService)
+        this.multifocus = injector.get(MultifocusService)
 
         this.logger = this.log.create('baseTerminalTab')
         this.setTitle(this.translate.instant('Terminal'))
@@ -279,9 +281,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
                         }[this.hostApp.platform])
                     })
                     break
-                case 'pane-focus-all':
-                    this.focusAllPanes()
-                    break
                 case 'copy-current-path':
                     this.copyCurrentPath()
                     break
@@ -387,7 +386,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
         this.frontend.focus()
 
         this.blurred$.subscribe(() => {
-            this.cancelFocusAllPanes()
+            this.multifocus.cancel()
         })
     }
 
@@ -533,36 +532,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
         this.frontend?.setZoom(this.zoom)
     }
 
-    focusAllPanes (): void {
-        if (this.allFocusModeSubscription) {
-            return
-        }
-        if (this.parent instanceof SplitTabComponent) {
-            const parent = this.parent
-            parent._allFocusMode = true
-            parent.layout()
-            this.allFocusModeSubscription = this.frontend?.input$.subscribe(data => {
-                for (const tab of parent.getAllTabs()) {
-                    if (tab !== this && tab instanceof BaseTerminalTabComponent) {
-                        tab.sendInput(data)
-                    }
-                }
-            }) ?? null
-        }
-    }
-
-    cancelFocusAllPanes (): void {
-        if (!this.allFocusModeSubscription) {
-            return
-        }
-        if (this.parent instanceof SplitTabComponent) {
-            this.allFocusModeSubscription.unsubscribe()
-            this.allFocusModeSubscription = null
-            this.parent._allFocusMode = false
-            this.parent.layout()
-        }
-    }
-
     async copyCurrentPath (): Promise<void> {
         let cwd: string|null = null
         if (this.session?.supportsWorkingDirectory()) {
@@ -666,7 +635,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
         this.termContainerSubscriptions.subscribe(this.frontend.mouseEvent$, event => {
             if (event.type === 'mousedown') {
                 if (event.which === 1) {
-                    this.cancelFocusAllPanes()
+                    this.multifocus.cancel()
                 }
                 if (event.which === 2) {
                     if (this.config.store.terminal.pasteOnMiddleClick) {

+ 9 - 0
tabby-terminal/src/config.ts

@@ -115,6 +115,9 @@ export class TerminalConfigProvider extends ConfigProvider {
                 'pane-focus-all': [
                     '⌘-Shift-I',
                 ],
+                'focus-all-tabs': [
+                    '⌘-⌥-Shift-I',
+                ],
                 'scroll-to-top': ['Shift-PageUp'],
                 'scroll-up': ['⌥-PageUp'],
                 'scroll-down': ['⌥-PageDown'],
@@ -163,6 +166,9 @@ export class TerminalConfigProvider extends ConfigProvider {
                 'pane-focus-all': [
                     'Ctrl-Shift-I',
                 ],
+                'focus-all-tabs': [
+                    'Ctrl-Alt-Shift-I',
+                ],
                 'scroll-to-top': ['Ctrl-PageUp'],
                 'scroll-up': ['Alt-PageUp'],
                 'scroll-down': ['Alt-PageDown'],
@@ -209,6 +215,9 @@ export class TerminalConfigProvider extends ConfigProvider {
                 'pane-focus-all': [
                     'Ctrl-Shift-I',
                 ],
+                'focus-all-tabs': [
+                    'Ctrl-Alt-Shift-I',
+                ],
                 'scroll-to-top': ['Ctrl-PageUp'],
                 'scroll-up': ['Alt-PageUp'],
                 'scroll-down': ['Alt-PageDown'],

+ 4 - 0
tabby-terminal/src/hotkeys.ts

@@ -77,6 +77,10 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
             id: 'pane-focus-all',
             name: this.translate.instant('Focus all panes at once (broadcast)'),
         },
+        {
+            id: 'focus-all-tabs',
+            name: this.translate.instant('Focus all tabs at once (broadcast)'),
+        },
         {
             id: 'scroll-to-top',
             name: this.translate.instant('Scroll terminal to top'),

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

@@ -96,3 +96,4 @@ export * from './middleware/oscProcessing'
 export * from './api/middleware'
 export * from './session'
 export { LoginScriptsSettingsComponent, StreamProcessingSettingsComponent }
+export { MultifocusService } from './services/multifocus.service'

+ 111 - 0
tabby-terminal/src/services/multifocus.service.ts

@@ -0,0 +1,111 @@
+import { Injectable } from '@angular/core'
+import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
+import { Subscription } from 'rxjs'
+import { SplitTabComponent, TranslateService, AppService, HotkeysService } from 'tabby-core'
+
+@Injectable({ providedIn: 'root' })
+export class MultifocusService {
+    private inputSubscription: Subscription|null = null
+    private currentTab: BaseTerminalTabComponent|null = null
+    private warningElement: HTMLElement
+
+    constructor (
+        private app: AppService,
+        hotkeys: HotkeysService,
+        translate: TranslateService,
+    ) {
+        this.warningElement = document.createElement('div')
+        this.warningElement.className = 'broadcast-status-warning'
+        this.warningElement.innerText = translate.instant('Broadcast mode. Click anywhere to cancel.')
+        this.warningElement.style.display = 'none'
+        document.body.appendChild(this.warningElement)
+
+        hotkeys.hotkey$.subscribe(hotkey => {
+            switch (hotkey) {
+                case 'focus-all-tabs':
+                    this.focusAllTabs()
+                    break
+                case 'pane-focus-all':
+                    this.focusAllPanes()
+                    break
+            }
+        })
+    }
+
+    start (currentTab: BaseTerminalTabComponent, tabs: BaseTerminalTabComponent[]): void {
+        if (this.inputSubscription) {
+            return
+        }
+
+        if (currentTab.parent instanceof SplitTabComponent) {
+            const parent = currentTab.parent
+            parent._allFocusMode = true
+            parent.layout()
+        }
+
+        this.currentTab = currentTab
+        this.inputSubscription = currentTab.frontend?.input$.subscribe(data => {
+            for (const tab of tabs) {
+                if (tab !== currentTab) {
+                    tab.sendInput(data)
+                }
+            }
+        }) ?? null
+    }
+
+    cancel (): void {
+        this.warningElement.style.display = 'none'
+        document.querySelector('app-root')!['style'].border = 'none'
+
+        if (!this.inputSubscription) {
+            return
+        }
+        this.inputSubscription.unsubscribe()
+        this.inputSubscription = null
+        if (this.currentTab?.parent instanceof SplitTabComponent) {
+            this.currentTab.parent._allFocusMode = false
+            this.currentTab.parent.layout()
+        }
+        this.currentTab = null
+    }
+
+    focusAllTabs (): void {
+        let currentTab = this.app.activeTab
+        if (currentTab && currentTab instanceof SplitTabComponent) {
+            currentTab = currentTab.getFocusedTab()
+        }
+        if (!currentTab || !(currentTab instanceof BaseTerminalTabComponent)) {
+            return
+        }
+        const tabs = this.app.tabs
+            .map((t => {
+                if (t instanceof BaseTerminalTabComponent) {
+                    return [t]
+                } else if (t instanceof SplitTabComponent) {
+                    return t.getAllTabs()
+                        .filter(x => x instanceof BaseTerminalTabComponent)
+                } else {
+                    return []
+                }
+            }) as (_) => BaseTerminalTabComponent[])
+            .flat()
+        this.start(currentTab, tabs)
+
+        this.warningElement.style.display = 'block'
+        document.querySelector('app-root')!['style'].border = '5px solid red'
+    }
+
+    focusAllPanes (): void {
+        const currentTab = this.app.activeTab
+        if (!currentTab || !(currentTab instanceof SplitTabComponent)) {
+            return
+        }
+
+        const pane = currentTab.getFocusedTab()
+        if (!pane || !(pane instanceof BaseTerminalTabComponent)) {
+            return
+        }
+        const tabs = currentTab.getAllTabs().filter(t => t instanceof BaseTerminalTabComponent)
+        this.start(pane, tabs as any)
+    }
+}