diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9528eb3..46f3501 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -10,8 +10,8 @@ ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse RUN cargo install cargo-audit RUN curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin -# Install zsh -RUN apt-get update && apt-get install zsh -y +# Install zsh and fish +RUN apt-get update && apt-get install zsh fish -y # Create default non-root user ARG USERNAME=rust @@ -35,3 +35,18 @@ RUN echo "\n# IntelliShell debug" >> ~/.bashrc RUN echo "alias intelli-shell=/workspaces/intelli-shell/target/debug/intelli-shell" >> ~/.bashrc RUN echo "source /workspaces/intelli-shell/intelli-shell.sh" >> ~/.bashrc RUN echo "alias ll='ls -alF'" >> ~/.bashrc + +RUN echo "\n# Search up & down" >> ~/.zshrc +RUN echo "bindkey '^[[A' up-line-or-search" >> ~/.zshrc +RUN echo "bindkey '^[[B' down-line-or-search" >> ~/.zshrc +RUN echo "\n# IntelliShell debug" >> ~/.zshrc +RUN echo "alias intelli-shell=/workspaces/intelli-shell/target/debug/intelli-shell" >> ~/.zshrc +RUN echo "source /workspaces/intelli-shell/intelli-shell.sh" >> ~/.zshrc +RUN echo "alias ll='ls -alF'" >> ~/.zshrc + +RUN echo "# IntelliShell debug" >> ~/.config/fish/config.fish +RUN echo "set INTELLI_HOME /workspaces/intelli-shell" >> ~/.config/fish/config.fish +RUN echo "source /workspaces/intelli-shell/intelli-shell.fish" >> ~/.config/fish/config.fish +RUN echo "function intelli-shell --description 'IntelliShell'" >> ~/.config/fish/config.fish +RUN echo '/workspaces/intelli-shell/target/debug/intelli-shell $argv;' >> ~/.config/fish/config.fish +RUN echo "end" >> ~/.config/fish/config.fish diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4027f74..5a8f18c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,6 +14,7 @@ "aaron-bond.better-comments", "oderwat.indent-rainbow", "gruntfuggly.todo-tree", + "skyapps.fish-vscode", "yzhang.markdown-all-in-one", "davidanson.vscode-markdownlint" ], @@ -25,6 +26,9 @@ }, "zsh": { "path": "/bin/zsh" + }, + "fish": { + "path": "/usr/bin/fish" } }, "files.watcherExclude": { diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6491977..90414ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,7 +57,7 @@ jobs: - name: Package shell: bash run: | - tar czvf intelli-shell-${{ matrix.target }}.tar.gz intelli-shell.sh -C target/${{ matrix.target }}/release intelli-shell + tar czvf intelli-shell-${{ matrix.target }}.tar.gz intelli-shell.sh intelli-shell.fish -C target/${{ matrix.target }}/release intelli-shell - name: Release uses: softprops/action-gh-release@v1 diff --git a/README.md b/README.md index f57faf2..655ab9b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ It currently works on Bash and Zsh and should be compatible with most Linux, Win 1. Install the binaries: ```sh - curl -sSf https://raw.githubusercontent.com/lasantosr/intelli-shell/main/install.sh | $SHELL + curl -sSf https://raw.githubusercontent.com/lasantosr/intelli-shell/main/install.sh | bash ``` 2. Bookmark your first command by typing it on a terminal and using `ctrl + b` @@ -42,7 +42,7 @@ Remember to bookmark some commands or fetch them after the installation! To install using prebuilt binaries: ```sh -curl -sSf https://raw.githubusercontent.com/lasantosr/intelli-shell/main/install.sh | $SHELL +curl -sSf https://raw.githubusercontent.com/lasantosr/intelli-shell/main/install.sh | bash ``` ### From source code @@ -90,8 +90,10 @@ You can customize key bindings using environment variables: `INTELLI_SAVE_HOTKEY - [x] Labels support to store most used labels and select them using a dedicated UI - [ ] Usability improvements to manage stored commands (including aliases) +- [ ] Support for more terminals + - [x] [Fish](https://fishshell.com/) +- [ ] Deploy to package managers - [ ] Sync user bookmarks using some public / private Git repo -- [ ] Support for more terminals, like PowerShell ## Alternatives diff --git a/install.sh b/install.sh index aeae705..30722c6 100755 --- a/install.sh +++ b/install.sh @@ -1,3 +1,5 @@ +#!/bin/bash + set -eo pipefail # Retrieve default shell @@ -35,23 +37,55 @@ curl -Lsf https://github.com/lasantosr/intelli-shell/releases/latest/download/in echo "Successfully installed IntelliShell at: $INTELLI_HOME" # Update rc -if [[ "$os" = 'apple-darwin' ]] && [[ "$shell" = 'bash' ]]; -then - rcfile=".bash_profile" -else - rcfile=".${shell}rc" +files=() +function update_rc () { + if [ -f "$1" ]; then + sourced=$(cat $1 | { grep -E '.*intelli-shell.*' || test $? = 1; }) + else + sourced= + fi + if [[ -z "$sourced" ]]; + then + files+=("$1") + echo -e '\n# IntelliShell' >> "$1" + echo "INTELLI_HOME=$INTELLI_HOME" >> "$1" + echo '# export INTELLI_SEARCH_HOTKEY=\C-@' >> "$1" + echo '# export INTELLI_LABEL_HOTKEY=C-l' >> "$1" + echo '# export INTELLI_SAVE_HOTKEY=C-b' >> "$1" + echo '# export INTELLI_SKIP_ESC_BIND=0' >> "$1" + echo 'alias intelli-shell="$INTELLI_HOME/bin/intelli-shell"' >> "$1" + echo 'source $INTELLI_HOME/bin/intelli-shell.sh' >> "$1" + fi +} + +update_rc "$HOME/.bashrc" +if [[ -f "$HOME/.bash_profile" ]]; then + update_rc "$HOME/.bash_profile" fi -sourced=$(cat ~/$rcfile | { grep -E '^source.*intelli-shell\.sh' || test $? = 1; }) -if [[ -z "$sourced" ]]; -then - echo -e '\n# IntelliShell' >> ~/$rcfile - echo "INTELLI_HOME=$INTELLI_HOME" >> ~/$rcfile - echo '# export INTELLI_SEARCH_HOTKEY=\C-@' >> ~/$rcfile - echo '# export INTELLI_LABEL_HOTKEY=C-l' >> ~/$rcfile - echo '# export INTELLI_SAVE_HOTKEY=C-b' >> ~/$rcfile - echo '# export INTELLI_SKIP_ESC_BIND=0' >> ~/$rcfile - echo 'alias intelli-shell="$INTELLI_HOME/bin/intelli-shell"' >> ~/$rcfile - echo 'source $INTELLI_HOME/bin/intelli-shell.sh' >> ~/$rcfile - - echo "Please restart the terminal or re-source ~/$rcfile, where further customizations can be made" +if [[ -f "/bin/zsh" ]]; then + update_rc "$HOME/.zshrc" +fi +if [[ -f "/usr/bin/fish" ]]; then + config="$HOME/.config/fish/config.fish" + if [ -f "$config" ]; then + sourced=$(cat $config | { grep -E '.*intelli-shell.*' || test $? = 1; }) + else + sourced= + fi + if [[ -z "$sourced" ]]; + then + files+=("$config") + echo -e '\n# IntelliShell' >> "$config" + echo "set INTELLI_HOME $INTELLI_HOME" >> "$config" + echo '# set INTELLI_SEARCH_HOTKEY \cr' >> "$config" + echo '# set INTELLI_LABEL_HOTKEY \cl' >> "$config" + echo '# set INTELLI_SAVE_HOTKEY \cb' >> "$config" + echo '# set INTELLI_SKIP_ESC_BIND 0' >> "$config" + echo 'source $INTELLI_HOME/bin/intelli-shell.fish' >> "$config" + # TODO include fish on tar.gz, bindings, test from each terminal + fi fi + +if [ ${#files[@]} -ne 0 ]; then + echo "The following files were updated: ${files[@]}" +fi \ No newline at end of file diff --git a/intelli-shell.fish b/intelli-shell.fish new file mode 100755 index 0000000..748733f --- /dev/null +++ b/intelli-shell.fish @@ -0,0 +1,58 @@ +function intelli-shell --description 'IntelliShell' + $INTELLI_HOME/bin/intelli-shell $argv; +end + +function _intelli_search + set LINE (commandline) + set TMP_FILE (mktemp -t intelli-shell.XXXXXXXX) + # Exec command + intelli-shell --inline --inline-extra-line --file-output="$TMP_FILE" search "$LINE" + set INTELLI_OUTPUT (cat "$TMP_FILE" | string collect) + rm -f $TMP_FILE + # Replace line + commandline -r "$INTELLI_OUTPUT" +end + +function _intelli_save + set LINE (commandline) + set TMP_FILE (mktemp -t intelli-shell.XXXXXXXX) + # Exec command + intelli-shell --inline --inline-extra-line --file-output="$TMP_FILE" save "$LINE" + set INTELLI_OUTPUT (cat "$TMP_FILE" | string collect) + rm -f $TMP_FILE + # Replace line + commandline -r "$INTELLI_OUTPUT" +end + +function _intelli_label + set LINE (commandline) + set TMP_FILE (mktemp -t intelli-shell.XXXXXXXX) + # Exec command + intelli-shell --inline --inline-extra-line --file-output="$TMP_FILE" label "$LINE" + set INTELLI_OUTPUT (cat "$TMP_FILE" | string collect) + rm -f $TMP_FILE + # Replace line + commandline -r "$INTELLI_OUTPUT" +end + +function fish_user_key_bindings + if [ "$INTELLI_SKIP_ESC_BIND" != "1" ] + bind --preset \e 'kill-whole-line' + end + if test -n "$INTELLI_SEARCH_HOTKEY" + bind $INTELLI_SEARCH_HOTKEY '_intelli_search' + else + bind -k nul '_intelli_search' + end + if test -n "$INTELLI_SAVE_HOTKEY" + bind $INTELLI_SAVE_HOTKEY '_intelli_save' + else + bind \cb '_intelli_save' + end + if test -n "$INTELLI_LABEL_HOTKEY" + bind $INTELLI_LABEL_HOTKEY '_intelli_label' + else + bind \cl '_intelli_label' + end +end + diff --git a/src/main.rs b/src/main.rs index 94c742b..e5ca79c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -145,18 +145,18 @@ fn run(cli: Args) -> Result<()> { LabelWidget::new(&mut storage, labeled_command)?, ) .map_output_str(), - None => Ok(WidgetOutput::new("The command contains no labels!", command)), + None => Ok(WidgetOutput::new(" -> The command contains no labels!", command)), }, Actions::Export { file } => { let file_path = file.as_deref().unwrap_or("user_commands.txt"); let exported = storage.export(USER_CATEGORY, file_path)?; Ok(WidgetOutput::message(format!( - "Successfully exported {exported} commands to '{file_path}'" + " -> Successfully exported {exported} commands to '{file_path}'" ))) } Actions::Import { file } => { let new = storage.import(USER_CATEGORY, file)?; - Ok(WidgetOutput::message(format!("Imported {new} new commands"))) + Ok(WidgetOutput::message(format!(" -> Imported {new} new commands"))) } #[cfg(feature = "tldr")] Actions::Fetch { category } => exec( diff --git a/src/storage.rs b/src/storage.rs index f326c31..c948d50 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -510,9 +510,16 @@ impl SqliteStorage { (CASE WHEN flat_label = ?2 THEN 1 ELSE 0 END) DESC "#; - let mut stmt = self - .conn - .prepare(&QUERY.replace("#LABELS#", ¶meters.iter().map(|_| "?").join(",")))?; + let mut stmt = self.conn.prepare( + &QUERY.replace( + "#LABELS#", + ¶meters + .iter() + .enumerate() + .map(|(i, _)| format!("?{}", i + 2)) + .join(","), + ), + )?; parameters.insert(0, flat_root_cmd); diff --git a/src/widgets/fetch.rs b/src/widgets/fetch.rs index c5cd71b..d4a5e31 100644 --- a/src/widgets/fetch.rs +++ b/src/widgets/fetch.rs @@ -30,9 +30,11 @@ impl<'a> Widget for FetchWidget<'a> { let new = self.storage.insert_commands(&mut commands)?; if new == 0 { - Ok(Some(WidgetOutput::message("No new commands to retrieve".to_owned()))) + Ok(Some(WidgetOutput::message( + " -> No new commands to retrieve".to_owned(), + ))) } else { - Ok(Some(WidgetOutput::message(format!("Retrieved {new} new commands")))) + Ok(Some(WidgetOutput::message(format!(" -> Retrieved {new} new commands")))) } } } diff --git a/src/widgets/save.rs b/src/widgets/save.rs index 860a5a4..dda74ff 100644 --- a/src/widgets/save.rs +++ b/src/widgets/save.rs @@ -53,8 +53,8 @@ impl<'s> SaveCommandWidget<'s> { let cmd = command.into(); let mut command = Command::new(USER_CATEGORY, cmd, description); Ok(match storage.insert_command(&mut command)? { - true => WidgetOutput::new("Command was saved successfully", command), - false => WidgetOutput::new("Command already existed, so it was updated", command), + true => WidgetOutput::new(" -> Command was saved successfully", command), + false => WidgetOutput::new(" -> Command already existed, so it was updated", command), }) } } @@ -68,7 +68,7 @@ impl<'s> Widget for SaveCommandWidget<'s> { fn peek(&mut self) -> Result>> { if self.command.is_empty() { - Ok(Some(WidgetOutput::message("A command must be typed first!"))) + Ok(Some(WidgetOutput::message(" -> A command must be typed first!"))) } else { match &self.description { Some(d) => Ok(Some(Self::insert_command(self.storage, &self.command, d)?)), diff --git a/src/widgets/search.rs b/src/widgets/search.rs index 54013ce..e810ac4 100644 --- a/src/widgets/search.rs +++ b/src/widgets/search.rs @@ -51,7 +51,7 @@ impl<'s> Widget for SearchWidget<'s> { fn peek(&mut self) -> Result>> { if self.storage.is_empty()? { let message = indoc::indoc! { r#" - There are no stored commands yet! + -> There are no stored commands yet! - Try to bookmark some command with 'Ctrl + B' - Or execute 'intelli-shell fetch' to download a bunch of tldr's useful commands"# };