UNPKG

hyper-launch-menu

Version:

Adds ability to launch other shells to hyper

288 lines (259 loc) 8.45 kB
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)})); } }