mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-12 20:18:41 -09:00
parent
342580e730
commit
93b2229ff5
24 changed files with 441 additions and 329 deletions
|
|
@ -1,136 +0,0 @@
|
|||
const platform = process.platform;
|
||||
|
||||
const isMac = platform === 'darwin';
|
||||
|
||||
const prefix = isMac ? 'Cmd' : 'Ctrl';
|
||||
|
||||
const applicationMenu = { // app/menu.js
|
||||
preferences: ',',
|
||||
quit: isMac ? 'Q' : '',
|
||||
|
||||
// Shell/File menu
|
||||
newWindow: 'N',
|
||||
newTab: 'T',
|
||||
splitVertically: isMac ? 'D' : 'Shift+E',
|
||||
splitHorizontally: isMac ? 'Shift+D' : 'Shift+O',
|
||||
closeSession: 'W',
|
||||
closeWindow: 'Shift+W',
|
||||
|
||||
// Edit menu
|
||||
undo: 'Z',
|
||||
redo: 'Shift+Z',
|
||||
cut: 'X',
|
||||
copy: isMac ? 'C' : 'Shift+C',
|
||||
paste: 'V',
|
||||
selectAll: 'A',
|
||||
clear: 'K',
|
||||
emojis: isMac ? 'Ctrl+Cmd+Space' : '',
|
||||
|
||||
// View menu
|
||||
reload: 'R',
|
||||
fullReload: 'Shift+R',
|
||||
toggleDevTools: isMac ? 'Alt+I' : 'Shift+I',
|
||||
resetZoom: '0',
|
||||
zoomIn: 'plus',
|
||||
zoomOut: '-',
|
||||
|
||||
// Plugins menu
|
||||
updatePlugins: 'Shift+U',
|
||||
|
||||
// Window menu
|
||||
minimize: 'M',
|
||||
showPreviousTab: 'Alt+Left',
|
||||
showNextTab: 'Alt+Right',
|
||||
selectNextPane: 'Ctrl+Alt+Tab',
|
||||
selectPreviousPane: 'Ctrl+Shift+Alt+Tab',
|
||||
enterFullScreen: isMac ? 'Ctrl+Cmd+F' : 'F11'
|
||||
};
|
||||
|
||||
const mousetrap = { // lib/containers/hyper.js
|
||||
moveTo1: '1',
|
||||
moveTo2: '2',
|
||||
moveTo3: '3',
|
||||
moveTo4: '4',
|
||||
moveTo5: '5',
|
||||
moveTo6: '6',
|
||||
moveTo7: '7',
|
||||
moveTo8: '8',
|
||||
moveToLast: '9',
|
||||
|
||||
// here `1`, `2` etc are used to "emulate" something like `moveLeft: ['...', '...', etc]`
|
||||
moveLeft1: 'Shift+Left',
|
||||
moveRight1: 'Shift+Right',
|
||||
moveLeft2: 'Shift+{',
|
||||
moveRight2: 'Shift+}',
|
||||
moveLeft3: 'Alt+Left',
|
||||
moveRight3: 'Alt+Right',
|
||||
moveLeft4: 'Ctrl+Shift+Tab',
|
||||
moveRight4: 'Ctrl+Tab',
|
||||
|
||||
// here we add `+` at the beginning to prevent the prefix from being added
|
||||
moveWordLeft: '+Alt+Left',
|
||||
moveWordRight: '+Alt+Right',
|
||||
deleteWordLeft: '+Alt+Backspace',
|
||||
deleteWordRight: '+Alt+Delete',
|
||||
deleteLine: 'Backspace',
|
||||
moveToStart: 'Left',
|
||||
moveToEnd: 'Right',
|
||||
selectAll: 'A'
|
||||
};
|
||||
|
||||
const allAccelerators = Object.assign({}, applicationMenu, mousetrap);
|
||||
const cache = [];
|
||||
// ^ here we store the shortcuts so we don't need to
|
||||
// look into the `allAccelerators` everytime
|
||||
|
||||
for (const key in allAccelerators) {
|
||||
if ({}.hasOwnProperty.call(allAccelerators, key)) {
|
||||
let value = allAccelerators[key];
|
||||
if (value) {
|
||||
if (value.startsWith('+')) {
|
||||
// we don't need to add the prefix to accelerators starting with `+`
|
||||
value = value.slice(1);
|
||||
} else if (!value.startsWith('Ctrl')) { // nor to the ones starting with `Ctrl`
|
||||
value = `${prefix}+${value}`;
|
||||
}
|
||||
cache.push(value.toLowerCase());
|
||||
allAccelerators[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// decides if a keybard event is a Hyper Accelerator
|
||||
function isAccelerator(e) {
|
||||
let keys = [];
|
||||
if (!e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||
// all accelerators needs Ctrl or Cmd or Alt
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.ctrlKey) {
|
||||
keys.push('ctrl');
|
||||
}
|
||||
if (e.metaKey && isMac) {
|
||||
keys.push('cmd');
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
keys.push('shift');
|
||||
}
|
||||
if (e.altKey) {
|
||||
keys.push('alt');
|
||||
}
|
||||
|
||||
if (e.key === ' ') {
|
||||
keys.push('space');
|
||||
} else {
|
||||
// we need `toLowerCase` for when the shortcut has `shift`
|
||||
// we need to replace `arrow` when the shortcut uses the arrow keys
|
||||
keys.push(e.key.toLowerCase().replace('arrow', ''));
|
||||
}
|
||||
|
||||
keys = keys.join('+');
|
||||
return cache.includes(keys);
|
||||
}
|
||||
|
||||
module.exports.isAccelerator = isAccelerator;
|
||||
module.exports.accelerators = allAccelerators;
|
||||
131
app/config.js
131
app/config.js
|
|
@ -1,82 +1,31 @@
|
|||
const {statSync, renameSync, readFileSync, writeFileSync} = require('fs');
|
||||
const vm = require('vm');
|
||||
|
||||
const {dialog} = require('electron');
|
||||
const gaze = require('gaze');
|
||||
const Config = require('electron-config');
|
||||
const notify = require('./notify');
|
||||
const _paths = require('./config/paths');
|
||||
const _import = require('./config/import');
|
||||
const _openConfig = require('./config/open');
|
||||
|
||||
// local storage
|
||||
const winCfg = new Config({
|
||||
defaults: {
|
||||
windowPosition: [50, 50],
|
||||
windowSize: [540, 380]
|
||||
}
|
||||
});
|
||||
|
||||
const path = _paths.confPath;
|
||||
const pathLegacy = _paths.pathLegacy;
|
||||
const win = require('./config/windows');
|
||||
const {cfgPath, cfgDir} = require('./config/paths');
|
||||
|
||||
const watchers = [];
|
||||
|
||||
// watch for changes on config every 2s on windows
|
||||
// https://github.com/zeit/hyper/pull/1772
|
||||
const watchCfg = process.platform === 'win32' ? {interval: 2000} : {};
|
||||
let cfg = {};
|
||||
|
||||
function watch() {
|
||||
// watch for changes on config every 2s
|
||||
// windows interval: https://github.com/zeit/hyper/pull/1772
|
||||
gaze(path, process.platform === 'win32' ? {interval: 2000} : {}, function (err) {
|
||||
const _watch = function () {
|
||||
gaze(cfgPath, watchCfg, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
this.on('changed', () => {
|
||||
try {
|
||||
if (exec(readFileSync(path, 'utf8'))) {
|
||||
notify('Hyper configuration reloaded!');
|
||||
watchers.forEach(fn => fn());
|
||||
}
|
||||
} catch (err) {
|
||||
dialog.showMessageBox({
|
||||
message: `An error occurred loading your configuration (${path}): ${err.message}`,
|
||||
buttons: ['Ok']
|
||||
});
|
||||
}
|
||||
cfg = _import();
|
||||
notify('Configuration updated', 'Hyper configuration reloaded!');
|
||||
watchers.forEach(fn => fn());
|
||||
});
|
||||
this.on('error', () => {
|
||||
// Ignore file watching errors
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let _str; // last script
|
||||
function exec(str) {
|
||||
if (str === _str) {
|
||||
return false;
|
||||
}
|
||||
_str = str;
|
||||
const script = new vm.Script(str);
|
||||
const module = {};
|
||||
script.runInNewContext({module});
|
||||
if (!module.exports) {
|
||||
throw new Error('Error reading configuration: `module.exports` not set');
|
||||
}
|
||||
const _cfg = module.exports;
|
||||
if (!_cfg.config) {
|
||||
throw new Error('Error reading configuration: `config` key is missing');
|
||||
}
|
||||
_cfg.plugins = _cfg.plugins || [];
|
||||
_cfg.localPlugins = _cfg.localPlugins || [];
|
||||
cfg = _cfg;
|
||||
return true;
|
||||
}
|
||||
|
||||
// This method will take text formatted as Unix line endings and transform it
|
||||
// to text formatted with DOS line endings. We do this because the default
|
||||
// text editor on Windows (notepad) doesn't Deal with LF files. Still. In 2017.
|
||||
function crlfify(str) {
|
||||
return str.replace(/\r?\n/g, '\r\n');
|
||||
}
|
||||
};
|
||||
|
||||
exports.subscribe = function (fn) {
|
||||
watchers.push(fn);
|
||||
|
|
@ -85,39 +34,9 @@ exports.subscribe = function (fn) {
|
|||
};
|
||||
};
|
||||
|
||||
exports.init = function () {
|
||||
// for backwards compatibility with hyperterm
|
||||
// (prior to the rename), we try to rename
|
||||
// on behalf of the user
|
||||
try {
|
||||
statSync(pathLegacy);
|
||||
renameSync(pathLegacy, path);
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
exec(readFileSync(path, 'utf8'));
|
||||
} catch (err) {
|
||||
console.log('read error', path, err.message);
|
||||
const defaultConfig = readFileSync(_paths.defaultConfig);
|
||||
try {
|
||||
console.log('attempting to write default config to', path);
|
||||
exec(defaultConfig);
|
||||
|
||||
writeFileSync(
|
||||
path,
|
||||
process.platform === 'win32' ? crlfify(defaultConfig.toString()) : defaultConfig);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to write config to ${path}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
watch();
|
||||
};
|
||||
|
||||
exports.getConfigDir = function () {
|
||||
// expose config directory to load plugin from the right place
|
||||
return _paths.confDir;
|
||||
return cfgDir;
|
||||
};
|
||||
|
||||
exports.getConfig = function () {
|
||||
|
|
@ -135,14 +54,20 @@ exports.getPlugins = function () {
|
|||
};
|
||||
};
|
||||
|
||||
exports.window = {
|
||||
get() {
|
||||
const position = winCfg.get('windowPosition');
|
||||
const size = winCfg.get('windowSize');
|
||||
return {position, size};
|
||||
},
|
||||
recordState(win) {
|
||||
winCfg.set('windowPosition', win.getPosition());
|
||||
winCfg.set('windowSize', win.getSize());
|
||||
exports.getKeymaps = function () {
|
||||
return cfg.keymaps;
|
||||
};
|
||||
|
||||
exports.extendKeymaps = function (keymaps) {
|
||||
if (keymaps) {
|
||||
cfg.keymaps = keymaps;
|
||||
}
|
||||
};
|
||||
|
||||
exports.setup = function () {
|
||||
cfg = _import();
|
||||
_watch();
|
||||
};
|
||||
|
||||
exports.getWin = win.get;
|
||||
exports.winRecord = win.recordState;
|
||||
|
|
|
|||
41
app/config/import.js
Normal file
41
app/config/import.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
const {writeFileSync, readFileSync} = require('fs');
|
||||
const {defaultCfg, cfgPath} = require('./paths');
|
||||
const _init = require('./init');
|
||||
const _keymaps = require('./keymaps');
|
||||
|
||||
const _write = function (path, data) {
|
||||
// This method will take text formatted as Unix line endings and transform it
|
||||
// to text formatted with DOS line endings. We do this because the default
|
||||
// text editor on Windows (notepad) doesn't Deal with LF files. Still. In 2017.
|
||||
const crlfify = function (str) {
|
||||
return str.replace(/\r?\n/g, '\r\n');
|
||||
};
|
||||
const format = process.platform === 'win32' ? crlfify(data.toString()) : data;
|
||||
writeFileSync(path, format, 'utf8');
|
||||
};
|
||||
|
||||
const _importConf = function () {
|
||||
try {
|
||||
const _defaultCfg = readFileSync(defaultCfg, 'utf8');
|
||||
try {
|
||||
const _cfgPath = readFileSync(cfgPath, 'utf8');
|
||||
return {userCfg: _cfgPath, defaultCfg: _defaultCfg};
|
||||
} catch (err) {
|
||||
_write(cfgPath, defaultCfg);
|
||||
return {userCfg: {}, defaultCfg: _defaultCfg};
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
const _import = function () {
|
||||
const cfg = _init(_importConf());
|
||||
|
||||
if (cfg) {
|
||||
cfg.keymaps = _keymaps.import(cfg.keymaps);
|
||||
}
|
||||
return cfg;
|
||||
};
|
||||
|
||||
module.exports = _import;
|
||||
44
app/config/init.js
Normal file
44
app/config/init.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
const vm = require('vm');
|
||||
const notify = require('../notify');
|
||||
|
||||
const _extract = function (script) {
|
||||
const module = {};
|
||||
script.runInNewContext({module});
|
||||
if (!module.exports) {
|
||||
throw new Error('Error reading configuration: `module.exports` not set');
|
||||
}
|
||||
return module.exports;
|
||||
};
|
||||
|
||||
const _syntaxValidation = function (cfg) {
|
||||
try {
|
||||
return new vm.Script(cfg, {filename: '.hyper.js', displayErrors: true});
|
||||
} catch (err) {
|
||||
notify(`Error loading config: ${err.name}, see DevTools for more info`);
|
||||
console.error('Error loading config:', err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const _extractDefault = function (cfg) {
|
||||
return _extract(_syntaxValidation(cfg));
|
||||
};
|
||||
|
||||
// init config
|
||||
const _init = function (cfg) {
|
||||
const script = _syntaxValidation(cfg.userCfg);
|
||||
if (script) {
|
||||
const _cfg = _extract(script);
|
||||
if (!_cfg.config) {
|
||||
_cfg.plugins = _cfg.plugins || [];
|
||||
_cfg.localPlugins = _cfg.localPlugins || [];
|
||||
_cfg.keymaps = _cfg.keymaps || {};
|
||||
notify('Error reading configuration: `config` key is missing');
|
||||
return _extractDefault(cfg.defaultCfg);
|
||||
}
|
||||
return _cfg;
|
||||
}
|
||||
return _extractDefault(cfg.defaultCfg);
|
||||
};
|
||||
|
||||
module.exports = _init;
|
||||
49
app/config/keymaps.js
Normal file
49
app/config/keymaps.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
const {readFileSync} = require('fs');
|
||||
const {defaultPlatformKeyPath} = require('./paths');
|
||||
|
||||
const commands = {};
|
||||
const keys = {};
|
||||
|
||||
const _setKeysForCommands = function (keymap) {
|
||||
for (const command in keymap) {
|
||||
if (command) {
|
||||
commands[command] = keymap[command].toLowerCase();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _setCommandsForKeys = function (commands) {
|
||||
for (const command in commands) {
|
||||
if (command) {
|
||||
keys[commands[command]] = command;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _import = function (customsKeys) {
|
||||
try {
|
||||
const mapping = JSON.parse(readFileSync(defaultPlatformKeyPath()));
|
||||
_setKeysForCommands(mapping);
|
||||
_setKeysForCommands(customsKeys);
|
||||
_setCommandsForKeys(commands);
|
||||
|
||||
return {commands, keys};
|
||||
} catch (err) {}
|
||||
};
|
||||
|
||||
const _extend = function (customsKeys) {
|
||||
if (customsKeys) {
|
||||
for (const command in customsKeys) {
|
||||
if (command) {
|
||||
commands[command] = customsKeys[command];
|
||||
keys[customsKeys[command]] = command;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {commands, keys};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
import: _import,
|
||||
extend: _extend
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
const {shell} = require('electron');
|
||||
const {confPath} = require('./paths');
|
||||
const {cfgPath} = require('./paths');
|
||||
|
||||
module.exports = () => Promise.resolve(shell.openItem(confPath));
|
||||
module.exports = () => Promise.resolve(shell.openItem(cfgPath));
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const exec = require('child_process').exec;
|
||||
|
|
@ -28,6 +28,6 @@ if (process.platform === 'win32') {
|
|||
});
|
||||
|
||||
module.exports = () => canOpenNative()
|
||||
.then(() => shell.openItem(confPath))
|
||||
.catch(() => openNotepad(confPath));
|
||||
.then(() => shell.openItem(cfgPath))
|
||||
.catch(() => openNotepad(cfgPath));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,48 @@
|
|||
// This module exports paths, names, and other metadata that is referenced
|
||||
const {homedir} = require('os');
|
||||
const {statSync} = require('fs');
|
||||
const {resolve} = require('path');
|
||||
const {resolve, join} = require('path');
|
||||
const isDev = require('electron-is-dev');
|
||||
|
||||
const conf = '.hyper.js';
|
||||
const defaultConf = 'config-default.js';
|
||||
const legacyConf = '.hyperterm.js';
|
||||
const cfgFile = '.hyper.js';
|
||||
const defaultCfgFile = 'config-default.js';
|
||||
const homeDir = homedir();
|
||||
|
||||
let confPath = resolve(homeDir, conf);
|
||||
let confDir = homeDir;
|
||||
let cfgPath = join(homeDir, cfgFile);
|
||||
let cfgDir = homeDir;
|
||||
|
||||
const devDir = resolve(__dirname, '../..');
|
||||
const devConfig = resolve(devDir, conf);
|
||||
const defaultConfig = resolve(__dirname, defaultConf);
|
||||
const pathLegacy = resolve(homeDir, legacyConf);
|
||||
const devCfg = join(devDir, cfgFile);
|
||||
const defaultCfg = resolve(__dirname, defaultCfgFile);
|
||||
|
||||
const icon = resolve(__dirname, '../static/icon.png');
|
||||
|
||||
const keymapPath = resolve(__dirname, '../keymaps');
|
||||
const darwinKeys = join(keymapPath, 'darwin.json');
|
||||
const win32Keys = join(keymapPath, 'win32.json');
|
||||
const linuxKeys = join(keymapPath, 'linux.json');
|
||||
|
||||
const defaultPlatformKeyPath = () => {
|
||||
switch (process.platform) {
|
||||
case 'darwin': return darwinKeys;
|
||||
case 'win32': return win32Keys;
|
||||
case 'linux': return linuxKeys;
|
||||
default: return darwinKeys;
|
||||
}
|
||||
};
|
||||
|
||||
if (isDev) {
|
||||
// if a local config file exists, use it
|
||||
try {
|
||||
statSync(devConfig);
|
||||
confPath = devConfig;
|
||||
confDir = devDir;
|
||||
console.log('using config file:', confPath);
|
||||
statSync(devCfg);
|
||||
cfgPath = devCfg;
|
||||
cfgDir = devDir;
|
||||
console.log('using config file:', cfgPath);
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
pathLegacy, confDir, confPath, conf, defaultConfig, defaultConf, icon
|
||||
cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath
|
||||
};
|
||||
|
|
|
|||
21
app/config/windows.js
Normal file
21
app/config/windows.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const Config = require('electron-config');
|
||||
|
||||
// local storage
|
||||
const cfg = new Config({
|
||||
defaults: {
|
||||
windowPosition: [50, 50],
|
||||
windowSize: [540, 380]
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
get() {
|
||||
const position = cfg.get('windowPosition');
|
||||
const size = cfg.get('windowSize');
|
||||
return {position, size};
|
||||
},
|
||||
recordState(win) {
|
||||
cfg.set('windowPosition', win.getPosition());
|
||||
cfg.set('windowSize', win.getSize());
|
||||
}
|
||||
};
|
||||
10
app/index.js
10
app/index.js
|
|
@ -53,13 +53,12 @@ const AppMenu = require('./menus/menu');
|
|||
const createRPC = require('./rpc');
|
||||
const notify = require('./notify');
|
||||
const fetchNotifications = require('./notifications');
|
||||
const config = require('./config');
|
||||
|
||||
app.commandLine.appendSwitch('js-flags', '--harmony-async-await');
|
||||
|
||||
// set up config
|
||||
const config = require('./config');
|
||||
|
||||
config.init();
|
||||
config.setup();
|
||||
|
||||
const plugins = require('./plugins');
|
||||
const Session = require('./session');
|
||||
|
|
@ -106,7 +105,7 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
|
|||
function createWindow(fn, options = {}) {
|
||||
let cfg = plugins.getDecoratedConfig();
|
||||
|
||||
const winSet = app.config.window.get();
|
||||
const winSet = config.getWin();
|
||||
let [startX, startY] = winSet.position;
|
||||
|
||||
const [width, height] = options.size ? options.size : (cfg.windowSize || winSet.size);
|
||||
|
|
@ -353,7 +352,7 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
|
|||
|
||||
// the window can be closed by the browser process itself
|
||||
win.on('close', () => {
|
||||
app.config.window.recordState(win);
|
||||
config.winRecord(win);
|
||||
windowSet.delete(win);
|
||||
rpc.destroy();
|
||||
deleteSessions();
|
||||
|
|
@ -415,6 +414,7 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
|
|||
|
||||
const load = () => {
|
||||
plugins.onApp(app);
|
||||
plugins.extendKeymaps();
|
||||
makeMenu();
|
||||
};
|
||||
|
||||
|
|
|
|||
31
app/keymaps/darwin.json
Normal file
31
app/keymaps/darwin.json
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"window:devtools": "cmd+alt+i",
|
||||
"window:reload": "cmd+r",
|
||||
"window:reloadFull": "cmd+shift+r",
|
||||
"window:preferences": "cmd+,",
|
||||
"zoom:reset": "cmd+0",
|
||||
"zoom:in": "cmd+plus",
|
||||
"zoom:out": "cmd+-",
|
||||
"window:new": "cmd+n",
|
||||
"window:minimize": "cmd+m",
|
||||
"window:zoom": "ctrl+alt+cmd+m",
|
||||
"window:toggleFullScreen": "cmd+ctrl+f",
|
||||
"window:close": "cmd+shift+w",
|
||||
"tab:new": "cmd+t",
|
||||
"tab:next": "cmd+shift+]",
|
||||
"tab:prev": "cmd+shift+[",
|
||||
"pane:next": "cmd+]",
|
||||
"pane:prev": "cmd+[",
|
||||
"pane:splitVertical": "cmd+d",
|
||||
"pane:splitHorizontal": "cmd+shift+d",
|
||||
"pane:close": "cmd+w",
|
||||
"editor:undo": "cmd+z",
|
||||
"editor:redo": "cmd+y",
|
||||
"editor:cut": "cmd+x",
|
||||
"editor:copy": "cmd+c",
|
||||
"editor:paste": "cmd+v",
|
||||
"editor:selectAll": "cmd+a",
|
||||
"editor:clearBuffer": "cmd+k",
|
||||
"editor:emojis": "cmd+ctrl+space",
|
||||
"plugins:update": "cmd+shift+u"
|
||||
}
|
||||
30
app/keymaps/linux.json
Normal file
30
app/keymaps/linux.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"window:devtools": "ctrl+shift+i",
|
||||
"window:reload": "ctrl+shift+r",
|
||||
"window:reloadFull": "ctrl+shift+f5",
|
||||
"window:preferences": "ctrl+,",
|
||||
"zoom:reset": "ctrl+0",
|
||||
"zoom:in": "ctrl+plus",
|
||||
"zoom:out": "ctrl+-",
|
||||
"window:new": "ctrl+shift+n",
|
||||
"window:minimize": "ctrl+shift+m",
|
||||
"window:zoom": "ctrl+shift+alt+m",
|
||||
"window:toggleFullScreen": "f11",
|
||||
"window:close": "ctrl+shift+w",
|
||||
"tab:new": "ctrl+shift+t",
|
||||
"tab:next": "ctrl+tab",
|
||||
"tab:prev": "ctrl+shift+tab",
|
||||
"pane:next": "ctrl+pageup",
|
||||
"pane:prev": "ctrl+pagedown",
|
||||
"pane:splitVertical": "ctrl+shift+d",
|
||||
"pane:splitHorizontal": "ctrl+shift+e",
|
||||
"pane:close": "ctrl+shift+w",
|
||||
"editor:undo": "ctrl+shift+z",
|
||||
"editor:redo": "ctrl+shift+y",
|
||||
"editor:cut": "ctrl+shift+x",
|
||||
"editor:copy": "ctrl+shift+c",
|
||||
"editor:paste": "ctrl+shift+v",
|
||||
"editor:selectAll": "ctrl+shift+a",
|
||||
"editor:clearBuffer": "ctrl+shift+k",
|
||||
"plugins:update": "ctrl+shift+u"
|
||||
}
|
||||
30
app/keymaps/win32.json
Normal file
30
app/keymaps/win32.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"window:devtools": "ctrl+shift+i",
|
||||
"window:reload": "ctrl+shift+r",
|
||||
"window:reloadFull": "ctrl+shift+f5",
|
||||
"window:preferences": "ctrl+,",
|
||||
"zoom:reset": "ctrl+0",
|
||||
"zoom:in": "ctrl+plus",
|
||||
"zoom:out": "ctrl+-",
|
||||
"window:new": "ctrl+shift+n",
|
||||
"window:minimize": "ctrl+m",
|
||||
"window:zoom": "ctrl+shift+alt+m",
|
||||
"window:toggleFullScreen": "f11",
|
||||
"window:close": "ctrl+shift+w",
|
||||
"tab:new": "ctrl+shift+t",
|
||||
"tab:next": "ctrl+tab",
|
||||
"tab:prev": "ctrl+shift+tab",
|
||||
"pane:next": "ctrl+pageup",
|
||||
"pane:prev": "ctrl+pagedown",
|
||||
"pane:splitVertical": "ctrl+shift+d",
|
||||
"pane:splitHorizontal": "ctrl+shift+e",
|
||||
"pane:close": "ctrl+shift+w",
|
||||
"editor:undo": "ctrl+shift+z",
|
||||
"editor:redo": "ctrl+shift+y",
|
||||
"editor:cut": "ctrl+shift+x",
|
||||
"editor:copy": "ctrl+shift+c",
|
||||
"editor:paste": "ctrl+shift+v",
|
||||
"editor:selectAll": "ctrl+shift+a",
|
||||
"editor:clearBuffer": "ctrl+shift+k",
|
||||
"plugins:update": "ctrl+shift+u"
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
const {getKeymaps} = require('../config');
|
||||
|
||||
// menus
|
||||
const viewMenu = require('./menus/view');
|
||||
const shellMenu = require('./menus/shell');
|
||||
|
|
@ -8,20 +10,16 @@ const helpMenu = require('./menus/help');
|
|||
const darwinMenu = require('./menus/darwin');
|
||||
|
||||
module.exports = (createWindow, updatePlugins) => {
|
||||
const menu = [].concat(
|
||||
shellMenu(createWindow),
|
||||
editMenu(),
|
||||
viewMenu(),
|
||||
pluginsMenu(updatePlugins),
|
||||
windowMenu(),
|
||||
helpMenu()
|
||||
);
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
menu.unshift(
|
||||
darwinMenu()
|
||||
);
|
||||
}
|
||||
const commands = getKeymaps().commands;
|
||||
const menu = [
|
||||
(process.platform === 'darwin' ? darwinMenu(commands) : []),
|
||||
shellMenu(commands, createWindow),
|
||||
editMenu(commands),
|
||||
viewMenu(commands),
|
||||
pluginsMenu(commands, updatePlugins),
|
||||
windowMenu(commands),
|
||||
helpMenu(commands)
|
||||
];
|
||||
|
||||
return menu;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// This menu label is overrided by OSX to be the appName
|
||||
// The label is set to appName here so it matches actual behavior
|
||||
const {app} = require('electron');
|
||||
const {accelerators} = require('../../accelerators');
|
||||
const {openConfig} = require('../../config');
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function (commands) {
|
||||
return {
|
||||
label: `${app.getName()}`,
|
||||
submenu: [
|
||||
|
|
@ -16,7 +15,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
label: 'Preferences...',
|
||||
accelerator: accelerators.preferences,
|
||||
accelerator: commands['window:preferences'],
|
||||
click() {
|
||||
openConfig();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,40 @@
|
|||
const {accelerators} = require('../../accelerators');
|
||||
const {openConfig} = require('../../config');
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function (commands) {
|
||||
const submenu = [
|
||||
{
|
||||
role: 'undo',
|
||||
accelerator: accelerators.undo
|
||||
accelerator: commands['editor:undo']
|
||||
},
|
||||
{
|
||||
role: 'redo',
|
||||
accelerator: accelerators.redo
|
||||
accelerator: commands['editor:redo']
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
role: 'cut',
|
||||
accelerator: accelerators.cut
|
||||
accelerator: commands['editor:cut']
|
||||
},
|
||||
{
|
||||
role: 'copy',
|
||||
accelerator: accelerators.copy
|
||||
accelerator: commands['editor:copy']
|
||||
},
|
||||
{
|
||||
role: 'paste',
|
||||
accelerator: accelerators.paste
|
||||
accelerator: commands['editor:paste']
|
||||
},
|
||||
{
|
||||
role: 'selectall',
|
||||
accelerator: accelerators.selectAll
|
||||
accelerator: commands['editor:selectAll']
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Clear Buffer',
|
||||
accelerator: accelerators.clear,
|
||||
accelerator: commands['editor:clearBuffer'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('session clear req');
|
||||
|
|
@ -49,7 +48,7 @@ module.exports = function () {
|
|||
{type: 'separator'},
|
||||
{
|
||||
label: 'Preferences...',
|
||||
accelerator: accelerators.preferences,
|
||||
accelerator: commands['window:preferences'],
|
||||
click() {
|
||||
openConfig();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
const {accelerators} = require('../../accelerators');
|
||||
|
||||
module.exports = function (update) {
|
||||
module.exports = function (commands, update) {
|
||||
return {
|
||||
label: 'Plugins',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Update',
|
||||
accelerator: accelerators.updatePlugins,
|
||||
accelerator: commands['plugins:update'],
|
||||
click() {
|
||||
update();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
const {accelerators} = require('../../accelerators');
|
||||
|
||||
module.exports = function (createWindow) {
|
||||
module.exports = function (commands, createWindow) {
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
return {
|
||||
|
|
@ -8,14 +6,14 @@ module.exports = function (createWindow) {
|
|||
submenu: [
|
||||
{
|
||||
label: 'New Window',
|
||||
accelerator: accelerators.newWindow,
|
||||
accelerator: commands['window:new'],
|
||||
click() {
|
||||
createWindow();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'New Tab',
|
||||
accelerator: accelerators.newTab,
|
||||
accelerator: commands['tab:new'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('termgroup add req');
|
||||
|
|
@ -29,7 +27,7 @@ module.exports = function (createWindow) {
|
|||
},
|
||||
{
|
||||
label: 'Split Vertically',
|
||||
accelerator: accelerators.splitVertically,
|
||||
accelerator: commands['pane:splitVertical'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('split request vertical');
|
||||
|
|
@ -38,7 +36,7 @@ module.exports = function (createWindow) {
|
|||
},
|
||||
{
|
||||
label: 'Split Horizontally',
|
||||
accelerator: accelerators.splitHorizontally,
|
||||
accelerator: commands['pane:splitHorizontal'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('split request horizontal');
|
||||
|
|
@ -50,7 +48,7 @@ module.exports = function (createWindow) {
|
|||
},
|
||||
{
|
||||
label: 'Close Session',
|
||||
accelerator: accelerators.closeSession,
|
||||
accelerator: commands['pane:close'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('termgroup close req');
|
||||
|
|
@ -60,7 +58,7 @@ module.exports = function (createWindow) {
|
|||
{
|
||||
label: isMac ? 'Close Window' : 'Quit',
|
||||
role: 'close',
|
||||
accelerator: accelerators.closeWindow
|
||||
accelerator: commands['window:close']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
const {accelerators} = require('../../accelerators');
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function (commands) {
|
||||
return {
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: accelerators.reload,
|
||||
accelerator: commands['window:reload'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('reload');
|
||||
|
|
@ -15,7 +13,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
label: 'Full Reload',
|
||||
accelerator: accelerators.fullReload,
|
||||
accelerator: commands['window:reloadFull'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
|
|
@ -24,7 +22,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
label: 'Developer Tools',
|
||||
accelerator: accelerators.toggleDevTools,
|
||||
accelerator: commands['window:devtools'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
const webContents = focusedWindow.webContents;
|
||||
|
|
@ -41,7 +39,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
label: 'Reset Zoom Level',
|
||||
accelerator: accelerators.resetZoom,
|
||||
accelerator: commands['zoom:reset'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('reset fontSize req');
|
||||
|
|
@ -50,7 +48,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
label: 'Zoom In',
|
||||
accelerator: accelerators.zoomIn,
|
||||
accelerator: commands['zoom:in'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('increase fontSize req');
|
||||
|
|
@ -59,7 +57,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
label: 'Zoom Out',
|
||||
accelerator: accelerators.zoomOut,
|
||||
accelerator: commands['zoom:out'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('decrease fontSize req');
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
const {accelerators} = require('../../accelerators');
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function (commands) {
|
||||
return {
|
||||
role: 'window',
|
||||
submenu: [
|
||||
{
|
||||
role: 'minimize',
|
||||
accelerator: accelerators.minimize
|
||||
},
|
||||
{
|
||||
role: 'zoom'
|
||||
accelerator: commands['window:minimize']
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{ // It's the same thing as clicking the green traffc-light on macOS
|
||||
role: 'zoom',
|
||||
accelerator: commands['window:zoom']
|
||||
},
|
||||
{
|
||||
label: 'Select Tab',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Previous',
|
||||
accelerator: accelerators.showPreviousTab,
|
||||
accelerator: commands['tab:prev'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('move left req');
|
||||
|
|
@ -28,7 +27,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
label: 'Next',
|
||||
accelerator: accelerators.showNextTab,
|
||||
accelerator: commands['tab:next'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('move right req');
|
||||
|
|
@ -45,7 +44,7 @@ module.exports = function () {
|
|||
submenu: [
|
||||
{
|
||||
label: 'Previous',
|
||||
accelerator: accelerators.selectNextPane,
|
||||
accelerator: commands['pane:prev'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('prev pane req');
|
||||
|
|
@ -54,7 +53,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
label: 'Next',
|
||||
accelerator: accelerators.selectPreviousPane,
|
||||
accelerator: commands['pane:next'],
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('next pane req');
|
||||
|
|
@ -71,7 +70,7 @@ module.exports = function () {
|
|||
},
|
||||
{
|
||||
role: 'togglefullscreen',
|
||||
accelerators: accelerators.enterFullScreen
|
||||
accelerators: commands['window:toggleFullScreen']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const shellEnv = require('shell-env');
|
|||
|
||||
const config = require('./config');
|
||||
const notify = require('./notify');
|
||||
const _keys = require('./config/keymaps');
|
||||
|
||||
// local storage
|
||||
const cache = new Config();
|
||||
|
|
@ -30,7 +31,8 @@ const availableExtensions = new Set([
|
|||
'mapHyperTermState', 'mapTermsState',
|
||||
'mapHeaderState', 'mapNotificationsState',
|
||||
'mapHyperTermDispatch', 'mapTermsDispatch',
|
||||
'mapHeaderDispatch', 'mapNotificationsDispatch'
|
||||
'mapHeaderDispatch', 'mapNotificationsDispatch',
|
||||
'extendKeymaps'
|
||||
]);
|
||||
|
||||
// init plugin directories if not present
|
||||
|
|
@ -354,6 +356,15 @@ function decorateObject(base, key) {
|
|||
return decorated;
|
||||
}
|
||||
|
||||
exports.extendKeymaps = function () {
|
||||
modules.forEach(plugin => {
|
||||
if (plugin.extendKeymaps) {
|
||||
const keys = _keys.extend(plugin.extendKeymaps());
|
||||
config.extendKeymaps(keys);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.decorateMenu = function (tpl) {
|
||||
return decorateObject(tpl, 'decorateMenu');
|
||||
};
|
||||
|
|
|
|||
23
lib/command-registry.js
Normal file
23
lib/command-registry.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
const commands = {};
|
||||
|
||||
class CommandRegistry {
|
||||
register(cmds) {
|
||||
if (cmds) {
|
||||
for (const command in cmds) {
|
||||
if (command) {
|
||||
commands[command] = cmds[command];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCommand(cmd) {
|
||||
return commands[cmd] !== undefined;
|
||||
}
|
||||
|
||||
exec(cmd, e) {
|
||||
commands[cmd](e);
|
||||
}
|
||||
}
|
||||
|
||||
export default new CommandRegistry();
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import Component from '../component';
|
||||
import {decorate, getTermGroupProps} from '../utils/plugins';
|
||||
import CommandRegistry from '../command-registry';
|
||||
import TermGroup_ from './term-group';
|
||||
|
||||
const TermGroup = decorate(TermGroup_, 'TermGroup');
|
||||
|
|
@ -13,6 +14,7 @@ export default class Terms extends Component {
|
|||
this.terms = {};
|
||||
this.bound = new WeakMap();
|
||||
this.onRef = this.onRef.bind(this);
|
||||
this.registerCommands = CommandRegistry.register;
|
||||
props.ref_(this);
|
||||
}
|
||||
|
||||
|
|
|
|||
16
lib/hterm.js
16
lib/hterm.js
|
|
@ -1,11 +1,10 @@
|
|||
import {clipboard} from 'electron';
|
||||
import {hterm, lib} from 'hterm-umdjs';
|
||||
import runes from 'runes';
|
||||
|
||||
import {isAccelerator} from '../app/accelerators';
|
||||
|
||||
import fromCharCode from './utils/key-code';
|
||||
import selection from './utils/selection';
|
||||
import returnKey from './utils/keymaps';
|
||||
import CommandRegistry from './command-registry';
|
||||
|
||||
hterm.defaultStorage = new lib.Storage.Memory();
|
||||
|
||||
|
|
@ -232,8 +231,15 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
|
|||
e.preventDefault();
|
||||
}
|
||||
|
||||
// hterm shouldn't consume a hyper accelerator
|
||||
if (e.altKey || e.metaKey || isAccelerator(e)) {
|
||||
// test key from keymaps before moving forward with actions
|
||||
const key = returnKey(e);
|
||||
if (key) {
|
||||
if (CommandRegistry.getCommand(key)) {
|
||||
CommandRegistry.exec(key, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.altKey || e.metaKey || key) {
|
||||
// If the `hyperCaret` was removed on `selectAll`, we need to insert it back
|
||||
if (e.key === 'v' && this.terminal.hyperCaret.parentNode !== this.terminal.cursorNode_) {
|
||||
this.terminal.focusHyperCaret();
|
||||
|
|
|
|||
34
lib/utils/keymaps.js
Normal file
34
lib/utils/keymaps.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import {remote} from 'electron';
|
||||
|
||||
const {getKeymaps} = remote.require('./config');
|
||||
|
||||
export default function returnKey(e) {
|
||||
let keys = [];
|
||||
|
||||
if (e.metaKey && process.platform === 'darwin') {
|
||||
keys.push('cmd');
|
||||
} else if (e.metaKey) {
|
||||
keys.push(e.key);
|
||||
}
|
||||
|
||||
if (e.ctrlKey) {
|
||||
keys.push('ctrl');
|
||||
}
|
||||
|
||||
if (e.shiftKey) {
|
||||
keys.push('shift');
|
||||
}
|
||||
|
||||
if (e.altKey) {
|
||||
keys.push('alt');
|
||||
}
|
||||
|
||||
if (e.key === ' ') {
|
||||
keys.push('space');
|
||||
} else if (e.key !== 'Meta' && e.key !== 'Control' && e.key !== 'Shift' && e.key !== 'Alt') {
|
||||
keys.push(e.key.replace('Arrow', ''));
|
||||
}
|
||||
|
||||
keys = keys.join('+');
|
||||
return getKeymaps().keys[keys.toLowerCase()];
|
||||
}
|
||||
Loading…
Reference in a new issue