Add prettier and resolve all lint errors

This commit is contained in:
CHaBou 2017-09-10 15:35:10 +02:00
parent 0fbf7cfc97
commit 1155bb54b1
No known key found for this signature in database
GPG key ID: EF8D073B729A0B33
64 changed files with 1120 additions and 1129 deletions

View file

@ -4,7 +4,8 @@ const ms = require('ms');
const retry = require('async-retry'); const retry = require('async-retry');
// Utilities // Utilities
const notify = require('./notify'); // eslint-disable-line no-unused-vars // eslint-disable-next-line no-unused-vars
const notify = require('./notify');
const {version} = require('./package'); const {version} = require('./package');
const {getConfig} = require('./config'); const {getConfig} = require('./config');
@ -14,6 +15,7 @@ let isInit = false;
function init() { function init() {
autoUpdater.on('error', (err, msg) => { autoUpdater.on('error', (err, msg) => {
//eslint-disable-next-line no-console
console.error('Error fetching updates', msg + ' (' + err.stack + ')'); console.error('Error fetching updates', msg + ' (' + err.stack + ')');
}); });
@ -51,7 +53,7 @@ function init() {
isInit = true; isInit = true;
} }
module.exports = function (win) { module.exports = win => {
if (!isInit) { if (!isInit) {
init(); init();
} }

View file

@ -12,7 +12,7 @@ const watchCfg = process.platform === 'win32' ? {interval: 2000} : {};
let cfg = {}; let cfg = {};
let _watcher; let _watcher;
const _watch = function () { const _watch = function() {
if (_watcher) { if (_watcher) {
return _watcher; return _watcher;
} }
@ -23,79 +23,74 @@ const _watch = function () {
cfg = _import(); cfg = _import();
notify('Configuration updated', 'Hyper configuration reloaded!'); notify('Configuration updated', 'Hyper configuration reloaded!');
watchers.forEach(fn => fn()); watchers.forEach(fn => fn());
checkDeprecatedConfig() checkDeprecatedConfig();
}); });
_watcher.on('error', error => { _watcher.on('error', error => {
//eslint-disable-next-line no-console
console.error('error watching config', error); console.error('error watching config', error);
}); });
}; };
exports.subscribe = function (fn) { exports.subscribe = fn => {
watchers.push(fn); watchers.push(fn);
return () => { return () => {
watchers.splice(watchers.indexOf(fn), 1); watchers.splice(watchers.indexOf(fn), 1);
}; };
}; };
exports.getConfigDir = function () { exports.getConfigDir = () => {
// expose config directory to load plugin from the right place // expose config directory to load plugin from the right place
return cfgDir; return cfgDir;
}; };
exports.getConfig = function () { exports.getConfig = () => {
return cfg.config; return cfg.config;
}; };
exports.openConfig = function () { exports.openConfig = () => {
return _openConfig(); return _openConfig();
}; };
exports.getPlugins = function () { exports.getPlugins = () => {
return { return {
plugins: cfg.plugins, plugins: cfg.plugins,
localPlugins: cfg.localPlugins localPlugins: cfg.localPlugins
}; };
}; };
exports.getKeymaps = function () { exports.getKeymaps = () => {
return cfg.keymaps; return cfg.keymaps;
}; };
exports.extendKeymaps = function (keymaps) { exports.extendKeymaps = keymaps => {
if (keymaps) { if (keymaps) {
cfg.keymaps = keymaps; cfg.keymaps = keymaps;
} }
}; };
exports.setup = function () { exports.setup = () => {
cfg = _import(); cfg = _import();
_watch(); _watch();
checkDeprecatedConfig() checkDeprecatedConfig();
}; };
exports.getWin = win.get; exports.getWin = win.get;
exports.winRecord = win.recordState; exports.winRecord = win.recordState;
const getDeprecatedCSS = function (config) { const getDeprecatedCSS = function(config) {
const deprecated = []; const deprecated = [];
const deprecatedCSS = [ const deprecatedCSS = ['x-screen', 'x-row', 'cursor-node', '::selection'];
'x-screen',
'x-row',
'cursor-node',
'::selection'
];
deprecatedCSS.forEach(css => { deprecatedCSS.forEach(css => {
if ((config.css && config.css.indexOf(css) !== -1) || if ((config.css && config.css.indexOf(css) !== -1) || (config.termCSS && config.termCSS.indexOf(css) !== -1)) {
(config.termCSS && config.termCSS.indexOf(css) !== -1)) {
deprecated.push(css); deprecated.push(css);
} }
}) });
return deprecated; return deprecated;
} };
exports.getDeprecatedCSS = getDeprecatedCSS; exports.getDeprecatedCSS = getDeprecatedCSS;
const checkDeprecatedConfig = function () { const checkDeprecatedConfig = function() {
if (!cfg.config) { if (!cfg.config) {
return; return;
} }
@ -104,22 +99,22 @@ const checkDeprecatedConfig = function () {
return; return;
} }
const deprecatedStr = deprecated.join(', '); const deprecatedStr = deprecated.join(', ');
notify('Configuration warning', `Your configuration uses some deprecated CSS classes (${deprecatedStr})`) notify('Configuration warning', `Your configuration uses some deprecated CSS classes (${deprecatedStr})`);
} };
exports.htermConfigTranslate = (config) => { exports.htermConfigTranslate = config => {
const cssReplacements = { const cssReplacements = {
'x-screen x-row([ \{\.\[])': '.xterm-rows > div$1', 'x-screen x-row([ {.[])': '.xterm-rows > div$1',
'.cursor-node([ \{\.\[])': '.terminal-cursor$1', '.cursor-node([ {.[])': '.terminal-cursor$1',
'::selection([ \{\.\[])': '.terminal .xterm-selection div$1', '::selection([ {.[])': '.terminal .xterm-selection div$1',
'x-screen a([ \{\.\[])': '.terminal a$1', 'x-screen a([ {.[])': '.terminal a$1',
'x-row a([ \{\.\[])': '.terminal a$1' 'x-row a([ {.[])': '.terminal a$1'
} };
Object.keys(cssReplacements).forEach(pattern => { Object.keys(cssReplacements).forEach(pattern => {
const searchvalue = new RegExp(pattern, 'g'); const searchvalue = new RegExp(pattern, 'g');
const newvalue = cssReplacements[pattern]; const newvalue = cssReplacements[pattern];
config.css = config.css.replace(searchvalue, newvalue); config.css = config.css.replace(searchvalue, newvalue);
config.termCSS = config.termCSS.replace(searchvalue, newvalue); config.termCSS = config.termCSS.replace(searchvalue, newvalue);
}) });
return config; return config;
} };

View file

@ -4,18 +4,18 @@ const {defaultCfg, cfgPath, plugs} = require('./paths');
const _init = require('./init'); const _init = require('./init');
const _keymaps = require('./keymaps'); const _keymaps = require('./keymaps');
const _write = function (path, data) { const _write = function(path, data) {
// This method will take text formatted as Unix line endings and transform it // 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 // 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. // text editor on Windows (notepad) doesn't Deal with LF files. Still. In 2017.
const crlfify = function (str) { const crlfify = function(str) {
return str.replace(/\r?\n/g, '\r\n'); return str.replace(/\r?\n/g, '\r\n');
}; };
const format = process.platform === 'win32' ? crlfify(data.toString()) : data; const format = process.platform === 'win32' ? crlfify(data.toString()) : data;
writeFileSync(path, format, 'utf8'); writeFileSync(path, format, 'utf8');
}; };
const _importConf = function () { const _importConf = function() {
// init plugin directories if not present // init plugin directories if not present
mkdirpSync(plugs.base); mkdirpSync(plugs.base);
mkdirpSync(plugs.local); mkdirpSync(plugs.local);
@ -30,11 +30,12 @@ const _importConf = function () {
return {userCfg: {}, defaultCfg: _defaultCfg}; return {userCfg: {}, defaultCfg: _defaultCfg};
} }
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.log(err); console.log(err);
} }
}; };
const _import = function () { const _import = function() {
const cfg = _init(_importConf()); const cfg = _init(_importConf());
if (cfg) { if (cfg) {

View file

@ -2,7 +2,7 @@ const vm = require('vm');
const merge = require('lodash/merge'); const merge = require('lodash/merge');
const notify = require('../notify'); const notify = require('../notify');
const _extract = function (script) { const _extract = function(script) {
const module = {}; const module = {};
script.runInNewContext({module}); script.runInNewContext({module});
if (!module.exports) { if (!module.exports) {
@ -11,21 +11,22 @@ const _extract = function (script) {
return module.exports; return module.exports;
}; };
const _syntaxValidation = function (cfg) { const _syntaxValidation = function(cfg) {
try { try {
return new vm.Script(cfg, {filename: '.hyper.js', displayErrors: true}); return new vm.Script(cfg, {filename: '.hyper.js', displayErrors: true});
} catch (err) { } catch (err) {
notify(`Error loading config: ${err.name}, see DevTools for more info`); notify(`Error loading config: ${err.name}, see DevTools for more info`);
//eslint-disable-next-line no-console
console.error('Error loading config:', err); console.error('Error loading config:', err);
} }
}; };
const _extractDefault = function (cfg) { const _extractDefault = function(cfg) {
return _extract(_syntaxValidation(cfg)); return _extract(_syntaxValidation(cfg));
}; };
// init config // init config
const _init = function (cfg) { const _init = function(cfg) {
const script = _syntaxValidation(cfg.userCfg); const script = _syntaxValidation(cfg.userCfg);
if (script) { if (script) {
const _cfg = _extract(script); const _cfg = _extract(script);

View file

@ -4,7 +4,7 @@ const {defaultPlatformKeyPath} = require('./paths');
const commands = {}; const commands = {};
const keys = {}; const keys = {};
const _setKeysForCommands = function (keymap) { const _setKeysForCommands = function(keymap) {
for (const command in keymap) { for (const command in keymap) {
if (command) { if (command) {
commands[command] = keymap[command].toLowerCase(); commands[command] = keymap[command].toLowerCase();
@ -12,15 +12,15 @@ const _setKeysForCommands = function (keymap) {
} }
}; };
const _setCommandsForKeys = function (commands) { const _setCommandsForKeys = function(commands_) {
for (const command in commands) { for (const command in commands_) {
if (command) { if (command) {
keys[commands[command]] = command; keys[commands_[command]] = command;
} }
} }
}; };
const _import = function (customsKeys) { const _import = function(customsKeys) {
try { try {
const mapping = JSON.parse(readFileSync(defaultPlatformKeyPath())); const mapping = JSON.parse(readFileSync(defaultPlatformKeyPath()));
_setKeysForCommands(mapping); _setKeysForCommands(mapping);
@ -28,10 +28,13 @@ const _import = function (customsKeys) {
_setCommandsForKeys(commands); _setCommandsForKeys(commands);
return {commands, keys}; return {commands, keys};
} catch (err) {} } catch (err) {
//eslint-disable-next-line no-console
console.error(err);
}
}; };
const _extend = function (customsKeys) { const _extend = function(customsKeys) {
if (customsKeys) { if (customsKeys) {
for (const command in customsKeys) { for (const command in customsKeys) {
if (command) { if (command) {

View file

@ -9,34 +9,39 @@ if (process.platform === 'win32') {
// Windows opens .js files with WScript.exe by default // Windows opens .js files with WScript.exe by default
// If the user hasn't set up an editor for .js files, we fallback to notepad. // If the user hasn't set up an editor for .js files, we fallback to notepad.
const getFileExtKeys = () => new Promise((resolve, reject) => { const getFileExtKeys = () =>
Registry({ new Promise((resolve, reject) => {
hive: Registry.HKCU, Registry({
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.js' hive: Registry.HKCU,
}) key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.js'
.keys((error, keys) => { }).keys((error, keys) => {
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
resolve(keys || []); resolve(keys || []);
} }
});
}); });
});
const hasDefaultSet = async () => { const hasDefaultSet = async () => {
const keys = await getFileExtKeys(); const keys = await getFileExtKeys();
const valueGroups = await Promise.all(keys.map(key => new Promise((resolve, reject) => { const valueGroups = await Promise.all(
key.values((error, items) => { keys.map(
if (error) { key =>
reject(error); new Promise((resolve, reject) => {
} key.values((error, items) => {
resolve(items.map(item => item.value || '') || []); if (error) {
}); reject(error);
}))); }
resolve(items.map(item => item.value || '') || []);
});
})
)
);
const values = valueGroups const values = valueGroups
.reduce((allValues, groupValues) => ([...allValues, ...groupValues]), []) .reduce((allValues, groupValues) => [...allValues, ...groupValues], [])
.filter(value => value && typeof value === 'string'); .filter(value => value && typeof value === 'string');
// No default app set // No default app set
@ -49,29 +54,33 @@ if (process.platform === 'win32') {
const userDefaults = values.filter(value => value.endsWith('.exe') && !value.includes('WScript.exe')); const userDefaults = values.filter(value => value.endsWith('.exe') && !value.includes('WScript.exe'));
// WScript.exe is overidden // WScript.exe is overidden
return (userDefaults.length > 0); return userDefaults.length > 0;
} }
return true; return true;
}; };
// This mimics shell.openItem, true if it worked, false if not. // This mimics shell.openItem, true if it worked, false if not.
const openNotepad = file => new Promise(resolve => { const openNotepad = file =>
exec(`start notepad.exe ${file}`, error => { new Promise(resolve => {
resolve(!error); exec(`start notepad.exe ${file}`, error => {
resolve(!error);
});
}); });
});
module.exports = () => hasDefaultSet() module.exports = () =>
.then(yes => { hasDefaultSet()
if (yes) { .then(yes => {
return shell.openItem(cfgPath); if (yes) {
} return shell.openItem(cfgPath);
console.warn('No default app set for .js files, using notepad.exe fallback'); }
return openNotepad(cfgPath); //eslint-disable-next-line no-console
}) console.warn('No default app set for .js files, using notepad.exe fallback');
.catch(err => { return openNotepad(cfgPath);
console.error('Open config with default app error:', err); })
return openNotepad(cfgPath); .catch(err => {
}); //eslint-disable-next-line no-console
console.error('Open config with default app error:', err);
return openNotepad(cfgPath);
});
} }

View file

@ -32,10 +32,14 @@ const linuxKeys = join(keymapPath, 'linux.json');
const defaultPlatformKeyPath = () => { const defaultPlatformKeyPath = () => {
switch (process.platform) { switch (process.platform) {
case 'darwin': return darwinKeys; case 'darwin':
case 'win32': return win32Keys; return darwinKeys;
case 'linux': return linuxKeys; case 'win32':
default: return darwinKeys; return win32Keys;
case 'linux':
return linuxKeys;
default:
return darwinKeys;
} }
}; };
@ -45,6 +49,7 @@ if (isDev) {
statSync(devCfg); statSync(devCfg);
cfgPath = devCfg; cfgPath = devCfg;
cfgDir = devDir; cfgDir = devDir;
//eslint-disable-next-line no-console
console.log('using config file:', cfgPath); console.log('using config file:', cfgPath);
} catch (err) { } catch (err) {
// ignore // ignore
@ -52,5 +57,12 @@ if (isDev) {
} }
module.exports = { module.exports = {
cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath, plugs, yarn cfgDir,
cfgPath,
cfgFile,
defaultCfg,
icon,
defaultPlatformKeyPath,
plugs,
yarn
}; };

View file

@ -2,8 +2,11 @@
if (['--help', '-v', '--version'].includes(process.argv[1])) { if (['--help', '-v', '--version'].includes(process.argv[1])) {
const {version} = require('./package'); const {version} = require('./package');
const configLocation = process.platform === 'win32' ? process.env.userprofile + '\\.hyper.js' : '~/.hyper.js'; const configLocation = process.platform === 'win32' ? process.env.userprofile + '\\.hyper.js' : '~/.hyper.js';
//eslint-disable-next-line no-console
console.log(`Hyper version ${version}`); console.log(`Hyper version ${version}`);
//eslint-disable-next-line no-console
console.log('Hyper does not accept any command line arguments. Please modify the config file instead.'); console.log('Hyper does not accept any command line arguments. Please modify the config file instead.');
//eslint-disable-next-line no-console
console.log(`Hyper configuration file located at: ${configLocation}`); console.log(`Hyper configuration file located at: ${configLocation}`);
// eslint-disable-next-line unicorn/no-process-exit // eslint-disable-next-line unicorn/no-process-exit
process.exit(); process.exit();
@ -14,6 +17,7 @@ const checkSquirrel = () => {
try { try {
squirrel = require('electron-squirrel-startup'); squirrel = require('electron-squirrel-startup');
//eslint-disable-next-line no-empty
} catch (err) {} } catch (err) {}
if (squirrel) { if (squirrel) {
@ -81,6 +85,7 @@ app.getLastFocusedWindow = () => {
}; };
if (isDev) { if (isDev) {
//eslint-disable-next-line no-console
console.log('running in dev mode'); console.log('running in dev mode');
// Overide default appVersion which is set from package.json // Overide default appVersion which is set from package.json
@ -90,123 +95,132 @@ if (isDev) {
} }
}); });
} else { } else {
//eslint-disable-next-line no-console
console.log('running in prod mode'); console.log('running in prod mode');
} }
const url = 'file://' + resolve( const url = 'file://' + resolve(isDev ? __dirname : app.getAppPath(), 'index.html');
isDev ? __dirname : app.getAppPath(), //eslint-disable-next-line no-console
'index.html'
);
console.log('electron will open', url); console.log('electron will open', url);
app.on('ready', () => installDevExtensions(isDev).then(() => { app.on('ready', () =>
function createWindow(fn, options = {}) { installDevExtensions(isDev)
const cfg = plugins.getDecoratedConfig(); .then(() => {
function createWindow(fn, options = {}) {
const cfg = plugins.getDecoratedConfig();
const winSet = config.getWin(); const winSet = config.getWin();
let [startX, startY] = winSet.position; let [startX, startY] = winSet.position;
const [width, height] = options.size ? options.size : (cfg.windowSize || winSet.size); const [width, height] = options.size ? options.size : cfg.windowSize || winSet.size;
const {screen} = require('electron'); const {screen} = require('electron');
const winPos = options.position; const winPos = options.position;
// Open the new window roughly the height of the header away from the // Open the new window roughly the height of the header away from the
// previous window. This also ensures in multi monitor setups that the // previous window. This also ensures in multi monitor setups that the
// new terminal is on the correct screen. // new terminal is on the correct screen.
const focusedWindow = BrowserWindow.getFocusedWindow() || app.getLastFocusedWindow(); const focusedWindow = BrowserWindow.getFocusedWindow() || app.getLastFocusedWindow();
// In case of options defaults position and size, we should ignore the focusedWindow. // In case of options defaults position and size, we should ignore the focusedWindow.
if (winPos !== undefined) { if (winPos !== undefined) {
[startX, startY] = winPos; [startX, startY] = winPos;
} else if (focusedWindow) { } else if (focusedWindow) {
const points = focusedWindow.getPosition(); const points = focusedWindow.getPosition();
const currentScreen = screen.getDisplayNearestPoint({x: points[0], y: points[1]}); const currentScreen = screen.getDisplayNearestPoint({
x: points[0],
y: points[1]
});
const biggestX = ((points[0] + 100 + width) - currentScreen.bounds.x); const biggestX = points[0] + 100 + width - currentScreen.bounds.x;
const biggestY = ((points[1] + 100 + height) - currentScreen.bounds.y); const biggestY = points[1] + 100 + height - currentScreen.bounds.y;
if (biggestX > currentScreen.size.width) { if (biggestX > currentScreen.size.width) {
startX = 50; startX = 50;
} else { } else {
startX = points[0] + 34; startX = points[0] + 34;
}
if (biggestY > currentScreen.size.height) {
startY = 50;
} else {
startY = points[1] + 34;
}
}
const hwin = new Window({width, height, x: startX, y: startY}, cfg, fn);
windowSet.add(hwin);
hwin.loadURL(url);
// the window can be closed by the browser process itself
hwin.on('close', () => {
hwin.clean();
windowSet.delete(hwin);
});
hwin.on('closed', () => {
if (process.platform !== 'darwin' && windowSet.size === 0) {
app.quit();
}
});
return hwin;
} }
if (biggestY > currentScreen.size.height) {
startY = 50;
} else {
startY = points[1] + 34;
}
}
const hwin = new Window({width, height, x: startX, y: startY}, cfg, fn); // when opening create a new window
windowSet.add(hwin);
hwin.loadURL(url);
// the window can be closed by the browser process itself
hwin.on('close', () => {
hwin.clean();
windowSet.delete(hwin);
});
hwin.on('closed', () => {
if (process.platform !== 'darwin' && windowSet.size === 0) {
app.quit();
}
});
return hwin;
}
// when opening create a new window
createWindow();
// expose to plugins
app.createWindow = createWindow;
// mac only. when the dock icon is clicked
// and we don't have any active windows open,
// we open one
app.on('activate', () => {
if (!windowSet.size) {
createWindow(); createWindow();
}
});
const makeMenu = () => { // expose to plugins
const menu = plugins.decorateMenu( app.createWindow = createWindow;
AppMenu(createWindow, () => {
plugins.updatePlugins({force: true});
},
plugins.getLoadedPluginVersions
));
// If we're on Mac make a Dock Menu // mac only. when the dock icon is clicked
if (process.platform === 'darwin') { // and we don't have any active windows open,
const dockMenu = Menu.buildFromTemplate([{ // we open one
label: 'New Window', app.on('activate', () => {
click() { if (!windowSet.size) {
createWindow(); createWindow();
} }
}]); });
app.dock.setMenu(dockMenu);
}
Menu.setApplicationMenu( const makeMenu = () => {
Menu.buildFromTemplate(menu) const menu = plugins.decorateMenu(
); AppMenu(
}; createWindow,
() => {
plugins.updatePlugins({force: true});
},
plugins.getLoadedPluginVersions
)
);
const load = () => { // If we're on Mac make a Dock Menu
plugins.onApp(app); if (process.platform === 'darwin') {
plugins.extendKeymaps(); const dockMenu = Menu.buildFromTemplate([
makeMenu(); {
}; label: 'New Window',
click() {
createWindow();
}
}
]);
app.dock.setMenu(dockMenu);
}
load(); Menu.setApplicationMenu(Menu.buildFromTemplate(menu));
plugins.subscribe(load); };
}).catch(err => {
console.error('Error while loading devtools extensions', err); const load = () => {
})); plugins.onApp(app);
plugins.extendKeymaps();
makeMenu();
};
load();
plugins.subscribe(load);
})
.catch(err => {
//eslint-disable-next-line no-console
console.error('Error while loading devtools extensions', err);
})
);
app.on('open-file', (event, path) => { app.on('open-file', (event, path) => {
const lastWindow = app.getLastFocusedWindow(); const lastWindow = app.getLastFocusedWindow();
@ -222,17 +236,14 @@ app.on('open-file', (event, path) => {
} }
}); });
function installDevExtensions(isDev) { function installDevExtensions(isDev_) {
if (!isDev) { if (!isDev_) {
return Promise.resolve(); return Promise.resolve();
} }
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
const installer = require('electron-devtools-installer'); const installer = require('electron-devtools-installer');
const extensions = [ const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
'REACT_DEVELOPER_TOOLS',
'REDUX_DEVTOOLS'
];
const forceDownload = Boolean(process.env.UPGRADE_EXTENSIONS); const forceDownload = Boolean(process.env.UPGRADE_EXTENSIONS);
return Promise.all(extensions.map(name => installer.default(installer[name], forceDownload))); return Promise.all(extensions.map(name => installer.default(installer[name], forceDownload)));

View file

@ -27,9 +27,8 @@ module.exports = (createWindow, updatePlugins, getLoadedPluginVersions) => {
const showAbout = () => { const showAbout = () => {
const loadedPlugins = getLoadedPluginVersions(); const loadedPlugins = getLoadedPluginVersions();
const pluginList = loadedPlugins.length === 0 ? const pluginList =
'none' : loadedPlugins.length === 0 ? 'none' : loadedPlugins.map(plugin => `\n ${plugin.name} (${plugin.version})`);
loadedPlugins.map(plugin => `\n ${plugin.name} (${plugin.version})`);
dialog.showMessageBox({ dialog.showMessageBox({
title: `About ${appName}`, title: `About ${appName}`,

View file

@ -3,7 +3,7 @@
const {app} = require('electron'); const {app} = require('electron');
const {openConfig} = require('../../config'); const {openConfig} = require('../../config');
module.exports = function (commands, showAbout) { module.exports = (commands, showAbout) => {
return { return {
label: `${app.getName()}`, label: `${app.getName()}`,
submenu: [ submenu: [

View file

@ -1,6 +1,6 @@
const {openConfig} = require('../../config'); const {openConfig} = require('../../config');
module.exports = function (commands) { module.exports = commands => {
const submenu = [ const submenu = [
{ {
role: 'undo', role: 'undo',

View file

@ -1,7 +1,7 @@
const os = require('os'); const os = require('os');
const {app, shell} = require('electron'); const {app, shell} = require('electron');
module.exports = function (commands, showAbout) { module.exports = (commands, showAbout) => {
const submenu = [ const submenu = [
{ {
label: `${app.getName()} Website`, label: `${app.getName()} Website`,

View file

@ -1,4 +1,4 @@
module.exports = function (commands, update) { module.exports = (commands, update) => {
return { return {
label: 'Plugins', label: 'Plugins',
submenu: [ submenu: [

View file

@ -1,4 +1,4 @@
module.exports = function (commands, createWindow) { module.exports = (commands, createWindow) => {
const isMac = process.platform === 'darwin'; const isMac = process.platform === 'darwin';
return { return {

View file

@ -1,4 +1,4 @@
module.exports = function (commands) { module.exports = commands => {
return { return {
label: 'View', label: 'View',
submenu: [ submenu: [

View file

@ -1,4 +1,4 @@
module.exports = function (commands) { module.exports = commands => {
return { return {
role: 'window', role: 'window',
submenu: [ submenu: [
@ -9,7 +9,8 @@ module.exports = function (commands) {
{ {
type: 'separator' type: 'separator'
}, },
{ // 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: commands['window:zoom']
}, },

View file

@ -10,10 +10,11 @@ module.exports = function fetchNotifications(win) {
const retry = err => { const retry = err => {
setTimeout(() => fetchNotifications(win), ms('30m')); setTimeout(() => fetchNotifications(win), ms('30m'));
if (err) { if (err) {
//eslint-disable-next-line no-console
console.error('Notification messages fetch error', err.stack); console.error('Notification messages fetch error', err.stack);
} }
}; };
//eslint-disable-next-line no-console
console.log('Checking for notification messages'); console.log('Checking for notification messages');
fetch(NEWS_URL, { fetch(NEWS_URL, {
headers: { headers: {
@ -21,19 +22,20 @@ module.exports = function fetchNotifications(win) {
'X-Hyper-Platform': process.platform 'X-Hyper-Platform': process.platform
} }
}) })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
const {message} = data || {}; const {message} = data || {};
if (typeof message !== 'object' && message !== '') { if (typeof message !== 'object' && message !== '') {
throw new Error('Bad response'); throw new Error('Bad response');
} }
if (message === '') { if (message === '') {
console.log('No matching notification messages'); //eslint-disable-next-line no-console
} else { console.log('No matching notification messages');
rpc.emit('add notification', message); } else {
} rpc.emit('add notification', message);
}
retry(); retry();
}) })
.catch(retry); .catch(retry);
}; };

View file

@ -16,10 +16,7 @@ app.on('ready', () => {
const win_ = new BrowserWindow({ const win_ = new BrowserWindow({
show: false show: false
}); });
const url = 'file://' + resolve( const url = 'file://' + resolve(isDev ? __dirname : app.getAppPath(), 'notify.html');
isDev ? __dirname : app.getAppPath(),
'notify.html'
);
win_.loadURL(url); win_.loadURL(url);
win_.webContents.on('dom-ready', () => { win_.webContents.on('dom-ready', () => {
win = win_; win = win_;
@ -31,6 +28,7 @@ app.on('ready', () => {
}); });
function notify(title, body) { function notify(title, body) {
//eslint-disable-next-line no-console
console.log(`[Notification] ${title}: ${body}`); console.log(`[Notification] ${title}: ${body}`);
if (win) { if (win) {
win.webContents.send('notification', {title, body}); win.webContents.send('notification', {title, body});

View file

@ -56,11 +56,9 @@ function updatePlugins({force = false} = {}) {
updating = false; updating = false;
if (err) { if (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
notify( notify('Error updating plugins.', err.message);
'Error updating plugins.',
err.message
);
} else { } else {
// flag successful plugin update // flag successful plugin update
cache.set('hyper.plugins', id_); cache.set('hyper.plugins', id_);
@ -83,15 +81,9 @@ function updatePlugins({force = false} = {}) {
// notify watchers // notify watchers
if (force || changed) { if (force || changed) {
if (changed) { if (changed) {
notify( notify('Plugins Updated', 'Restart the app or hot-reload with "View" > "Reload" to enjoy the updates!');
'Plugins Updated',
'Restart the app or hot-reload with "View" > "Reload" to enjoy the updates!'
);
} else { } else {
notify( notify('Plugins Updated', 'No changes!');
'Plugins Updated',
'No changes!'
);
} }
watchers.forEach(fn => fn(err, {force})); watchers.forEach(fn => fn(err, {force}));
} }
@ -101,16 +93,14 @@ function updatePlugins({force = false} = {}) {
function getPluginVersions() { function getPluginVersions() {
const paths_ = paths.plugins.concat(paths.localPlugins); const paths_ = paths.plugins.concat(paths.localPlugins);
return paths_.map(path => { return paths_.map(path_ => {
let version = null; let version = null;
try { try {
// eslint-disable-next-line import/no-dynamic-require //eslint-disable-next-line import/no-dynamic-require
version = require(resolve(path, 'package.json')).version; version = require(resolve(path_, 'package.json')).version;
} catch (err) { } //eslint-disable-next-line no-empty
return [ } catch (err) {}
basename(path), return [basename(path_), version];
version
];
}); });
} }
@ -132,7 +122,7 @@ function clearCache() {
exports.updatePlugins = updatePlugins; exports.updatePlugins = updatePlugins;
exports.getLoadedPluginVersions = function () { exports.getLoadedPluginVersions = () => {
return modules.map(mod => ({name: mod._name, version: mod._version})); return modules.map(mod => ({name: mod._name, version: mod._version}));
}; };
@ -141,6 +131,7 @@ exports.getLoadedPluginVersions = function () {
// to prevent slowness // to prevent slowness
if (cache.get('hyper.plugins') !== id || process.env.HYPER_FORCE_UPDATE) { if (cache.get('hyper.plugins') !== id || process.env.HYPER_FORCE_UPDATE) {
// install immediately if the user changed plugins // install immediately if the user changed plugins
//eslint-disable-next-line no-console
console.log('plugins have changed / not init, scheduling plugins installation'); console.log('plugins have changed / not init, scheduling plugins installation');
setTimeout(() => { setTimeout(() => {
updatePlugins(); updatePlugins();
@ -178,9 +169,9 @@ function alert(message) {
}); });
} }
function toDependencies(plugins) { function toDependencies(plugins_) {
const obj = {}; const obj = {};
plugins.plugins.forEach(plugin => { plugins_.plugins.forEach(plugin => {
const regex = /.(@|#)/; const regex = /.(@|#)/;
const match = regex.exec(plugin); const match = regex.exec(plugin);
@ -198,7 +189,7 @@ function toDependencies(plugins) {
return obj; return obj;
} }
exports.subscribe = function (fn) { exports.subscribe = fn => {
watchers.push(fn); watchers.push(fn);
return () => { return () => {
watchers.splice(watchers.indexOf(fn), 1); watchers.splice(watchers.indexOf(fn), 1);
@ -220,56 +211,59 @@ function getPaths() {
exports.getPaths = getPaths; exports.getPaths = getPaths;
// get paths from renderer // get paths from renderer
exports.getBasePaths = function () { exports.getBasePaths = () => {
return {path, localPath}; return {path, localPath};
}; };
function requirePlugins() { function requirePlugins() {
const {plugins, localPlugins} = paths; const {plugins: plugins_, localPlugins} = paths;
const load = path => { const load = path_ => {
let mod; let mod;
try { try {
// eslint-disable-next-line import/no-dynamic-require // eslint-disable-next-line import/no-dynamic-require
mod = require(path); mod = require(path_);
const exposed = mod && Object.keys(mod).some(key => availableExtensions.has(key)); const exposed = mod && Object.keys(mod).some(key => availableExtensions.has(key));
if (!exposed) { if (!exposed) {
notify('Plugin error!', `Plugin "${basename(path)}" does not expose any ` + notify('Plugin error!', `Plugin "${basename(path_)}" does not expose any ` + 'Hyper extension API methods');
'Hyper extension API methods');
return; return;
} }
// populate the name for internal errors here // populate the name for internal errors here
mod._name = basename(path); mod._name = basename(path_);
try { try {
// eslint-disable-next-line import/no-dynamic-require // eslint-disable-next-line import/no-dynamic-require
mod._version = require(resolve(path, 'package.json')).version; mod._version = require(resolve(path_, 'package.json')).version;
} catch (err) { } catch (err) {
console.warn(`No package.json found in ${path}`); //eslint-disable-next-line no-console
console.warn(`No package.json found in ${path_}`);
} }
//eslint-disable-next-line no-console
console.log(`Plugin ${mod._name} (${mod._version}) loaded.`); console.log(`Plugin ${mod._name} (${mod._version}) loaded.`);
return mod; return mod;
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error(err); console.error(err);
notify('Plugin error!', `Plugin "${basename(path)}" failed to load (${err.message})`); notify('Plugin error!', `Plugin "${basename(path_)}" failed to load (${err.message})`);
} }
}; };
return plugins.map(load) return plugins_
.map(load)
.concat(localPlugins.map(load)) .concat(localPlugins.map(load))
.filter(v => Boolean(v)); .filter(v => Boolean(v));
} }
exports.onApp = function (app) { exports.onApp = app_ => {
modules.forEach(plugin => { modules.forEach(plugin => {
if (plugin.onApp) { if (plugin.onApp) {
plugin.onApp(app); plugin.onApp(app_);
} }
}); });
}; };
exports.onWindow = function (win) { exports.onWindow = win => {
modules.forEach(plugin => { modules.forEach(plugin => {
if (plugin.onWindow) { if (plugin.onWindow) {
plugin.onWindow(win); plugin.onWindow(win);
@ -295,7 +289,7 @@ function decorateObject(base, key) {
return decorated; return decorated;
} }
exports.extendKeymaps = function () { exports.extendKeymaps = () => {
modules.forEach(plugin => { modules.forEach(plugin => {
if (plugin.extendKeymaps) { if (plugin.extendKeymaps) {
const keys = _keys.extend(plugin.extendKeymaps()); const keys = _keys.extend(plugin.extendKeymaps());
@ -318,26 +312,26 @@ exports.getDeprecatedConfig = () => {
return; return;
} }
deprecated[plugin._name] = {css: pluginCSSDeprecated}; deprecated[plugin._name] = {css: pluginCSSDeprecated};
}) });
return deprecated; return deprecated;
} };
exports.decorateMenu = function (tpl) { exports.decorateMenu = tpl => {
return decorateObject(tpl, 'decorateMenu'); return decorateObject(tpl, 'decorateMenu');
}; };
exports.getDecoratedEnv = function (baseEnv) { exports.getDecoratedEnv = baseEnv => {
return decorateObject(baseEnv, 'decorateEnv'); return decorateObject(baseEnv, 'decorateEnv');
}; };
exports.getDecoratedConfig = function () { exports.getDecoratedConfig = () => {
const baseConfig = config.getConfig(); const baseConfig = config.getConfig();
const decoratedConfig = decorateObject(baseConfig, 'decorateConfig'); const decoratedConfig = decorateObject(baseConfig, 'decorateConfig');
const translatedConfig = config.htermConfigTranslate(decoratedConfig); const translatedConfig = config.htermConfigTranslate(decoratedConfig);
return translatedConfig; return translatedConfig;
}; };
exports.getDecoratedBrowserOptions = function (defaults) { exports.getDecoratedBrowserOptions = defaults => {
return decorateObject(defaults, 'decorateBrowserOptions'); return decorateObject(defaults, 'decorateBrowserOptions');
}; };

View file

@ -1,18 +1,39 @@
module.exports = { module.exports = {
availableExtensions: new Set([ availableExtensions: new Set([
'onApp', 'onWindow', 'onRendererWindow', 'onUnload', 'middleware', 'onApp',
'reduceUI', 'reduceSessions', 'reduceTermGroups', 'onWindow',
'decorateMenu', 'decorateTerm', 'decorateHyper', 'onRendererWindow',
'onUnload',
'middleware',
'reduceUI',
'reduceSessions',
'reduceTermGroups',
'decorateMenu',
'decorateTerm',
'decorateHyper',
'decorateHyperTerm', // for backwards compatibility with hyperterm 'decorateHyperTerm', // for backwards compatibility with hyperterm
'decorateHeader', 'decorateTerms', 'decorateTab', 'decorateHeader',
'decorateNotification', 'decorateNotifications', 'decorateTerms',
'decorateTabs', 'decorateConfig', 'decorateEnv', 'decorateTab',
'decorateTermGroup', 'decorateSplitPane', 'getTermProps', 'decorateNotification',
'getTabProps', 'getTabsProps', 'getTermGroupProps', 'decorateNotifications',
'mapHyperTermState', 'mapTermsState', 'decorateTabs',
'mapHeaderState', 'mapNotificationsState', 'decorateConfig',
'mapHyperTermDispatch', 'mapTermsDispatch', 'decorateEnv',
'mapHeaderDispatch', 'mapNotificationsDispatch', 'decorateTermGroup',
'decorateSplitPane',
'getTermProps',
'getTabProps',
'getTabsProps',
'getTermGroupProps',
'mapHyperTermState',
'mapTermsState',
'mapHeaderState',
'mapNotificationsState',
'mapHyperTermDispatch',
'mapTermsDispatch',
'mapHeaderDispatch',
'mapNotificationsDispatch',
'extendKeymaps' 'extendKeymaps'
]) ])
}; };

View file

@ -13,24 +13,29 @@ module.exports = {
}; };
spawnQueue.push(end => { spawnQueue.push(end => {
const cmd = [process.execPath, yarn].concat(args).join(' '); const cmd = [process.execPath, yarn].concat(args).join(' ');
//eslint-disable-next-line no-console
console.log('Launching yarn:', cmd); console.log('Launching yarn:', cmd);
cp.exec(cmd, { cp.exec(
cwd: plugs.base, cmd,
env, {
shell: true, cwd: plugs.base,
timeout: ms('5m'), env,
stdio: ['ignore', 'ignore', 'inherit'] shell: true,
}, err => { timeout: ms('5m'),
if (err) { stdio: ['ignore', 'ignore', 'inherit']
cb(err); },
} else { err => {
cb(null); if (err) {
} cb(err);
} else {
cb(null);
}
end(); end();
spawnQueue.start(); spawnQueue.start();
}); }
);
}); });
spawnQueue.start(); spawnQueue.start();

View file

@ -3,7 +3,6 @@ const {ipcMain} = require('electron');
const uuid = require('uuid'); const uuid = require('uuid');
class Server extends EventEmitter { class Server extends EventEmitter {
constructor(win) { constructor(win) {
super(); super();
this.win = win; this.win = win;
@ -48,7 +47,6 @@ class Server extends EventEmitter {
this.destroyed = true; this.destroyed = true;
} }
} }
} }
module.exports = win => { module.exports = win => {

View file

@ -8,7 +8,10 @@ const {getDecoratedEnv} = require('./plugins');
const {productName, version} = require('./package'); const {productName, version} = require('./package');
const config = require('./config'); const config = require('./config');
const createNodePtyError = () => new Error('`node-pty` failed to load. Typically this means that it was built incorrectly. Please check the `readme.md` to more info.'); const createNodePtyError = () =>
new Error(
'`node-pty` failed to load. Typically this means that it was built incorrectly. Please check the `readme.md` to more info.'
);
let spawn; let spawn;
try { try {
@ -20,15 +23,19 @@ try {
const envFromConfig = config.getConfig().env || {}; const envFromConfig = config.getConfig().env || {};
module.exports = class Session extends EventEmitter { module.exports = class Session extends EventEmitter {
constructor({rows, cols: columns, cwd, shell, shellArgs}) { constructor({rows, cols: columns, cwd, shell, shellArgs}) {
super(); super();
const baseEnv = Object.assign({}, process.env, { const baseEnv = Object.assign(
LANG: app.getLocale().replace('-', '_') + '.UTF-8', {},
TERM: 'xterm-256color', process.env,
TERM_PROGRAM: productName, {
TERM_PROGRAM_VERSION: version LANG: app.getLocale().replace('-', '_') + '.UTF-8',
}, envFromConfig); TERM: 'xterm-256color',
TERM_PROGRAM: productName,
TERM_PROGRAM_VERSION: version
},
envFromConfig
);
// Electron has a default value for process.env.GOOGLE_API_KEY // Electron has a default value for process.env.GOOGLE_API_KEY
// We don't want to leak this to the shell // We don't want to leak this to the shell
@ -85,6 +92,7 @@ module.exports = class Session extends EventEmitter {
try { try {
this.pty.resize(cols, rows); this.pty.resize(cols, rows);
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
} }
} }
@ -93,10 +101,10 @@ module.exports = class Session extends EventEmitter {
try { try {
this.pty.kill(); this.pty.kill();
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error('exit error', err.stack); console.error('exit error', err.stack);
} }
this.emit('exit'); this.emit('exit');
this.ended = true; this.ended = true;
} }
}; };

View file

@ -3,23 +3,26 @@ const Registry = require('winreg');
const appPath = `"${process.execPath}"`; const appPath = `"${process.execPath}"`;
const regKey = `\\Software\\Classes\\Directory\\background\\shell\\Hyper`; const regKey = `\\Software\\Classes\\Directory\\background\\shell\\Hyper`;
const regParts = [ const regParts = [
{key: 'command', name: '', value: `${appPath} "%V"`}, {key: 'command', name: '', value: `${appPath} "%V"`},
{name: '', value: 'Open Hyper here'}, {name: '', value: 'Open Hyper here'},
{name: 'Icon', value: `${appPath}`} {name: 'Icon', value: `${appPath}`}
]; ];
function addValues(hyperKey, commandKey, callback) { function addValues(hyperKey, commandKey, callback) {
hyperKey.set(regParts[1].name, Registry.REG_SZ, regParts[1].value, err => { hyperKey.set(regParts[1].name, Registry.REG_SZ, regParts[1].value, error => {
if (err) { if (error) {
console.error(err.message); //eslint-disable-next-line no-console
console.error(error.message);
} }
hyperKey.set(regParts[2].name, Registry.REG_SZ, regParts[2].value, err => { hyperKey.set(regParts[2].name, Registry.REG_SZ, regParts[2].value, err => {
if (err) { if (err) {
//eslint-disable-next-line no-console
console.error(err.message); console.error(err.message);
} }
commandKey.set(regParts[0].name, Registry.REG_SZ, regParts[0].value, err => { commandKey.set(regParts[0].name, Registry.REG_SZ, regParts[0].value, err_ => {
if (err) { if (err_) {
console.error(err.message); //eslint-disable-next-line no-console
console.error(err_.message);
} }
callback(); callback();
}); });
@ -27,24 +30,30 @@ function addValues(hyperKey, commandKey, callback) {
}); });
} }
exports.add = function (callback) { exports.add = callback => {
const hyperKey = new Registry({hive: 'HKCU', key: regKey}); const hyperKey = new Registry({hive: 'HKCU', key: regKey});
const commandKey = new Registry({hive: 'HKCU', key: `${regKey}\\${regParts[0].key}`}); const commandKey = new Registry({
hive: 'HKCU',
key: `${regKey}\\${regParts[0].key}`
});
hyperKey.keyExists((err, exists) => { hyperKey.keyExists((error, exists) => {
if (err) { if (error) {
console.error(err.message); //eslint-disable-next-line no-console
console.error(error.message);
} }
if (exists) { if (exists) {
commandKey.keyExists((err, exists) => { commandKey.keyExists((err_, exists_) => {
if (err) { if (err_) {
console.error(err.message); //eslint-disable-next-line no-console
console.error(err_.message);
} }
if (exists) { if (exists_) {
addValues(hyperKey, commandKey, callback); addValues(hyperKey, commandKey, callback);
} else { } else {
commandKey.create(err => { commandKey.create(err => {
if (err) { if (err) {
//eslint-disable-next-line no-console
console.error(err.message); console.error(err.message);
} }
addValues(hyperKey, commandKey, callback); addValues(hyperKey, commandKey, callback);
@ -54,11 +63,13 @@ exports.add = function (callback) {
} else { } else {
hyperKey.create(err => { hyperKey.create(err => {
if (err) { if (err) {
//eslint-disable-next-line no-console
console.error(err.message); console.error(err.message);
} }
commandKey.create(err => { commandKey.create(err_ => {
if (err) { if (err_) {
console.error(err.message); //eslint-disable-next-line no-console
console.error(err_.message);
} }
addValues(hyperKey, commandKey, callback); addValues(hyperKey, commandKey, callback);
}); });
@ -67,9 +78,10 @@ exports.add = function (callback) {
}); });
}; };
exports.remove = function (callback) { exports.remove = callback => {
new Registry({hive: 'HKCU', key: regKey}).destroy(err => { new Registry({hive: 'HKCU', key: regKey}).destroy(err => {
if (err) { if (err) {
//eslint-disable-next-line no-console
console.error(err.message); console.error(err.message);
} }
callback(); callback();

View file

@ -13,21 +13,24 @@ const fetchNotifications = require('../notifications');
const Session = require('../session'); const Session = require('../session');
module.exports = class Window { module.exports = class Window {
constructor(options, cfg, fn) { constructor(options_, cfg, fn) {
const opts = Object.assign({ const winOpts = Object.assign(
minWidth: 370, {
minHeight: 190, minWidth: 370,
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'), minHeight: 190,
titleBarStyle: 'hidden-inset', backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
title: 'Hyper.app', titleBarStyle: 'hidden-inset',
// we want to go frameless on windows and linux title: 'Hyper.app',
frame: process.platform === 'darwin', // we want to go frameless on windows and linux
transparent: process.platform === 'darwin', frame: process.platform === 'darwin',
icon, transparent: process.platform === 'darwin',
show: process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev, icon,
acceptFirstMouse: true show: process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev,
}, options); acceptFirstMouse: true
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(opts)); },
options_
);
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(winOpts));
const rpc = createRPC(window); const rpc = createRPC(window);
const sessions = new Map(); const sessions = new Map();
@ -39,12 +42,8 @@ module.exports = class Window {
window.webContents.send('config change'); window.webContents.send('config change');
// notify user that shell changes require new sessions // notify user that shell changes require new sessions
if (cfg_.shell !== cfg.shell || if (cfg_.shell !== cfg.shell || JSON.stringify(cfg_.shellArgs) !== JSON.stringify(cfg.shellArgs)) {
JSON.stringify(cfg_.shellArgs) !== JSON.stringify(cfg.shellArgs)) { notify('Shell configuration changed!', 'Open a new tab or window to start using the new shell');
notify(
'Shell configuration changed!',
'Open a new tab or window to start using the new shell'
);
} }
// update background color if necessary // update background color if necessary
@ -66,37 +65,41 @@ module.exports = class Window {
// and createWindow deifinition. It's executed in place of // and createWindow deifinition. It's executed in place of
// the callback passed as parameter, and deleted right after. // the callback passed as parameter, and deleted right after.
(app.windowCallback || fn)(window); (app.windowCallback || fn)(window);
delete (app.windowCallback); delete app.windowCallback;
fetchNotifications(window); fetchNotifications(window);
// auto updates // auto updates
if (!isDev && process.platform !== 'linux') { if (!isDev && process.platform !== 'linux') {
AutoUpdater(window); AutoUpdater(window);
} else { } else {
//eslint-disable-next-line no-console
console.log('ignoring auto updates during dev'); console.log('ignoring auto updates during dev');
} }
}); });
rpc.on('new', options => { rpc.on('new', options => {
const opts = Object.assign({ const sessionOpts = Object.assign(
rows: 40, {
cols: 100, rows: 40,
cwd: process.argv[1] && isAbsolute(process.argv[1]) ? process.argv[1] : cfgDir, cols: 100,
splitDirection: undefined, cwd: process.argv[1] && isAbsolute(process.argv[1]) ? process.argv[1] : cfgDir,
shell: cfg.shell, splitDirection: undefined,
shellArgs: cfg.shellArgs && Array.from(cfg.shellArgs) shell: cfg.shell,
}, options); shellArgs: cfg.shellArgs && Array.from(cfg.shellArgs)
},
options
);
const initSession = (opts, fn) => { const initSession = (opts, fn_) => {
fn(uuid.v4(), new Session(opts)); fn_(uuid.v4(), new Session(opts));
}; };
initSession(opts, (uid, session) => { initSession(sessionOpts, (uid, session) => {
sessions.set(uid, session); sessions.set(uid, session);
rpc.emit('session add', { rpc.emit('session add', {
rows: opts.rows, rows: sessionOpts.rows,
cols: opts.cols, cols: sessionOpts.cols,
uid, uid,
splitDirection: opts.splitDirection, splitDirection: sessionOpts.splitDirection,
shell: session.shell, shell: session.shell,
pid: session.pty.pid pid: session.pty.pid
}); });
@ -116,6 +119,7 @@ module.exports = class Window {
if (session) { if (session) {
session.exit(); session.exit();
} else { } else {
//eslint-disable-next-line no-console
console.log('session not found by', uid); console.log('session not found by', uid);
} }
}); });
@ -136,9 +140,9 @@ module.exports = class Window {
const session = sessions.get(uid); const session = sessions.get(uid);
if (escaped) { if (escaped) {
const escapedData = session.shell.endsWith('cmd.exe') ? const escapedData = session.shell.endsWith('cmd.exe')
`"${data}"` : // This is how cmd.exe does it ? `"${data}"` // This is how cmd.exe does it
`'${data.replace(/'/g, `'\\''`)}'`; // Inside a single-quoted string nothing is interpreted : `'${data.replace(/'/g, `'\\''`)}'`; // Inside a single-quoted string nothing is interpreted
session.write(escapedData); session.write(escapedData);
} else { } else {

View file

@ -13,5 +13,12 @@ module.exports = bgColor => {
// http://stackoverflow.com/a/11019879/1202488 // http://stackoverflow.com/a/11019879/1202488
const alphaHex = Math.round(color.alpha() * 255).toString(16); const alphaHex = Math.round(color.alpha() * 255).toString(16);
return '#' + alphaHex + color.hex().toString().substr(1); return (
'#' +
alphaHex +
color
.hex()
.toString()
.substr(1)
);
}; };

View file

@ -1,5 +1,11 @@
import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs'; import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs';
import {UI_WINDOW_MAXIMIZE, UI_WINDOW_UNMAXIMIZE, UI_OPEN_HAMBURGER_MENU, UI_WINDOW_MINIMIZE, UI_WINDOW_CLOSE} from '../constants/ui'; import {
UI_WINDOW_MAXIMIZE,
UI_WINDOW_UNMAXIMIZE,
UI_OPEN_HAMBURGER_MENU,
UI_WINDOW_MINIMIZE,
UI_WINDOW_CLOSE
} from '../constants/ui';
import rpc from '../rpc'; import rpc from '../rpc';
import {userExitTermGroup, setActiveGroup} from './term-groups'; import {userExitTermGroup, setActiveGroup} from './term-groups';

View file

@ -1,7 +1,4 @@
import { import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications';
NOTIFICATION_MESSAGE,
NOTIFICATION_DISMISS
} from '../constants/notifications';
export function dismissNotification(id) { export function dismissNotification(id) {
return { return {

View file

@ -48,7 +48,7 @@ export function requestSession() {
} }
export function addSessionData(uid, data) { export function addSessionData(uid, data) {
return function (dispatch, getState) { return (dispatch, getState) => {
dispatch({ dispatch({
type: SESSION_ADD_DATA, type: SESSION_ADD_DATA,
data, data,
@ -141,7 +141,7 @@ export function resizeSession(uid, cols, rows) {
} }
export function sendSessionData(uid, data, escaped) { export function sendSessionData(uid, data, escaped) {
return function (dispatch, getState) { return (dispatch, getState) => {
dispatch({ dispatch({
type: SESSION_USER_DATA, type: SESSION_USER_DATA,
data, data,

View file

@ -5,11 +5,7 @@ import getRootGroups from '../selectors';
import findBySession from '../utils/term-groups'; import findBySession from '../utils/term-groups';
import notify from '../utils/notify'; import notify from '../utils/notify';
import rpc from '../rpc'; import rpc from '../rpc';
import { import {requestSession, sendSessionData, setActiveSession} from '../actions/sessions';
requestSession,
sendSessionData,
setActiveSession
} from '../actions/sessions';
import { import {
UI_FONT_SIZE_SET, UI_FONT_SIZE_SET,
UI_FONT_SIZE_INCR, UI_FONT_SIZE_INCR,
@ -74,9 +70,7 @@ export function setFontSmoothing() {
return dispatch => { return dispatch => {
setTimeout(() => { setTimeout(() => {
const devicePixelRatio = window.devicePixelRatio; const devicePixelRatio = window.devicePixelRatio;
const fontSmoothing = devicePixelRatio < 2 ? const fontSmoothing = devicePixelRatio < 2 ? 'subpixel-antialiased' : 'antialiased';
'subpixel-antialiased' :
'antialiased';
dispatch({ dispatch({
type: UI_FONT_SMOOTHING_SET, type: UI_FONT_SMOOTHING_SET,
@ -100,11 +94,7 @@ const findChildSessions = (termGroups, uid) => {
return [uid]; return [uid];
} }
return group return group.children.reduce((total, childUid) => total.concat(findChildSessions(termGroups, childUid)), []);
.children
.reduce((total, childUid) => total.concat(
findChildSessions(termGroups, childUid)
), []);
}; };
// Get the index of the next or previous group, // Get the index of the next or previous group,
@ -126,6 +116,7 @@ function moveToNeighborPane(type) {
const {uid} = findBySession(termGroups, sessions.activeUid); const {uid} = findBySession(termGroups, sessions.activeUid);
const childGroups = findChildSessions(termGroups.termGroups, termGroups.activeRootGroup); const childGroups = findChildSessions(termGroups.termGroups, termGroups.activeRootGroup);
if (childGroups.length === 1) { if (childGroups.length === 1) {
//eslint-disable-next-line no-console
console.log('ignoring move for single group'); console.log('ignoring move for single group');
} else { } else {
const index = getNeighborIndex(childGroups, uid, type); const index = getNeighborIndex(childGroups, uid, type);
@ -156,6 +147,7 @@ export function moveLeft() {
const index = groupUids.indexOf(uid); const index = groupUids.indexOf(uid);
const next = groupUids[index - 1] || last(groupUids); const next = groupUids[index - 1] || last(groupUids);
if (!next || uid === next) { if (!next || uid === next) {
//eslint-disable-next-line no-console
console.log('ignoring left move action'); console.log('ignoring left move action');
} else { } else {
dispatch(setActiveGroup(next)); dispatch(setActiveGroup(next));
@ -176,6 +168,7 @@ export function moveRight() {
const index = groupUids.indexOf(uid); const index = groupUids.indexOf(uid);
const next = groupUids[index + 1] || groupUids[0]; const next = groupUids[index + 1] || groupUids[0];
if (!next || uid === next) { if (!next || uid === next) {
//eslint-disable-next-line no-console
console.log('ignoring right move action'); console.log('ignoring right move action');
} else { } else {
dispatch(setActiveGroup(next)); dispatch(setActiveGroup(next));
@ -195,10 +188,12 @@ export function moveTo(i) {
const groupUids = getGroupUids(state); const groupUids = getGroupUids(state);
const uid = state.termGroups.activeRootGroup; const uid = state.termGroups.activeRootGroup;
if (uid === groupUids[i]) { if (uid === groupUids[i]) {
//eslint-disable-next-line no-console
console.log('ignoring same uid'); console.log('ignoring same uid');
} else if (groupUids[i]) { } else if (groupUids[i]) {
dispatch(setActiveGroup(groupUids[i])); dispatch(setActiveGroup(groupUids[i]));
} else { } else {
//eslint-disable-next-line no-console
console.log('ignoring inexistent index', i); console.log('ignoring inexistent index', i);
} }
} }
@ -235,6 +230,7 @@ export function openFile(path) {
effect() { effect() {
stat(path, (err, stats) => { stat(path, (err, stats) => {
if (err) { if (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
notify('Unable to open path', `"${path}" doesn't exist.`); notify('Unable to open path', `"${path}" doesn't exist.`);
} else { } else {

View file

@ -1,7 +1,4 @@
import { import {UPDATE_INSTALL, UPDATE_AVAILABLE} from '../constants/updater';
UPDATE_INSTALL,
UPDATE_AVAILABLE
} from '../constants/updater';
import rpc from '../rpc'; import rpc from '../rpc';
export function installUpdate() { export function installUpdate() {

View file

@ -2,7 +2,6 @@ import React from 'react';
import {StyleSheet, css} from 'aphrodite-simple'; import {StyleSheet, css} from 'aphrodite-simple';
export default class Component extends React.PureComponent { export default class Component extends React.PureComponent {
constructor() { constructor() {
super(); super();
this.styles_ = this.createStyleSheet(); this.styles_ = this.createStyleSheet();
@ -39,9 +38,7 @@ export default class Component extends React.PureComponent {
// //
// it's important classes never get mangled by // it's important classes never get mangled by
// uglifiers so that we can avoid collisions // uglifiers so that we can avoid collisions
const component = this.constructor.name const component = this.constructor.name.toString().toLowerCase();
.toString()
.toLowerCase();
const globalName = `${component}_${c}`; const globalName = `${component}_${c}`;
return [globalName, css(this.styles_[c])]; return [globalName, css(this.styles_[c])];
} }
@ -58,11 +55,10 @@ export default class Component extends React.PureComponent {
// convert static objects from `babel-plugin-transform-jsx` // convert static objects from `babel-plugin-transform-jsx`
// to `React.Element`. // to `React.Element`.
if (!this.template) { if (!this.template) {
throw new TypeError('Component doesn\'t define `template`'); throw new TypeError("Component doesn't define `template`");
} }
// invoke the template creator passing our css helper // invoke the template creator passing our css helper
return this.template(this.cssHelper); return this.template(this.cssHelper);
} }
} }

View file

@ -8,7 +8,6 @@ import Tabs_ from './tabs';
const Tabs = decorate(Tabs_, 'Tabs'); const Tabs = decorate(Tabs_, 'Tabs');
export default class Header extends Component { export default class Header extends Component {
constructor() { constructor() {
super(); super();
this.onChangeIntent = this.onChangeIntent.bind(this); this.onChangeIntent = this.onChangeIntent.bind(this);
@ -22,8 +21,7 @@ export default class Header extends Component {
onChangeIntent(active) { onChangeIntent(active) {
// we ignore clicks if they're a byproduct of a drag // we ignore clicks if they're a byproduct of a drag
// motion to move the window // motion to move the window
if (window.screenX !== this.headerMouseDownWindowX || if (window.screenX !== this.headerMouseDownWindowX || window.screenY !== this.headerMouseDownWindowY) {
window.screenY !== this.headerMouseDownWindowY) {
return; return;
} }
@ -105,60 +103,47 @@ export default class Header extends Component {
} }
const {hambMenu, winCtrls} = this.getWindowHeaderConfig(); const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
const left = winCtrls === 'left'; const left = winCtrls === 'left';
const maxButtonHref = this.props.maximized ? const maxButtonHref = this.props.maximized
'./renderer/assets/icons.svg#restore-window' : ? './renderer/assets/icons.svg#restore-window'
'./renderer/assets/icons.svg#maximize-window'; : './renderer/assets/icons.svg#maximize-window';
return (<header return (
className={css('header', isMac && 'headerRounded')} <header
onMouseDown={this.handleHeaderMouseDown} className={css('header', isMac && 'headerRounded')}
onDoubleClick={this.handleMaximizeClick} onMouseDown={this.handleHeaderMouseDown}
onDoubleClick={this.handleMaximizeClick}
> >
{ {!isMac && (
!isMac && <div className={css('windowHeader', props.tabs.length > 1 && 'windowHeaderWithBorder')} style={{borderColor}}>
<div {hambMenu && (
className={css('windowHeader', props.tabs.length > 1 && 'windowHeaderWithBorder')} <svg
style={{borderColor}} className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
> onClick={this.handleHamburgerMenuClick}
{
hambMenu &&
<svg
className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
onClick={this.handleHamburgerMenuClick}
> >
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu"/> <use xlinkHref="./renderer/assets/icons.svg#hamburger-menu" />
</svg>
}
<span className={css('appTitle')}>{title}</span>
{
winCtrls &&
<div className={css('windowControls', left && 'windowControlsLeft')}>
<svg
className={css('shape', left && 'minimizeWindowLeft')}
onClick={this.handleMinimizeClick}
>
<use xlinkHref="./renderer/assets/icons.svg#minimize-window"/>
</svg> </svg>
<svg )}
className={css('shape', left && 'maximizeWindowLeft')} <span className={css('appTitle')}>{title}</span>
onClick={this.handleMaximizeClick} {winCtrls && (
> <div className={css('windowControls', left && 'windowControlsLeft')}>
<use xlinkHref={maxButtonHref}/> <svg className={css('shape', left && 'minimizeWindowLeft')} onClick={this.handleMinimizeClick}>
</svg> <use xlinkHref="./renderer/assets/icons.svg#minimize-window" />
<svg </svg>
className={css('shape', 'closeWindow', left && 'closeWindowLeft')} <svg className={css('shape', left && 'maximizeWindowLeft')} onClick={this.handleMaximizeClick}>
onClick={this.handleCloseClick} <use xlinkHref={maxButtonHref} />
> </svg>
<use xlinkHref="./renderer/assets/icons.svg#close-window"/> <svg className={css('shape', 'closeWindow', left && 'closeWindowLeft')} onClick={this.handleCloseClick}>
</svg> <use xlinkHref="./renderer/assets/icons.svg#close-window" />
</div> </svg>
} </div>
</div> )}
} </div>
{ this.props.customChildrenBefore } )}
<Tabs {...props}/> {this.props.customChildrenBefore}
{ this.props.customChildren } <Tabs {...props} />
</header>); {this.props.customChildren}
</header>
);
} }
styles() { styles() {
@ -248,5 +233,4 @@ export default class Header extends Component {
closeWindow: {':hover': {color: '#FE354E'}, ':active': {color: '#FE354E'}} closeWindow: {':hover': {color: '#FE354E'}, ':active': {color: '#FE354E'}}
}; };
} }
} }

View file

@ -2,7 +2,6 @@ import React from 'react';
import Component from '../component'; import Component from '../component';
export default class Notification extends Component { export default class Notification extends Component {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
@ -44,11 +43,7 @@ export default class Notification extends Component {
}); });
const {backgroundColor} = this.props; const {backgroundColor} = this.props;
if (backgroundColor) { if (backgroundColor) {
el.style.setProperty( el.style.setProperty('background-color', backgroundColor, 'important');
'background-color',
backgroundColor,
'important'
);
} }
} }
} }
@ -71,24 +66,18 @@ export default class Notification extends Component {
template(css) { template(css) {
const {backgroundColor} = this.props; const {backgroundColor} = this.props;
const opacity = this.state.dismissing ? 0 : 1; const opacity = this.state.dismissing ? 0 : 1;
return (<div return (
ref={this.onElement} <div ref={this.onElement} style={{opacity, backgroundColor}} className={css('indicator')}>
style={{opacity, backgroundColor}} {this.props.customChildrenBefore}
className={css('indicator')} {this.props.children || this.props.text}
> {this.props.userDismissable ? (
{ this.props.customChildrenBefore } <a className={css('dismissLink')} onClick={this.handleDismiss} style={{color: this.props.userDismissColor}}>
{ this.props.children || this.props.text } [x]
{ </a>
this.props.userDismissable ? ) : null}
<a {this.props.customChildren}
className={css('dismissLink')} </div>
onClick={this.handleDismiss} );
style={{color: this.props.userDismissColor}}
>[x]</a> :
null
}
{ this.props.customChildren }
</div>);
} }
styles() { styles() {
@ -121,5 +110,4 @@ export default class Notification extends Component {
} }
}; };
} }
} }

View file

@ -8,12 +8,11 @@ import Notification_ from './notification';
const Notification = decorate(Notification_); const Notification = decorate(Notification_);
export default class Notifications extends Component { export default class Notifications extends Component {
template(css) { template(css) {
return (<div className={css('view')}> return (
{ this.props.customChildrenBefore } <div className={css('view')}>
{ {this.props.customChildrenBefore}
this.props.fontShowing && {this.props.fontShowing && (
<Notification <Notification
key="font" key="font"
backgroundColor="rgba(255, 255, 255, .2)" backgroundColor="rgba(255, 255, 255, .2)"
@ -21,11 +20,10 @@ export default class Notifications extends Component {
userDismissable={false} userDismissable={false}
onDismiss={this.props.onDismissFont} onDismiss={this.props.onDismissFont}
dismissAfter={1000} dismissAfter={1000}
/> />
} )}
{ {this.props.resizeShowing && (
this.props.resizeShowing &&
<Notification <Notification
key="resize" key="resize"
backgroundColor="rgba(255, 255, 255, .2)" backgroundColor="rgba(255, 255, 255, .2)"
@ -33,11 +31,10 @@ export default class Notifications extends Component {
userDismissable={false} userDismissable={false}
onDismiss={this.props.onDismissResize} onDismiss={this.props.onDismissResize}
dismissAfter={1000} dismissAfter={1000}
/> />
} )}
{ {this.props.messageShowing && (
this.props.messageShowing &&
<Notification <Notification
key="message" key="message"
backgroundColor="#FE354E" backgroundColor="#FE354E"
@ -45,8 +42,9 @@ export default class Notifications extends Component {
onDismiss={this.props.onDismissMessage} onDismiss={this.props.onDismissMessage}
userDismissable={this.props.messageDismissable} userDismissable={this.props.messageDismissable}
userDismissColor="#AA2D3C" userDismissColor="#AA2D3C"
>{ >
this.props.messageURL ? [ {this.props.messageURL ? (
[
this.props.messageText, this.props.messageText,
' (', ' (',
<a <a
@ -57,34 +55,34 @@ export default class Notifications extends Component {
ev.preventDefault(); ev.preventDefault();
}} }}
href={this.props.messageURL} href={this.props.messageURL}
>more</a>, >
more
</a>,
')' ')'
] : null ]
} ) : null}
</Notification> </Notification>
} )}
{ {this.props.updateShowing && (
this.props.updateShowing &&
<Notification <Notification
key="update" key="update"
backgroundColor="#7ED321" backgroundColor="#7ED321"
text={`Version ${this.props.updateVersion} ready`} text={`Version ${this.props.updateVersion} ready`}
onDismiss={this.props.onDismissUpdate} onDismiss={this.props.onDismissUpdate}
userDismissable userDismissable
> >
Version <b>{this.props.updateVersion}</b> ready. Version <b>{this.props.updateVersion}</b> ready.
{this.props.updateNote && ` ${this.props.updateNote.trim().replace(/\.$/, '')}`} {this.props.updateNote && ` ${this.props.updateNote.trim().replace(/\.$/, '')}`} (<a
{' '}
(<a
style={{color: '#fff'}} style={{color: '#fff'}}
onClick={ev => { onClick={ev => {
window.require('electron').shell.openExternal(ev.target.href); window.require('electron').shell.openExternal(ev.target.href);
ev.preventDefault(); ev.preventDefault();
}} }}
href={`https://github.com/zeit/hyper/releases/tag/${this.props.updateVersion}`} href={`https://github.com/zeit/hyper/releases/tag/${this.props.updateVersion}`}
>notes</a>). >
{' '} notes
</a>).{' '}
<a <a
style={{ style={{
cursor: 'pointer', cursor: 'pointer',
@ -92,14 +90,14 @@ export default class Notifications extends Component {
fontWeight: 'bold' fontWeight: 'bold'
}} }}
onClick={this.props.onUpdateInstall} onClick={this.props.onUpdateInstall}
> >
Restart Restart
</a>. </a>.{' '}
{ ' ' }
</Notification> </Notification>
} )}
{ this.props.customChildren } {this.props.customChildren}
</div>); </div>
);
} }
styles() { styles() {
@ -111,5 +109,4 @@ export default class Notifications extends Component {
} }
}; };
} }
} }

View file

@ -3,7 +3,6 @@ import React from 'react';
import Component from '../component'; import Component from '../component';
export default class SplitPane extends Component { export default class SplitPane extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleDragStart = this.handleDragStart.bind(this); this.handleDragStart = this.handleDragStart.bind(this);
@ -82,7 +81,7 @@ export default class SplitPane extends Component {
template(css) { template(css) {
const children = this.props.children; const children = this.props.children;
const {direction, borderColor} = this.props; const {direction, borderColor} = this.props;
const sizeProperty = direction === 'horizontal' ? 'height' : 'flexBasis' const sizeProperty = direction === 'horizontal' ? 'height' : 'flexBasis';
let {sizes} = this.props; let {sizes} = this.props;
if (!sizes) { if (!sizes) {
// workaround for the fact that if we don't specify // workaround for the fact that if we don't specify
@ -90,39 +89,32 @@ export default class SplitPane extends Component {
// right height for the horizontal panes // right height for the horizontal panes
sizes = new Array(children.length).fill(1 / children.length); sizes = new Array(children.length).fill(1 / children.length);
} }
return (<div className={css('panes', `panes_${direction}`)}> return (
{ <div className={css('panes', `panes_${direction}`)}>
React.Children.map(children, (child, i) => { {React.Children.map(children, (child, i) => {
const style = { const style = {
// flexBasis doesn't work for the first horizontal pane, height need to be specified // flexBasis doesn't work for the first horizontal pane, height need to be specified
[sizeProperty]: (sizes[i] * 100) + '%', [sizeProperty]: sizes[i] * 100 + '%',
flexBasis: (sizes[i] * 100) + '%', flexBasis: sizes[i] * 100 + '%',
flexGrow: 0 flexGrow: 0
}; };
return [ return [
<div <div key="pane" className={css('pane')} style={style}>
key="pane" {child}
className={css('pane')}
style={style}
>
{ child }
</div>, </div>,
i < children.length - 1 ? i < children.length - 1 ? (
<div <div
key="divider" key="divider"
onMouseDown={this.handleDragStart} onMouseDown={this.handleDragStart}
style={{backgroundColor: borderColor}} style={{backgroundColor: borderColor}}
className={css('divider', `divider_${direction}`)} className={css('divider', `divider_${direction}`)}
/> : />
null ) : null
]; ];
}) })}
} <div style={{display: this.state.dragging ? 'block' : 'none'}} className={css('shim')} />
<div </div>
style={{display: this.state.dragging ? 'block' : 'none'}} );
className={css('shim')}
/>
</div>);
} }
styles() { styles() {
@ -136,11 +128,11 @@ export default class SplitPane extends Component {
height: '100%' height: '100%'
}, },
'panes_vertical': { panes_vertical: {
flexDirection: 'row' flexDirection: 'row'
}, },
'panes_horizontal': { panes_horizontal: {
flexDirection: 'column' flexDirection: 'column'
}, },
@ -157,7 +149,7 @@ export default class SplitPane extends Component {
flexShrink: 0 flexShrink: 0
}, },
'divider_vertical': { divider_vertical: {
borderLeft: '5px solid rgba(255, 255, 255, 0)', borderLeft: '5px solid rgba(255, 255, 255, 0)',
borderRight: '5px solid rgba(255, 255, 255, 0)', borderRight: '5px solid rgba(255, 255, 255, 0)',
width: '11px', width: '11px',
@ -165,7 +157,7 @@ export default class SplitPane extends Component {
cursor: 'col-resize' cursor: 'col-resize'
}, },
'divider_horizontal': { divider_horizontal: {
height: '11px', height: '11px',
margin: '-5px 0', margin: '-5px 0',
borderTop: '5px solid rgba(255, 255, 255, 0)', borderTop: '5px solid rgba(255, 255, 255, 0)',
@ -195,5 +187,4 @@ export default class SplitPane extends Component {
this.onDragEnd(); this.onDragEnd();
} }
} }
} }

View file

@ -1,7 +1,7 @@
import React from 'react' import React from 'react';
export default class StyleSheet extends React.PureComponent { export default class StyleSheet extends React.PureComponent {
render () { render() {
const { const {
customCSS, customCSS,
colors, colors,
@ -11,11 +11,12 @@ export default class StyleSheet extends React.PureComponent {
fontSmoothing, fontSmoothing,
foregroundColor, foregroundColor,
borderColor borderColor
} = this.props } = this.props;
return ( return (
<style dangerouslySetInnerHTML={{ <style
__html: ` dangerouslySetInnerHTML={{
__html: `
.terminal { .terminal {
${foregroundColor ? `color: ${foregroundColor};` : ''} ${foregroundColor ? `color: ${foregroundColor};` : ''}
${fontFamily ? `font-family: ${fontFamily};` : ''} ${fontFamily ? `font-family: ${fontFamily};` : ''}
@ -2261,7 +2262,8 @@ export default class StyleSheet extends React.PureComponent {
${customCSS} ${customCSS}
` `
}} /> }}
) />
);
} }
} }

View file

@ -45,48 +45,34 @@ export default class Tab extends Component {
const {isActive, isFirst, isLast, borderColor, hasActivity} = this.props; const {isActive, isFirst, isLast, borderColor, hasActivity} = this.props;
const {hovered} = this.state; const {hovered} = this.state;
return (<li return (
onMouseEnter={this.handleHover} <li
onMouseLeave={this.handleBlur} onMouseEnter={this.handleHover}
onClick={this.props.onClick} onMouseLeave={this.handleBlur}
style={{borderColor}} onClick={this.props.onClick}
className={css( style={{borderColor}}
'tab', className={css(
isFirst && 'first', 'tab',
isActive && 'active', isFirst && 'first',
isFirst && isActive && 'firstActive', isActive && 'active',
hasActivity && 'hasActivity' isFirst && isActive && 'firstActive',
)} hasActivity && 'hasActivity'
)}
> >
{ this.props.customChildrenBefore } {this.props.customChildrenBefore}
<span <span className={css('text', isLast && 'textLast', isActive && 'textActive')} onClick={this.handleClick}>
className={css( <span title={this.props.text} className={css('textInner')}>
'text', {this.props.text}
isLast && 'textLast', </span>
isActive && 'textActive'
)}
onClick={this.handleClick}
>
<span
title={this.props.text}
className={css('textInner')}
>
{ this.props.text }
</span> </span>
</span> <i className={css('icon', hovered && 'iconHovered')} onClick={this.props.onClose}>
<i <svg className={css('shape')}>
className={css( <use xlinkHref="./renderer/assets/icons.svg#close-tab" />
'icon', </svg>
hovered && 'iconHovered' </i>
)} {this.props.customChildren}
onClick={this.props.onClose} </li>
> );
<svg className={css('shape')}>
<use xlinkHref="./renderer/assets/icons.svg#close-tab"/>
</svg>
</i>
{ this.props.customChildren }
</li>);
} }
styles() { styles() {
@ -196,5 +182,4 @@ export default class Tab extends Component {
} }
}; };
} }
} }

View file

@ -9,33 +9,19 @@ const Tab = decorate(Tab_, 'Tab');
const isMac = /Mac/.test(navigator.userAgent); const isMac = /Mac/.test(navigator.userAgent);
export default class Tabs extends Component { export default class Tabs extends Component {
template(css) { template(css) {
const { const {tabs = [], borderColor, onChange, onClose} = this.props;
tabs = [],
borderColor,
onChange,
onClose
} = this.props;
const hide = !isMac && tabs.length === 1; const hide = !isMac && tabs.length === 1;
return (<nav className={css('nav', hide && 'hiddenNav')}> return (
{ this.props.customChildrenBefore } <nav className={css('nav', hide && 'hiddenNav')}>
{ {this.props.customChildrenBefore}
tabs.length === 1 && isMac ? {tabs.length === 1 && isMac ? <div className={css('title')}>{tabs[0].title}</div> : null}
<div className={css('title')}>{tabs[0].title}</div> : {tabs.length > 1 ? (
null [
} <ul key="list" className={css('list')}>
{ {tabs.map((tab, i) => {
tabs.length > 1 ?
[
<ul
key="list"
className={css('list')}
>
{
tabs.map((tab, i) => {
const {uid, title, isActive, hasActivity} = tab; const {uid, title, isActive, hasActivity} = tab;
const props = getTabProps(tab, this.props, { const props = getTabProps(tab, this.props, {
text: title === '' ? 'Shell' : title, text: title === '' ? 'Shell' : title,
@ -47,20 +33,15 @@ export default class Tabs extends Component {
onSelect: onChange.bind(null, uid), onSelect: onChange.bind(null, uid),
onClose: onClose.bind(null, uid) onClose: onClose.bind(null, uid)
}); });
return <Tab key={`tab-${uid}`} {...props}/>; return <Tab key={`tab-${uid}`} {...props} />;
}) })}
} </ul>,
</ul>, isMac && <div key="shim" style={{borderColor}} className={css('borderShim')} />
isMac && <div ]
key="shim" ) : null}
style={{borderColor}} {this.props.customChildren}
className={css('borderShim')} </nav>
/> );
] :
null
}
{ this.props.customChildren }
</nav>);
} }
styles() { styles() {
@ -104,5 +85,4 @@ export default class Tabs extends Component {
} }
}; };
} }
} }

View file

@ -10,11 +10,10 @@ const Term = decorate(Term_, 'Term');
const SplitPane = decorate(SplitPane_, 'SplitPane'); const SplitPane = decorate(SplitPane_, 'SplitPane');
class TermGroup_ extends Component { class TermGroup_ extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.bound = new WeakMap(); this.bound = new WeakMap();
this.termRefs = {} this.termRefs = {};
this.sizeChanged = false; this.sizeChanged = false;
this.onTermRef = this.onTermRef.bind(this); this.onTermRef = this.onTermRef.bind(this);
} }
@ -37,19 +36,21 @@ class TermGroup_ extends Component {
} }
const direction = this.props.termGroup.direction.toLowerCase(); const direction = this.props.termGroup.direction.toLowerCase();
return (<SplitPane return (
direction={direction} <SplitPane
sizes={this.props.termGroup.sizes} direction={direction}
onResize={this.props.onTermGroupResize} sizes={this.props.termGroup.sizes}
borderColor={this.props.borderColor} onResize={this.props.onTermGroupResize}
borderColor={this.props.borderColor}
> >
{ groups } {groups}
</SplitPane>); </SplitPane>
);
} }
onTermRef(uid, term) { onTermRef(uid, term) {
this.term = term; this.term = term;
this.props.ref_(uid, term) this.props.ref_(uid, term);
} }
renderTerm(uid) { renderTerm(uid) {
@ -85,15 +86,10 @@ class TermGroup_ extends Component {
// This will create a new ref_ function for every render, // This will create a new ref_ function for every render,
// which is inefficient. Should maybe do something similar // which is inefficient. Should maybe do something similar
// to this.bind. // to this.bind.
return (<Term return <Term ref_={this.onTermRef} key={uid} {...props} />;
ref_={this.onTermRef}
key={uid}
{...props}
/>);
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (this.props.termGroup.sizes != nextProps.termGroup.sizes || nextProps.sizeChanged) { if (this.props.termGroup.sizes != nextProps.termGroup.sizes || nextProps.sizeChanged) {
this.term && this.term.measureResize(); this.term && this.term.measureResize();
// Indicate to children that their size has changed even if their ratio hasn't // Indicate to children that their size has changed even if their ratio hasn't
@ -101,7 +97,6 @@ class TermGroup_ extends Component {
} else { } else {
this.sizeChanged = false; this.sizeChanged = false;
} }
} }
template() { template() {
@ -111,15 +106,16 @@ class TermGroup_ extends Component {
} }
const groups = childGroups.map(child => { const groups = childGroups.map(child => {
const props = getTermGroupProps(child.uid, this.props.parentProps, Object.assign({}, this.props, { const props = getTermGroupProps(
termGroup: child, child.uid,
sizeChanged: this.sizeChanged this.props.parentProps,
})); Object.assign({}, this.props, {
termGroup: child,
sizeChanged: this.sizeChanged
})
);
return (<DecoratedTermGroup return <DecoratedTermGroup key={child.uid} {...props} />;
key={child.uid}
{...props}
/>);
}); });
return this.renderSplit(groups); return this.renderSplit(groups);
@ -128,9 +124,7 @@ class TermGroup_ extends Component {
const TermGroup = connect( const TermGroup = connect(
(state, ownProps) => ({ (state, ownProps) => ({
childGroups: ownProps.termGroup.children.map(uid => childGroups: ownProps.termGroup.children.map(uid => state.termGroups.termGroups[uid])
state.termGroups.termGroups[uid]
)
}), }),
(dispatch, ownProps) => ({ (dispatch, ownProps) => ({
onTermGroupResize(splitSizes) { onTermGroupResize(splitSizes) {

View file

@ -11,20 +11,19 @@ const CURSOR_STYLES = {
BEAM: 'bar', BEAM: 'bar',
UNDERLINE: 'underline', UNDERLINE: 'underline',
BLOCK: 'block' BLOCK: 'block'
} };
export default class Term extends Component { export default class Term extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
props.ref_(props.uid, this); props.ref_(props.uid, this);
this.termRef = null this.termRef = null;
this.termWrapperRef = null this.termWrapperRef = null;
this.termRect = null this.termRect = null;
this.onOpen = this.onOpen.bind(this) this.onOpen = this.onOpen.bind(this);
this.onWindowResize = this.onWindowResize.bind(this) this.onWindowResize = this.onWindowResize.bind(this);
this.onTermRef = this.onTermRef.bind(this) this.onTermRef = this.onTermRef.bind(this);
this.onTermWrapperRef = this.onTermWrapperRef.bind(this) this.onTermWrapperRef = this.onTermWrapperRef.bind(this);
} }
componentDidMount() { componentDidMount() {
@ -34,59 +33,49 @@ export default class Term extends Component {
// as we move the term around splits, until xterm adds // as we move the term around splits, until xterm adds
// support for getState / setState // support for getState / setState
if (props.term) { if (props.term) {
this.term = props.term this.term = props.term;
this.termRef.appendChild(this.term.element) this.termRef.appendChild(this.term.element);
this.onOpen() this.onOpen();
} else { } else {
this.term = props.term || new Terminal({ this.term =
cursorStyle: CURSOR_STYLES[props.cursorShape], props.term ||
cursorBlink: props.cursorBlink new Terminal({
}) cursorStyle: CURSOR_STYLES[props.cursorShape],
this.term.attachCustomKeyEventHandler(this.keyboardHandler) cursorBlink: props.cursorBlink
this.term.on('open', this.onOpen) });
this.term.attachCustomKeyEventHandler(this.keyboardHandler);
this.term.on('open', this.onOpen);
this.term.open(this.termRef, { this.term.open(this.termRef, {
focus: false focus: false
}) });
} }
if (props.onTitle) { if (props.onTitle) {
this.term.on( this.term.on('title', props.onTitle);
'title',
props.onTitle
)
} }
if (props.onActive) { if (props.onActive) {
this.term.on( this.term.on('focus', props.onActive);
'focus',
props.onActive
)
} }
if (props.onData) { if (props.onData) {
this.term.on( this.term.on('data', props.onData);
'data',
props.onData
)
} }
if (props.onResize) { if (props.onResize) {
this.term.on( this.term.on('resize', ({cols, rows}) => {
'resize', props.onResize(cols, rows);
({ cols, rows }) => { });
props.onResize(cols, rows)
}
)
} }
window.addEventListener('resize', this.onWindowResize, { window.addEventListener('resize', this.onWindowResize, {
passive: true passive: true
}) });
terms[this.props.uid] = this; terms[this.props.uid] = this;
} }
onOpen () { onOpen() {
// we need to delay one frame so that aphrodite styles // we need to delay one frame so that aphrodite styles
// get applied and we can make an accurate measurement // get applied and we can make an accurate measurement
// of the container width and height // of the container width and height
@ -97,26 +86,26 @@ export default class Term extends Component {
// we force it instead // we force it instead
this.term.charMeasure.measure(); this.term.charMeasure.measure();
this.measureResize(); this.measureResize();
}) });
} }
getTermDocument () { getTermDocument() {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error('unimplemented') console.error('unimplemented');
} }
// measures the container and makes the decision // measures the container and makes the decision
// whether to resize the term to fit the container // whether to resize the term to fit the container
measureResize () { measureResize() {
console.log('performing measure resize') //eslint-disable-next-line no-console
const termRect = this.termWrapperRef.getBoundingClientRect() console.log('performing measure resize');
const termRect = this.termWrapperRef.getBoundingClientRect();
if (!this.termRect || if (!this.termRect || termRect.width !== this.termRect.width || termRect.height !== this.termRect.height) {
termRect.width !== this.termRect.width ||
termRect.height !== this.termRect.height) {
this.termRect = termRect; this.termRect = termRect;
console.log('performing fit resize') //eslint-disable-next-line no-console
this.fitResize() console.log('performing fit resize');
this.fitResize();
} }
} }
@ -136,24 +125,20 @@ export default class Term extends Component {
this.term.clear(); this.term.clear();
} }
reset () { reset() {
this.term.reset(); this.term.reset();
} }
resize (cols, rows) { resize(cols, rows) {
this.term.resize(cols, rows); this.term.resize(cols, rows);
} }
fitResize () { fitResize() {
const cols = Math.floor( const cols = Math.floor(this.termRect.width / this.term.charMeasure.width);
this.termRect.width / this.term.charMeasure.width const rows = Math.floor(this.termRect.height / this.term.charMeasure.height);
)
const rows = Math.floor(
this.termRect.height / this.term.charMeasure.height
)
if (cols !== this.props.cols || rows !== this.props.rows) { if (cols !== this.props.cols || rows !== this.props.rows) {
this.resize(cols, rows) this.resize(cols, rows);
} }
} }
@ -173,19 +158,17 @@ export default class Term extends Component {
this.clear(); this.clear();
} }
if (this.props.fontSize !== nextProps.fontSize || if (this.props.fontSize !== nextProps.fontSize || this.props.fontFamily !== nextProps.fontFamily) {
this.props.fontFamily !== nextProps.fontFamily) {
// invalidate xterm cache about how wide each // invalidate xterm cache about how wide each
// character is // character is
this.term.charMeasure.measure() this.term.charMeasure.measure();
// resize to fit the container // resize to fit the container
this.fitResize() this.fitResize();
} }
if (nextProps.rows !== this.props.rows || if (nextProps.rows !== this.props.rows || nextProps.cols !== this.props.cols) {
nextProps.cols !== this.props.cols) { this.resize(nextProps.cols, nextProps.rows);
this.resize(nextProps.cols, nextProps.rows)
} }
} }
@ -202,33 +185,26 @@ export default class Term extends Component {
this.props.ref_(this.props.uid, null); this.props.ref_(this.props.uid, null);
// to clean up the terminal, we remove the listeners // to clean up the terminal, we remove the listeners
// instead of invoking `destroy`, since it will make the // instead of invoking `destroy`, since it will make the
// term insta un-attachable in the future (which we need // term insta un-attachable in the future (which we need
// to do in case of splitting, see `componentDidMount` // to do in case of splitting, see `componentDidMount`
this.term._events = {} this.term._events = {};
window.removeEventListener('resize', this.onWindowResize, { window.removeEventListener('resize', this.onWindowResize, {
passive: true passive: true
}) });
} }
template(css) { template(css) {
return (<div return (
className={css('fit', this.props.isTermActive && 'active')} <div className={css('fit', this.props.isTermActive && 'active')} style={{padding: this.props.padding}}>
style={{padding: this.props.padding}} {this.props.customChildrenBefore}
> <div ref={this.onTermWrapperRef} className={css('fit', 'wrapper')}>
{ this.props.customChildrenBefore } <div ref={this.onTermRef} className={css('term')} />
<div </div>
ref={this.onTermWrapperRef} {this.props.customChildren}
className={css('fit', 'wrapper')}
>
<div
ref={this.onTermRef}
className={css('term')}
/>
</div> </div>
{ this.props.customChildren } );
</div>);
} }
styles() { styles() {

View file

@ -11,7 +11,6 @@ const StyleSheet = decorate(StyleSheet_, 'StyleSheet');
const isMac = /Mac/.test(navigator.userAgent); const isMac = /Mac/.test(navigator.userAgent);
export default class Terms extends Component { export default class Terms extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.terms = {}; this.terms = {};
@ -71,12 +70,10 @@ export default class Terms extends Component {
template(css) { template(css) {
const shift = !isMac && this.props.termGroups.length > 1; const shift = !isMac && this.props.termGroups.length > 1;
return (<div return (
className={css('terms', shift && 'termsShifted')} <div className={css('terms', shift && 'termsShifted')}>
> {this.props.customChildrenBefore}
{ this.props.customChildrenBefore } {this.props.termGroups.map(termGroup => {
{
this.props.termGroups.map(termGroup => {
const {uid} = termGroup; const {uid} = termGroup;
const isActive = uid === this.props.activeRootGroup; const isActive = uid === this.props.activeRootGroup;
const props = getTermGroupProps(uid, this.props, { const props = getTermGroupProps(uid, this.props, {
@ -105,31 +102,24 @@ export default class Terms extends Component {
}); });
return ( return (
<div <div key={`d${uid}`} className={css('termGroup', isActive && 'termGroupActive')}>
key={`d${uid}`} <TermGroup key={uid} ref_={this.onRef} {...props} />
className={css('termGroup', isActive && 'termGroupActive')}
>
<TermGroup
key={uid}
ref_={this.onRef}
{...props}
/>
</div> </div>
); );
}) })}
} {this.props.customChildren}
{ this.props.customChildren } <StyleSheet
<StyleSheet colors={this.props.colors}
colors={this.props.colors} customCSS={this.props.customCSS}
customCSS={this.props.customCSS} cursorColor={this.props.cursorColor}
cursorColor={this.props.cursorColor} fontSize={this.props.fontSize}
fontSize={this.props.fontSize} fontFamily={this.props.fontFamily}
fontFamily={this.props.fontFamily} fontSmoothing={this.props.fontSmoothing}
fontSmoothing={this.props.fontSmoothing} foregroundColor={this.props.foregroundColor}
foregroundColor={this.props.foregroundColor} borderColor={this.props.borderColor}
borderColor={this.props.borderColor} />
/> </div>
</div>); );
} }
styles() { styles() {

View file

@ -14,16 +14,17 @@ const getActiveSessions = ({termGroups}) => termGroups.activeSessions;
const getActivityMarkers = ({ui}) => ui.activityMarkers; const getActivityMarkers = ({ui}) => ui.activityMarkers;
const getTabs = createSelector( const getTabs = createSelector(
[getSessions, getRootGroups, getActiveSessions, getActiveRootGroup, getActivityMarkers], [getSessions, getRootGroups, getActiveSessions, getActiveRootGroup, getActivityMarkers],
(sessions, rootGroups, activeSessions, activeRootGroup, activityMarkers) => rootGroups.map(t => { (sessions, rootGroups, activeSessions, activeRootGroup, activityMarkers) =>
const activeSessionUid = activeSessions[t.uid]; rootGroups.map(t => {
const session = sessions[activeSessionUid]; const activeSessionUid = activeSessions[t.uid];
return { const session = sessions[activeSessionUid];
uid: t.uid, return {
title: session.title, uid: t.uid,
isActive: t.uid === activeRootGroup, title: session.title,
hasActivity: activityMarkers[session.uid] isActive: t.uid === activeRootGroup,
}; hasActivity: activityMarkers[session.uid]
}) };
})
); );
const HeaderContainer = connect( const HeaderContainer = connect(

View file

@ -36,7 +36,7 @@ class Hyper extends Component {
attachKeyListeners() { attachKeyListeners() {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error('removed key listeners') console.error('removed key listeners');
} }
onTermsRef(terms) { onTermsRef(terms) {
@ -62,8 +62,7 @@ class Hyper extends Component {
template(css) { template(css) {
const {isMac: isMac_, customCSS, uiFontFamily, borderColor, maximized} = this.props; const {isMac: isMac_, customCSS, uiFontFamily, borderColor, maximized} = this.props;
const borderWidth = isMac_ ? '' : const borderWidth = isMac_ ? '' : `${maximized ? '0' : '1'}px`;
`${maximized ? '0' : '1'}px`;
return ( return (
<div> <div>
@ -71,14 +70,14 @@ class Hyper extends Component {
style={{fontFamily: uiFontFamily, borderColor, borderWidth}} style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
className={css('main', isMac_ && 'mainRounded')} className={css('main', isMac_ && 'mainRounded')}
> >
<HeaderContainer/> <HeaderContainer />
<TermsContainer ref_={this.onTermsRef}/> <TermsContainer ref_={this.onTermsRef} />
{ this.props.customInnerChildren } {this.props.customInnerChildren}
</div> </div>
<NotificationsContainer/> <NotificationsContainer />
<style dangerouslySetInnerHTML={{__html: customCSS}}/> <style dangerouslySetInnerHTML={{__html: customCSS}} />
{ this.props.customChildren } {this.props.customChildren}
</div> </div>
); );
} }

View file

@ -21,9 +21,7 @@ const TermsContainer = connect(
activeSession: state.sessions.activeUid, activeSession: state.sessions.activeUid,
customCSS: state.ui.termCSS, customCSS: state.ui.termCSS,
write: state.sessions.write, write: state.sessions.write,
fontSize: state.ui.fontSizeOverride ? fontSize: state.ui.fontSizeOverride ? state.ui.fontSizeOverride : state.ui.fontSize,
state.ui.fontSizeOverride :
state.ui.fontSize,
fontFamily: state.ui.fontFamily, fontFamily: state.ui.fontFamily,
uiFontFamily: state.ui.uiFontFamily, uiFontFamily: state.ui.uiFontFamily,
fontSmoothing: state.ui.fontSmoothingOverride, fontSmoothing: state.ui.fontSmoothingOverride,
@ -49,7 +47,6 @@ const TermsContainer = connect(
}, },
onTitle(uid, title) { onTitle(uid, title) {
console.log(title)
dispatch(setSessionXtermTitle(uid, title)); dispatch(setSessionXtermTitle(uid, title));
}, },

View file

@ -9,7 +9,7 @@ import CommandRegistry from './command-registry';
hterm.defaultStorage = new lib.Storage.Memory(); hterm.defaultStorage = new lib.Storage.Memory();
// Provide selectAll to terminal viewport // Provide selectAll to terminal viewport
hterm.Terminal.prototype.selectAll = function () { hterm.Terminal.prototype.selectAll = () => {
// If the cursorNode_ having hyperCaret we need to remove it // If the cursorNode_ having hyperCaret we need to remove it
if (this.cursorNode_.contains(this.hyperCaret)) { if (this.cursorNode_.contains(this.hyperCaret)) {
this.cursorNode_.removeChild(this.hyperCaret); this.cursorNode_.removeChild(this.hyperCaret);
@ -21,9 +21,10 @@ hterm.Terminal.prototype.selectAll = function () {
// override double click behavior to copy // override double click behavior to copy
const oldMouse = hterm.Terminal.prototype.onMouse_; const oldMouse = hterm.Terminal.prototype.onMouse_;
hterm.Terminal.prototype.onMouse_ = function (e) { hterm.Terminal.prototype.onMouse_ = e => {
if (e.type === 'dblclick') { if (e.type === 'dblclick') {
selection.extend(this); selection.extend(this);
//eslint-disable-next-line no-console
console.log('[hyper+hterm] ignore double click'); console.log('[hyper+hterm] ignore double click');
return; return;
} }
@ -35,20 +36,23 @@ function containsNonLatinCodepoints(s) {
} }
// hterm Unicode patch // hterm Unicode patch
hterm.TextAttributes.splitWidecharString = function (str) { hterm.TextAttributes.splitWidecharString = str => {
const context = runes(str).reduce((ctx, rune) => { const context = runes(str).reduce(
const code = rune.codePointAt(0); (ctx, rune) => {
if (code < 128 || lib.wc.charWidth(code) === 1) { const code = rune.codePointAt(0);
ctx.acc += rune; 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; return ctx;
} },
if (ctx.acc) { {items: [], acc: ''}
ctx.items.push({str: ctx.acc}); );
ctx.acc = '';
}
ctx.items.push({str: rune, wcNode: true});
return ctx;
}, {items: [], acc: ''});
if (context.acc) { if (context.acc) {
context.items.push({str: context.acc}); context.items.push({str: context.acc});
} }
@ -57,7 +61,7 @@ hterm.TextAttributes.splitWidecharString = function (str) {
// hterm Unicode patch // hterm Unicode patch
const cache = []; const cache = [];
lib.wc.strWidth = function (str) { lib.wc.strWidth = str => {
const shouldCache = str.length === 1; const shouldCache = str.length === 1;
if (shouldCache && cache[str] !== undefined) { if (shouldCache && cache[str] !== undefined) {
return cache[str]; return cache[str];
@ -72,7 +76,7 @@ lib.wc.strWidth = function (str) {
if (width < 0) { if (width < 0) {
return -1; return -1;
} }
rv += width * ((codePoint <= 0xFFFF) ? 1 : 2); rv += width * (codePoint <= 0xffff ? 1 : 2);
} }
if (shouldCache) { if (shouldCache) {
cache[str] = rv; cache[str] = rv;
@ -81,7 +85,7 @@ lib.wc.strWidth = function (str) {
}; };
// hterm Unicode patch // hterm Unicode patch
lib.wc.substr = function (str, start, optWidth) { lib.wc.substr = (str, start, optWidth) => {
const chars = runes(str); const chars = runes(str);
let startIndex; let startIndex;
let endIndex; let endIndex;
@ -90,7 +94,7 @@ lib.wc.substr = function (str, start, optWidth) {
for (let i = 0; i < chars.length; i++) { for (let i = 0; i < chars.length; i++) {
const codePoint = chars[i].codePointAt(0); const codePoint = chars[i].codePointAt(0);
const charWidth = lib.wc.charWidth(codePoint); const charWidth = lib.wc.charWidth(codePoint);
if ((width + charWidth) > start) { if (width + charWidth > start) {
startIndex = i; startIndex = i;
break; break;
} }
@ -112,14 +116,14 @@ lib.wc.substr = function (str, start, optWidth) {
}; };
// MacOS emoji bar support // MacOS emoji bar support
hterm.Keyboard.prototype.onTextInput_ = function (e) { hterm.Keyboard.prototype.onTextInput_ = e => {
if (!e.data) { if (!e.data) {
return; return;
} }
runes(e.data).forEach(this.terminal.onVTKeystroke.bind(this.terminal)); runes(e.data).forEach(this.terminal.onVTKeystroke.bind(this.terminal));
}; };
hterm.Terminal.IO.prototype.writeUTF8 = function (string) { hterm.Terminal.IO.prototype.writeUTF8 = string => {
if (this.terminal_.io !== this) { if (this.terminal_.io !== this) {
throw new Error('Attempt to print from inactive IO object.'); throw new Error('Attempt to print from inactive IO object.');
} }
@ -137,12 +141,12 @@ hterm.Terminal.IO.prototype.writeUTF8 = function (string) {
}; };
const oldIsDefault = hterm.TextAttributes.prototype.isDefault; const oldIsDefault = hterm.TextAttributes.prototype.isDefault;
hterm.TextAttributes.prototype.isDefault = function () { hterm.TextAttributes.prototype.isDefault = () => {
return !this.unicodeNode && oldIsDefault.call(this); return !this.unicodeNode && oldIsDefault.call(this);
}; };
const oldSetFontSize = hterm.Terminal.prototype.setFontSize; const oldSetFontSize = hterm.Terminal.prototype.setFontSize;
hterm.Terminal.prototype.setFontSize = function (px) { hterm.Terminal.prototype.setFontSize = px => {
oldSetFontSize.call(this, px); oldSetFontSize.call(this, px);
const doc = this.getDocument(); const doc = this.getDocument();
let unicodeNodeStyle = doc.getElementById('hyper-unicode-styles'); let unicodeNodeStyle = doc.getElementById('hyper-unicode-styles');
@ -161,7 +165,7 @@ hterm.Terminal.prototype.setFontSize = function (px) {
}; };
const oldCreateContainer = hterm.TextAttributes.prototype.createContainer; const oldCreateContainer = hterm.TextAttributes.prototype.createContainer;
hterm.TextAttributes.prototype.createContainer = function (text) { hterm.TextAttributes.prototype.createContainer = text => {
const container = oldCreateContainer.call(this, text); const container = oldCreateContainer.call(this, text);
if (container.style && runes(text).length === 1 && containsNonLatinCodepoints(text)) { if (container.style && runes(text).length === 1 && containsNonLatinCodepoints(text)) {
container.className += ' unicode-node'; container.className += ' unicode-node';
@ -171,19 +175,17 @@ hterm.TextAttributes.prototype.createContainer = function (text) {
// Do not match containers when one of them has unicode text (unicode chars need to be alone in their containers) // 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; const oldMatchesContainer = hterm.TextAttributes.prototype.matchesContainer;
hterm.TextAttributes.prototype.matchesContainer = function (obj) { hterm.TextAttributes.prototype.matchesContainer = obj => {
return oldMatchesContainer.call(this, obj) && return oldMatchesContainer.call(this, obj) && !this.unicodeNode && !containsNonLatinCodepoints(obj.textContent);
!this.unicodeNode &&
!containsNonLatinCodepoints(obj.textContent);
}; };
// there's no option to turn off the size overlay // there's no option to turn off the size overlay
hterm.Terminal.prototype.overlaySize = function () {}; hterm.Terminal.prototype.overlaySize = () => {};
// fixing a bug in hterm where a double click triggers // fixing a bug in hterm where a double click triggers
// a non-collapsed selection whose text is '', and results // a non-collapsed selection whose text is '', and results
// in an infinite copy loop // in an infinite copy loop
hterm.Terminal.prototype.copySelectionToClipboard = function () { hterm.Terminal.prototype.copySelectionToClipboard = () => {
const text = this.getSelectionText(); const text = this.getSelectionText();
if (text) { if (text) {
this.copyStringToClipboard(text); this.copyStringToClipboard(text);
@ -195,7 +197,7 @@ let lastEventKey;
// passthrough all the commands that are meant to control // passthrough all the commands that are meant to control
// hyper and not the terminal itself // hyper and not the terminal itself
const oldKeyDown = hterm.Keyboard.prototype.onKeyDown_; const oldKeyDown = hterm.Keyboard.prototype.onKeyDown_;
hterm.Keyboard.prototype.onKeyDown_ = function (e) { hterm.Keyboard.prototype.onKeyDown_ = e => {
const modifierKeysConf = this.terminal.modifierKeys; const modifierKeysConf = this.terminal.modifierKeys;
if (e.timeStamp === lastEventTimeStamp && e.key === lastEventKey) { if (e.timeStamp === lastEventTimeStamp && e.key === lastEventKey) {
// Event was already processed. // Event was already processed.
@ -207,25 +209,29 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
lastEventTimeStamp = e.timeStamp; lastEventTimeStamp = e.timeStamp;
lastEventKey = e.key; lastEventKey = e.key;
if (e.altKey && if (
e.which !== 16 && // Ignore other modifer keys e.altKey &&
e.which !== 17 && e.which !== 16 && // Ignore other modifer keys
e.which !== 18 && e.which !== 17 &&
e.which !== 91 && e.which !== 18 &&
modifierKeysConf.altIsMeta) { e.which !== 91 &&
modifierKeysConf.altIsMeta
) {
const char = fromCharCode(e); const char = fromCharCode(e);
this.terminal.onVTKeystroke('\x1b' + char); this.terminal.onVTKeystroke('\x1b' + char);
e.preventDefault(); e.preventDefault();
} }
if (e.metaKey && if (
e.code !== 'MetaLeft' && e.metaKey &&
e.code !== 'MetaRight' && e.code !== 'MetaLeft' &&
e.which !== 16 && e.code !== 'MetaRight' &&
e.which !== 17 && e.which !== 16 &&
e.which !== 18 && e.which !== 17 &&
e.which !== 91 && e.which !== 18 &&
modifierKeysConf.cmdIsMeta) { e.which !== 91 &&
modifierKeysConf.cmdIsMeta
) {
const char = fromCharCode(e); const char = fromCharCode(e);
this.terminal.onVTKeystroke('\x1b' + char); this.terminal.onVTKeystroke('\x1b' + char);
e.preventDefault(); e.preventDefault();
@ -248,14 +254,8 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
} }
// Test for valid keys in order to accept clear status // Test for valid keys in order to accept clear status
const clearBlacklist = [ const clearBlacklist = ['control', 'shift', 'capslock', 'dead'];
'control', if (!clearBlacklist.includes(e.code.toLowerCase()) && !clearBlacklist.includes(e.key.toLowerCase())) {
'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 // Since Electron 1.6.X, there is a race condition with character composition
// if this selection clearing is made synchronously. See #2140. // if this selection clearing is made synchronously. See #2140.
setTimeout(() => selection.clear(this.terminal), 0); setTimeout(() => selection.clear(this.terminal), 0);
@ -269,7 +269,7 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
}; };
const oldOnMouse = hterm.Terminal.prototype.onMouse_; const oldOnMouse = hterm.Terminal.prototype.onMouse_;
hterm.Terminal.prototype.onMouse_ = function (e) { hterm.Terminal.prototype.onMouse_ = e => {
// override `preventDefault` to not actually // override `preventDefault` to not actually
// prevent default when the type of event is // prevent default when the type of event is
// mousedown, so that we can still trigger // mousedown, so that we can still trigger
@ -278,14 +278,14 @@ hterm.Terminal.prototype.onMouse_ = function (e) {
// case of programs like `vtop` that allow for // case of programs like `vtop` that allow for
// the user to click on rows // the user to click on rows
if (e.type === 'mousedown') { if (e.type === 'mousedown') {
e.preventDefault = function () { }; e.preventDefault = () => {};
return; return;
} }
return oldOnMouse.call(this, e); return oldOnMouse.call(this, e);
}; };
hterm.Terminal.prototype.onMouseDown_ = function (e) { hterm.Terminal.prototype.onMouseDown_ = e => {
// copy/paste on right click // copy/paste on right click
if (e.button === 2) { if (e.button === 2) {
const text = this.getSelectionText(); const text = this.getSelectionText();
@ -300,29 +300,25 @@ hterm.Terminal.prototype.onMouseDown_ = function (e) {
// override `ScrollPort.resize` to avoid an expensive calculation // override `ScrollPort.resize` to avoid an expensive calculation
// just to get the size of the scrollbar, which for Hyper is always // just to get the size of the scrollbar, which for Hyper is always
// set to overlay (hence with `0`) // set to overlay (hence with `0`)
hterm.ScrollPort.prototype.resize = function () { hterm.ScrollPort.prototype.resize = () => {
this.currentScrollbarWidthPx = 0; this.currentScrollbarWidthPx = 0;
this.syncScrollHeight(); this.syncScrollHeight();
this.syncRowNodesDimensions_(); this.syncRowNodesDimensions_();
this.publish( this.publish('resize', {scrollPort: this}, () => {
'resize', this.scrollRowToBottom(this.rowProvider_.getRowCount());
{scrollPort: this}, this.scheduleRedraw();
() => { });
this.scrollRowToBottom(this.rowProvider_.getRowCount());
this.scheduleRedraw();
}
);
}; };
// make background transparent to avoid transparency issues // make background transparent to avoid transparency issues
hterm.ScrollPort.prototype.setBackgroundColor = function () { hterm.ScrollPort.prototype.setBackgroundColor = () => {
this.screen_.style.backgroundColor = 'transparent'; this.screen_.style.backgroundColor = 'transparent';
}; };
// will be called by the <Term/> right after the `hterm.Terminal` is instantiated // will be called by the <Term/> right after the `hterm.Terminal` is instantiated
hterm.Terminal.prototype.onHyperCaret = function (caret) { hterm.Terminal.prototype.onHyperCaret = caret => {
this.hyperCaret = caret; this.hyperCaret = caret;
let ongoingComposition = false; let ongoingComposition = false;
@ -389,14 +385,14 @@ hterm.Terminal.prototype.onHyperCaret = function (caret) {
// ensure that our contenteditable caret is injected // ensure that our contenteditable caret is injected
// inside the term's cursor node and that it's focused // inside the term's cursor node and that it's focused
hterm.Terminal.prototype.focusHyperCaret = function () { hterm.Terminal.prototype.focusHyperCaret = () => {
if (!this.hyperCaret.parentNode !== this.cursorNode_) { if (!this.hyperCaret.parentNode !== this.cursorNode_) {
this.cursorNode_.appendChild(this.hyperCaret); this.cursorNode_.appendChild(this.hyperCaret);
} }
this.hyperCaret.focus(); this.hyperCaret.focus();
}; };
hterm.Screen.prototype.syncSelectionCaret = function () { hterm.Screen.prototype.syncSelectionCaret = () => {
const p = this.terminal.hyperCaret; const p = this.terminal.hyperCaret;
const doc = this.terminal.document_; const doc = this.terminal.document_;
const win = doc.defaultView; const win = doc.defaultView;
@ -413,14 +409,14 @@ hterm.Screen.prototype.syncSelectionCaret = function () {
// the original function) was causing the issue. So right now we're overriding // the original function) was causing the issue. So right now we're overriding
// the function to prevent the `iframe_` from being focused. // the function to prevent the `iframe_` from being focused.
// This shouldn't create any side effects we're _stealing_ the focus from `htem` anyways. // This shouldn't create any side effects we're _stealing_ the focus from `htem` anyways.
hterm.ScrollPort.prototype.focus = function () { hterm.ScrollPort.prototype.focus = () => {
this.screen_.focus(); this.screen_.focus();
}; };
// fixes a bug in hterm, where the cursor goes back to `BLOCK` // fixes a bug in hterm, where the cursor goes back to `BLOCK`
// after the bell rings // after the bell rings
const oldRingBell = hterm.Terminal.prototype.ringBell; const oldRingBell = hterm.Terminal.prototype.ringBell;
hterm.Terminal.prototype.ringBell = function () { hterm.Terminal.prototype.ringBell = () => {
oldRingBell.call(this); oldRingBell.call(this);
setTimeout(() => { setTimeout(() => {
this.restyleCursor_(); this.restyleCursor_();
@ -429,7 +425,7 @@ hterm.Terminal.prototype.ringBell = function () {
// fixes a bug in hterm, where the shorthand hex // fixes a bug in hterm, where the shorthand hex
// is not properly converted to rgb // is not properly converted to rgb
lib.colors.hexToRGB = function (arg) { lib.colors.hexToRGB = arg => {
const hex16 = lib.colors.re_.hex16; const hex16 = lib.colors.re_.hex16;
const hex24 = lib.colors.re_.hex24; const hex24 = lib.colors.re_.hex24;
@ -444,11 +440,7 @@ lib.colors.hexToRGB = function (arg) {
return null; return null;
} }
return 'rgb(' + return 'rgb(' + parseInt(ary[1], 16) + ', ' + parseInt(ary[2], 16) + ', ' + parseInt(ary[3], 16) + ')';
parseInt(ary[1], 16) + ', ' +
parseInt(ary[2], 16) + ', ' +
parseInt(ary[3], 16) +
')';
} }
if (Array.isArray(arg)) { if (Array.isArray(arg)) {
@ -463,7 +455,7 @@ lib.colors.hexToRGB = function (arg) {
}; };
// add support for cursor styles 5 and 6, fixes #270 // add support for cursor styles 5 and 6, fixes #270
hterm.VT.CSI[' q'] = function (parseState) { hterm.VT.CSI[' q'] = parseState => {
const arg = parseState.args[0]; const arg = parseState.args[0];
if (arg === '0' || arg === '1') { if (arg === '0' || arg === '1') {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK); this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
@ -484,6 +476,7 @@ hterm.VT.CSI[' q'] = function (parseState) {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM); this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
this.terminal.setCursorBlink(false); this.terminal.setCursorBlink(false);
} else { } else {
//eslint-disable-next-line no-console
console.warn('Unknown cursor style: ' + arg); console.warn('Unknown cursor style: ' + arg);
} }
}; };

View file

@ -148,7 +148,7 @@ rpc.on('add notification', ({text, url, dismissable}) => {
const app = render( const app = render(
<Provider store={store_}> <Provider store={store_}>
<HyperContainer/> <HyperContainer />
</Provider>, </Provider>,
document.getElementById('mount') document.getElementById('mount')
); );

View file

@ -35,15 +35,16 @@ function Session(obj) {
const reducer = (state = initialState, action) => { const reducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case SESSION_ADD: case SESSION_ADD:
return state return state.set('activeUid', action.uid).setIn(
.set('activeUid', action.uid) ['sessions', action.uid],
.setIn(['sessions', action.uid], Session({ Session({
cols: action.cols, cols: action.cols,
rows: action.rows, rows: action.rows,
uid: action.uid, uid: action.uid,
shell: action.shell.split('/').pop(), shell: action.shell.split('/').pop(),
pid: action.pid pid: action.pid
})); })
);
case SESSION_URL_SET: case SESSION_URL_SET:
return state.setIn(['sessions', action.uid, 'url'], action.url); return state.setIn(['sessions', action.uid, 'url'], action.url);
@ -55,27 +56,31 @@ const reducer = (state = initialState, action) => {
return state.set('activeUid', action.uid); return state.set('activeUid', action.uid);
case SESSION_CLEAR_ACTIVE: case SESSION_CLEAR_ACTIVE:
return state.merge({ return state.merge(
sessions: { {
[state.activeUid]: { sessions: {
cleared: true [state.activeUid]: {
cleared: true
}
} }
} },
}, {deep: true}); {deep: true}
);
case SESSION_PTY_DATA: case SESSION_PTY_DATA:
// we avoid a direct merge for perf reasons // we avoid a direct merge for perf reasons
// as this is the most common action // as this is the most common action
if (state.sessions[action.uid] && if (state.sessions[action.uid] && state.sessions[action.uid].cleared) {
state.sessions[action.uid].cleared) { return state.merge(
return state {
.merge({
sessions: { sessions: {
[action.uid]: { [action.uid]: {
cleared: false cleared: false
} }
} }
}, {deep: true}); },
{deep: true}
);
} }
return state; return state;
@ -99,11 +104,14 @@ const reducer = (state = initialState, action) => {
); );
case SESSION_RESIZE: case SESSION_RESIZE:
return state.setIn(['sessions', action.uid], state.sessions[action.uid].merge({ return state.setIn(
rows: action.rows, ['sessions', action.uid],
cols: action.cols, state.sessions[action.uid].merge({
resizeAt: Date.now() rows: action.rows,
})); cols: action.cols,
resizeAt: Date.now()
})
);
case SESSION_SET_CWD: case SESSION_SET_CWD:
return state.setIn(['sessions', state.activeUid, 'cwd'], action.cwd); return state.setIn(['sessions', state.activeUid, 'cwd'], action.cwd);

View file

@ -40,9 +40,7 @@ const setActiveGroup = (state, action) => {
const childGroup = findBySession(state, action.uid); const childGroup = findBySession(state, action.uid);
const rootGroup = findRootGroup(state.termGroups, childGroup.uid); const rootGroup = findRootGroup(state.termGroups, childGroup.uid);
return state return state.set('activeRootGroup', rootGroup.uid).setIn(['activeSessions', rootGroup.uid], action.uid);
.set('activeRootGroup', rootGroup.uid)
.setIn(['activeSessions', rootGroup.uid], action.uid);
}; };
// Reduce existing sizes to fit a new split: // Reduce existing sizes to fit a new split:
@ -50,7 +48,7 @@ const insertRebalance = (oldSizes, index) => {
const newSize = 1 / (oldSizes.length + 1); const newSize = 1 / (oldSizes.length + 1);
// We spread out how much each pane should be reduced // We spread out how much each pane should be reduced
// with based on their existing size: // with based on their existing size:
const balanced = oldSizes.map(size => size - (newSize * size)); const balanced = oldSizes.map(size => size - newSize * size);
return [...balanced.slice(0, index), newSize, ...balanced.slice(index)]; return [...balanced.slice(0, index), newSize, ...balanced.slice(index)];
}; };
@ -58,9 +56,7 @@ const insertRebalance = (oldSizes, index) => {
const removalRebalance = (oldSizes, index) => { const removalRebalance = (oldSizes, index) => {
const removedSize = oldSizes[index]; const removedSize = oldSizes[index];
const increase = removedSize / (oldSizes.length - 1); const increase = removedSize / (oldSizes.length - 1);
return oldSizes return oldSizes.filter((_size, i) => i !== index).map(size => size + increase);
.filter((_size, i) => i !== index)
.map(size => size + increase);
}; };
const splitGroup = (state, action) => { const splitGroup = (state, action) => {
@ -96,23 +92,27 @@ const splitGroup = (state, action) => {
parentUid: parentGroup.uid parentUid: parentGroup.uid
}); });
return state return state.setIn(['termGroups', existingSession.uid], existingSession).setIn(
.setIn(['termGroups', existingSession.uid], existingSession) ['termGroups', parentGroup.uid],
.setIn(['termGroups', parentGroup.uid], parentGroup.merge({ parentGroup.merge({
sessionUid: null, sessionUid: null,
direction: splitDirection, direction: splitDirection,
children: [existingSession.uid, newSession.uid] children: [existingSession.uid, newSession.uid]
})); })
);
} }
const {children} = parentGroup; const {children} = parentGroup;
// Insert the new child pane right after the active one: // Insert the new child pane right after the active one:
const index = children.indexOf(activeGroup.uid) + 1; const index = children.indexOf(activeGroup.uid) + 1;
const newChildren = [...children.slice(0, index), newSession.uid, ...children.slice(index)]; const newChildren = [...children.slice(0, index), newSession.uid, ...children.slice(index)];
state = state.setIn(['termGroups', parentGroup.uid], parentGroup.merge({ state = state.setIn(
direction: splitDirection, ['termGroups', parentGroup.uid],
children: newChildren parentGroup.merge({
})); direction: splitDirection,
children: newChildren
})
);
if (parentGroup.sizes) { if (parentGroup.sizes) {
const newSizes = insertRebalance(parentGroup.sizes, index); const newSizes = insertRebalance(parentGroup.sizes, index);
@ -131,17 +131,13 @@ const replaceParent = (state, parent, child) => {
// If the parent we're replacing has a parent, // If the parent we're replacing has a parent,
// we need to change the uid in its children array // we need to change the uid in its children array
// with `child`: // with `child`:
const newChildren = parentParent.children.map(uid => const newChildren = parentParent.children.map(uid => (uid === parent.uid ? child.uid : uid));
uid === parent.uid ? child.uid : uid
);
state = state.setIn(['termGroups', parentParent.uid, 'children'], newChildren); state = state.setIn(['termGroups', parentParent.uid, 'children'], newChildren);
} else { } else {
// This means the given child will be // This means the given child will be
// a root group, so we need to set it up as such: // a root group, so we need to set it up as such:
const newSessions = state.activeSessions const newSessions = state.activeSessions.without(parent.uid).set(child.uid, state.activeSessions[parent.uid]);
.without(parent.uid)
.set(child.uid, state.activeSessions[parent.uid]);
state = state state = state
.set('activeTermGroup', child.uid) .set('activeTermGroup', child.uid)
@ -185,10 +181,7 @@ const resizeGroup = (state, uid, sizes) => {
return state; return state;
} }
return state.setIn( return state.setIn(['termGroups', uid, 'sizes'], sizes);
['termGroups', uid, 'sizes'],
sizes
);
}; };
const reducer = (state = initialState, action) => { const reducer = (state = initialState, action) => {

View file

@ -40,7 +40,8 @@ const initial = Immutable({
fontSize: 12, fontSize: 12,
padding: '12px 14px', padding: '12px 14px',
fontFamily: 'Menlo, "DejaVu Sans Mono", "Lucida Console", monospace', fontFamily: 'Menlo, "DejaVu Sans Mono", "Lucida Console", monospace',
uiFontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif', uiFontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
fontSizeOverride: null, fontSizeOverride: null,
fontSmoothingOverride: 'antialiased', fontSmoothingOverride: 'antialiased',
css: '', css: '',
@ -97,128 +98,128 @@ const currentWindow = remote.getCurrentWindow();
const reducer = (state = initial, action) => { const reducer = (state = initial, action) => {
let state_ = state; let state_ = state;
let isMax; let isMax;
//eslint-disable-next-line default-case
switch (action.type) { // eslint-disable-line default-case switch (action.type) {
case CONFIG_LOAD: case CONFIG_LOAD:
case CONFIG_RELOAD: // eslint-disable-line no-case-declarations // eslint-disable-next-line no-case-declarations, no-fallthrough
case CONFIG_RELOAD:
const {config} = action; const {config} = action;
state_ = state state_ = state
// unset the user font size override if the // unset the user font size override if the
// font size changed from the config // font size changed from the config
.merge((() => { .merge(
const ret = {}; (() => {
const ret = {};
if (state.fontSizeOverride && config.fontSize !== state.fontSize) { if (state.fontSizeOverride && config.fontSize !== state.fontSize) {
ret.fontSizeOverride = null; ret.fontSizeOverride = null;
} }
if (config.fontSize) { if (config.fontSize) {
ret.fontSize = config.fontSize; ret.fontSize = config.fontSize;
} }
if (config.fontFamily) { if (config.fontFamily) {
ret.fontFamily = config.fontFamily; ret.fontFamily = config.fontFamily;
} }
if (config.uiFontFamily) { if (config.uiFontFamily) {
ret.uiFontFamily = config.uiFontFamily; ret.uiFontFamily = config.uiFontFamily;
} }
if (config.cursorColor) { if (config.cursorColor) {
ret.cursorColor = config.cursorColor; ret.cursorColor = config.cursorColor;
} }
if (allowedCursorShapes.has(config.cursorShape)) { if (allowedCursorShapes.has(config.cursorShape)) {
ret.cursorShape = config.cursorShape; ret.cursorShape = config.cursorShape;
} }
if (allowedCursorBlinkValues.has(config.cursorBlink)) { if (allowedCursorBlinkValues.has(config.cursorBlink)) {
ret.cursorBlink = config.cursorBlink; ret.cursorBlink = config.cursorBlink;
} }
if (config.borderColor) { if (config.borderColor) {
ret.borderColor = config.borderColor; ret.borderColor = config.borderColor;
} }
if (typeof (config.padding) !== 'undefined' && if (typeof config.padding !== 'undefined' && config.padding !== null) {
config.padding !== null) { ret.padding = config.padding;
ret.padding = config.padding; }
}
if (config.foregroundColor) { if (config.foregroundColor) {
ret.foregroundColor = config.foregroundColor; ret.foregroundColor = config.foregroundColor;
} }
if (config.backgroundColor) { if (config.backgroundColor) {
ret.backgroundColor = config.backgroundColor; ret.backgroundColor = config.backgroundColor;
} }
if (config.css) { if (config.css) {
ret.css = config.css; ret.css = config.css;
} }
if (config.termCSS) { if (config.termCSS) {
ret.termCSS = config.termCSS; ret.termCSS = config.termCSS;
} }
if (allowedBells.has(config.bell)) { if (allowedBells.has(config.bell)) {
ret.bell = config.bell; ret.bell = config.bell;
} }
if (config.bellSoundURL) { if (config.bellSoundURL) {
ret.bellSoundURL = config.bellSoundURL || initial.bellSoundURL; ret.bellSoundURL = config.bellSoundURL || initial.bellSoundURL;
} }
if (typeof (config.copyOnSelect) !== 'undefined' && if (typeof config.copyOnSelect !== 'undefined' && config.copyOnSelect !== null) {
config.copyOnSelect !== null) { ret.copyOnSelect = config.copyOnSelect;
ret.copyOnSelect = config.copyOnSelect; }
}
if (config.colors) { if (config.colors) {
if (Array.isArray(config.colors)) { if (Array.isArray(config.colors)) {
const stateColors = Array.isArray(state.colors) ? const stateColors = Array.isArray(state.colors) ? state.colors : values(state.colors);
state.colors :
values(state.colors);
if (stateColors.toString() !== config.colors.toString()) { if (stateColors.toString() !== config.colors.toString()) {
ret.colors = config.colors;
}
} else if (JSON.stringify(state.colors) !== JSON.stringify(config.colors)) {
ret.colors = config.colors; ret.colors = config.colors;
} }
} else if (JSON.stringify(state.colors) !== JSON.stringify(config.colors)) {
ret.colors = config.colors;
} }
}
if (config.modifierKeys) { if (config.modifierKeys) {
ret.modifierKeys = config.modifierKeys; ret.modifierKeys = config.modifierKeys;
} }
if (allowedHamburgerMenuValues.has(config.showHamburgerMenu)) { if (allowedHamburgerMenuValues.has(config.showHamburgerMenu)) {
ret.showHamburgerMenu = config.showHamburgerMenu; ret.showHamburgerMenu = config.showHamburgerMenu;
} }
if (allowedWindowControlsValues.has(config.showWindowControls)) { if (allowedWindowControlsValues.has(config.showWindowControls)) {
ret.showWindowControls = config.showWindowControls; ret.showWindowControls = config.showWindowControls;
} }
if (process.platform === 'win32' && if (process.platform === 'win32' && (config.quickEdit === undefined || config.quickEdit === null)) {
(config.quickEdit === undefined || config.quickEdit === null)) { ret.quickEdit = true;
ret.quickEdit = true; } else if (typeof config.quickEdit !== 'undefined' && config.quickEdit !== null) {
} else if (typeof (config.quickEdit) !== 'undefined' && ret.quickEdit = config.quickEdit;
config.quickEdit !== null) { }
ret.quickEdit = config.quickEdit;
}
return ret; return ret;
})()); })()
);
break; break;
case SESSION_ADD: case SESSION_ADD:
state_ = state.merge({ state_ = state.merge(
activeUid: action.uid, {
openAt: { activeUid: action.uid,
[action.uid]: Date.now() openAt: {
} [action.uid]: Date.now()
}, {deep: true}); }
},
{deep: true}
);
break; break;
case SESSION_RESIZE: case SESSION_RESIZE:
@ -250,15 +251,19 @@ const reducer = (state = initial, action) => {
break; break;
case SESSION_SET_ACTIVE: case SESSION_SET_ACTIVE:
state_ = state.merge({ state_ = state.merge(
activeUid: action.uid, {
activityMarkers: { activeUid: action.uid,
[action.uid]: false activityMarkers: {
} [action.uid]: false
}, {deep: true}); }
},
{deep: true}
);
break; break;
case SESSION_PTY_DATA: // eslint-disable-line no-case-declarations // eslint-disable-next-line no-case-declarations
case SESSION_PTY_DATA:
// ignore activity markers for current tab // ignore activity markers for current tab
if (action.uid === state.activeUid) { if (action.uid === state.activeUid) {
break; break;
@ -277,11 +282,14 @@ const reducer = (state = initial, action) => {
// expect to get data packets from the resize // expect to get data packets from the resize
// of the ptys as a result // of the ptys as a result
if (!state.resizeAt || now - state.resizeAt > 1000) { if (!state.resizeAt || now - state.resizeAt > 1000) {
state_ = state.merge({ state_ = state.merge(
activityMarkers: { {
[action.uid]: true activityMarkers: {
} [action.uid]: true
}, {deep: true}); }
},
{deep: true}
);
} }
break; break;
@ -318,11 +326,14 @@ const reducer = (state = initial, action) => {
break; break;
case NOTIFICATION_DISMISS: case NOTIFICATION_DISMISS:
state_ = state.merge({ state_ = state.merge(
notifications: { {
[action.id]: false notifications: {
} [action.id]: false
}, {deep: true}); }
},
{deep: true}
);
break; break;
case NOTIFICATION_MESSAGE: case NOTIFICATION_MESSAGE:
@ -343,15 +354,17 @@ const reducer = (state = initial, action) => {
// Show a notification if any of the font size values have changed // Show a notification if any of the font size values have changed
if (CONFIG_LOAD !== action.type) { if (CONFIG_LOAD !== action.type) {
if (state_.fontSize !== state.fontSize || if (state_.fontSize !== state.fontSize || state_.fontSizeOverride !== state.fontSizeOverride) {
state_.fontSizeOverride !== state.fontSizeOverride) {
state_ = state_.merge({notifications: {font: true}}, {deep: true}); state_ = state_.merge({notifications: {font: true}}, {deep: true});
} }
} }
if ((typeof (state.cols) !== 'undefined' && state.cols !== null) && if (
(typeof (state.rows) !== 'undefined' && state.rows !== null) && typeof state.cols !== 'undefined' &&
(state.rows !== state_.rows || state.cols !== state_.cols)) { state.cols !== null &&
(typeof state.rows !== 'undefined' && state.rows !== null) &&
(state.rows !== state_.rows || state.cols !== state_.cols)
) {
state_ = state_.merge({notifications: {resize: true}}, {deep: true}); state_ = state_.merge({notifications: {resize: true}}, {deep: true});
} }

View file

@ -1,9 +1,8 @@
import {createSelector} from 'reselect'; import {createSelector} from 'reselect';
const getTermGroups = ({termGroups}) => termGroups.termGroups; const getTermGroups = ({termGroups}) => termGroups.termGroups;
const getRootGroups = createSelector( const getRootGroups = createSelector(getTermGroups, termGroups =>
getTermGroups, Object.keys(termGroups)
termGroups => Object.keys(termGroups)
.map(uid => termGroups[uid]) .map(uid => termGroups[uid])
.filter(({parentUid}) => !parentUid) .filter(({parentUid}) => !parentUid)
); );

View file

@ -13,19 +13,9 @@ export default () => {
}); });
const enhancer = compose( const enhancer = compose(
applyMiddleware( applyMiddleware(thunk, plugins.middleware, thunk, effects, writeMiddleware, logger),
thunk,
plugins.middleware,
thunk,
effects,
writeMiddleware,
logger
),
window.devToolsExtension() window.devToolsExtension()
); );
return createStore( return createStore(rootReducer, enhancer);
rootReducer,
enhancer
);
}; };

View file

@ -6,13 +6,4 @@ import * as plugins from '../utils/plugins';
import writeMiddleware from './write-middleware'; import writeMiddleware from './write-middleware';
export default () => export default () =>
createStore( createStore(rootReducer, applyMiddleware(thunk, plugins.middleware, thunk, effects, writeMiddleware));
rootReducer,
applyMiddleware(
thunk,
plugins.middleware,
thunk,
effects,
writeMiddleware
)
);

View file

@ -15,9 +15,5 @@ export default function isExecutable(fileStat) {
return true; return true;
} }
return Boolean( return Boolean(fileStat.mode & 0o0001 || fileStat.mode & 0o0010 || fileStat.mode & 0o0100);
(fileStat.mode & 0o0001) ||
(fileStat.mode & 0o0010) ||
(fileStat.mode & 0o0100)
);
} }

View file

@ -17,7 +17,7 @@ const _toAscii = {
173: '45', 173: '45',
187: '61', // IE Key codes 187: '61', // IE Key codes
186: '59', // IE Key codes 186: '59', // IE Key codes
189: '45' // IE Key codes 189: '45' // IE Key codes
}; };
const _shiftUps = { const _shiftUps = {
@ -38,7 +38,7 @@ const _shiftUps = {
93: '}', 93: '}',
92: '|', 92: '|',
59: ':', 59: ':',
39: '\'', 39: "'",
44: '<', 44: '<',
46: '>', 46: '>',
47: '?' 47: '?'

View file

@ -1,6 +1,7 @@
/* global Notification */ /* global Notification */
/* eslint no-new:0 */ /* eslint no-new:0 */
export default function notify(title, body) { export default function notify(title, body) {
//eslint-disable-next-line no-console
console.log(`[Notification] ${title}: ${body}`); console.log(`[Notification] ${title}: ${body}`);
new Notification(title, {body}); new Notification(title, {body});
} }

View file

@ -10,9 +10,10 @@ import Component from '../component';
import Notification from '../components/notification'; import Notification from '../components/notification';
import notify from './notify'; import notify from './notify';
const Module = require('module'); // eslint-disable-line import/newline-after-import //eslint-disable-next-line import/newline-after-import
const Module = require('module');
const originalLoad = Module._load; const originalLoad = Module._load;
Module._load = function (path) { Module._load = function _load(path) {
switch (path) { switch (path) {
case 'react': case 'react':
return React; return React;
@ -53,10 +54,10 @@ let termGroupPropsDecorators;
let propsDecorators; let propsDecorators;
let reducersDecorators; let reducersDecorators;
// the fs locations where user plugins are stored
const {path, localPath} = plugins.getBasePaths();
const clearModulesCache = () => { const clearModulesCache = () => {
// the fs locations where user plugins are stored
const {path, localPath} = plugins.getBasePaths();
// trigger unload hooks // trigger unload hooks
modules.forEach(mod => { modules.forEach(mod => {
if (mod.onRendererUnload) { if (mod.onRendererUnload) {
@ -82,12 +83,14 @@ const getPluginVersion = path => {
try { try {
version = window.require(pathModule.resolve(path, 'package.json')).version; version = window.require(pathModule.resolve(path, 'package.json')).version;
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.warn(`No package.json found in ${path}`); console.warn(`No package.json found in ${path}`);
} }
return version; return version;
}; };
const loadModules = () => { const loadModules = () => {
//eslint-disable-next-line no-console
console.log('(re)loading renderer plugins'); console.log('(re)loading renderer plugins');
const paths = plugins.getPaths(); const paths = plugins.getPaths();
@ -120,7 +123,8 @@ const loadModules = () => {
reduceTermGroups: termGroupsReducers reduceTermGroups: termGroupsReducers
}; };
modules = paths.plugins.concat(paths.localPlugins) modules = paths.plugins
.concat(paths.localPlugins)
.map(path => { .map(path => {
let mod; let mod;
const pluginName = getPluginName(path); const pluginName = getPluginName(path);
@ -131,8 +135,12 @@ const loadModules = () => {
try { try {
mod = window.require(path); mod = window.require(path);
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
notify('Plugin load error', `"${pluginName}" failed to load in the renderer process. Check Developer Tools for details.`); notify(
'Plugin load error',
`"${pluginName}" failed to load in the renderer process. Check Developer Tools for details.`
);
return undefined; return undefined;
} }
@ -146,12 +154,14 @@ const loadModules = () => {
// mapHyperTermState mapping for backwards compatibility with hyperterm // mapHyperTermState mapping for backwards compatibility with hyperterm
if (mod.mapHyperTermState) { if (mod.mapHyperTermState) {
mod.mapHyperState = mod.mapHyperTermState; mod.mapHyperState = mod.mapHyperTermState;
//eslint-disable-next-line no-console
console.error('mapHyperTermState is deprecated. Use mapHyperState instead.'); console.error('mapHyperTermState is deprecated. Use mapHyperState instead.');
} }
// mapHyperTermDispatch mapping for backwards compatibility with hyperterm // mapHyperTermDispatch mapping for backwards compatibility with hyperterm
if (mod.mapHyperTermDispatch) { if (mod.mapHyperTermDispatch) {
mod.mapHyperDispatch = mod.mapHyperTermDispatch; mod.mapHyperDispatch = mod.mapHyperTermDispatch;
//eslint-disable-next-line no-console
console.error('mapHyperTermDispatch is deprecated. Use mapHyperDispatch instead.'); console.error('mapHyperTermDispatch is deprecated. Use mapHyperDispatch instead.');
} }
@ -222,7 +232,7 @@ const loadModules = () => {
if (mod.onRendererWindow) { if (mod.onRendererWindow) {
mod.onRendererWindow(window); mod.onRendererWindow(window);
} }
//eslint-disable-next-line no-console
console.log(`Plugin ${pluginName} (${pluginVersion}) loaded.`); console.log(`Plugin ${pluginName} (${pluginVersion}) loaded.`);
return mod; return mod;
@ -231,8 +241,9 @@ const loadModules = () => {
const deprecatedPlugins = plugins.getDeprecatedConfig(); const deprecatedPlugins = plugins.getDeprecatedConfig();
Object.keys(deprecatedPlugins).forEach(name => { Object.keys(deprecatedPlugins).forEach(name => {
const { css } = deprecatedPlugins[name]; const {css} = deprecatedPlugins[name];
if (css) { if (css) {
//eslint-disable-next-line no-console
console.warn(`Warning: "${name}" plugin uses some deprecated CSS classes (${css.join(', ')}).`); console.warn(`Warning: "${name}" plugin uses some deprecated CSS classes (${css.join(', ')}).`);
} }
}); });
@ -263,6 +274,7 @@ function getProps(name, props, ...fnArgs) {
try { try {
ret_ = fn(...fnArgs, props_); ret_ = fn(...fnArgs, props_);
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`${name}\`. Check Developer Tools for details.`); notify('Plugin error', `${fn._pluginName}: Error occurred in \`${name}\`. Check Developer Tools for details.`);
return; return;
@ -309,8 +321,12 @@ export function connect(stateFn, dispatchFn, c, d = {}) {
try { try {
ret_ = fn(state, ret); ret_ = fn(state, ret);
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`map${name}State\`. Check Developer Tools for details.`); notify(
'Plugin error',
`${fn._pluginName}: Error occurred in \`map${name}State\`. Check Developer Tools for details.`
);
return; return;
} }
@ -331,13 +347,20 @@ export function connect(stateFn, dispatchFn, c, d = {}) {
try { try {
ret_ = fn(dispatch, ret); ret_ = fn(dispatch, ret);
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`map${name}Dispatch\`. Check Developer Tools for details.`); notify(
'Plugin error',
`${fn._pluginName}: Error occurred in \`map${name}Dispatch\`. Check Developer Tools for details.`
);
return; return;
} }
if (!ret_ || typeof ret_ !== 'object') { if (!ret_ || typeof ret_ !== 'object') {
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`map${name}Dispatch\` (object expected).`); notify(
'Plugin error',
`${fn._pluginName}: Invalid return value of \`map${name}Dispatch\` (object expected).`
);
return; return;
} }
@ -362,6 +385,7 @@ function decorateReducer(name, fn) {
try { try {
state__ = pluginReducer(state_, action); state__ = pluginReducer(state_, action);
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`${name}\`. Check Developer Tools for details.`); notify('Plugin error', `${fn._pluginName}: Error occurred in \`${name}\`. Check Developer Tools for details.`);
return; return;
@ -393,26 +417,25 @@ export function decorateSessionsReducer(fn) {
// redux middleware generator // redux middleware generator
export const middleware = store => next => action => { export const middleware = store => next => action => {
const nextMiddleware = remaining => action => remaining.length ? const nextMiddleware = remaining => action_ =>
remaining[0](store)(nextMiddleware(remaining.slice(1)))(action) : remaining.length ? remaining[0](store)(nextMiddleware(remaining.slice(1)))(action_) : next(action_);
next(action);
nextMiddleware(middlewares)(action); nextMiddleware(middlewares)(action);
}; };
// expose decorated component instance to the higher-order components // expose decorated component instance to the higher-order components
function exposeDecorated(Component) { function exposeDecorated(Component_) {
return class extends React.Component { return class DecoratedComponent extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.onRef = this.onRef.bind(this); this.onRef = this.onRef.bind(this);
} }
onRef(decorated) { onRef(decorated_) {
if (this.props.onDecorated) { if (this.props.onDecorated) {
this.props.onDecorated(decorated); this.props.onDecorated(decorated_);
} }
} }
render() { render() {
return React.createElement(Component, Object.assign({}, this.props, {ref: this.onRef})); return React.createElement(Component_, Object.assign({}, this.props, {ref: this.onRef}));
} }
}; };
} }
@ -433,13 +456,20 @@ function getDecorated(parent, name) {
class__ = fn(class_, {React, Component, Notification, notify}); class__ = fn(class_, {React, Component, Notification, notify});
class__.displayName = `${fn._pluginName}(${name})`; class__.displayName = `${fn._pluginName}(${name})`;
} catch (err) { } catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack); console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`${method}\`. Check Developer Tools for details`); notify(
'Plugin error',
`${fn._pluginName}: Error occurred in \`${method}\`. Check Developer Tools for details`
);
return; return;
} }
if (!class__ || typeof class__.prototype.render !== 'function') { if (!class__ || typeof class__.prototype.render !== 'function') {
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`${method}\`. No \`render\` method found. Please return a \`React.Component\`.`); notify(
'Plugin error',
`${fn._pluginName}: Invalid return value of \`${method}\`. No \`render\` method found. Please return a \`React.Component\`.`
);
return; return;
} }
@ -456,10 +486,10 @@ function getDecorated(parent, name) {
// for each component, we return a higher-order component // for each component, we return a higher-order component
// that wraps with the higher-order components // that wraps with the higher-order components
// exposed by plugins // exposed by plugins
export function decorate(Component, name) { export function decorate(Component_, name) {
return class extends React.Component { return class DecoratedCompenent extends React.Component {
render() { render() {
const Sub = getDecorated(Component, name); const Sub = getDecorated(Component_, name);
return React.createElement(Sub, this.props); return React.createElement(Sub, this.props);
} }
}; };

View file

@ -1,5 +1,4 @@
export default class Client { export default class Client {
constructor() { constructor() {
const electron = window.require('electron'); const electron = window.require('electron');
const EventEmitter = window.require('events'); const EventEmitter = window.require('events');
@ -56,5 +55,4 @@ export default class Client {
this.removeAllListeners(); this.removeAllListeners();
this.ipc.removeAllListeners(); this.ipc.removeAllListeners();
} }
} }

View file

@ -1,11 +1,11 @@
// Clear selection range of current selected term view // Clear selection range of current selected term view
// Fix event when terminal text is selected and keyboard action is invoked // Fix event when terminal text is selected and keyboard action is invoked
exports.clear = function (terminal) { exports.clear = terminal => {
terminal.document_.getSelection().removeAllRanges(); terminal.document_.getSelection().removeAllRanges();
}; };
// Use selection extend upon dblclick // Use selection extend upon dblclick
exports.extend = function (terminal) { exports.extend = terminal => {
const sel = terminal.document_.getSelection(); const sel = terminal.document_.getSelection();
// Test if focusNode exist and nodeName is #text // Test if focusNode exist and nodeName is #text
@ -19,7 +19,7 @@ exports.extend = function (terminal) {
// Fix a bug in ScrollPort selectAll behavior // Fix a bug in ScrollPort selectAll behavior
// Select all rows in the viewport // Select all rows in the viewport
exports.all = function (terminal) { exports.all = terminal => {
const scrollPort = terminal.scrollPort_; const scrollPort = terminal.scrollPort_;
let firstRow; let firstRow;
let lastRow; let lastRow;

View file

@ -2,7 +2,7 @@ import path from 'path';
import * as regex from './url-regex'; import * as regex from './url-regex';
export default function isUrlCommand(shell, data) { export default function isUrlCommand(shell, data) {
const matcher = regex[path.parse(shell).name]; // eslint-disable-line import/namespace const matcher = regex[path.parse(shell).name];
if (undefined === matcher || !data) { if (undefined === matcher || !data) {
return null; return null;
} }

View file

@ -17,11 +17,13 @@
}, },
"eslintConfig": { "eslintConfig": {
"plugins": [ "plugins": [
"react" "react",
"prettier"
], ],
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:react/recommended" "plugin:react/recommended",
"prettier"
], ],
"parserOptions": { "parserOptions": {
"ecmaVersion": 8, "ecmaVersion": 8,
@ -49,7 +51,21 @@
"react/react-in-jsx-scope": 0, "react/react-in-jsx-scope": 0,
"react/no-unescaped-entities": 0, "react/no-unescaped-entities": 0,
"react/jsx-no-target-blank": 0, "react/jsx-no-target-blank": 0,
"react/no-string-refs": 0 "react/no-string-refs": 0,
"prettier/prettier": [
"error",
{
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"semi": true,
"useTabs": false,
"parser": "babylon",
"jsxBracketSameLine": false
}
]
} }
}, },
"babel": { "babel": {
@ -88,7 +104,7 @@
"appId": "co.zeit.hyper", "appId": "co.zeit.hyper",
"extraResources": "./bin/yarn-standalone.js", "extraResources": "./bin/yarn-standalone.js",
"linux": { "linux": {
"category": "TerminalEmulator", "category": "TerminalEmulator",
"target": [ "target": [
{ {
"target": "deb", "target": "deb",
@ -162,9 +178,12 @@
"electron-devtools-installer": "2.2.0", "electron-devtools-installer": "2.2.0",
"electron-rebuild": "1.6.0", "electron-rebuild": "1.6.0",
"eslint": "3.19.0", "eslint": "3.19.0",
"eslint-config-prettier": "2.4.0",
"eslint-plugin-prettier": "2.2.0",
"eslint-plugin-react": "7.3.0", "eslint-plugin-react": "7.3.0",
"husky": "0.14.3", "husky": "0.14.3",
"node-gyp": "3.6.2", "node-gyp": "3.6.2",
"prettier": "1.6.1",
"redux-logger": "3.0.6", "redux-logger": "3.0.6",
"spectron": "3.7.2", "spectron": "3.7.2",
"style-loader": "0.18.2", "style-loader": "0.18.2",

View file

@ -14,39 +14,15 @@ test(`returns a color that's in hex`, t => {
const hslaColor = 'hsl(15, 100%, 50%, 1)'; const hslaColor = 'hsl(15, 100%, 50%, 1)';
const colorKeyword = 'pink'; const colorKeyword = 'pink';
t.true( t.true(isHexColor(toElectronBackgroundColor(hexColor)));
isHexColor(
toElectronBackgroundColor(hexColor)
)
);
t.true( t.true(isHexColor(toElectronBackgroundColor(rgbColor)));
isHexColor(
toElectronBackgroundColor(rgbColor)
)
);
t.true( t.true(isHexColor(toElectronBackgroundColor(rgbaColor)));
isHexColor(
toElectronBackgroundColor(rgbaColor)
)
);
t.true( t.true(isHexColor(toElectronBackgroundColor(hslColor)));
isHexColor(
toElectronBackgroundColor(hslColor)
)
);
t.true( t.true(isHexColor(toElectronBackgroundColor(hslaColor)));
isHexColor(
toElectronBackgroundColor(hslaColor)
)
);
t.true( t.true(isHexColor(toElectronBackgroundColor(colorKeyword)));
isHexColor(
toElectronBackgroundColor(colorKeyword)
)
);
}); });

View file

@ -36,7 +36,7 @@ module.exports = {
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { // eslint-disable-line quote-props 'process.env': {
NODE_ENV: JSON.stringify(nodeEnv) NODE_ENV: JSON.stringify(nodeEnv)
} }
}), }),

View file

@ -2385,6 +2385,19 @@ escope@^3.6.0:
esrecurse "^4.1.0" esrecurse "^4.1.0"
estraverse "^4.1.1" estraverse "^4.1.1"
eslint-config-prettier@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.4.0.tgz#fb7cf29c0ab2ba61af5164fb1930f9bef3be2872"
dependencies:
get-stdin "^5.0.1"
eslint-plugin-prettier@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.2.0.tgz#f2837ad063903d73c621e7188fb3d41486434088"
dependencies:
fast-diff "^1.1.1"
jest-docblock "^20.0.1"
eslint-plugin-react@7.3.0: eslint-plugin-react@7.3.0:
version "7.3.0" version "7.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.3.0.tgz#ca9368da36f733fbdc05718ae4e91f778f38e344" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.3.0.tgz#ca9368da36f733fbdc05718ae4e91f778f38e344"
@ -2835,6 +2848,10 @@ get-stdin@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
get-stdin@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
get-stream@^3.0.0: get-stream@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@ -3483,6 +3500,10 @@ isstream@~0.1.2:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
jest-docblock@^20.0.1:
version "20.0.3"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712"
js-base64@^2.1.9: js-base64@^2.1.9:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.2.0.tgz#5e8a8d193a908198dd23d1704826d207b0e5a8f6" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.2.0.tgz#5e8a8d193a908198dd23d1704826d207b0e5a8f6"
@ -4739,6 +4760,10 @@ preserve@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.6.1.tgz#850f411a3116226193e32ea5acfc21c0f9a76d7d"
pretty-bytes@^1.0.2: pretty-bytes@^1.0.2:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"