1
1
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:
Eugene Pankov 2018-10-12 17:59:22 +02:00
parent cc610e158e
commit 9b6a09129c
10 changed files with 128 additions and 25 deletions

View File

@ -5,4 +5,6 @@ export interface IHotkeyDescription {
export abstract class HotkeyProvider { export abstract class HotkeyProvider {
hotkeys: IHotkeyDescription[] = [] hotkeys: IHotkeyDescription[] = []
abstract provide (): Promise<IHotkeyDescription[]>
} }

View File

@ -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()

View File

@ -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
}
} }

View File

@ -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')

View File

@ -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
}
} }

View File

@ -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,

View File

@ -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}`
})))
}
} }

View File

@ -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)) {

View File

@ -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()
} }

View File

@ -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),
}] }]