Skip to content

Commit

Permalink
fixed #5193 - all-tabs broadcast mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Eugeny committed Nov 1, 2022
1 parent 8b408bd commit 1f5ed21
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 43 deletions.
15 changes: 15 additions & 0 deletions tabby-core/src/components/appRoot.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 1 addition & 1 deletion tabby-core/src/configDefaults.macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ hotkeys:
move-tab-right:
- '⌘-Shift-Right'
rearrange-panes:
- '-Shift'
- 'Ctrl-Shift'
tab-1:
- '⌘-1'
tab-2:
Expand Down
16 changes: 13 additions & 3 deletions tabby-local/src/tabContextMenu.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -65,6 +66,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
private terminalService: TerminalService,
private uac: UACService,
private translate: TranslateService,
private multifocus: MultifocusService,
) {
super()
}
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tabby-ssh/src/tabContextMenu.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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 []
}
Expand Down
43 changes: 6 additions & 37 deletions tabby-terminal/src/api/baseTerminalTab.component.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand Down Expand Up @@ -117,14 +118,14 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
protected contextMenuProviders: TabContextMenuItemProvider[]
protected hostWindow: HostWindowService
protected translate: TranslateService
protected multifocus: MultifocusService
// Deps end

protected logger: Logger
protected output = new Subject<string>()
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: {
Expand Down Expand Up @@ -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'))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -387,7 +386,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.frontend.focus()

this.blurred$.subscribe(() => {
this.cancelFocusAllPanes()
this.multifocus.cancel()
})
}

Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 9 additions & 0 deletions tabby-terminal/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down Expand Up @@ -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'],
Expand Down Expand Up @@ -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'],
Expand Down
4 changes: 4 additions & 0 deletions tabby-terminal/src/hotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
1 change: 1 addition & 0 deletions tabby-terminal/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 111 additions & 0 deletions tabby-terminal/src/services/multifocus.service.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 1f5ed21

Please sign in to comment.