mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-22 08:16:09 +08:00
205 lines
5.7 KiB
JavaScript
205 lines
5.7 KiB
JavaScript
import fs from 'fs-plus'
|
|
import path from 'path'
|
|
import mousetrap from 'mousetrap'
|
|
import {ipcRenderer} from 'electron'
|
|
import {Emitter, Disposable} from 'event-kit'
|
|
|
|
let suspended = false
|
|
const templateConfigKey = 'core.keymapTemplate'
|
|
|
|
/*
|
|
By default, Mousetrap stops all hotkeys within text inputs. Override this to
|
|
more specifically block only hotkeys that have no modifier keys (things like
|
|
Gmail's "x", while allowing standard hotkeys.)
|
|
*/
|
|
mousetrap.prototype.stopCallback = (e, element, combo) => {
|
|
if (suspended) {
|
|
return true;
|
|
}
|
|
const withinTextInput = element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.isContentEditable
|
|
const withinWebview = element.tagName === 'WEBVIEW';
|
|
if (withinWebview) {
|
|
return true;
|
|
}
|
|
if (withinTextInput) {
|
|
const isPlainKey = !/(mod|command|ctrl)/.test(combo);
|
|
const isReservedTextEditingShortcut = /(mod|command|ctrl)\+(a|x|c|v)/.test(combo);
|
|
return isPlainKey || isReservedTextEditingShortcut;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class KeymapFile {
|
|
constructor(manager, filePath) {
|
|
this._manager = manager;
|
|
this._path = filePath;
|
|
this._bindings = {};
|
|
this._disposable = null;
|
|
}
|
|
|
|
load = () => {
|
|
let keymaps = null;
|
|
try {
|
|
keymaps = JSON.parse(fs.readFileSync(this._path))
|
|
} catch (e) {
|
|
if (e.code === 'ENOENT') {
|
|
return;
|
|
}
|
|
console.error(e);
|
|
return;
|
|
}
|
|
|
|
this._bindings = {};
|
|
Object.keys(keymaps).forEach((command) => {
|
|
let keystrokesArray = keymaps[command];
|
|
if (!(keystrokesArray instanceof Array)) {
|
|
keystrokesArray = [keystrokesArray];
|
|
}
|
|
for (const keystrokes of keystrokesArray) {
|
|
this._manager.ensureKeystrokesRegistered(keystrokes);
|
|
this._bindings[command] = this._bindings[command] || [];
|
|
this._bindings[command].push(keystrokes);
|
|
}
|
|
});
|
|
this._manager.keymapCacheInvalidated();
|
|
}
|
|
|
|
watch() {
|
|
fs.watch(this._path, this.load);
|
|
}
|
|
|
|
bindings() {
|
|
return this._bindings;
|
|
}
|
|
}
|
|
|
|
export default class KeymapManager {
|
|
|
|
constructor({configDirPath, resourcePath}) {
|
|
this.configDirPath = configDirPath;
|
|
this.resourcePath = resourcePath;
|
|
this._emitter = new Emitter();
|
|
this._registered = {};
|
|
this._files = [];
|
|
}
|
|
|
|
getUserKeymapPath() {
|
|
return path.join(this.configDirPath, 'keymap.json');
|
|
}
|
|
|
|
suspendAllKeymaps() {
|
|
NylasEnv.menu.sendToBrowserProcess(NylasEnv.menu.template, {});
|
|
suspended = true;
|
|
}
|
|
|
|
resumeAllKeymaps() {
|
|
NylasEnv.menu.update();
|
|
suspended = false;
|
|
}
|
|
|
|
loadKeymaps = () => {
|
|
// Load the base keymap and the base.platform keymap
|
|
this.loadKeymap(path.join(this.resourcePath, 'keymaps', 'base.json'))
|
|
this.loadKeymap(path.join(this.resourcePath, 'keymaps', `base-${process.platform}.json`))
|
|
|
|
// Load the template keymap (Gmail, Mail.app, etc.) the user has chosen
|
|
if (this._unobserveTemplate) {
|
|
this._unobserveTemplate.dispose();
|
|
}
|
|
this._unobserveTemplate = NylasEnv.config.observe(templateConfigKey, this.loadTemplateKeymap);
|
|
|
|
const userKeymapPath = this.getUserKeymapPath()
|
|
if (!fs.existsSync(userKeymapPath)) {
|
|
fs.writeFileSync(userKeymapPath, "{}");
|
|
}
|
|
this.userKeymap = new KeymapFile(this, userKeymapPath)
|
|
this.userKeymap.load()
|
|
this.userKeymap.watch()
|
|
}
|
|
|
|
loadTemplateKeymap = () => {
|
|
if (this._removeTemplate) {
|
|
this._removeTemplate.dispose();
|
|
}
|
|
let templateFile = NylasEnv.config.get(templateConfigKey);
|
|
if (templateFile) {
|
|
templateFile = templateFile.replace("GoogleInbox", "Inbox by Gmail");
|
|
const templateKeymapPath = path.join(this.resourcePath, 'keymaps', 'templates', `${templateFile}.json`);
|
|
this._removeTemplate = this.loadKeymap(templateKeymapPath);
|
|
}
|
|
}
|
|
|
|
loadKeymap(filePath) {
|
|
const file = new KeymapFile(this, filePath);
|
|
this._files.push(file);
|
|
file.load();
|
|
|
|
return new Disposable(() => {
|
|
this._files = this._files.filter(f => f !== file);
|
|
this.keymapCacheInvalidated();
|
|
});
|
|
}
|
|
|
|
ensureKeystrokesRegistered(keystrokes) {
|
|
if (this._registered[keystrokes]) {
|
|
return;
|
|
}
|
|
this._registered[keystrokes] = true;
|
|
|
|
mousetrap.bind(keystrokes, () => {
|
|
for (const command of (this._commandsCache[keystrokes] || [])) {
|
|
if (command.startsWith('application:')) {
|
|
ipcRenderer.send('command', command);
|
|
} else {
|
|
NylasEnv.commands.dispatch(command);
|
|
}
|
|
}
|
|
return false
|
|
});
|
|
}
|
|
|
|
keymapCacheInvalidated() {
|
|
this._bindingsCache = {};
|
|
|
|
for (const file of this._files) {
|
|
const fileBindings = file.bindings();
|
|
for (const command of Object.keys(fileBindings)) {
|
|
const keystrokesArray = fileBindings[command];
|
|
this._bindingsCache[command] = (this._bindingsCache[command] || []).concat(keystrokesArray);
|
|
}
|
|
}
|
|
if (this.userKeymap) {
|
|
const userBindings = this.userKeymap.bindings();
|
|
for (const command of Object.keys(userBindings)) {
|
|
this._bindingsCache[command] = userBindings[command];
|
|
}
|
|
}
|
|
|
|
this._commandsCache = {};
|
|
for (const command of Object.keys(this._bindingsCache)) {
|
|
for (const keystrokes of this._bindingsCache[command]) {
|
|
if (!this._commandsCache[keystrokes]) {
|
|
this._commandsCache[keystrokes] = [];
|
|
}
|
|
if (!this._commandsCache[keystrokes].includes(command)) {
|
|
this._commandsCache[keystrokes].push(command);
|
|
}
|
|
}
|
|
}
|
|
|
|
this._emitter.emit('on-did-reload-keymap');
|
|
}
|
|
|
|
onDidReloadKeymap = (callback) => {
|
|
return this._emitter.on('on-did-reload-keymap', callback);
|
|
}
|
|
|
|
getBindingsForAllCommands() {
|
|
return this._bindingsCache;
|
|
}
|
|
|
|
getBindingsForCommand(command) {
|
|
return this._bindingsCache[command] || [];
|
|
}
|
|
}
|