mirror of
https://github.com/Eugeny/tabby
synced 2024-11-15 01:17:14 +00:00
fixed #5193 - all-tabs broadcast mode
This commit is contained in:
parent
8b408bdcbc
commit
1f5ed218f9
9 changed files with 162 additions and 43 deletions
|
@ -185,3 +185,18 @@ hotkey-hint {
|
||||||
::ng-deep .btn-update svg {
|
::ng-deep .btn-update svg {
|
||||||
fill: cyan;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ hotkeys:
|
||||||
move-tab-right:
|
move-tab-right:
|
||||||
- '⌘-Shift-Right'
|
- '⌘-Shift-Right'
|
||||||
rearrange-panes:
|
rearrange-panes:
|
||||||
- '⌘-Shift'
|
- 'Ctrl-Shift'
|
||||||
tab-1:
|
tab-1:
|
||||||
- '⌘-1'
|
- '⌘-1'
|
||||||
tab-2:
|
tab-2:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, SplitTabComponent, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent, TranslateService } from 'tabby-core'
|
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 { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
import { UACService } from './services/uac.service'
|
import { UACService } from './services/uac.service'
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
|
@ -65,6 +66,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||||
private terminalService: TerminalService,
|
private terminalService: TerminalService,
|
||||||
private uac: UACService,
|
private uac: UACService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
|
private multifocus: MultifocusService,
|
||||||
) {
|
) {
|
||||||
super()
|
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({
|
items.push({
|
||||||
label: this.translate.instant('Focus all panes'),
|
label: this.translate.instant('Focus all tabs'),
|
||||||
click: () => {
|
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
|
return items
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@angular/core'
|
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 { SSHTabComponent } from './components/sshTab.component'
|
||||||
import { SSHService } from './services/ssh.service'
|
import { SSHService } from './services/ssh.service'
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ export class SFTPContextMenu extends TabContextMenuItemProvider {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<MenuItemOptions[]> {
|
async getItems (tab: BaseTabComponent): Promise<MenuItemOptions[]> {
|
||||||
if (!(tab instanceof SSHTabComponent) || !tab.profile) {
|
if (!(tab instanceof SSHTabComponent) || !tab.profile) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { Spinner } from 'cli-spinner'
|
||||||
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'
|
||||||
|
@ -12,6 +12,7 @@ import { XTermFrontend, XTermWebGLFrontend } from '../frontends/xtermFrontend'
|
||||||
import { ResizeEvent } from './interfaces'
|
import { ResizeEvent } from './interfaces'
|
||||||
import { TerminalDecorator } from './decorator'
|
import { TerminalDecorator } from './decorator'
|
||||||
import { SearchPanelComponent } from '../components/searchPanel.component'
|
import { SearchPanelComponent } from '../components/searchPanel.component'
|
||||||
|
import { MultifocusService } from '../services/multifocus.service'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to base your custom terminal tabs on
|
* A class to base your custom terminal tabs on
|
||||||
|
@ -117,6 +118,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||||
protected contextMenuProviders: TabContextMenuItemProvider[]
|
protected contextMenuProviders: TabContextMenuItemProvider[]
|
||||||
protected hostWindow: HostWindowService
|
protected hostWindow: HostWindowService
|
||||||
protected translate: TranslateService
|
protected translate: TranslateService
|
||||||
|
protected multifocus: MultifocusService
|
||||||
// Deps end
|
// Deps end
|
||||||
|
|
||||||
protected logger: Logger
|
protected logger: Logger
|
||||||
|
@ -124,7 +126,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||||
protected sessionChanged = new Subject<BaseSession|null>()
|
protected sessionChanged = new Subject<BaseSession|null>()
|
||||||
private bellPlayer: HTMLAudioElement
|
private bellPlayer: HTMLAudioElement
|
||||||
private termContainerSubscriptions = new SubscriptionContainer()
|
private termContainerSubscriptions = new SubscriptionContainer()
|
||||||
private allFocusModeSubscription: Subscription|null = null
|
|
||||||
private sessionHandlers = new SubscriptionContainer()
|
private sessionHandlers = new SubscriptionContainer()
|
||||||
private spinner = new Spinner({
|
private spinner = new Spinner({
|
||||||
stream: {
|
stream: {
|
||||||
|
@ -187,6 +188,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||||
this.contextMenuProviders = injector.get<any>(TabContextMenuItemProvider, null, InjectFlags.Optional) as TabContextMenuItemProvider[]
|
this.contextMenuProviders = injector.get<any>(TabContextMenuItemProvider, null, InjectFlags.Optional) as TabContextMenuItemProvider[]
|
||||||
this.hostWindow = injector.get(HostWindowService)
|
this.hostWindow = injector.get(HostWindowService)
|
||||||
this.translate = injector.get(TranslateService)
|
this.translate = injector.get(TranslateService)
|
||||||
|
this.multifocus = injector.get(MultifocusService)
|
||||||
|
|
||||||
this.logger = this.log.create('baseTerminalTab')
|
this.logger = this.log.create('baseTerminalTab')
|
||||||
this.setTitle(this.translate.instant('Terminal'))
|
this.setTitle(this.translate.instant('Terminal'))
|
||||||
|
@ -279,9 +281,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||||
}[this.hostApp.platform])
|
}[this.hostApp.platform])
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'pane-focus-all':
|
|
||||||
this.focusAllPanes()
|
|
||||||
break
|
|
||||||
case 'copy-current-path':
|
case 'copy-current-path':
|
||||||
this.copyCurrentPath()
|
this.copyCurrentPath()
|
||||||
break
|
break
|
||||||
|
@ -387,7 +386,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||||
this.frontend.focus()
|
this.frontend.focus()
|
||||||
|
|
||||||
this.blurred$.subscribe(() => {
|
this.blurred$.subscribe(() => {
|
||||||
this.cancelFocusAllPanes()
|
this.multifocus.cancel()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,36 +532,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||||
this.frontend?.setZoom(this.zoom)
|
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> {
|
async copyCurrentPath (): Promise<void> {
|
||||||
let cwd: string|null = null
|
let cwd: string|null = null
|
||||||
if (this.session?.supportsWorkingDirectory()) {
|
if (this.session?.supportsWorkingDirectory()) {
|
||||||
|
@ -666,7 +635,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||||
this.termContainerSubscriptions.subscribe(this.frontend.mouseEvent$, event => {
|
this.termContainerSubscriptions.subscribe(this.frontend.mouseEvent$, event => {
|
||||||
if (event.type === 'mousedown') {
|
if (event.type === 'mousedown') {
|
||||||
if (event.which === 1) {
|
if (event.which === 1) {
|
||||||
this.cancelFocusAllPanes()
|
this.multifocus.cancel()
|
||||||
}
|
}
|
||||||
if (event.which === 2) {
|
if (event.which === 2) {
|
||||||
if (this.config.store.terminal.pasteOnMiddleClick) {
|
if (this.config.store.terminal.pasteOnMiddleClick) {
|
||||||
|
|
|
@ -115,6 +115,9 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||||
'pane-focus-all': [
|
'pane-focus-all': [
|
||||||
'⌘-Shift-I',
|
'⌘-Shift-I',
|
||||||
],
|
],
|
||||||
|
'focus-all-tabs': [
|
||||||
|
'⌘-⌥-Shift-I',
|
||||||
|
],
|
||||||
'scroll-to-top': ['Shift-PageUp'],
|
'scroll-to-top': ['Shift-PageUp'],
|
||||||
'scroll-up': ['⌥-PageUp'],
|
'scroll-up': ['⌥-PageUp'],
|
||||||
'scroll-down': ['⌥-PageDown'],
|
'scroll-down': ['⌥-PageDown'],
|
||||||
|
@ -163,6 +166,9 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||||
'pane-focus-all': [
|
'pane-focus-all': [
|
||||||
'Ctrl-Shift-I',
|
'Ctrl-Shift-I',
|
||||||
],
|
],
|
||||||
|
'focus-all-tabs': [
|
||||||
|
'Ctrl-Alt-Shift-I',
|
||||||
|
],
|
||||||
'scroll-to-top': ['Ctrl-PageUp'],
|
'scroll-to-top': ['Ctrl-PageUp'],
|
||||||
'scroll-up': ['Alt-PageUp'],
|
'scroll-up': ['Alt-PageUp'],
|
||||||
'scroll-down': ['Alt-PageDown'],
|
'scroll-down': ['Alt-PageDown'],
|
||||||
|
@ -209,6 +215,9 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||||
'pane-focus-all': [
|
'pane-focus-all': [
|
||||||
'Ctrl-Shift-I',
|
'Ctrl-Shift-I',
|
||||||
],
|
],
|
||||||
|
'focus-all-tabs': [
|
||||||
|
'Ctrl-Alt-Shift-I',
|
||||||
|
],
|
||||||
'scroll-to-top': ['Ctrl-PageUp'],
|
'scroll-to-top': ['Ctrl-PageUp'],
|
||||||
'scroll-up': ['Alt-PageUp'],
|
'scroll-up': ['Alt-PageUp'],
|
||||||
'scroll-down': ['Alt-PageDown'],
|
'scroll-down': ['Alt-PageDown'],
|
||||||
|
|
|
@ -77,6 +77,10 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
|
||||||
id: 'pane-focus-all',
|
id: 'pane-focus-all',
|
||||||
name: this.translate.instant('Focus all panes at once (broadcast)'),
|
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',
|
id: 'scroll-to-top',
|
||||||
name: this.translate.instant('Scroll terminal to top'),
|
name: this.translate.instant('Scroll terminal to top'),
|
||||||
|
|
|
@ -96,3 +96,4 @@ export * from './middleware/oscProcessing'
|
||||||
export * from './api/middleware'
|
export * from './api/middleware'
|
||||||
export * from './session'
|
export * from './session'
|
||||||
export { LoginScriptsSettingsComponent, StreamProcessingSettingsComponent }
|
export { LoginScriptsSettingsComponent, StreamProcessingSettingsComponent }
|
||||||
|
export { MultifocusService } from './services/multifocus.service'
|
||||||
|
|
111
tabby-terminal/src/services/multifocus.service.ts
Normal file
111
tabby-terminal/src/services/multifocus.service.ts
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue