From 1987da5fdb61019e7ec0111b3b41203ade3a6aa9 Mon Sep 17 00:00:00 2001 From: Chapman Pendery Date: Sun, 8 Oct 2023 00:37:44 -0700 Subject: [PATCH] feat: implement bind command Signed-off-by: Chapman Pendery --- package.json | 1 + src/commands/bind.ts | 18 +++---- src/ui/ui-bind.tsx | 77 ++++++++++++++++++++++++++++++ src/utils/bindings.ts | 108 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 src/ui/ui-bind.tsx create mode 100644 src/utils/bindings.ts diff --git a/package.json b/package.json index af619d2..743aadc 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "files": [ "build/**", + "shell/**", "*.md", "LICENSE" ], diff --git a/src/commands/bind.ts b/src/commands/bind.ts index f9ad20b..9fff237 100644 --- a/src/commands/bind.ts +++ b/src/commands/bind.ts @@ -1,17 +1,13 @@ -import {Command} from "commander"; +import { Command } from "commander"; +import { supportedShells } from "../utils/bindings.js"; +import { render } from "../ui/ui-bind.js"; -const supportedShells = ["bash", "powershell", "windows-powershell"] - -const action = (shell: string) => { - if (!supportedShells.includes(shell)) { - console.error(`Unsupported shell: ${shell}`); - process.exit(1); - } - console.log(`Adding keybindings to ${shell} shell`); +const action = async () => { + await render(); }; const cmd = new Command("bind"); -cmd.description(`adds keybindings to the selected shell: ${supportedShells}`); +cmd.description(`adds keybindings to the selected shell: ${supportedShells.join(", ")}`); cmd.action(action); -export default cmd \ No newline at end of file +export default cmd; diff --git a/src/ui/ui-bind.tsx b/src/ui/ui-bind.tsx new file mode 100644 index 0000000..4cff19c --- /dev/null +++ b/src/ui/ui-bind.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from "react"; +import { Box, Text, render as inkRender, useInput, useApp } from "ink"; +import chalk from "chalk"; + +import { availableBindings, bind, supportedShells, Shell } from "../utils/bindings.js"; + +let uiResult = ""; + +function UI() { + const { exit } = useApp(); + const [selectionIdx, setSelectionIdx] = useState(0); + const [availableShells, setAvailableShells] = useState([]); + + useEffect(() => { + availableBindings().then((bindings) => { + if (bindings.length == 0) { + exit(); + } + setAvailableShells(bindings); + }); + }, []); + + useInput(async (_, key) => { + if (key.upArrow) { + setSelectionIdx(Math.max(0, selectionIdx - 1)); + } else if (key.downArrow) { + setSelectionIdx(Math.min(availableShells.length - 1, selectionIdx + 1)); + } else if (key.return) { + await bind(availableShells[selectionIdx]); + uiResult = availableShells[selectionIdx]; + exit(); + } + }); + + return ( + + + Select your desired shell for keybinding creation + + + {availableShells.map((shell, idx) => { + if (idx == selectionIdx) { + return ( + + {">"} {shell} + + ); + } + return ( + + {" "} + {shell} + + ); + })} + {supportedShells + .filter((s) => !availableShells.includes(s)) + .map((shell, idx) => ( + + {" "} + {shell} (already bound) + + ))} + + + ); +} + +export const render = async () => { + const { waitUntilExit } = inkRender(); + await waitUntilExit(); + if (uiResult.length !== 0) { + process.stdout.write("\n" + chalk.green("✓") + " successfully created new bindings \n"); + } else { + process.stdout.write("\n"); + } +}; diff --git a/src/utils/bindings.ts b/src/utils/bindings.ts new file mode 100644 index 0000000..cc941f9 --- /dev/null +++ b/src/utils/bindings.ts @@ -0,0 +1,108 @@ +import os from "node:os"; +import path from "node:path"; +import fsAsync from "node:fs/promises"; +import fs from "node:fs"; +import process from "node:process"; +import url from "node:url"; + +const __filename = url.fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export enum Shell { + Bash = "bash", + Powershell = "powershell", + Pwsh = "pwsh", +} + +export const supportedShells = [Shell.Bash, Shell.Powershell, Shell.Pwsh]; + +const bashScriptCommand = (): string => { + return `[ -f ~/.sa/key-bindings.bash ] && source ~/.sa/key-bindings.bash`; +}; + +const powershellScriptCommand = (): string => { + const bindingsPath = path.join(os.homedir(), ".sa", "key-bindings-powershell.ps1"); + return `if(Test-Path '${bindingsPath}' -PathType Leaf){. ${bindingsPath}}`; +}; + +const pwshScriptCommand = (): string => { + const bindingsPath = path.join(os.homedir(), ".sa", "key-bindings-pwsh.ps1"); + return `if(Test-Path '${bindingsPath}' -PathType Leaf){. ${bindingsPath}}`; +}; + +const pwshConfigPath = (): string => { + switch (process.platform) { + case "win32": + return path.join(os.homedir(), "Documents", "Powershell", "Microsoft.PowerShell_profile.ps1"); + case "linux": + case "darwin": + return path.join(os.homedir(), ".config", "powershell", "Microsoft.PowerShell_profile.ps1"); + default: + throw new Error("Unsupported platform"); + } +}; + +export const availableBindings = async (): Promise => { + const saConfigPath = path.join(os.homedir(), ".sa"); + if (!fs.existsSync(saConfigPath)) { + await fsAsync.mkdir(saConfigPath); + } + + const bindings = []; + const bashConfigPath = path.join(os.homedir(), ".bashrc"); + if (!fs.existsSync(bashConfigPath)) { + bindings.push(Shell.Bash); + } else { + const bashConfigContent = fsAsync.readFile(bashConfigPath, { encoding: "utf-8" }); + if (!(await bashConfigContent).includes(bashScriptCommand())) { + bindings.push(Shell.Bash); + } + } + + const powershellConfigPath = path.join(os.homedir(), "Documents", "WindowsPowershell", "Microsoft.PowerShell_profile.ps1"); + if (!fs.existsSync(powershellConfigPath)) { + bindings.push(Shell.Powershell); + } else { + const powershellConfigContent = fsAsync.readFile(powershellConfigPath, { encoding: "utf-8" }); + if (!(await powershellConfigContent).includes(powershellScriptCommand())) { + bindings.push(Shell.Powershell); + } + } + + if (!fs.existsSync(pwshConfigPath())) { + bindings.push(Shell.Pwsh); + } else { + const pwshConfigContent = fsAsync.readFile(pwshConfigPath(), { encoding: "utf-8" }); + if (!(await pwshConfigContent).includes(pwshScriptCommand())) { + bindings.push(Shell.Pwsh); + } + } + + return bindings; +}; + +export const bind = async (shell: Shell): Promise => { + const saConfigPath = path.join(os.homedir(), ".sa"); + if (!fs.existsSync(saConfigPath)) { + await fsAsync.mkdir(saConfigPath); + } + switch (shell) { + case Shell.Bash: + const bashConfigPath = path.join(os.homedir(), ".bashrc"); + await fsAsync.appendFile(bashConfigPath, `\n${bashScriptCommand()}`); + await fsAsync.copyFile(path.join(__dirname, "..", "..", "shell", "key-bindings.bash"), path.join(os.homedir(), ".sa", "key-bindings.bash")); + break; + case Shell.Powershell: + const powershellConfigPath = path.join(os.homedir(), "Documents", "WindowsPowershell", "Microsoft.PowerShell_profile.ps1"); + await fsAsync.appendFile(powershellConfigPath, `\n${powershellScriptCommand()}`); + await fsAsync.copyFile( + path.join(__dirname, "..", "..", "shell", "key-bindings-powershell.ps1"), + path.join(os.homedir(), ".sa", "key-bindings-powershell.ps1") + ); + break; + case Shell.Pwsh: + await fsAsync.appendFile(pwshConfigPath(), `\n${pwshScriptCommand()}`); + await fsAsync.copyFile(path.join(__dirname, "..", "..", "shell", "key-bindings-pwsh.ps1"), path.join(os.homedir(), ".sa", "key-bindings-pwsh.ps1")); + break; + } +};