hyper-launch-menu
Version:
Adds ability to launch other shells to hyper
288 lines (259 loc) • 8.45 kB
JavaScript
const { Menu, dialog, clipboard } = require("electron");
const detector = require("./shellDetector");
const fs = require('fs');
// attrs: shells, shellMenu, showInfo, currentShell
module.exports = class {
constructor(cfg) {
// Optional parameters
this.showInfo = cfg.showShellInfo;
this.openOnSelect = cfg.openOnSelect;
this.setOnSelect =
cfg.setOnSelect === undefined
? true
: cfg.setOnSelect;
this.detectShells = cfg.detectShells;
this.showNotifications =
cfg.showShellNotifications === undefined
? true
: cfg.showShellNotifications;
this.keymap = cfg.selectShellKeymap;
let shells;
if (cfg.shells) {
shells = cfg.shells.slice()
} else if (this.detectShells) {
shells = detector.detectShells();
} else {
shells = [];
}
this.defaultShellCfg = this.getShellFromConfig(cfg);
this.currentShell = {}
this.importShells(shells);
this.bindShellToConfig(cfg);
}
importShells(shells) {
this.shells = shells;
this.shellsForExport = JSON.stringify(shells, null, 4);
this.defaultShell = this.getDefaultShell(shells);
if (!this.defaultShell) {
this.defaultShell = this.defaultShellCfg;
shells.unshift(this.defaultShell);
}
this.menu = this.createShellMenu(shells);
this.setShell(this.defaultShell);
}
// Binds our shell and shellArgs attributes with the config ones
bindShellToConfig(cfg) {
Object.defineProperties(cfg, {
shell: { get: () => this.currentShell.shell },
shellArgs: { get: () => this.currentShell.args }
})
}
getShellFromConfig(cfg) {
return {
name: cfg.shellName || 'Default',
shell: cfg.shell,
args: cfg.shellArgs,
env: cfg.env
};
}
/**
* @return The shell in the shells list specified as 'default' (if any).
*/
getDefaultShell(shells) {
for (const shell of shells) {
if (this.isShellGroup(shell)) {
let groupDefault = this.getDefaultShell(shell.group);
if (groupDefault) return groupDefault;
}
else if (shell.default) {
return shell;
}
}
return null;
}
createShellMenu(shells) {
let template = [];
template.push(
this.getShowCurrentItem(),
{ label: 'Select', submenu: this.getMenuTemplate(shells) },
{
label: 'Advanced', submenu: [
this.getImportShells(),
this.getExportShells()
]
}
);
return Menu.buildFromTemplate(template);
}
getShowCurrentItem() {
return {
label: "Show current",
click: (item, window, event) => {
window.rpc.emit('notify', {
title: "The current shell is...",
body: this.getCurrentShellInfo()
})
}
}
}
getImportShells() {
return {
label: "Import",
click: (item, window, event) => {
this.showAdvancedDialog(window, 'import', response => {
switch (response) {
case 0:
dialog.showOpenDialog(window, {
filters: [{ name: 'json', extensions: ['json'] }],
properties: ['openFile']
}, filePaths => {
if (filePaths) {
try {
let shellsToImport = JSON.parse(fs.readFileSync(filePaths[0]));
this.importShells(shellsToImport);
window.rpc.emit('notify', {
title: "Shells imported from file!",
body: "Check the select menu to see the changes"
})
} catch (error) {
window.rpc.emit('notify', {
title: "An error has ocurred. Import aborted.",
body: "Check the syntax of your file.",
details: { error: error }
})
}
}
})
break;
case 1:
try {
let shellsToImport = JSON.parse(clipboard.readText());
this.importShells(shellsToImport);
window.rpc.emit('notify', {
title: "Shells imported from clipboard!",
body: "Check the select menu to see the changes"
})
} catch (error) {
window.rpc.emit('notify', {
title: "An error has ocurred. Import aborted.",
body: "Ensure that your clipboard has the right content.",
details: { error: error }
})
}
break;
default:
return;
}
})
}
}
}
getExportShells() {
return {
label: "Export",
click: (item, window, event) => {
this.showAdvancedDialog(window, 'export', response => {
switch (response) {
case 0:
dialog.showSaveDialog(window, {
filters: [{ name: 'json', extensions: ['json'] }],
}, filePath => {
if (filePath) {
fs.writeFileSync(filePath, this.shellsForExport)
}
})
break;
case 1:
clipboard.writeText(this.shellsForExport);
window.rpc.emit('notify', {
title: "Shells added to clipboard!"
})
break;
default:
return;
}
})
}
}
}
showAdvancedDialog(window, mode, callback) {
let capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1)
dialog.showMessageBox(window, {
title: `${capitalizedMode} shells`,
message: `Select an option to ${mode} ${mode === 'import' ? 'from' : 'to'}:`,
buttons: ["File", "Clipboard", "Cancel"],
cancelId: 2
}, callback)
}
getMenuTemplate(shells) {
let template = [];
shells.forEach(shell => template.push(this.getShellTemplate(shell)));
return template;
}
getShellTemplate(shell) {
if (this.isShellGroup(shell)) {
return this.getShellGroupTemplate(shell);
} else {
return this.getShellItemTemplate(shell);
}
}
// If the 'shell' actually has a group of shells
isShellGroup(shell) {
return 'group' in shell;
}
getShellGroupTemplate(shellGroup) {
shellGroup.group.forEach(shell =>
shell.fullName = shellGroup.name + ' : ' + (shell.fullName || shell.name));
return {
label: shellGroup.name,
submenu: this.getMenuTemplate(shellGroup.group)
};
}
getShellItemTemplate(shell) {
// If it hasnt a full name defined (it doesnt have a group),
// just make it its regular name
if (!shell.fullName) shell.fullName = shell.name;
// Default values for args and env
if (!shell.args) shell.args = []
if (!shell.env) shell.env = {}
return {
label: shell.name || shell.shell,
sublabel: this.showInfo ? this.getShellCmd(shell) : undefined,
accelerator:
this.keymap && shell.shortcut
? this.keymap + '+' + shell.shortcut
: undefined,
click: (item, window, event) => {
this.selectShell(shell)
if (window) {
if (this.showNotifications) {
window.rpc.emit('notify', {
title: "Shell selected!",
body: "Open a new tab or window to start using it"
})
}
if (this.openOnSelect) {
window.rpc.emit('termgroup add req'); // Open new tab
}
}
}
};
}
getShellCmd(shell) {
return shell.shell + ' ' + shell.args
}
getCurrentShellInfo() {
let info = this.currentShell.fullName
if (this.showInfo) info += ' ( ' + this.getShellCmd(this.currentShell) + ' )'
return info;
}
setShell(shell) {
Object.assign(this.currentShell, shell);
}
selectShell(shell) {
if (this.setOnSelect || (this.currentShell && this.currentShell.revertTo)) {
return this.setShell(shell);
}
return this.setShell(Object.assign(shell, {revertTo: Object.assign({}, this.currentShell)}));
}
}