mirror of
https://github.com/Eugeny/tabby.git
synced 2024-11-24 06:04:04 +03:00
hotkeys for specific shells
This commit is contained in:
parent
cc610e158e
commit
9b6a09129c
@ -5,4 +5,6 @@ export interface IHotkeyDescription {
|
|||||||
|
|
||||||
export abstract class HotkeyProvider {
|
export abstract class HotkeyProvider {
|
||||||
hotkeys: IHotkeyDescription[] = []
|
hotkeys: IHotkeyDescription[] = []
|
||||||
|
|
||||||
|
abstract provide (): Promise<IHotkeyDescription[]>
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,19 @@ import { HostAppService } from './hostApp.service'
|
|||||||
|
|
||||||
const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s })
|
const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s })
|
||||||
|
|
||||||
|
function isStructuralMember (v) {
|
||||||
|
return v instanceof Object && !(v instanceof Array) &&
|
||||||
|
Object.keys(v).length > 0 && !v.__nonStructural
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNonStructuralObjectMember (v) {
|
||||||
|
return v instanceof Object && !(v instanceof Array) && v.__nonStructural
|
||||||
|
}
|
||||||
|
|
||||||
export class ConfigProxy {
|
export class ConfigProxy {
|
||||||
constructor (real: any, defaults: any) {
|
constructor (real: any, defaults: any) {
|
||||||
for (let key in defaults) {
|
for (let key in defaults) {
|
||||||
if (
|
if (isStructuralMember(defaults[key])) {
|
||||||
defaults[key] instanceof Object &&
|
|
||||||
!(defaults[key] instanceof Array) &&
|
|
||||||
Object.keys(defaults[key]).length > 0 &&
|
|
||||||
!defaults[key].__nonStructural
|
|
||||||
) {
|
|
||||||
if (!real[key]) {
|
if (!real[key]) {
|
||||||
real[key] = {}
|
real[key] = {}
|
||||||
}
|
}
|
||||||
@ -38,15 +42,36 @@ export class ConfigProxy {
|
|||||||
{
|
{
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: false,
|
configurable: false,
|
||||||
get: () => (real[key] !== undefined) ? real[key] : defaults[key],
|
get: () => this.getValue(key),
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
real[key] = value
|
this.setValue(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.getValue = (key: string) => {
|
||||||
|
if (real[key] !== undefined) {
|
||||||
|
return real[key]
|
||||||
|
} else {
|
||||||
|
if (isNonStructuralObjectMember(defaults[key])) {
|
||||||
|
real[key] = {...defaults[key]}
|
||||||
|
delete real[key].__nonStructural
|
||||||
|
return real[key]
|
||||||
|
} else {
|
||||||
|
return defaults[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setValue = (key: string, value: any) => {
|
||||||
|
real[key] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getValue (key: string): any { } // tslint:disable-line
|
||||||
|
setValue (key: string, value: any) { } // tslint:disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -24,13 +24,13 @@ export class HotkeysService {
|
|||||||
globalHotkey = new EventEmitter()
|
globalHotkey = new EventEmitter()
|
||||||
private currentKeystrokes: EventBufferEntry[] = []
|
private currentKeystrokes: EventBufferEntry[] = []
|
||||||
private disabledLevel = 0
|
private disabledLevel = 0
|
||||||
private hotkeyDescriptions: IHotkeyDescription[]
|
private hotkeyDescriptions: IHotkeyDescription[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
|
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
|
||||||
) {
|
) {
|
||||||
let events = ['keydown', 'keyup']
|
let events = ['keydown', 'keyup']
|
||||||
events.forEach((event) => {
|
events.forEach((event) => {
|
||||||
@ -42,11 +42,13 @@ export class HotkeysService {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
this.hotkeyDescriptions = this.config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
|
||||||
this.config.changed$.subscribe(() => {
|
this.config.changed$.subscribe(() => {
|
||||||
this.registerGlobalHotkey()
|
this.registerGlobalHotkey()
|
||||||
})
|
})
|
||||||
this.registerGlobalHotkey()
|
this.registerGlobalHotkey()
|
||||||
|
this.getHotkeyDescriptions().then(hotkeys => {
|
||||||
|
this.hotkeyDescriptions = hotkeys
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pushKeystroke (name, nativeEvent) {
|
pushKeystroke (name, nativeEvent) {
|
||||||
@ -102,14 +104,25 @@ export class HotkeysService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getHotkeysConfig () {
|
getHotkeysConfig () {
|
||||||
|
return this.getHotkeysConfigRecursive(this.config.store.hotkeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHotkeysConfigRecursive (branch) {
|
||||||
let keys = {}
|
let keys = {}
|
||||||
for (let key in this.config.store.hotkeys) {
|
for (let key in branch) {
|
||||||
let value = this.config.store.hotkeys[key]
|
let value = branch[key]
|
||||||
if (typeof value === 'string') {
|
if (value instanceof Object && !(value instanceof Array)) {
|
||||||
value = [value]
|
let subkeys = this.getHotkeysConfigRecursive(value)
|
||||||
|
for (let subkey in subkeys) {
|
||||||
|
keys[key + '.' + subkey] = subkeys[subkey]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
value = [value]
|
||||||
|
}
|
||||||
|
value = value.map((item) => (typeof item === 'string') ? [item] : item)
|
||||||
|
keys[key] = value
|
||||||
}
|
}
|
||||||
value = value.map((item) => (typeof item === 'string') ? [item] : item)
|
|
||||||
keys[key] = value
|
|
||||||
}
|
}
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
@ -169,6 +182,15 @@ export class HotkeysService {
|
|||||||
isEnabled () {
|
isEnabled () {
|
||||||
return this.disabledLevel === 0
|
return this.disabledLevel === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getHotkeyDescriptions (): Promise<IHotkeyDescription[]> {
|
||||||
|
return (
|
||||||
|
await Promise.all(
|
||||||
|
this.config.enabledServices(this.hotkeyProviders)
|
||||||
|
.map(async x => x.provide ? x.provide() : x.hotkeys)
|
||||||
|
)
|
||||||
|
).reduce((a, b) => a.concat(b))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -243,4 +265,8 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
name: 'Tab 10',
|
name: 'Tab 10',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
async provide (): Promise<IHotkeyDescription[]> {
|
||||||
|
return this.hotkeys
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,8 +244,8 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
td {{hotkey.id}}
|
td {{hotkey.id}}
|
||||||
td
|
td
|
||||||
multi-hotkey-input(
|
multi-hotkey-input(
|
||||||
[(model)]='config.store.hotkeys[hotkey.id]',
|
[model]='getHotkey(hotkey.id)',
|
||||||
(modelChange)='config.save(); docking.dock()'
|
(modelChange)='setHotkey(hotkey.id, $event); config.save(); docking.dock()'
|
||||||
)
|
)
|
||||||
|
|
||||||
ngb-tab(*ngFor='let provider of settingsProviders', [id]='provider.id')
|
ngb-tab(*ngFor='let provider of settingsProviders', [id]='provider.id')
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { Component, Inject, Input } from '@angular/core'
|
import { Component, Inject, Input } from '@angular/core'
|
||||||
|
import { HotkeysService } from 'terminus-core'
|
||||||
import {
|
import {
|
||||||
ElectronService,
|
ElectronService,
|
||||||
DockingService,
|
DockingService,
|
||||||
@ -43,12 +44,12 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
public hostApp: HostAppService,
|
public hostApp: HostAppService,
|
||||||
public homeBase: HomeBaseService,
|
public homeBase: HomeBaseService,
|
||||||
public shellIntegration: ShellIntegrationService,
|
public shellIntegration: ShellIntegrationService,
|
||||||
|
hotkeys: HotkeysService,
|
||||||
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
|
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
|
||||||
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
|
||||||
@Inject(Theme) public themes: Theme[],
|
@Inject(Theme) public themes: Theme[],
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
|
||||||
this.setTitle('Settings')
|
this.setTitle('Settings')
|
||||||
this.screens = this.docking.getScreens()
|
this.screens = this.docking.getScreens()
|
||||||
this.settingsProviders = config.enabledServices(this.settingsProviders)
|
this.settingsProviders = config.enabledServices(this.settingsProviders)
|
||||||
@ -59,6 +60,10 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
this.configSubscription = config.changed$.subscribe(() => {
|
this.configSubscription = config.changed$.subscribe(() => {
|
||||||
this.configFile = config.readRaw()
|
this.configFile = config.readRaw()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
hotkeys.getHotkeyDescriptions().then(descriptions => {
|
||||||
|
this.hotkeyDescriptions = descriptions
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
@ -98,4 +103,22 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
await this.shellIntegration.install()
|
await this.shellIntegration.install()
|
||||||
this.isShellIntegrationInstalled = true
|
this.isShellIntegrationInstalled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHotkey (id: string) {
|
||||||
|
let ptr = this.config.store.hotkeys
|
||||||
|
for (let token of id.split(/\./g)) {
|
||||||
|
ptr = ptr[token]
|
||||||
|
}
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
setHotkey (id: string, value) {
|
||||||
|
let ptr = this.config.store
|
||||||
|
let prop = 'hotkeys'
|
||||||
|
for (let token of id.split(/\./g)) {
|
||||||
|
ptr = ptr[prop]
|
||||||
|
prop = token
|
||||||
|
}
|
||||||
|
ptr[prop] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,11 @@ import { ConfigProvider, Platform } from 'terminus-core'
|
|||||||
|
|
||||||
export class TerminalConfigProvider extends ConfigProvider {
|
export class TerminalConfigProvider extends ConfigProvider {
|
||||||
defaults = {
|
defaults = {
|
||||||
|
hotkeys: {
|
||||||
|
shell: {
|
||||||
|
__nonStructural: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
terminal: {
|
terminal: {
|
||||||
frontend: 'hterm',
|
frontend: 'hterm',
|
||||||
autoOpen: false,
|
autoOpen: false,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { IHotkeyDescription, HotkeyProvider } from 'terminus-core'
|
import { IHotkeyDescription, HotkeyProvider } from 'terminus-core'
|
||||||
|
import { TerminalService } from './services/terminal.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TerminalHotkeyProvider extends HotkeyProvider {
|
export class TerminalHotkeyProvider extends HotkeyProvider {
|
||||||
@ -61,4 +62,16 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
|
|||||||
name: 'Intelligent Ctrl-C (copy/abort)',
|
name: 'Intelligent Ctrl-C (copy/abort)',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private terminal: TerminalService,
|
||||||
|
) { super() }
|
||||||
|
|
||||||
|
async provide (): Promise<IHotkeyDescription[]> {
|
||||||
|
let shells = await this.terminal.shells$.toPromise()
|
||||||
|
return this.hotkeys.concat(shells.map(shell => ({
|
||||||
|
id: `shell.${shell.id}`,
|
||||||
|
name: `New tab: ${shell.name}`
|
||||||
|
})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,11 +148,16 @@ export default class TerminalModule {
|
|||||||
if (hotkey === 'new-tab') {
|
if (hotkey === 'new-tab') {
|
||||||
terminal.openTab()
|
terminal.openTab()
|
||||||
}
|
}
|
||||||
})
|
|
||||||
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
|
||||||
if (hotkey === 'new-window') {
|
if (hotkey === 'new-window') {
|
||||||
hostApp.newWindow()
|
hostApp.newWindow()
|
||||||
}
|
}
|
||||||
|
if (hotkey.startsWith('shell.')) {
|
||||||
|
let shells = await terminal.shells$
|
||||||
|
let shell = shells.find(x => x.id === hotkey.split('.')[1])
|
||||||
|
if (shell) {
|
||||||
|
terminal.openTab(shell)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
hostApp.cliOpenDirectory$.subscribe(async directory => {
|
hostApp.cliOpenDirectory$.subscribe(async directory => {
|
||||||
if (await fs.exists(directory)) {
|
if (await fs.exists(directory)) {
|
||||||
|
@ -27,10 +27,14 @@ export class TerminalService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getShells (): Promise<IShell[]> {
|
||||||
|
let shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
|
||||||
|
return shellLists.reduce((a, b) => a.concat(b))
|
||||||
|
}
|
||||||
|
|
||||||
async reloadShells () {
|
async reloadShells () {
|
||||||
this.shells = new AsyncSubject<IShell[]>()
|
this.shells = new AsyncSubject<IShell[]>()
|
||||||
let shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
|
this.shells.next(await this.getShells())
|
||||||
this.shells.next(shellLists.reduce((a, b) => a.concat(b)))
|
|
||||||
this.shells.complete()
|
this.shells.complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export class CustomShellProvider extends ShellProvider {
|
|||||||
let args = this.config.store.terminal.customShell.split(' ')
|
let args = this.config.store.terminal.customShell.split(' ')
|
||||||
return [{
|
return [{
|
||||||
id: 'custom',
|
id: 'custom',
|
||||||
name: 'Custom',
|
name: 'Custom shell',
|
||||||
command: args[0],
|
command: args[0],
|
||||||
args: args.slice(1),
|
args: args.slice(1),
|
||||||
}]
|
}]
|
||||||
|
Loading…
Reference in New Issue
Block a user