mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-12 20:18:41 -09:00
Multiple keymaps and mousetrap (#2412)
* WIP * WIP * Wip * Wip * wip * Refactor without normalize and plugin * Replace extendKeymaps by decorateKeymaps * WIP * Add mousetrap * Add first command over rpc * More commands * Add all commands * Begin to hook commands * Working multiple keymaps * Use redux action to trigger command * Use forked version of Mousetrap to capture key events * Fix lint * Add command in redux action to debug purpose * ExecCommand from menu click * Remove unused files * Fix xterm should ignore catched events * Re-enable IntelliSense checking * Remove unused runes dep
This commit is contained in:
parent
95f52e20e1
commit
2af575c3c0
31 changed files with 441 additions and 1068 deletions
81
app/commands.js
Normal file
81
app/commands.js
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
const {app} = require('electron');
|
||||||
|
const {openConfig} = require('./config');
|
||||||
|
const {updatePlugins} = require('./plugins');
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
'window:new': () => {
|
||||||
|
// If window is created on the same tick, it will consume event too
|
||||||
|
setTimeout(app.createWindow, 0);
|
||||||
|
},
|
||||||
|
'tab:new': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('termgroup add req');
|
||||||
|
},
|
||||||
|
'pane:splitVertical': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('split request vertical');
|
||||||
|
},
|
||||||
|
'pane:splitHorizontal': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('split request horizontal');
|
||||||
|
},
|
||||||
|
'pane:close': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('termgroup close req');
|
||||||
|
},
|
||||||
|
'window:preferences': () => {
|
||||||
|
openConfig();
|
||||||
|
},
|
||||||
|
'editor:clearBuffer': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('session clear req');
|
||||||
|
},
|
||||||
|
'plugins:update': () => {
|
||||||
|
updatePlugins();
|
||||||
|
},
|
||||||
|
'window:reload': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('reload');
|
||||||
|
},
|
||||||
|
'window:reloadFull': focusedWindow => {
|
||||||
|
focusedWindow.reload();
|
||||||
|
},
|
||||||
|
'window:devtools': focusedWindow => {
|
||||||
|
const webContents = focusedWindow.webContents;
|
||||||
|
if (webContents.isDevToolsOpened()) {
|
||||||
|
webContents.closeDevTools();
|
||||||
|
} else {
|
||||||
|
webContents.openDevTools({mode: 'detach'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'zoom:reset': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('reset fontSize req');
|
||||||
|
},
|
||||||
|
'zoom:in': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('increase fontSize req');
|
||||||
|
},
|
||||||
|
'zoom:out': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('decrease fontSize req');
|
||||||
|
},
|
||||||
|
'tab:prev': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('move left req');
|
||||||
|
},
|
||||||
|
'tab:next': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('move right req');
|
||||||
|
},
|
||||||
|
'pane:prev': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('prev pane req');
|
||||||
|
},
|
||||||
|
'pane:next': focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('next pane req');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Special numeric command
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 'last'].forEach(cmdIndex => {
|
||||||
|
const index = cmdIndex === 'last' ? cmdIndex : cmdIndex - 1;
|
||||||
|
commands[`tab:jump:${cmdIndex}`] = focusedWindow => {
|
||||||
|
focusedWindow.rpc.emit('move jump req', index);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.execCommand = (command, focusedWindow) => {
|
||||||
|
const fn = commands[command];
|
||||||
|
if (fn) {
|
||||||
|
fn(focusedWindow);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -73,12 +73,6 @@ exports.getKeymaps = () => {
|
||||||
return cfg.keymaps;
|
return cfg.keymaps;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.extendKeymaps = keymaps => {
|
|
||||||
if (keymaps) {
|
|
||||||
cfg.keymaps = keymaps;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.setup = () => {
|
exports.setup = () => {
|
||||||
cfg = _import();
|
cfg = _import();
|
||||||
_watch();
|
_watch();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
const {writeFileSync, readFileSync} = require('fs');
|
const {writeFileSync, readFileSync} = require('fs');
|
||||||
const {sync: mkdirpSync} = require('mkdirp');
|
const {sync: mkdirpSync} = require('mkdirp');
|
||||||
const {defaultCfg, cfgPath, plugs} = require('./paths');
|
const {defaultCfg, cfgPath, plugs, defaultPlatformKeyPath} = require('./paths');
|
||||||
const {_init, _extractDefault} = require('./init');
|
const {_init, _extractDefault} = require('./init');
|
||||||
const _keymaps = require('./keymaps');
|
|
||||||
|
|
||||||
let defaultConfig;
|
let defaultConfig;
|
||||||
|
|
||||||
|
|
@ -23,7 +22,17 @@ const _importConf = function() {
|
||||||
mkdirpSync(plugs.local);
|
mkdirpSync(plugs.local);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const _defaultCfg = readFileSync(defaultCfg, 'utf8');
|
const _defaultCfg = _extractDefault(readFileSync(defaultCfg, 'utf8'));
|
||||||
|
// Importing platform specific keymap
|
||||||
|
try {
|
||||||
|
const content = readFileSync(defaultPlatformKeyPath(), 'utf8');
|
||||||
|
const mapping = JSON.parse(content);
|
||||||
|
_defaultCfg.keymaps = mapping;
|
||||||
|
} catch (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
// Importing user config
|
||||||
try {
|
try {
|
||||||
const _cfgPath = readFileSync(cfgPath, 'utf8');
|
const _cfgPath = readFileSync(cfgPath, 'utf8');
|
||||||
return {userCfg: _cfgPath, defaultCfg: _defaultCfg};
|
return {userCfg: _cfgPath, defaultCfg: _defaultCfg};
|
||||||
|
|
@ -39,13 +48,8 @@ const _importConf = function() {
|
||||||
|
|
||||||
exports._import = () => {
|
exports._import = () => {
|
||||||
const imported = _importConf();
|
const imported = _importConf();
|
||||||
defaultConfig = _extractDefault(imported.defaultCfg);
|
defaultConfig = imported.defaultCfg;
|
||||||
const cfg = _init(imported);
|
return _init(imported);
|
||||||
|
|
||||||
if (cfg) {
|
|
||||||
cfg.keymaps = _keymaps.import(cfg.keymaps);
|
|
||||||
}
|
|
||||||
return cfg;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getDefaultConfig = () => {
|
exports.getDefaultConfig = () => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const vm = require('vm');
|
const vm = require('vm');
|
||||||
const notify = require('../notify');
|
const notify = require('../notify');
|
||||||
|
const mapKeys = require('../utils/map-keys');
|
||||||
|
|
||||||
const _extract = function(script) {
|
const _extract = function(script) {
|
||||||
const module = {};
|
const module = {};
|
||||||
|
|
@ -30,19 +31,17 @@ const _init = function(cfg) {
|
||||||
if (script) {
|
if (script) {
|
||||||
const _cfg = _extract(script);
|
const _cfg = _extract(script);
|
||||||
if (!_cfg.config) {
|
if (!_cfg.config) {
|
||||||
_cfg.plugins = _cfg.plugins || [];
|
|
||||||
_cfg.localPlugins = _cfg.localPlugins || [];
|
|
||||||
_cfg.keymaps = _cfg.keymaps || {};
|
|
||||||
notify('Error reading configuration: `config` key is missing');
|
notify('Error reading configuration: `config` key is missing');
|
||||||
return _extractDefault(cfg.defaultCfg);
|
return cfg.defaultCfg;
|
||||||
}
|
}
|
||||||
|
// Merging platform specific keymaps with user defined keymaps
|
||||||
|
_cfg.keymaps = mapKeys(Object.assign({}, cfg.defaultCfg.keymaps, _cfg.keymaps));
|
||||||
// Ignore undefined values in plugin and localPlugins array Issue #1862
|
// Ignore undefined values in plugin and localPlugins array Issue #1862
|
||||||
_cfg.plugins = (_cfg.plugins && _cfg.plugins.filter(Boolean)) || [];
|
_cfg.plugins = (_cfg.plugins && _cfg.plugins.filter(Boolean)) || [];
|
||||||
_cfg.localPlugins = (_cfg.localPlugins && _cfg.localPlugins.filter(Boolean)) || [];
|
_cfg.localPlugins = (_cfg.localPlugins && _cfg.localPlugins.filter(Boolean)) || [];
|
||||||
return _cfg;
|
return _cfg;
|
||||||
}
|
}
|
||||||
return _extractDefault(cfg.defaultCfg);
|
return cfg.defaultCfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
const {readFileSync} = require('fs');
|
|
||||||
const normalize = require('../utils/keymaps/normalize');
|
|
||||||
const {defaultPlatformKeyPath} = require('./paths');
|
|
||||||
|
|
||||||
const commands = {};
|
|
||||||
const keys = {};
|
|
||||||
|
|
||||||
const generatePrefixedCommand = function(command, key) {
|
|
||||||
const baseCmd = command.replace(/:prefix$/, '');
|
|
||||||
for (let i = 1; i <= 9; i++) {
|
|
||||||
// 9 is a special number because it means 'last'
|
|
||||||
const index = i === 9 ? 'last' : i;
|
|
||||||
commands[`${baseCmd}:${index}`] = normalize(`${key}+${i}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _setKeysForCommands = function(keymap) {
|
|
||||||
for (const command in keymap) {
|
|
||||||
if (command) {
|
|
||||||
// In case of a command finishing by :prefix
|
|
||||||
// we need to generate commands and keys
|
|
||||||
if (command.endsWith(':prefix')) {
|
|
||||||
generatePrefixedCommand(command, keymap[command]);
|
|
||||||
} else {
|
|
||||||
commands[command] = normalize(keymap[command]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _setCommandsForKeys = function(commands_) {
|
|
||||||
for (const command in commands_) {
|
|
||||||
if (command) {
|
|
||||||
keys[commands_[command]] = command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _import = function(customKeys) {
|
|
||||||
try {
|
|
||||||
const mapping = JSON.parse(readFileSync(defaultPlatformKeyPath()));
|
|
||||||
_setKeysForCommands(mapping);
|
|
||||||
_setKeysForCommands(customKeys);
|
|
||||||
_setCommandsForKeys(commands);
|
|
||||||
|
|
||||||
return {commands, keys};
|
|
||||||
} catch (err) {
|
|
||||||
//eslint-disable-next-line no-console
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _extend = function(customKeys) {
|
|
||||||
if (customKeys) {
|
|
||||||
for (const command in customKeys) {
|
|
||||||
if (command) {
|
|
||||||
commands[command] = normalize(customKeys[command]);
|
|
||||||
keys[normalize(customKeys[command])] = command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {commands, keys};
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
import: _import,
|
|
||||||
extend: _extend
|
|
||||||
};
|
|
||||||
17
app/index.js
17
app/index.js
|
|
@ -56,7 +56,6 @@ const {app, BrowserWindow, Menu} = require('electron');
|
||||||
const {gitDescribe} = require('git-describe');
|
const {gitDescribe} = require('git-describe');
|
||||||
const isDev = require('electron-is-dev');
|
const isDev = require('electron-is-dev');
|
||||||
|
|
||||||
const AppMenu = require('./menus/menu');
|
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
|
||||||
// set up config
|
// set up config
|
||||||
|
|
@ -64,6 +63,8 @@ config.setup();
|
||||||
|
|
||||||
const plugins = require('./plugins');
|
const plugins = require('./plugins');
|
||||||
|
|
||||||
|
const AppMenu = require('./menus/menu');
|
||||||
|
|
||||||
const Window = require('./ui/window');
|
const Window = require('./ui/window');
|
||||||
|
|
||||||
const windowSet = new Set([]);
|
const windowSet = new Set([]);
|
||||||
|
|
@ -181,15 +182,7 @@ app.on('ready', () =>
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMenu = () => {
|
const makeMenu = () => {
|
||||||
const menu = plugins.decorateMenu(
|
const menu = plugins.decorateMenu(AppMenu.createMenu(createWindow, plugins.getLoadedPluginVersions));
|
||||||
AppMenu(
|
|
||||||
createWindow,
|
|
||||||
() => {
|
|
||||||
plugins.updatePlugins({force: true});
|
|
||||||
},
|
|
||||||
plugins.getLoadedPluginVersions
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// If we're on Mac make a Dock Menu
|
// If we're on Mac make a Dock Menu
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
|
|
@ -204,12 +197,12 @@ app.on('ready', () =>
|
||||||
app.dock.setMenu(dockMenu);
|
app.dock.setMenu(dockMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.setApplicationMenu(Menu.buildFromTemplate(menu));
|
Menu.setApplicationMenu(AppMenu.buildMenu(menu));
|
||||||
};
|
};
|
||||||
|
|
||||||
const load = () => {
|
const load = () => {
|
||||||
plugins.onApp(app);
|
plugins.onApp(app);
|
||||||
plugins.extendKeymaps();
|
plugins.checkDeprecatedExtendKeymaps();
|
||||||
makeMenu();
|
makeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,32 @@
|
||||||
{
|
{
|
||||||
"window:devtools": "cmd+alt+i",
|
"window:devtools": "command+alt+i",
|
||||||
"window:reload": "cmd+r",
|
"window:reload": "command+r",
|
||||||
"window:reloadFull": "cmd+shift+r",
|
"window:reloadFull": "command+shift+r",
|
||||||
"window:preferences": "cmd+,",
|
"window:preferences": "command+,",
|
||||||
"zoom:reset": "cmd+0",
|
"zoom:reset": "command+0",
|
||||||
"zoom:in": "cmd+plus",
|
"zoom:in": "command+plus",
|
||||||
"zoom:out": "cmd+minus",
|
"zoom:out": "command+minus",
|
||||||
"window:new": "cmd+n",
|
"window:new": "command+n",
|
||||||
"window:minimize": "cmd+m",
|
"window:minimize": "command+m",
|
||||||
"window:zoom": "ctrl+alt+cmd+m",
|
"window:zoom": "ctrl+alt+command+m",
|
||||||
"window:toggleFullScreen": "cmd+ctrl+f",
|
"window:toggleFullScreen": "command+ctrl+f",
|
||||||
"window:close": "cmd+shift+w",
|
"window:close": "command+shift+w",
|
||||||
"tab:new": "cmd+t",
|
"tab:new": "command+t",
|
||||||
"tab:next": "cmd+shift+]",
|
"tab:next": "command+shift+]",
|
||||||
"tab:prev": "cmd+shift+[",
|
"tab:prev": "command+shift+[",
|
||||||
"tab:jump:prefix": "cmd",
|
"tab:jump:prefix": "command",
|
||||||
"pane:next": "cmd+]",
|
"pane:next": "command+]",
|
||||||
"pane:prev": "cmd+[",
|
"pane:prev": "command+[",
|
||||||
"pane:splitVertical": "cmd+d",
|
"pane:splitVertical": "command+d",
|
||||||
"pane:splitHorizontal": "cmd+shift+d",
|
"pane:splitHorizontal": "command+shift+d",
|
||||||
"pane:close": "cmd+w",
|
"pane:close": "command+w",
|
||||||
"editor:undo": "cmd+z",
|
"editor:undo": "command+z",
|
||||||
"editor:redo": "cmd+y",
|
"editor:redo": "command+y",
|
||||||
"editor:cut": "cmd+x",
|
"editor:cut": "command+x",
|
||||||
"editor:copy": "cmd+c",
|
"editor:copy": "command+c",
|
||||||
"editor:paste": "cmd+v",
|
"editor:paste": "command+v",
|
||||||
"editor:selectAll": "cmd+a",
|
"editor:selectAll": "command+a",
|
||||||
"editor:clearBuffer": "cmd+k",
|
"editor:clearBuffer": "command+k",
|
||||||
"editor:emojis": "cmd+ctrl+space",
|
"editor:emojis": "command+ctrl+space",
|
||||||
"plugins:update": "cmd+shift+u"
|
"plugins:update": "command+shift+u"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Packages
|
// Packages
|
||||||
const {app, dialog} = require('electron');
|
const {app, dialog, Menu} = require('electron');
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
const {getKeymaps, getConfig} = require('../config');
|
const {getConfig} = require('../config');
|
||||||
const {icon} = require('../config/paths');
|
const {icon} = require('../config/paths');
|
||||||
const viewMenu = require('./menus/view');
|
const viewMenu = require('./menus/view');
|
||||||
const shellMenu = require('./menus/shell');
|
const shellMenu = require('./menus/shell');
|
||||||
|
|
@ -11,13 +11,22 @@ const pluginsMenu = require('./menus/plugins');
|
||||||
const windowMenu = require('./menus/window');
|
const windowMenu = require('./menus/window');
|
||||||
const helpMenu = require('./menus/help');
|
const helpMenu = require('./menus/help');
|
||||||
const darwinMenu = require('./menus/darwin');
|
const darwinMenu = require('./menus/darwin');
|
||||||
|
const {getDecoratedKeymaps} = require('../plugins');
|
||||||
|
const {execCommand} = require('../commands');
|
||||||
|
|
||||||
const appName = app.getName();
|
const appName = app.getName();
|
||||||
const appVersion = app.getVersion();
|
const appVersion = app.getVersion();
|
||||||
|
|
||||||
module.exports = (createWindow, updatePlugins, getLoadedPluginVersions) => {
|
let menu_ = [];
|
||||||
|
|
||||||
|
exports.createMenu = (createWindow, getLoadedPluginVersions) => {
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
const {commands} = getKeymaps();
|
// We take only first shortcut in array for each command
|
||||||
|
const allCommandKeys = getDecoratedKeymaps();
|
||||||
|
const commandKeys = Object.keys(allCommandKeys).reduce((result, command) => {
|
||||||
|
result[command] = allCommandKeys[command][0];
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
|
||||||
let updateChannel = 'stable';
|
let updateChannel = 'stable';
|
||||||
|
|
||||||
|
|
@ -39,14 +48,19 @@ module.exports = (createWindow, updatePlugins, getLoadedPluginVersions) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const menu = [
|
const menu = [
|
||||||
...(process.platform === 'darwin' ? [darwinMenu(commands, showAbout)] : []),
|
...(process.platform === 'darwin' ? [darwinMenu(commandKeys, showAbout)] : []),
|
||||||
shellMenu(commands, createWindow),
|
shellMenu(commandKeys, execCommand),
|
||||||
editMenu(commands),
|
editMenu(commandKeys, execCommand),
|
||||||
viewMenu(commands),
|
viewMenu(commandKeys, execCommand),
|
||||||
pluginsMenu(commands, updatePlugins),
|
pluginsMenu(commandKeys, execCommand),
|
||||||
windowMenu(commands),
|
windowMenu(commandKeys, execCommand),
|
||||||
helpMenu(commands, showAbout)
|
helpMenu(commandKeys, showAbout)
|
||||||
];
|
];
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.buildMenu = template => {
|
||||||
|
menu_ = Menu.buildFromTemplate(template);
|
||||||
|
return menu_;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
// This menu label is overrided by OSX to be the appName
|
// This menu label is overrided by OSX to be the appName
|
||||||
// The label is set to appName here so it matches actual behavior
|
// The label is set to appName here so it matches actual behavior
|
||||||
const {app} = require('electron');
|
const {app} = require('electron');
|
||||||
const {openConfig} = require('../../config');
|
|
||||||
|
|
||||||
module.exports = (commands, showAbout) => {
|
module.exports = (commandKeys, showAbout) => {
|
||||||
return {
|
return {
|
||||||
label: `${app.getName()}`,
|
label: `${app.getName()}`,
|
||||||
submenu: [
|
submenu: [
|
||||||
|
|
@ -18,10 +17,7 @@ module.exports = (commands, showAbout) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Preferences...',
|
label: 'Preferences...',
|
||||||
accelerator: commands['window:preferences'],
|
accelerator: commandKeys['window:preferences']
|
||||||
click() {
|
|
||||||
openConfig();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,41 @@
|
||||||
const {openConfig} = require('../../config');
|
module.exports = (commandKeys, execCommand) => {
|
||||||
|
|
||||||
module.exports = commands => {
|
|
||||||
const submenu = [
|
const submenu = [
|
||||||
{
|
{
|
||||||
role: 'undo',
|
role: 'undo',
|
||||||
accelerator: commands['editor:undo']
|
accelerator: commandKeys['editor:undo']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'redo',
|
role: 'redo',
|
||||||
accelerator: commands['editor:redo']
|
accelerator: commandKeys['editor:redo']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'cut',
|
role: 'cut',
|
||||||
accelerator: commands['editor:cut']
|
accelerator: commandKeys['editor:cut']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'copy',
|
role: 'copy',
|
||||||
accelerator: commands['editor:copy']
|
command: 'editor:copy',
|
||||||
|
accelerator: commandKeys['editor:copy']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'paste',
|
role: 'paste',
|
||||||
accelerator: commands['editor:paste']
|
accelerator: commandKeys['editor:paste']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'selectall',
|
role: 'selectall',
|
||||||
accelerator: commands['editor:selectAll']
|
accelerator: commandKeys['editor:selectAll']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Clear Buffer',
|
label: 'Clear Buffer',
|
||||||
accelerator: commands['editor:clearBuffer'],
|
accelerator: commandKeys['editor:clearBuffer'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('editor:clearBuffer', focusedWindow);
|
||||||
focusedWindow.rpc.emit('session clear req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -48,10 +45,7 @@ module.exports = commands => {
|
||||||
{type: 'separator'},
|
{type: 'separator'},
|
||||||
{
|
{
|
||||||
label: 'Preferences...',
|
label: 'Preferences...',
|
||||||
accelerator: commands['window:preferences'],
|
accelerator: commandKeys['window:preferences']
|
||||||
click() {
|
|
||||||
openConfig();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
module.exports = (commands, createWindow) => {
|
module.exports = (commandKeys, execCommand) => {
|
||||||
const isMac = process.platform === 'darwin';
|
const isMac = process.platform === 'darwin';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -6,20 +6,16 @@ module.exports = (commands, createWindow) => {
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'New Window',
|
label: 'New Window',
|
||||||
accelerator: commands['window:new'],
|
accelerator: commandKeys['window:new'],
|
||||||
click() {
|
click(item, focusedWindow) {
|
||||||
createWindow();
|
execCommand('window:new', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'New Tab',
|
label: 'New Tab',
|
||||||
accelerator: commands['tab:new'],
|
accelerator: commandKeys['tab:new'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('tab:new', focusedWindow);
|
||||||
focusedWindow.rpc.emit('termgroup add req');
|
|
||||||
} else {
|
|
||||||
createWindow();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -27,20 +23,16 @@ module.exports = (commands, createWindow) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Split Vertically',
|
label: 'Split Vertically',
|
||||||
accelerator: commands['pane:splitVertical'],
|
accelerator: commandKeys['pane:splitVertical'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('pane:splitVertical', focusedWindow);
|
||||||
focusedWindow.rpc.emit('split request vertical');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Split Horizontally',
|
label: 'Split Horizontally',
|
||||||
accelerator: commands['pane:splitHorizontal'],
|
accelerator: commandKeys['pane:splitHorizontal'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('pane:splitHorizontal', focusedWindow);
|
||||||
focusedWindow.rpc.emit('split request horizontal');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -48,17 +40,15 @@ module.exports = (commands, createWindow) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Close Session',
|
label: 'Close Session',
|
||||||
accelerator: commands['pane:close'],
|
accelerator: commandKeys['pane:close'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('pane:close', focusedWindow);
|
||||||
focusedWindow.rpc.emit('termgroup close req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: isMac ? 'Close Window' : 'Quit',
|
label: isMac ? 'Close Window' : 'Quit',
|
||||||
role: 'close',
|
role: 'close',
|
||||||
accelerator: commands['window:close']
|
accelerator: commandKeys['window:close']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,26 @@
|
||||||
module.exports = commands => {
|
module.exports = (commandKeys, execCommand) => {
|
||||||
return {
|
return {
|
||||||
label: 'View',
|
label: 'View',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
accelerator: commands['window:reload'],
|
accelerator: commandKeys['window:reload'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('window:reload', focusedWindow);
|
||||||
focusedWindow.rpc.emit('reload');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Full Reload',
|
label: 'Full Reload',
|
||||||
accelerator: commands['window:reloadFull'],
|
accelerator: commandKeys['window:reloadFull'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('window:reloadFull', focusedWindow);
|
||||||
focusedWindow.reload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Developer Tools',
|
label: 'Developer Tools',
|
||||||
accelerator: commands['window:devtools'],
|
accelerator: commandKeys['window:devtools'],
|
||||||
click(item, focusedWindow) {
|
click: (item, focusedWindow) => {
|
||||||
if (focusedWindow) {
|
execCommand('window:reloadFull', focusedWindow);
|
||||||
const webContents = focusedWindow.webContents;
|
|
||||||
if (webContents.isDevToolsOpened()) {
|
|
||||||
webContents.closeDevTools();
|
|
||||||
} else {
|
|
||||||
webContents.openDevTools({mode: 'detach'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -39,29 +28,23 @@ module.exports = commands => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Reset Zoom Level',
|
label: 'Reset Zoom Level',
|
||||||
accelerator: commands['zoom:reset'],
|
accelerator: commandKeys['zoom:reset'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('zoom:reset', focusedWindow);
|
||||||
focusedWindow.rpc.emit('reset fontSize req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Zoom In',
|
label: 'Zoom In',
|
||||||
accelerator: commands['zoom:in'],
|
accelerator: commandKeys['zoom:in'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('zoom:in', focusedWindow);
|
||||||
focusedWindow.rpc.emit('increase fontSize req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Zoom Out',
|
label: 'Zoom Out',
|
||||||
accelerator: commands['zoom:out'],
|
accelerator: commandKeys['zoom:out'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
if (focusedWindow) {
|
execCommand('zoom:out', focusedWindow);
|
||||||
focusedWindow.rpc.emit('decrease fontSize req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,12 @@
|
||||||
module.exports = commands => {
|
module.exports = (commandKeys, execCommand) => {
|
||||||
// Generating tab:jump array
|
// Generating tab:jump array
|
||||||
const tabJump = [];
|
const tabJump = [];
|
||||||
for (let i = 1; i <= 9; i++) {
|
for (let i = 1; i <= 9; i++) {
|
||||||
// 9 is a special number because it means 'last'
|
// 9 is a special number because it means 'last'
|
||||||
const label = i === 9 ? 'Last' : `${i}`;
|
const label = i === 9 ? 'Last' : `${i}`;
|
||||||
const tabIndex = i === 9 ? 'last' : i - 1;
|
|
||||||
tabJump.push({
|
tabJump.push({
|
||||||
label: label,
|
label: label,
|
||||||
accelerator: commands[`tab:jump:${label.toLowerCase()}`],
|
accelerator: commandKeys[`tab:jump:${label.toLowerCase()}`]
|
||||||
click(item, focusedWindow) {
|
|
||||||
if (focusedWindow) {
|
|
||||||
focusedWindow.rpc.emit('move jump req', tabIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,7 +15,7 @@ module.exports = commands => {
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
role: 'minimize',
|
role: 'minimize',
|
||||||
accelerator: commands['window:minimize']
|
accelerator: commandKeys['window:minimize']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
|
|
@ -29,27 +23,23 @@ module.exports = commands => {
|
||||||
{
|
{
|
||||||
// It's the same thing as clicking the green traffc-light on macOS
|
// It's the same thing as clicking the green traffc-light on macOS
|
||||||
role: 'zoom',
|
role: 'zoom',
|
||||||
accelerator: commands['window:zoom']
|
accelerator: commandKeys['window:zoom']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Select Tab',
|
label: 'Select Tab',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Previous',
|
label: 'Previous',
|
||||||
accelerator: commands['tab:prev'],
|
accelerator: commandKeys['tab:prev'],
|
||||||
click(item, focusedWindow) {
|
click: (item, focusedWindow) => {
|
||||||
if (focusedWindow) {
|
execCommand('tab:prev', focusedWindow);
|
||||||
focusedWindow.rpc.emit('move left req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next',
|
label: 'Next',
|
||||||
accelerator: commands['tab:next'],
|
accelerator: commandKeys['tab:next'],
|
||||||
click(item, focusedWindow) {
|
click: (item, focusedWindow) => {
|
||||||
if (focusedWindow) {
|
execCommand('tab:next', focusedWindow);
|
||||||
focusedWindow.rpc.emit('move right req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -66,20 +56,16 @@ module.exports = commands => {
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Previous',
|
label: 'Previous',
|
||||||
accelerator: commands['pane:prev'],
|
accelerator: commandKeys['pane:prev'],
|
||||||
click(item, focusedWindow) {
|
click: (item, focusedWindow) => {
|
||||||
if (focusedWindow) {
|
execCommand('pane:prev', focusedWindow);
|
||||||
focusedWindow.rpc.emit('prev pane req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next',
|
label: 'Next',
|
||||||
accelerator: commands['pane:next'],
|
accelerator: commandKeys['pane:next'],
|
||||||
click(item, focusedWindow) {
|
click: (item, focusedWindow) => {
|
||||||
if (focusedWindow) {
|
execCommand('pane:next', focusedWindow);
|
||||||
focusedWindow.rpc.emit('next pane req');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -92,7 +78,7 @@ module.exports = commands => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'togglefullscreen',
|
role: 'togglefullscreen',
|
||||||
accelerators: commands['window:toggleFullScreen']
|
accelerators: commandKeys['window:toggleFullScreen']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ const ms = require('ms');
|
||||||
|
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const notify = require('./notify');
|
const notify = require('./notify');
|
||||||
const _keys = require('./config/keymaps');
|
|
||||||
const {availableExtensions} = require('./plugins/extensions');
|
const {availableExtensions} = require('./plugins/extensions');
|
||||||
const {install} = require('./plugins/install');
|
const {install} = require('./plugins/install');
|
||||||
const {plugs} = require('./config/paths');
|
const {plugs} = require('./config/paths');
|
||||||
|
const mapKeys = require('./utils/map-keys');
|
||||||
|
|
||||||
// local storage
|
// local storage
|
||||||
const cache = new Config();
|
const cache = new Config();
|
||||||
|
|
@ -19,7 +19,7 @@ const localPath = plugs.local;
|
||||||
|
|
||||||
// caches
|
// caches
|
||||||
let plugins = config.getPlugins();
|
let plugins = config.getPlugins();
|
||||||
let paths = getPaths(plugins);
|
let paths = getPaths();
|
||||||
let id = getId(plugins);
|
let id = getId(plugins);
|
||||||
let modules = requirePlugins();
|
let modules = requirePlugins();
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ function updatePlugins({force = false} = {}) {
|
||||||
cache.set('hyper.plugins', id_);
|
cache.set('hyper.plugins', id_);
|
||||||
|
|
||||||
// cache paths
|
// cache paths
|
||||||
paths = getPaths(plugins);
|
paths = getPaths();
|
||||||
|
|
||||||
// clear require cache
|
// clear require cache
|
||||||
clearCache();
|
clearCache();
|
||||||
|
|
@ -289,7 +289,7 @@ function decorateObject(base, key) {
|
||||||
try {
|
try {
|
||||||
res = plugin[key](decorated);
|
res = plugin[key](decorated);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notify('Plugin error!', `"${plugin._name}" has encountered an error. Check Developer Tools for details.`);
|
notify('Plugin error!', `"${plugin._name}" when decorating ${key}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res && typeof res === 'object') {
|
if (res && typeof res === 'object') {
|
||||||
|
|
@ -303,22 +303,6 @@ function decorateObject(base, key) {
|
||||||
return decorated;
|
return decorated;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.extendKeymaps = () => {
|
|
||||||
modules.forEach(plugin => {
|
|
||||||
if (plugin.extendKeymaps) {
|
|
||||||
let pluginKeymap;
|
|
||||||
try {
|
|
||||||
pluginKeymap = plugin.extendKeymaps();
|
|
||||||
} catch (e) {
|
|
||||||
notify('Plugin error!', `"${plugin._name}" has encountered an error. Check Developer Tools for details.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const keys = _keys.extend(pluginKeymap);
|
|
||||||
config.extendKeymaps(keys);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getDeprecatedConfig = () => {
|
exports.getDeprecatedConfig = () => {
|
||||||
const deprecated = {};
|
const deprecated = {};
|
||||||
const baseConfig = config.getConfig();
|
const baseConfig = config.getConfig();
|
||||||
|
|
@ -359,6 +343,22 @@ exports.getDecoratedConfig = () => {
|
||||||
return translatedConfig;
|
return translatedConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.checkDeprecatedExtendKeymaps = () => {
|
||||||
|
modules.forEach(plugin => {
|
||||||
|
if (plugin.extendKeymaps) {
|
||||||
|
notify('Plugin warning!', `"${plugin._name}" use deprecated "extendKeymaps" handler`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getDecoratedKeymaps = () => {
|
||||||
|
const baseKeymaps = config.getKeymaps();
|
||||||
|
// Ensure that all keys are in an array and don't use deprecated key combination`
|
||||||
|
const decoratedKeymaps = mapKeys(decorateObject(baseKeymaps, 'decorateKeymaps'));
|
||||||
|
return decoratedKeymaps;
|
||||||
|
};
|
||||||
|
|
||||||
exports.getDecoratedBrowserOptions = defaults => {
|
exports.getDecoratedBrowserOptions = defaults => {
|
||||||
return decorateObject(defaults, 'decorateBrowserOptions');
|
return decorateObject(defaults, 'decorateBrowserOptions');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ module.exports = {
|
||||||
'decorateNotifications',
|
'decorateNotifications',
|
||||||
'decorateTabs',
|
'decorateTabs',
|
||||||
'decorateConfig',
|
'decorateConfig',
|
||||||
|
'decorateKeymaps',
|
||||||
'decorateEnv',
|
'decorateEnv',
|
||||||
'decorateTermGroup',
|
'decorateTermGroup',
|
||||||
'decorateSplitPane',
|
'decorateSplitPane',
|
||||||
|
|
@ -34,7 +35,6 @@ module.exports = {
|
||||||
'mapHyperTermDispatch',
|
'mapHyperTermDispatch',
|
||||||
'mapTermsDispatch',
|
'mapTermsDispatch',
|
||||||
'mapHeaderDispatch',
|
'mapHeaderDispatch',
|
||||||
'mapNotificationsDispatch',
|
'mapNotificationsDispatch'
|
||||||
'extendKeymaps'
|
|
||||||
])
|
])
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const createRPC = require('../rpc');
|
||||||
const notify = require('../notify');
|
const notify = require('../notify');
|
||||||
const fetchNotifications = require('../notifications');
|
const fetchNotifications = require('../notifications');
|
||||||
const Session = require('../session');
|
const Session = require('../session');
|
||||||
|
const {execCommand} = require('../commands');
|
||||||
|
|
||||||
module.exports = class Window {
|
module.exports = class Window {
|
||||||
constructor(options_, cfg, fn) {
|
constructor(options_, cfg, fn) {
|
||||||
|
|
@ -167,6 +168,10 @@ module.exports = class Window {
|
||||||
rpc.on('close', () => {
|
rpc.on('close', () => {
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
|
rpc.on('command', command => {
|
||||||
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||||
|
execCommand(command, focusedWindow);
|
||||||
|
});
|
||||||
const deleteSessions = () => {
|
const deleteSessions = () => {
|
||||||
sessions.forEach((session, key) => {
|
sessions.forEach((session, key) => {
|
||||||
session.removeAllListeners();
|
session.removeAllListeners();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
const normalize = require('./normalize');
|
|
||||||
|
|
||||||
module.exports = (keys, commands) => {
|
|
||||||
return commands[normalize(keys)];
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
const {getKeymaps} = require('../../config');
|
|
||||||
const findCommandByKeys = require('./find-command-by-keys');
|
|
||||||
|
|
||||||
module.exports = keys => {
|
|
||||||
return findCommandByKeys(keys, getKeymaps().keys);
|
|
||||||
};
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
// This function receives a keymap in any key order and returns
|
|
||||||
// the same keymap alphatetically sorted by the clients locale.
|
|
||||||
// eg.: cmd+alt+o -> alt+cmd+o
|
|
||||||
// We do this in order to normalize what the user defined to what we
|
|
||||||
// internally parse. By doing this, you can set your keymaps in any given order
|
|
||||||
// eg.: alt+cmd+o, cmd+alt+o, o+alt+cmd, etc. #2195
|
|
||||||
module.exports = keybinding => {
|
|
||||||
function sortAlphabetically(a, b) {
|
|
||||||
return a.localeCompare(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keybinding
|
|
||||||
.toLowerCase()
|
|
||||||
.split('+')
|
|
||||||
.sort(sortAlphabetically)
|
|
||||||
.join('+');
|
|
||||||
};
|
|
||||||
41
app/utils/map-keys.js
Normal file
41
app/utils/map-keys.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
const generatePrefixedCommand = (command, shortcuts) => {
|
||||||
|
const result = {};
|
||||||
|
const baseCmd = command.replace(/:prefix$/, '');
|
||||||
|
for (let i = 1; i <= 9; i++) {
|
||||||
|
// 9 is a special number because it means 'last'
|
||||||
|
const index = i === 9 ? 'last' : i;
|
||||||
|
const prefixedShortcuts = shortcuts.map(shortcut => `${shortcut}+${i}`);
|
||||||
|
result[`${baseCmd}:${index}`] = prefixedShortcuts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config => {
|
||||||
|
return Object.keys(config).reduce((keymap, command) => {
|
||||||
|
if (!command) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We can have different keys for a same command.
|
||||||
|
const shortcuts = Array.isArray(config[command]) ? config[command] : [config[command]];
|
||||||
|
const fixedShortcuts = [];
|
||||||
|
shortcuts.forEach(shortcut => {
|
||||||
|
let newShortcut = shortcut;
|
||||||
|
if (newShortcut.indexOf('cmd') !== -1) {
|
||||||
|
// Mousetrap use `command` and not `cmd`
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.warn('Your config use deprecated `cmd` in key combination. Please use `command` instead.');
|
||||||
|
newShortcut = newShortcut.replace('cmd', 'command');
|
||||||
|
}
|
||||||
|
fixedShortcuts.push(newShortcut);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (command.endsWith(':prefix')) {
|
||||||
|
return Object.assign(keymap, generatePrefixedCommand(command, fixedShortcuts));
|
||||||
|
}
|
||||||
|
|
||||||
|
keymap[command] = fixedShortcuts;
|
||||||
|
|
||||||
|
return keymap;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
@ -19,7 +19,8 @@ import {
|
||||||
UI_MOVE_PREV_PANE,
|
UI_MOVE_PREV_PANE,
|
||||||
UI_WINDOW_GEOMETRY_CHANGED,
|
UI_WINDOW_GEOMETRY_CHANGED,
|
||||||
UI_WINDOW_MOVE,
|
UI_WINDOW_MOVE,
|
||||||
UI_OPEN_FILE
|
UI_OPEN_FILE,
|
||||||
|
UI_COMMAND_EXEC
|
||||||
} from '../constants/ui';
|
} from '../constants/ui';
|
||||||
|
|
||||||
import {setActiveGroup} from './term-groups';
|
import {setActiveGroup} from './term-groups';
|
||||||
|
|
@ -260,3 +261,18 @@ export function openFile(path) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function execCommand(command, fn, e) {
|
||||||
|
return dispatch =>
|
||||||
|
dispatch({
|
||||||
|
type: UI_COMMAND_EXEC,
|
||||||
|
command,
|
||||||
|
effect() {
|
||||||
|
if (fn) {
|
||||||
|
fn(e);
|
||||||
|
} else {
|
||||||
|
rpc.emit('command', command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,46 @@
|
||||||
const commands = {};
|
import {remote} from 'electron';
|
||||||
|
|
||||||
class CommandRegistry {
|
const {getDecoratedKeymaps} = remote.require('./plugins');
|
||||||
register(cmds) {
|
|
||||||
if (cmds) {
|
let commands = {};
|
||||||
for (const command in cmds) {
|
|
||||||
if (command) {
|
export const getRegisteredKeys = () => {
|
||||||
commands[command] = cmds[command];
|
const keymaps = getDecoratedKeymaps();
|
||||||
}
|
|
||||||
}
|
return Object.keys(keymaps).reduce((result, actionName) => {
|
||||||
}
|
const commandKeys = keymaps[actionName];
|
||||||
|
commandKeys.forEach(shortcut => {
|
||||||
|
result[shortcut] = actionName;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registerCommandHandlers = cmds => {
|
||||||
|
if (!cmds) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCommand(cmd) {
|
commands = Object.assign(commands, cmds);
|
||||||
return commands[cmd] !== undefined;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
exec(cmd, e) {
|
export const getCommandHandler = command => {
|
||||||
commands[cmd](e);
|
return commands[command];
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default new CommandRegistry();
|
// Some commands are firectly excuted by Electron menuItem role.
|
||||||
|
// They should not be prevented to reach Electron.
|
||||||
|
const roleCommands = [
|
||||||
|
'window:close',
|
||||||
|
'editor:undo',
|
||||||
|
'editor:redo',
|
||||||
|
'editor:cut',
|
||||||
|
'editor:copy',
|
||||||
|
'editor:paste',
|
||||||
|
'editor:selectAll',
|
||||||
|
'window:minimize',
|
||||||
|
'window:zoom',
|
||||||
|
'window:toggleFullScreen'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const shouldPreventDefault = command => !roleCommands.includes(command);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import Terminal from 'xterm';
|
||||||
import {clipboard} from 'electron';
|
import {clipboard} from 'electron';
|
||||||
import {PureComponent} from '../base-components';
|
import {PureComponent} from '../base-components';
|
||||||
import terms from '../terms';
|
import terms from '../terms';
|
||||||
import returnKey from '../utils/keymaps';
|
|
||||||
import CommandRegistry from '../command-registry';
|
|
||||||
import processClipboard from '../utils/paste';
|
import processClipboard from '../utils/paste';
|
||||||
|
|
||||||
// map old hterm constants to xterm.js
|
// map old hterm constants to xterm.js
|
||||||
|
|
@ -166,17 +164,8 @@ export default class Term extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboardHandler(e) {
|
keyboardHandler(e) {
|
||||||
if (e.type !== 'keydown') {
|
// Has Mousetrap flagged this event as a command?
|
||||||
return true;
|
return !e.catched;
|
||||||
}
|
|
||||||
// test key from keymaps before moving forward with actions
|
|
||||||
const key = returnKey(e);
|
|
||||||
if (key) {
|
|
||||||
if (CommandRegistry.getCommand(key)) {
|
|
||||||
CommandRegistry.exec(key, e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Component} from '../base-components';
|
import {Component} from '../base-components';
|
||||||
import {decorate, getTermGroupProps} from '../utils/plugins';
|
import {decorate, getTermGroupProps} from '../utils/plugins';
|
||||||
import CommandRegistry from '../command-registry';
|
import {registerCommandHandlers} from '../command-registry';
|
||||||
import TermGroup_ from './term-group';
|
import TermGroup_ from './term-group';
|
||||||
import StyleSheet_ from './style-sheet';
|
import StyleSheet_ from './style-sheet';
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default class Terms extends Component {
|
||||||
this.terms = {};
|
this.terms = {};
|
||||||
this.bound = new WeakMap();
|
this.bound = new WeakMap();
|
||||||
this.onRef = this.onRef.bind(this);
|
this.onRef = this.onRef.bind(this);
|
||||||
this.registerCommands = CommandRegistry.register;
|
this.registerCommands = registerCommandHandlers;
|
||||||
props.ref_(this);
|
props.ref_(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,4 @@ export const UI_OPEN_FILE = 'UI_OPEN_FILE';
|
||||||
export const UI_OPEN_HAMBURGER_MENU = 'UI_OPEN_HAMBURGER_MENU';
|
export const UI_OPEN_HAMBURGER_MENU = 'UI_OPEN_HAMBURGER_MENU';
|
||||||
export const UI_WINDOW_MINIMIZE = 'UI_WINDOW_MINIMIZE';
|
export const UI_WINDOW_MINIMIZE = 'UI_WINDOW_MINIMIZE';
|
||||||
export const UI_WINDOW_CLOSE = 'UI_WINDOW_CLOSE';
|
export const UI_WINDOW_CLOSE = 'UI_WINDOW_CLOSE';
|
||||||
|
export const UI_COMMAND_EXEC = 'UI_COMMAND_EXEC';
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
/* eslint-disable react/no-danger */
|
/* eslint-disable react/no-danger */
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Mousetrap from 'mousetrap';
|
||||||
|
|
||||||
import {PureComponent} from '../base-components';
|
import {PureComponent} from '../base-components';
|
||||||
import {connect} from '../utils/plugins';
|
import {connect} from '../utils/plugins';
|
||||||
import * as uiActions from '../actions/ui';
|
import * as uiActions from '../actions/ui';
|
||||||
|
import {getRegisteredKeys, getCommandHandler, shouldPreventDefault} from '../command-registry';
|
||||||
|
|
||||||
import HeaderContainer from './header';
|
import HeaderContainer from './header';
|
||||||
import TermsContainer from './terms';
|
import TermsContainer from './terms';
|
||||||
|
|
@ -17,6 +19,7 @@ class Hyper extends PureComponent {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleFocusActive = this.handleFocusActive.bind(this);
|
this.handleFocusActive = this.handleFocusActive.bind(this);
|
||||||
this.onTermsRef = this.onTermsRef.bind(this);
|
this.onTermsRef = this.onTermsRef.bind(this);
|
||||||
|
this.mousetrap = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(next) {
|
componentWillReceiveProps(next) {
|
||||||
|
|
@ -35,8 +38,34 @@ class Hyper extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachKeyListeners() {
|
attachKeyListeners() {
|
||||||
// eslint-disable-next-line no-console
|
if (!this.mousetrap) {
|
||||||
console.error('unimplemented');
|
this.mousetrap = new Mousetrap(window, true);
|
||||||
|
this.mousetrap.stopCallback = () => {
|
||||||
|
// All events should be intercepted even if focus is in an input/textarea
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.mousetrap.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = getRegisteredKeys();
|
||||||
|
Object.keys(keys).forEach(commandKeys => {
|
||||||
|
this.mousetrap.bind(
|
||||||
|
commandKeys,
|
||||||
|
e => {
|
||||||
|
const command = keys[commandKeys];
|
||||||
|
// We should tell to xterm that it should ignore this event.
|
||||||
|
e.catched = true;
|
||||||
|
this.props.execCommand(command, getCommandHandler(command), e);
|
||||||
|
shouldPreventDefault(command) && e.preventDefault();
|
||||||
|
},
|
||||||
|
'keydown'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.attachKeyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
onTermsRef(terms) {
|
onTermsRef(terms) {
|
||||||
|
|
@ -108,16 +137,8 @@ const HyperContainer = connect(
|
||||||
},
|
},
|
||||||
dispatch => {
|
dispatch => {
|
||||||
return {
|
return {
|
||||||
moveTo: i => {
|
execCommand: (command, fn, e) => {
|
||||||
dispatch(uiActions.moveTo(i));
|
dispatch(uiActions.execCommand(command, fn, e));
|
||||||
},
|
|
||||||
|
|
||||||
moveLeft: () => {
|
|
||||||
dispatch(uiActions.moveLeft());
|
|
||||||
},
|
|
||||||
|
|
||||||
moveRight: () => {
|
|
||||||
dispatch(uiActions.moveRight());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
485
lib/hterm.js
485
lib/hterm.js
|
|
@ -1,485 +0,0 @@
|
||||||
import {clipboard} from 'electron';
|
|
||||||
import {hterm, lib} from 'hterm-umdjs';
|
|
||||||
import runes from 'runes';
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Provide selectAll to terminal viewport
|
|
||||||
hterm.Terminal.prototype.selectAll = () => {
|
|
||||||
// If the cursorNode_ having hyperCaret we need to remove it
|
|
||||||
if (this.cursorNode_.contains(this.hyperCaret)) {
|
|
||||||
this.cursorNode_.removeChild(this.hyperCaret);
|
|
||||||
// We need to clear the DOM range to reset anchorNode
|
|
||||||
selection.clear(this);
|
|
||||||
selection.all(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// override double click behavior to copy
|
|
||||||
const oldMouse = hterm.Terminal.prototype.onMouse_;
|
|
||||||
hterm.Terminal.prototype.onMouse_ = e => {
|
|
||||||
if (e.type === 'dblclick') {
|
|
||||||
selection.extend(this);
|
|
||||||
//eslint-disable-next-line no-console
|
|
||||||
console.log('[hyper+hterm] ignore double click');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return oldMouse.call(this, e);
|
|
||||||
};
|
|
||||||
|
|
||||||
function containsNonLatinCodepoints(s) {
|
|
||||||
return /[^\u0000-\u00ff]/.test(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hterm Unicode patch
|
|
||||||
hterm.TextAttributes.splitWidecharString = str => {
|
|
||||||
const context = runes(str).reduce(
|
|
||||||
(ctx, rune) => {
|
|
||||||
const code = rune.codePointAt(0);
|
|
||||||
if (code < 128 || lib.wc.charWidth(code) === 1) {
|
|
||||||
ctx.acc += rune;
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
if (ctx.acc) {
|
|
||||||
ctx.items.push({str: ctx.acc});
|
|
||||||
ctx.acc = '';
|
|
||||||
}
|
|
||||||
ctx.items.push({str: rune, wcNode: true});
|
|
||||||
return ctx;
|
|
||||||
},
|
|
||||||
{items: [], acc: ''}
|
|
||||||
);
|
|
||||||
if (context.acc) {
|
|
||||||
context.items.push({str: context.acc});
|
|
||||||
}
|
|
||||||
return context.items;
|
|
||||||
};
|
|
||||||
|
|
||||||
// hterm Unicode patch
|
|
||||||
const cache = [];
|
|
||||||
lib.wc.strWidth = str => {
|
|
||||||
const shouldCache = str.length === 1;
|
|
||||||
if (shouldCache && cache[str] !== undefined) {
|
|
||||||
return cache[str];
|
|
||||||
}
|
|
||||||
const chars = runes(str);
|
|
||||||
let width = 0;
|
|
||||||
let rv = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < chars.length; i++) {
|
|
||||||
const codePoint = chars[i].codePointAt(0);
|
|
||||||
width = lib.wc.charWidth(codePoint);
|
|
||||||
if (width < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
rv += width * (codePoint <= 0xffff ? 1 : 2);
|
|
||||||
}
|
|
||||||
if (shouldCache) {
|
|
||||||
cache[str] = rv;
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
};
|
|
||||||
|
|
||||||
// hterm Unicode patch
|
|
||||||
lib.wc.substr = (str, start, optWidth) => {
|
|
||||||
const chars = runes(str);
|
|
||||||
let startIndex;
|
|
||||||
let endIndex;
|
|
||||||
let width = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < chars.length; i++) {
|
|
||||||
const codePoint = chars[i].codePointAt(0);
|
|
||||||
const charWidth = lib.wc.charWidth(codePoint);
|
|
||||||
if (width + charWidth > start) {
|
|
||||||
startIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
width += charWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (optWidth) {
|
|
||||||
width = 0;
|
|
||||||
for (endIndex = startIndex; endIndex < chars.length && width < optWidth; endIndex++) {
|
|
||||||
width += lib.wc.charWidth(chars[endIndex].charCodeAt(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (width > optWidth) {
|
|
||||||
endIndex--;
|
|
||||||
}
|
|
||||||
return chars.slice(startIndex, endIndex).join('');
|
|
||||||
}
|
|
||||||
return chars.slice(startIndex).join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
// MacOS emoji bar support
|
|
||||||
hterm.Keyboard.prototype.onTextInput_ = e => {
|
|
||||||
if (!e.data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
runes(e.data).forEach(this.terminal.onVTKeystroke.bind(this.terminal));
|
|
||||||
};
|
|
||||||
|
|
||||||
hterm.Terminal.IO.prototype.writeUTF8 = string => {
|
|
||||||
if (this.terminal_.io !== this) {
|
|
||||||
throw new Error('Attempt to print from inactive IO object.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!containsNonLatinCodepoints(string)) {
|
|
||||||
this.terminal_.interpret(string);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
runes(string).forEach(rune => {
|
|
||||||
this.terminal_.getTextAttributes().unicodeNode = containsNonLatinCodepoints(rune);
|
|
||||||
this.terminal_.interpret(rune);
|
|
||||||
this.terminal_.getTextAttributes().unicodeNode = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const oldIsDefault = hterm.TextAttributes.prototype.isDefault;
|
|
||||||
hterm.TextAttributes.prototype.isDefault = () => {
|
|
||||||
return !this.unicodeNode && oldIsDefault.call(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
const oldSetFontSize = hterm.Terminal.prototype.setFontSize;
|
|
||||||
hterm.Terminal.prototype.setFontSize = px => {
|
|
||||||
oldSetFontSize.call(this, px);
|
|
||||||
const doc = this.getDocument();
|
|
||||||
let unicodeNodeStyle = doc.getElementById('hyper-unicode-styles');
|
|
||||||
if (!unicodeNodeStyle) {
|
|
||||||
unicodeNodeStyle = doc.createElement('style');
|
|
||||||
unicodeNodeStyle.setAttribute('id', 'hyper-unicode-styles');
|
|
||||||
doc.head.appendChild(unicodeNodeStyle);
|
|
||||||
}
|
|
||||||
unicodeNodeStyle.innerHTML = `
|
|
||||||
.unicode-node {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
width: ${this.scrollPort_.characterSize.width}px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const oldCreateContainer = hterm.TextAttributes.prototype.createContainer;
|
|
||||||
hterm.TextAttributes.prototype.createContainer = text => {
|
|
||||||
const container = oldCreateContainer.call(this, text);
|
|
||||||
if (container.style && runes(text).length === 1 && containsNonLatinCodepoints(text)) {
|
|
||||||
container.className += ' unicode-node';
|
|
||||||
}
|
|
||||||
return container;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Do not match containers when one of them has unicode text (unicode chars need to be alone in their containers)
|
|
||||||
const oldMatchesContainer = hterm.TextAttributes.prototype.matchesContainer;
|
|
||||||
hterm.TextAttributes.prototype.matchesContainer = obj => {
|
|
||||||
return oldMatchesContainer.call(this, obj) && !this.unicodeNode && !containsNonLatinCodepoints(obj.textContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
// there's no option to turn off the size overlay
|
|
||||||
hterm.Terminal.prototype.overlaySize = () => {};
|
|
||||||
|
|
||||||
// fixing a bug in hterm where a double click triggers
|
|
||||||
// a non-collapsed selection whose text is '', and results
|
|
||||||
// in an infinite copy loop
|
|
||||||
hterm.Terminal.prototype.copySelectionToClipboard = () => {
|
|
||||||
const text = this.getSelectionText();
|
|
||||||
if (text) {
|
|
||||||
this.copyStringToClipboard(text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let lastEventTimeStamp;
|
|
||||||
let lastEventKey;
|
|
||||||
// passthrough all the commands that are meant to control
|
|
||||||
// hyper and not the terminal itself
|
|
||||||
const oldKeyDown = hterm.Keyboard.prototype.onKeyDown_;
|
|
||||||
hterm.Keyboard.prototype.onKeyDown_ = e => {
|
|
||||||
const modifierKeysConf = this.terminal.modifierKeys;
|
|
||||||
if (e.timeStamp === lastEventTimeStamp && e.key === lastEventKey) {
|
|
||||||
// Event was already processed.
|
|
||||||
// It seems to occur after a char composition ended by Tab and cause a blur.
|
|
||||||
// See https://github.com/zeit/hyper/issues/1341
|
|
||||||
e.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastEventTimeStamp = e.timeStamp;
|
|
||||||
lastEventKey = e.key;
|
|
||||||
|
|
||||||
if (
|
|
||||||
e.altKey &&
|
|
||||||
e.which !== 16 && // Ignore other modifer keys
|
|
||||||
e.which !== 17 &&
|
|
||||||
e.which !== 18 &&
|
|
||||||
e.which !== 91 &&
|
|
||||||
modifierKeysConf.altIsMeta
|
|
||||||
) {
|
|
||||||
const char = fromCharCode(e);
|
|
||||||
this.terminal.onVTKeystroke('\x1b' + char);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
e.metaKey &&
|
|
||||||
e.code !== 'MetaLeft' &&
|
|
||||||
e.code !== 'MetaRight' &&
|
|
||||||
e.which !== 16 &&
|
|
||||||
e.which !== 17 &&
|
|
||||||
e.which !== 18 &&
|
|
||||||
e.which !== 91 &&
|
|
||||||
modifierKeysConf.cmdIsMeta
|
|
||||||
) {
|
|
||||||
const char = fromCharCode(e);
|
|
||||||
this.terminal.onVTKeystroke('\x1b' + char);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for valid keys in order to accept clear status
|
|
||||||
const clearBlacklist = ['control', 'shift', 'capslock', 'dead'];
|
|
||||||
if (!clearBlacklist.includes(e.code.toLowerCase()) && !clearBlacklist.includes(e.key.toLowerCase())) {
|
|
||||||
// Since Electron 1.6.X, there is a race condition with character composition
|
|
||||||
// if this selection clearing is made synchronously. See #2140.
|
|
||||||
setTimeout(() => selection.clear(this.terminal), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the `hyperCaret` was removed on `selectAll`, we need to insert it back
|
|
||||||
if (this.terminal.hyperCaret.parentNode !== this.terminal.cursorNode_) {
|
|
||||||
this.terminal.focusHyperCaret();
|
|
||||||
}
|
|
||||||
return oldKeyDown.call(this, e);
|
|
||||||
};
|
|
||||||
|
|
||||||
const oldOnMouse = hterm.Terminal.prototype.onMouse_;
|
|
||||||
hterm.Terminal.prototype.onMouse_ = e => {
|
|
||||||
// override `preventDefault` to not actually
|
|
||||||
// prevent default when the type of event is
|
|
||||||
// mousedown, so that we can still trigger
|
|
||||||
// focus on the terminal when the underlying
|
|
||||||
// VT is interested in mouse events, as is the
|
|
||||||
// case of programs like `vtop` that allow for
|
|
||||||
// the user to click on rows
|
|
||||||
if (e.type === 'mousedown') {
|
|
||||||
e.preventDefault = () => {};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldOnMouse.call(this, e);
|
|
||||||
};
|
|
||||||
|
|
||||||
hterm.Terminal.prototype.onMouseDown_ = e => {
|
|
||||||
// copy/paste on right click
|
|
||||||
if (e.button === 2) {
|
|
||||||
const text = this.getSelectionText();
|
|
||||||
if (text) {
|
|
||||||
this.copyStringToClipboard(text);
|
|
||||||
} else {
|
|
||||||
this.onVTKeystroke(clipboard.readText());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// override `ScrollPort.resize` to avoid an expensive calculation
|
|
||||||
// just to get the size of the scrollbar, which for Hyper is always
|
|
||||||
// set to overlay (hence with `0`)
|
|
||||||
hterm.ScrollPort.prototype.resize = () => {
|
|
||||||
this.currentScrollbarWidthPx = 0;
|
|
||||||
|
|
||||||
this.syncScrollHeight();
|
|
||||||
this.syncRowNodesDimensions_();
|
|
||||||
|
|
||||||
this.publish('resize', {scrollPort: this}, () => {
|
|
||||||
this.scrollRowToBottom(this.rowProvider_.getRowCount());
|
|
||||||
this.scheduleRedraw();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// make background transparent to avoid transparency issues
|
|
||||||
hterm.ScrollPort.prototype.setBackgroundColor = () => {
|
|
||||||
this.screen_.style.backgroundColor = 'transparent';
|
|
||||||
};
|
|
||||||
|
|
||||||
// will be called by the <Term/> right after the `hterm.Terminal` is instantiated
|
|
||||||
hterm.Terminal.prototype.onHyperCaret = caret => {
|
|
||||||
this.hyperCaret = caret;
|
|
||||||
let ongoingComposition = false;
|
|
||||||
|
|
||||||
caret.addEventListener('compositionstart', () => {
|
|
||||||
ongoingComposition = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// we can ignore `compositionstart` since chromium always fire it with ''
|
|
||||||
caret.addEventListener('compositionupdate', () => {
|
|
||||||
this.cursorNode_.style.backgroundColor = 'yellow';
|
|
||||||
this.cursorNode_.style.borderColor = 'yellow';
|
|
||||||
});
|
|
||||||
|
|
||||||
// at this point the char(s) is ready
|
|
||||||
caret.addEventListener('compositionend', () => {
|
|
||||||
ongoingComposition = false;
|
|
||||||
this.cursorNode_.style.backgroundColor = '';
|
|
||||||
this.setCursorShape(this.getCursorShape());
|
|
||||||
this.cursorNode_.style.borderColor = this.getCursorColor();
|
|
||||||
caret.innerText = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
// if you open the `Emoji & Symbols` (ctrl+cmd+space)
|
|
||||||
// and select an emoji, it'll be inserted into our caret
|
|
||||||
// and stay there until you star a compositon event.
|
|
||||||
// to avoid that, we'll just check if there's an ongoing
|
|
||||||
// compostion event. if there's one, we do nothing.
|
|
||||||
// otherwise, we just remove the emoji and stop the event
|
|
||||||
// propagation.
|
|
||||||
// PS: this event will *not* be fired when a standard char
|
|
||||||
// (a, b, c, 1, 2, 3, etc) is typed – only for composed
|
|
||||||
// ones and `Emoji & Symbols`
|
|
||||||
caret.addEventListener('input', e => {
|
|
||||||
if (!ongoingComposition) {
|
|
||||||
caret.innerText = '';
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// we need to capture pastes, prevent them and send its contents to the terminal
|
|
||||||
caret.addEventListener('paste', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
const text = e.clipboardData.getData('text');
|
|
||||||
this.onVTKeystroke(text);
|
|
||||||
});
|
|
||||||
|
|
||||||
// here we replicate the focus/blur state of our caret on the `hterm` caret
|
|
||||||
caret.addEventListener('focus', () => {
|
|
||||||
this.cursorNode_.setAttribute('focus', true);
|
|
||||||
this.restyleCursor_();
|
|
||||||
});
|
|
||||||
caret.addEventListener('blur', () => {
|
|
||||||
this.cursorNode_.setAttribute('focus', false);
|
|
||||||
this.restyleCursor_();
|
|
||||||
});
|
|
||||||
|
|
||||||
// this is necessary because we need to access the `document_` and the hyperCaret
|
|
||||||
// on `hterm.Screen.prototype.syncSelectionCaret`
|
|
||||||
this.primaryScreen_.terminal = this;
|
|
||||||
this.alternateScreen_.terminal = this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ensure that our contenteditable caret is injected
|
|
||||||
// inside the term's cursor node and that it's focused
|
|
||||||
hterm.Terminal.prototype.focusHyperCaret = () => {
|
|
||||||
if (!this.hyperCaret.parentNode !== this.cursorNode_) {
|
|
||||||
this.cursorNode_.appendChild(this.hyperCaret);
|
|
||||||
}
|
|
||||||
this.hyperCaret.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
hterm.Screen.prototype.syncSelectionCaret = () => {
|
|
||||||
const p = this.terminal.hyperCaret;
|
|
||||||
const doc = this.terminal.document_;
|
|
||||||
const win = doc.defaultView;
|
|
||||||
const s = win.getSelection();
|
|
||||||
const r = doc.createRange();
|
|
||||||
r.selectNodeContents(p);
|
|
||||||
s.removeAllRanges();
|
|
||||||
s.addRange(r);
|
|
||||||
};
|
|
||||||
|
|
||||||
// For some reason, when the original version of this function was called right
|
|
||||||
// after a new tab was created, it was breaking the focus of the other tab.
|
|
||||||
// After some investigation, I (matheuss) found that `this.iframe_.focus();` (from
|
|
||||||
// the original function) was causing the issue. So right now we're overriding
|
|
||||||
// the function to prevent the `iframe_` from being focused.
|
|
||||||
// This shouldn't create any side effects – we're _stealing_ the focus from `htem` anyways.
|
|
||||||
hterm.ScrollPort.prototype.focus = () => {
|
|
||||||
this.screen_.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
// fixes a bug in hterm, where the cursor goes back to `BLOCK`
|
|
||||||
// after the bell rings
|
|
||||||
const oldRingBell = hterm.Terminal.prototype.ringBell;
|
|
||||||
hterm.Terminal.prototype.ringBell = () => {
|
|
||||||
oldRingBell.call(this);
|
|
||||||
setTimeout(() => {
|
|
||||||
this.restyleCursor_();
|
|
||||||
}, 200);
|
|
||||||
};
|
|
||||||
|
|
||||||
// fixes a bug in hterm, where the shorthand hex
|
|
||||||
// is not properly converted to rgb
|
|
||||||
lib.colors.hexToRGB = arg => {
|
|
||||||
const hex16 = lib.colors.re_.hex16;
|
|
||||||
const hex24 = lib.colors.re_.hex24;
|
|
||||||
|
|
||||||
function convert(hex) {
|
|
||||||
if (hex.length === 4) {
|
|
||||||
hex = hex.replace(hex16, (h, r, g, b) => {
|
|
||||||
return '#' + r + r + g + g + b + b;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const ary = hex.match(hex24);
|
|
||||||
if (!ary) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'rgb(' + parseInt(ary[1], 16) + ', ' + parseInt(ary[2], 16) + ', ' + parseInt(ary[3], 16) + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(arg)) {
|
|
||||||
for (let i = 0; i < arg.length; i++) {
|
|
||||||
arg[i] = convert(arg[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
arg = convert(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return arg;
|
|
||||||
};
|
|
||||||
|
|
||||||
// add support for cursor styles 5 and 6, fixes #270
|
|
||||||
hterm.VT.CSI[' q'] = parseState => {
|
|
||||||
const arg = parseState.args[0];
|
|
||||||
if (arg === '0' || arg === '1') {
|
|
||||||
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
|
|
||||||
this.terminal.setCursorBlink(true);
|
|
||||||
} else if (arg === '2') {
|
|
||||||
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
|
|
||||||
this.terminal.setCursorBlink(false);
|
|
||||||
} else if (arg === '3') {
|
|
||||||
this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
|
|
||||||
this.terminal.setCursorBlink(true);
|
|
||||||
} else if (arg === '4') {
|
|
||||||
this.terminal.setCursorShape(hterm.Terminal.cursorShape.UNDERLINE);
|
|
||||||
this.terminal.setCursorBlink(false);
|
|
||||||
} else if (arg === '5') {
|
|
||||||
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
|
|
||||||
this.terminal.setCursorBlink(true);
|
|
||||||
} else if (arg === '6') {
|
|
||||||
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
|
|
||||||
this.terminal.setCursorBlink(false);
|
|
||||||
} else {
|
|
||||||
//eslint-disable-next-line no-console
|
|
||||||
console.warn('Unknown cursor style: ' + arg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default hterm;
|
|
||||||
export {lib};
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* Keyboard event keyCodes have proven to be really unreliable.
|
|
||||||
* This util function will cover most of the edge cases where
|
|
||||||
* String.fromCharCode() doesn't work.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const _toAscii = {
|
|
||||||
188: '44',
|
|
||||||
109: '45',
|
|
||||||
190: '46',
|
|
||||||
191: '47',
|
|
||||||
192: '96',
|
|
||||||
220: '92',
|
|
||||||
222: '39',
|
|
||||||
221: '93',
|
|
||||||
219: '91',
|
|
||||||
173: '45',
|
|
||||||
187: '61', // IE Key codes
|
|
||||||
186: '59', // IE Key codes
|
|
||||||
189: '45' // IE Key codes
|
|
||||||
};
|
|
||||||
|
|
||||||
const _shiftUps = {
|
|
||||||
96: '~',
|
|
||||||
49: '!',
|
|
||||||
50: '@',
|
|
||||||
51: '#',
|
|
||||||
52: '$',
|
|
||||||
53: '%',
|
|
||||||
54: '^',
|
|
||||||
55: '&',
|
|
||||||
56: '*',
|
|
||||||
57: '(',
|
|
||||||
48: ')',
|
|
||||||
45: '_',
|
|
||||||
61: '+',
|
|
||||||
91: '{',
|
|
||||||
93: '}',
|
|
||||||
92: '|',
|
|
||||||
59: ':',
|
|
||||||
39: "'",
|
|
||||||
44: '<',
|
|
||||||
46: '>',
|
|
||||||
47: '?'
|
|
||||||
};
|
|
||||||
|
|
||||||
const _arrowKeys = {
|
|
||||||
38: '[A',
|
|
||||||
40: '[B',
|
|
||||||
39: '[C',
|
|
||||||
37: '[D'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This fn takes a keyboard event and returns
|
|
||||||
* the character that was pressed. This fn
|
|
||||||
* purposely doesn't take into account if the alt/meta
|
|
||||||
* key was pressed.
|
|
||||||
*/
|
|
||||||
export default function fromCharCode(e) {
|
|
||||||
let code = String(e.which);
|
|
||||||
|
|
||||||
if ({}.hasOwnProperty.call(_arrowKeys, code)) {
|
|
||||||
return _arrowKeys[code];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ({}.hasOwnProperty.call(_toAscii, code)) {
|
|
||||||
code = _toAscii[code];
|
|
||||||
}
|
|
||||||
|
|
||||||
const char = String.fromCharCode(code);
|
|
||||||
if (e.shiftKey) {
|
|
||||||
if ({}.hasOwnProperty.call(_shiftUps, code)) {
|
|
||||||
return _shiftUps[code];
|
|
||||||
}
|
|
||||||
return char.toUpperCase();
|
|
||||||
}
|
|
||||||
return char.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
import {remote} from 'electron';
|
|
||||||
|
|
||||||
const getCommand = remote.require('./utils/keymaps/get-command');
|
|
||||||
|
|
||||||
// Key handling is deeply inspired by Mousetrap
|
|
||||||
// https://github.com/ccampbell/mousetrap
|
|
||||||
|
|
||||||
const _EXCLUDE = {
|
|
||||||
16: 'shift',
|
|
||||||
17: 'ctrl',
|
|
||||||
18: 'alt',
|
|
||||||
91: 'meta',
|
|
||||||
93: 'meta',
|
|
||||||
224: 'meta'
|
|
||||||
};
|
|
||||||
|
|
||||||
const _MAP = {
|
|
||||||
8: 'backspace',
|
|
||||||
9: 'tab',
|
|
||||||
13: 'enter',
|
|
||||||
20: 'capslock',
|
|
||||||
27: 'esc',
|
|
||||||
32: 'space',
|
|
||||||
33: 'pageup',
|
|
||||||
34: 'pagedown',
|
|
||||||
35: 'end',
|
|
||||||
36: 'home',
|
|
||||||
37: 'left',
|
|
||||||
38: 'up',
|
|
||||||
39: 'right',
|
|
||||||
40: 'down',
|
|
||||||
45: 'ins',
|
|
||||||
46: 'del'
|
|
||||||
};
|
|
||||||
|
|
||||||
const _KEYCODE_MAP = {
|
|
||||||
106: '*',
|
|
||||||
107: '+',
|
|
||||||
109: '-',
|
|
||||||
110: '.',
|
|
||||||
111: '/',
|
|
||||||
186: ';',
|
|
||||||
187: '=',
|
|
||||||
188: ',',
|
|
||||||
189: '-',
|
|
||||||
190: '.',
|
|
||||||
191: '/',
|
|
||||||
192: '`',
|
|
||||||
219: '[',
|
|
||||||
220: '\\',
|
|
||||||
221: ']',
|
|
||||||
222: "'"
|
|
||||||
};
|
|
||||||
|
|
||||||
const characterFromEvent = e => {
|
|
||||||
if (_EXCLUDE[e.which]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_MAP[e.which]) {
|
|
||||||
return _MAP[e.which];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_KEYCODE_MAP[e.which]) {
|
|
||||||
return _KEYCODE_MAP[e.which];
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it is not in the special map
|
|
||||||
|
|
||||||
// with keydown and keyup events the character seems to always
|
|
||||||
// come in as an uppercase character whether you are pressing shift
|
|
||||||
// or not. we should make sure it is always lowercase for comparisons
|
|
||||||
|
|
||||||
return String.fromCharCode(e.which).toLowerCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function returnKey(e) {
|
|
||||||
const character = characterFromEvent(e);
|
|
||||||
if (!character) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
keys.push(character);
|
|
||||||
|
|
||||||
return getCommand(keys.join('+'));
|
|
||||||
}
|
|
||||||
59
package.json
59
package.json
|
|
@ -1,8 +1,7 @@
|
||||||
{
|
{
|
||||||
"repository": "zeit/hyper",
|
"repository": "zeit/hyper",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start":
|
"start": "echo 'please run `yarn run dev` in one tab and then `yarn run app` in another one'",
|
||||||
"echo 'please run `yarn run dev` in one tab and then `yarn run app` in another one'",
|
|
||||||
"app": "electron app",
|
"app": "electron app",
|
||||||
"dev": "webpack -w",
|
"dev": "webpack -w",
|
||||||
"build": "cross-env NODE_ENV=production webpack",
|
"build": "cross-env NODE_ENV=production webpack",
|
||||||
|
|
@ -11,18 +10,21 @@
|
||||||
"test:unit": "ava test/unit",
|
"test:unit": "ava test/unit",
|
||||||
"test:unit:watch": "yarn run test:unit -- --watch",
|
"test:unit:watch": "yarn run test:unit -- --watch",
|
||||||
"prepush": "yarn test",
|
"prepush": "yarn test",
|
||||||
"postinstall":
|
"postinstall": "electron-builder install-app-deps && yarn run rebuild-node-pty",
|
||||||
"electron-builder install-app-deps && yarn run rebuild-node-pty",
|
"rebuild-node-pty": "electron-rebuild -f -w app/node_modules/node-pty -m app",
|
||||||
"rebuild-node-pty":
|
"dist": "yarn run build && cross-env BABEL_ENV=production babel --out-file app/renderer/bundle.js --no-comments --minified app/renderer/bundle.js && build",
|
||||||
"electron-rebuild -f -w app/node_modules/node-pty -m app",
|
"clean": "node ./bin/rimraf-standalone.js node_modules && node ./bin/rimraf-standalone.js ./app/node_modules && node ./bin/rimraf-standalone.js ./app/renderer"
|
||||||
"dist":
|
|
||||||
"yarn run build && cross-env BABEL_ENV=production babel --out-file app/renderer/bundle.js --no-comments --minified app/renderer/bundle.js && build",
|
|
||||||
"clean":
|
|
||||||
"node ./bin/rimraf-standalone.js node_modules && node ./bin/rimraf-standalone.js ./app/node_modules && node ./bin/rimraf-standalone.js ./app/renderer"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"plugins": ["react", "prettier"],
|
"plugins": [
|
||||||
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier"],
|
"react",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 8,
|
"ecmaVersion": 8,
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
|
|
@ -39,7 +41,10 @@
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"func-names": ["error", "as-needed"],
|
"func-names": [
|
||||||
|
"error",
|
||||||
|
"as-needed"
|
||||||
|
],
|
||||||
"no-shadow": "error",
|
"no-shadow": "error",
|
||||||
"no-extra-semi": 0,
|
"no-extra-semi": 0,
|
||||||
"react/prop-types": 0,
|
"react/prop-types": 0,
|
||||||
|
|
@ -62,7 +67,8 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"overrides": [{
|
"overrides": [
|
||||||
|
{
|
||||||
"files": "app/config/config-default.js",
|
"files": "app/config/config-default.js",
|
||||||
"rules": {
|
"rules": {
|
||||||
"prettier/prettier": [
|
"prettier/prettier": [
|
||||||
|
|
@ -80,10 +86,13 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
"presets": ["react"],
|
"presets": [
|
||||||
|
"react"
|
||||||
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"production": {
|
"production": {
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
|
@ -120,20 +129,28 @@
|
||||||
"target": [
|
"target": [
|
||||||
{
|
{
|
||||||
"target": "deb",
|
"target": "deb",
|
||||||
"arch": ["x64"]
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"target": "AppImage",
|
"target": "AppImage",
|
||||||
"arch": ["x64"]
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"target": "rpm",
|
"target": "rpm",
|
||||||
"arch": ["x64"]
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": ["squirrel"]
|
"target": [
|
||||||
|
"squirrel"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.developer-tools",
|
"category": "public.app-category.developer-tools",
|
||||||
|
|
@ -150,6 +167,7 @@
|
||||||
"color": "2.0.0",
|
"color": "2.0.0",
|
||||||
"css-loader": "0.28.7",
|
"css-loader": "0.28.7",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
|
"mousetrap": "chabou/mousetrap#useCapture",
|
||||||
"ms": "2.0.0",
|
"ms": "2.0.0",
|
||||||
"php-escape-shell": "1.0.0",
|
"php-escape-shell": "1.0.0",
|
||||||
"react": "16.0.0",
|
"react": "16.0.0",
|
||||||
|
|
@ -159,7 +177,6 @@
|
||||||
"redux": "3.7.2",
|
"redux": "3.7.2",
|
||||||
"redux-thunk": "2.2.0",
|
"redux-thunk": "2.2.0",
|
||||||
"reselect": "3.0.1",
|
"reselect": "3.0.1",
|
||||||
"runes": "0.4.3",
|
|
||||||
"seamless-immutable": "7.1.2",
|
"seamless-immutable": "7.1.2",
|
||||||
"semver": "5.4.1",
|
"semver": "5.4.1",
|
||||||
"uuid": "3.1.0",
|
"uuid": "3.1.0",
|
||||||
|
|
|
||||||
20
yarn.lock
20
yarn.lock
|
|
@ -1061,14 +1061,10 @@ balanced-match@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
|
||||||
base64-js@1.2.0:
|
base64-js@1.2.0, base64-js@^1.0.2:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
|
||||||
|
|
||||||
base64-js@^1.0.2:
|
|
||||||
version "1.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
|
|
||||||
|
|
||||||
bcrypt-pbkdf@^1.0.0:
|
bcrypt-pbkdf@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
|
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
|
||||||
|
|
@ -3992,7 +3988,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@0.0.8:
|
minimist@0.0.8, minimist@~0.0.1:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
|
|
||||||
|
|
@ -4000,10 +3996,6 @@ minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||||
|
|
||||||
minimist@~0.0.1:
|
|
||||||
version "0.0.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
|
||||||
|
|
||||||
mkdirp@0.5.0:
|
mkdirp@0.5.0:
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12"
|
||||||
|
|
@ -4016,6 +4008,10 @@ mkdirp@0.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
|
mousetrap@chabou/mousetrap#useCapture:
|
||||||
|
version "1.6.1"
|
||||||
|
resolved "https://codeload.github.com/chabou/mousetrap/tar.gz/c95eeeaafba1131dd8d35bc130d4a79b2ff9261a"
|
||||||
|
|
||||||
ms@0.7.1:
|
ms@0.7.1:
|
||||||
version "0.7.1"
|
version "0.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
|
||||||
|
|
@ -5314,10 +5310,6 @@ run-async@^2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-promise "^2.1.0"
|
is-promise "^2.1.0"
|
||||||
|
|
||||||
runes@0.4.3:
|
|
||||||
version "0.4.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
|
|
||||||
|
|
||||||
rx-lite-aggregates@^4.0.8:
|
rx-lite-aggregates@^4.0.8:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
|
resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue