{"id":50623407,"url":"https://github.com/nkh/zcooter","last_synced_at":"2026-06-06T14:30:35.591Z","repository":{"id":355803034,"uuid":"1229649904","full_name":"nkh/zcooter","owner":"nkh","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-12T21:57:48.000Z","size":52094,"stargazers_count":1,"open_issues_count":9,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T23:32:01.658Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nkh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-05T08:54:49.000Z","updated_at":"2026-05-10T10:29:21.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nkh/zcooter","commit_stats":null,"previous_names":["nkh/zcooter"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nkh/zcooter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkh%2Fzcooter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkh%2Fzcooter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkh%2Fzcooter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkh%2Fzcooter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nkh","download_url":"https://codeload.github.com/nkh/zcooter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkh%2Fzcooter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33986901,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-06T14:30:34.911Z","updated_at":"2026-06-06T14:30:35.572Z","avatar_url":"https://github.com/nkh.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zcooter is a lightweight, interactive find-and-replace terminal UI app. \n\nA terminal-native, interactive find-and-replace tool. Search through files with regex or fixed strings, preview every match with syntax highlighting, cherry-pick which occurrences to replace, and get back to coding.\n\nNo web browser. No mouse required. Just you, your keyboard, and the files.\n\nZcooter  **is** scooter (https://github.com/thomasschafer/scooter) and all the credit for functionality goes to that project.\n\nzcooter is a fork of scooter with a different UI and some additional features I added with the AI assitance ( I do read rust code, but I am not a rust developer, so the AI help was very much needed to navigate the codebase and add features without breaking much).\n\nThe Zcooter/ directory contains the documentation for zcooter, especially zcooter/changelog.md.\n\n## Why zcooter instead of sed, ripgrep, or your editor's built-in replace?\n\n`sed -i 's/foo/bar/g'` doesn't show you what it's about to change. Your editor's replace dialog buries the matches in a side panel. Zcooter puts every match front and center — file, line number, surrounding context, syntax-highlighted preview — and lets you accept or reject each one before anything touches disk.\n\nIt's the workflow you already know from `:s/old/new/gc` in vim, but working across your entire project at once, with a live preview pane and zero risk of accidentally clobbering the wrong line.\n\n## Built for people who live in the terminal\n\nZcooter is a single binary with no runtime dependencies. It runs in your terminal, respects your `.gitignore`, and uses the same file walker as ripgrep under the hood. Pipe stdin into it, point it at a directory, or launch it from your editor — it adapts to however you work.\n\nThe interface is minimal by design. One screen holds your search fields, match list, and preview. No tab-switching, no modal dialogs, no clutter. Everything you need is visible at once, and every keybinding is configurable — but the defaults are chosen to feel right out of the box.\n\n## Minimalistic interface\n\n![zcooter preview1](zcooter/media/screenshot_1.png)\n\n## Vim\n\n### Bindings\n\nIf you're a vim or neovim user, zcooter speaks your language. Drop in the included vim config and you get:\n\n- **`:q`** to quit, **`:r`** to reset, **`:h`** for help — colon commands work exactly as you expect\n- **`j`/`k`** to navigate results, **`J`/`K`** to jump between files, **`g`/`G`** for top/bottom\n- **`v`** for visual multiselect, **`o`** to flip direction — just like vim's visual mode\n- **`z`-leader** for all toggles: `zl` (line wrap), `zh` (hidden files), `zm` (multiline), `zc` (fixed strings), `zw` (whole word), `zs` (case)\n- **`/`** to jump to search, **`%`** to jump to replace, **`i`** for insert mode, **`backspace`** to jump back and delete — the keys you already know\n\nThe config is a single TOML file. Every single keybinding is remappable. If you prefer helix-style `C-s`/`C-r` for field focus, or want to swap `space` for `enter` on toggles, just change the config. The help screen always reflects your actual bindings, not some hard-coded reference card.\n\n### Integration\n\nSee the zcooter/vim-bindings.md file for details on how to integrate zcooter with Vim or Neovim; make sure you read the instructions on auto-reloading files, as that is important to ensure you don't lose work when switching back to Vim after running scooter.\n\n![zcooter preview2](zcooter/media/screenshot_2.png)\n\n#### example of zcooter opened from vim to replace text in the current buffer\n\n- press \u003cleader\u003eSF, that will modify the current file only\n- type the text you want to find in the \"Search text\" field, and the replacement text in the \"Replace text\" field\n- press arrow down to move to the results, and press space to select which instances you want to replace (or toggle all with ctrl+w)\n- press enter to perform the replacement\n- you can perform multiple replacements without restarting scooter\n- press control-c to exit scooter and return to vim\n- vim will detect the changes and warn you\n\n## Build/Install\n\n**Build:**\n\n```\ncargo build --release\n```\n\nThe resulting **scooter** binary will be located at `target/release/scooter`.  The binary is still called *scooter* because it **is** scooter just a different UI/UX.\n\n**Install:**\n\n```\ncargo install --path scooter\n```\n\n---\n\n## Scooter documentation\n\nBelow is the README of scooter, you may like that interface more.\n\n# scooter\n\nscooter is an interactive find-and-replace terminal UI app. By default it recursively searches through files in the current directory, but can also be used to process text from stdin.\n\nSearch with either a fixed string or a regular expression, enter a replacement, and interactively toggle which instances you want to replace. If the instance you're attempting to replace has changed since the search was performed, e.g. if you've switched branches and that line no longer exists, that particular replacement won't occur: you'll see all such cases at the end.\n\nBy default scooter searches line-by-line, but you can enable multiline searching by either launching scooter with the `-U` flag, or by hitting `alt-m` when already running.\n\nBy default, escape sequences such as `\\n` will be entered literally. If you want them to be interpreted, i.e. for `\\n` to enter a newline instead of the characters `\\` and `n`, then use `-e` (or `--interpret-escape-sequences`). This applies to `\\n`, `\\t`, and `\\\\` in replacement text.\n\n![scooter preview](media/preview.gif)\n\nYou can use custom themes for syntax highlighting (see [here](#syntax_highlighting_theme) for more info):\n\n![scooter with Catppuccin Macchiato theme](media/preview_catppuccin_macchiato.png)\n\n## Contents\n\n\u003c!-- TOC START --\u003e\n- [Features](#features)\n- [Usage](#usage)\n- [Performance](#performance)\n- [Installation](#installation)\n- [Configuration options](#configuration-options)\n- [Editor configuration](#editor-configuration)\n- [Contributing](#contributing)\n\u003c!-- TOC END --\u003e\n\n\n## Features\n\nA set of keymappings will be shown at the bottom of the window: these vary slightly depending on the screen you're on.\n\nWhen searching through files, scooter respects both `.gitignore` and `.ignore` files.\n\nYou can add capture groups to the search regex and use them in the replacement string: for instance, if you use `(\\d) - (\\w+)` for the search text and `($2) \"$1\"` as the replacement, then `9 - foo` would be replaced with `(foo) \"9\"`.\n\nWhen viewing search results, you can open the selected file at the relevant line by pressing `e`. This will use the editor defined by your `EDITOR` environment variable. scooter will automatically attempt to open the editor at the correct line number, but if you'd like to override the command used then you can set `editor_open` in your [config file](#configuration-options).\n\nBy default, scooter uses a regex engine that supports only a subset of features to maximise performance. To use the full range of regex features, such as negative lookahead, start scooter with the `-a` (`--advanced-regex`) flag.\n\nHidden files (such as those starting with a `.`) are ignored by default, but can be included by using the `--hidden` flag.\n\n\n## Usage\n\n### Files\n\nRun\n\n```sh\nscooter\n```\n\nin a terminal to launch scooter. By default the current directory is used to search and replace in, but you can pass in a directory as the first argument to override this behaviour:\n\n```sh\nscooter ../foo/bar\n```\n\n### Stdin\n\nscooter can operate on content piped from stdin. For instance:\n\n```sh\necho \"hello world\" | scooter\n```\n\nIn standard TUI mode the results are written to stderr, to avoid clashing with the TUI which writes to stdout. Given this, you can write the results using `2\u003e`, e.g.:\n\n```sh\ncat input.txt | scooter 2\u003e output.txt\n```\n\nIn `--no-tui` mode (`-N`), the results are instead written to stdout. For example:\n\n```sh\ncat input.txt | scooter -N -s before -r after \u003e output.txt\n```\n\n### Search fields\n\nWhen on the search screen the following fields are available:\n\n- **Search text**: Text to search with. Defaults to regex, unless \"Fixed strings\" is enabled, in which case this reverts to case-sensitive string search.\n- **Replace text**: Text to replace the search text with. If searching with regex, this can include capture groups.\n- **Fixed strings**: If enabled, search with plain strings. If disabled, search with regex.\n- **Match whole word**: If enabled, only match when the search string forms an entire word and not a substring in a larger word. For instance, if the search string is \"foo\", \"foo bar\" would be matched but not \"foobar\".\n- **Match case**: If enabled, match the case of the search string exactly, e.g. a search string of `Bar` would match `foo Bar baz` but not `foo bar baz`.\n- **Files to include**: Glob patterns, separated by commas (`,`), that file paths must match. For instance, `*.rs, *.py` matches all files with the `.rs` or `.py` extensions.\n- **Files to exclude**: Glob patterns, separated by commas (`,`), that file paths must not match. For instance, `env/**` ignores all files in the `env` directory. This field takes precedence over the pattern in the \"Files to include\" field.\n\nNote that the glob matching library used in scooter comes from the brilliant [ripgrep](https://github.com/BurntSushi/ripgrep), and matches the behaviour there: for instance, if you wanted to include only files in the directory `dir1`, you'd need to add `dir1/**` in the \"Files to include\" field - `dir1` alone would not work.\n\n#### Pre-populating search fields\n\nYou can pre-populate the search fields using command-line flags, for instance:\n\n```sh\nscooter \\\n  --search-text \"old\" \\\n  --replace-text \"new\" \\\n  --fixed-strings \\\n  --files-to-include \"*.rs,*.py\"\n```\n\nNote that, by default, pre-populated fields are disabled in the UI. To make these fields editable by default, you can set `search.disable_prepopulated_fields` to `false` in your config - see [here](#disable_prepopulated_fields).\nYou can also temporarily unlock the pre-populated fields with `\u003calt\u003e+u`.\n\nWhen pre-populating the fields in this way, you can skip the initial search screen entirely and jump straight to searching with the `--immediate-search` flag, e.g.:\n\n```sh\nscooter --search-text \"old\" --replace-text \"new\" --immediate-search\n```\n\nRun `scooter --help` to see the full list of command-line args that can be used to pre-populate fields.\n\n\n## Performance\n\nscooter is fast. Below is a benchmark for comparison, performing a find and replace on the entire [Linux kernel](https://github.com/torvalds/linux), finding and replacing the string \"before\" with \"after\":\n\n\u003c!-- BENCHMARK START --\u003e\n| Command | Mean [s] | Min [s] | Max [s] | Relative |\n|:---|---:|---:|---:|---:|\n| `scooter` | 6.403 ± 0.093 | 6.287 | 6.543 | 1.46 ± 0.03 |\n| `scooter (--no-tui)` | 4.491 ± 0.066 | 4.424 | 4.572 | 1.02 ± 0.02 |\n| `ripgrep + sd` | 4.894 ± 0.061 | 4.816 | 4.964 | 1.11 ± 0.03 |\n| `fastmod` | 4.395 ± 0.084 | 4.322 | 4.533 | 1.00 |\n| `fd + sd` | 10.010 ± 0.000 | 10.010 | 10.011 | 2.28 ± 0.04 |\n\n\u003c!-- BENCHMARK END --\u003e\n\nMuch of the speed is thanks to using the [ripgrep](https://github.com/BurntSushi/ripgrep) file walker, found in the [ignore](https://github.com/BurntSushi/ripgrep/tree/master/crates/ignore) crate.\n\nRun `scooter --help` for a full list of flags.\n\n\n## Installation\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/scooter.svg)](https://repology.org/project/scooter/versions)\n\n### Homebrew\n\nOn macOS and Linux, you can install scooter using [Homebrew](https://formulae.brew.sh/formula/scooter):\n\n```sh\nbrew install scooter\n```\n\n### Nix\n\nscooter is available as `scooter` in [nixpkgs](https://search.nixos.org/packages?show=scooter).\n\n### AUR\n\nInstall from the Arch User Repository with:\n\n```\nyay -S scooter\n```\n\nOr, to build from the latest commit:\n\n```\nyay -S scooter-git\n```\n\n### Winget\n\nInstall from Winget with\n\n```sh\nwinget install thomasschafer.scooter\n```\n\n### Scoop\n\nOn Windows, you can install scooter using [Scoop](https://scoop.sh/#/apps?q=scooter\u0026p=1\u0026id=e0378d34e35574873c593119f965372f17d74c9f):\n\n```sh\nscoop install scooter\n```\n\n### NetBSD\n\nInstall from the official repositories with:\n\n```sh\npkgin install scooter\n```\n\n### Prebuilt binaries\n\nDownload the appropriate binary for your system from the [releases page](https://github.com/thomasschafer/scooter/releases/latest):\n\n| Platform | Architecture | Download file |\n|-|-|-|\n| Linux | Intel/AMD | `*-x86_64-unknown-linux-musl.tar.gz` |\n| Linux | ARM64 | `*-aarch64-unknown-linux-musl.tar.gz` |\n| macOS | Apple Silicon| `*-aarch64-apple-darwin.tar.gz` |\n| macOS | Intel | `*-x86_64-apple-darwin.tar.gz` |\n| Windows | x64 | `*-x86_64-pc-windows-msvc.zip` |\n\nAfter downloading, extract the binary and move it to a directory in your `PATH`.\n\n### Cargo\n\nEnsure you have cargo installed (see [here](https://doc.rust-lang.org/cargo/getting-started/installation.html)), then run:\n\n```sh\ncargo install scooter\n```\n\n### Building from source\n\nEnsure you have cargo installed (see [here](https://doc.rust-lang.org/cargo/getting-started/installation.html)), then run the following commands:\n\n```sh\ngit clone git@github.com:thomasschafer/scooter.git\ncd scooter\ncargo install --path scooter --locked\n```\n\n\n## Configuration options\n\nBy default, scooter looks for a TOML configuration file at:\n\n- Linux or macOS: `~/.config/scooter/config.toml`\n- Windows: `%AppData%\\scooter\\config.toml`\n\nYou can override the config directory by using the `--config-dir` flag, or point to a specific config file with `--config \u003cfile\u003e`.\n\nThe following options can be set in your configuration file:\n\n\u003c!-- CONFIG START --\u003e\n### `[editor_open]` section\n\n#### `command`\n\nThe command used when pressing `e` on the search results page. Two variables are available: `%file`, which will be replaced\nwith the file path of the search result, and `%line`, which will be replaced with the line number of the result. For example:\n```toml\n[editor_open]\ncommand = \"vi %file +%line\"\n```\nIf not set explicitly, scooter will attempt to use the editor set by the `$EDITOR` environment variable.\n\nThis can be overridden using the `--editor-command` flag, for example: `scooter --editor-command \"vi %file +%line\"`.\n\n#### `exit`\n\nWhether to exit scooter after running the command defined by `editor_open.command`. Defaults to `false`.\n\n### `[preview]` section\n\n#### `syntax_highlighting`\n\nWhether to apply syntax highlighting to the preview. Defaults to `true`.\n\n#### `syntax_highlighting_theme`\n\nThe theme to use when syntax highlighting is enabled.\n\nThe default is `\"base16-eighties.dark\"`. Other built-in options are\n`\"base16-mocha.dark\"`, `\"base16-ocean.dark\"`, `\"base16-ocean.light\"`, `\"InspiredGitHub\"`, `\"Solarized (dark)\"` and `\"Solarized (light)\"`.\n\nYou can use other themes by adding `.tmTheme` files to `\u003cscooter-config-dir\u003e/themes` and then specifying their name here.\nBy default, `\u003cscooter-config-dir\u003e` is `~/.config/scooter/` on Linux or macOS, or `%AppData%\\scooter\\` on Windows, and can be overridden with the `--config-dir` flag.\n\nFor instance, to use Catppuccin Macchiato (from [here](https://github.com/catppuccin/bat)), on Linux or macOS run:\n```sh\nwget -P ~/.config/scooter/themes https://github.com/catppuccin/bat/raw/main/themes/Catppuccin%20Macchiato.tmTheme\n```\nand then set `syntax_highlighting_theme = \"Catppuccin Macchiato\"`.\n\n#### `wrap_text`\n\nWrap text onto the next line if it is wider than the preview window. Defaults to `false`. (Can be toggled in the UI using `ctrl+l`.)\n\n#### `file_column_percentage`\n\nWidth of the file name column as a percentage of the results area (10–80).\nThe preview takes the remaining space. Defaults to `25` (i.e. preview is 75%).\nAdjustable at runtime with Ctrl+Left / Ctrl+Right.\n\n#### `file_list_height`\n\nNumber of lines shown in the file list when the terminal is too narrow\nfor a side-by-side layout (width \u003c= 110). Defaults to `5`.\n\n### `[style]` section\n\n#### `true_color`\n\nForce enable or disable true color. `true` forces true color (supported by most modern terminals but not e.g. Apple Terminal), while `false` forces 256 colors (supported by almost all terminals including Apple Terminal).\nIf omitted, scooter will attempt to determine whether the terminal being used supports true color.\n\n### `[search]` section\n\n#### `disable_prepopulated_fields`\n\nWhether to disable fields set by CLI flags. Set to `false` to allow editing of these pre-populated fields. Defaults to `true`.\n\n#### `interpret_escape_sequences`\n\nWhether to interpret escape sequences in replacement text. When enabled, `\\n` becomes a newline,\n`\\t` becomes a tab, and `\\\\` becomes a literal backslash. Defaults to `false`.\n\n#### `focus_fields`\n\nOrdered list of field names that receive focus during Tab navigation.\nFields not in this list are skipped entirely.\nAccepted names: \"search\", \"replace\", \"fixed\", \"word\", \"case\", \"include\", \"exclude\".\nWhen omitted (None), all fields are focusable in their default order.\n\nExample — skip include/exclude filters:\n```toml\n[search]\nfocus_fields = [\"search\", \"replace\", \"fixed\", \"word\", \"case\"]\n```\n\nExample — custom tab order:\n```toml\n[search]\nfocus_fields = [\"search\", \"replace\", \"include\", \"exclude\"]\n```\n\n#### `file_finder_command`\n\nExternal command to use for file selection instead of the built-in file finder.\nThe command is executed via `sh -c` with the search directory as the working directory.\nEach line of stdout is treated as a file/directory path to include or exclude.\nSet to `null` (or omit) to use the built-in file finder.\n\nExample — use fzf:\n```toml\n[search]\nfile_finder_command = \"fzf --multi\"\n```\n\n\u003c!-- CONFIG END --\u003e\n\n### `[keys]` section\n\nThis section can be used to override the default keymappings. These defaults are shown in the snippet below, but note that all keys are optional, so you only need to set those you wish to override.\n\n\u003c!-- KEYS START --\u003e\n```toml\n# Commands available on all screens\n[keys.general]\nquit = \"C-c\"            # Exit scooter\nreset = \"C-r\"           # Cancel in-progress operations, reset fields to default values and return to search screen\nshow_help_menu = \"C-h\"  # Show the help menu containing keymaps\n\n# Commands available on the search screen\n[keys.search]\ntoggle_preview_wrapping = \"C-l\"            # Toggle wrapping of lines that don't fit within the width of the preview\ntoggle_hidden_files = \"C-t\"                # Toggle inclusion of hidden files and directories, such as those whose name starts with a dot (.)\ntoggle_multiline = \"A-m\"                   # Toggle multiline search mode, which allows patterns to match across line boundaries\ntoggle_interpret_escape_sequences = \"A-e\"  # Toggle interpretation of escape sequences in replacement text (\\n becomes newline, \\t becomes tab, \\\\ becomes backslash)\n\n# Commands available on the search screen, when the search fields are focussed\n[keys.search.fields]\nunlock_prepopulated_fields = \"A-u\"  # Allow editing of fields that were populated using CLI args, such as `--search_text foo`. (Note that you can use the `disable_prepopulated_fields` config option to change the default behaviour.)\ntrigger_search = \"enter\"            # Trigger a search\nfocus_next_field = \"tab\"            # Focus on the next field\nfocus_previous_field = \"S-tab\"      # Focus on the previous field\nopen_file_finder = \"A-f\"            # Open file finder popup (when focused on include/exclude fields)\n\n# Commands available on the search screen, when the search results are focussed\n[keys.search.results]\ntrigger_replacement = \"enter\"              # Trigger a replacement\nback_to_fields = \"C-o\"                     # Move focus back to the search fields\nopen_in_editor = \"e\"                       # Open the currently selected search result in your editor. The editor command can be overriden using the `editor_open` section of your config.\nmove_down = [\"j\", \"C-n\", \"down\"]           # Navigate to the search result below (wraps to top at end)\nmove_up = [\"k\", \"C-p\", \"up\"]               # Navigate to the search result above (wraps to bottom at top)\nmove_next_file = \"C-down\"                  # Navigate to the next file (first match in next file)\nmove_prev_file = \"C-up\"                    # Navigate to the previous file (first match in previous file)\nmove_down_half_page = \"C-d\"                # Navigate to the search result half a page below\nmove_up_half_page = \"C-u\"                  # Navigate to the search result half a page above\nmove_down_full_page = [\"C-f\", \"pagedown\"]  # Navigate to the search result a page below\nmove_up_full_page = [\"C-b\", \"pageup\"]      # Navigate to the search result a page above\nmove_top = \"g\"                             # Navigate to the first search result\nmove_bottom = \"G\"                          # Navigate to the last search result\ntoggle_selected_inclusion = \"space\"        # Toggle whether the currently highlighted result will be replaced or ignored\ntoggle_all_selected = \"C-w\"                # Toggle whether all results will be replaced or ignored\ntoggle_multiselect_mode = \"v\"              # Toggle whether multiselect mode is enabled\nflip_multiselect_direction = \"A-;\"         # Flip the direction of the multiselect selection\n\n# Commands available on the replacement-in-progress screen\n[keys.performing_replacement]\n\n# Commands available on the results screen\n[keys.results]\nscroll_errors_down = [\"j\", \"down\", \"C-n\"]  # Navigate to the error below\nscroll_errors_up = [\"k\", \"up\", \"C-p\"]      # Navigate to the error above\nquit = [\"enter\", \"q\"]                      # Exit scooter. This is in addition to the `quit` command in the `general` section.\n\n```\n\u003c!-- KEYS END --\u003e\n\nThere is a `general` section for keymaps that apply to all screens, and then there are screen-specific sections which only apply to certain screens. Note that if a keymap is bound in both the screen-specific and general sections, the screen-specific mapping takes priority.\n\nYou can set any number of mappings for a given command using an array, e.g. `quit = [\"esc\", \"C-c\"]`. If you are only setting one keymap, you can omit the square brackets, e.g. `quit = \"C-c\"`.\n\nKeybindings are specified using optional modifier prefixes combined with key names. For example, `C-a` represents Ctrl+a, `A-S-x` represents Alt+Shift+x, and `j` represents the j key with no modifiers.\n\n#### Available modifiers\n\u003c!-- MODIFIERS LIST START --\u003e\n`S-`, `C-`, `A-`\n\u003c!-- MODIFIERS LIST END --\u003e\n\n#### Available keys\n\nAlphanumeric characters (`a`-`z`, `A`-`Z`, `0`-`9`), function keys (`F1`-`F24`), and the following special keys:\n\u003c!-- KEYS LIST START --\u003e\n`backspace`, `enter`, `left`, `right`, `up`, `down`, `home`, `end`, `pageup`, `pagedown`, `tab`, `del`, `ins`, `null`, `esc`, `space`\n\u003c!-- KEYS LIST END --\u003e\n\n#### Note on terminal limitations\n\nSome key combinations may not work due to terminal protocol limitations.\n\nMost modern terminals (Alacritty, Ghostty, Kitty, WezTerm etc.) support [Kitty's enhanced keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/), which handles most key combinations correctly. However, there are still some combinations which won't work, such as Control in combination with some punctuation or special characters: for instance, `C-?` generally results in `/` being sent to the terminal application.\n\nTerminals that do not support the enhanced protocol face more limitations still, such as interpreting `C-h` as backspace, `C-i` as tab, `C-m` as enter, and `C-[` as escape.\n\n## Editor configuration\n\nBelow are a couple of ways to run scooter without leaving your editor.\n\n### Helix\n\nAs explained [here](https://github.com/helix-editor/helix/wiki/Recipes#project-wide-search-and-replace-with-scooter), you can add a keybinding like the following to open scooter directly in Helix with `\u003center\u003e s`:\n\n```toml\n[keys.select.ret]\ns = [\n    \":write-all\",\n    \":insert-output scooter --no-stdin \u003e/dev/tty\",\n    \":redraw\",\n    \":reload-all\"\n]\n```\n\nNote that this saves all files before opening scooter.\n\n#### Floating window\n\nIf you are using Helix in Tmux, you can add a keymap like the following to your Helix config to open scooter in a popup with `\u003center\u003e s`:\n\n```toml\n[keys.select.ret]\ns = \":sh tmux popup -xC -yC -w90%% -h90%% -E scooter\"\n```\n\nYou can also add the following to your [scooter config file](#configuration-options) to open files back in Helix from the search results page with `e`:\n\n```toml\n[editor_open]\ncommand = 'tmux send-keys -t \"$TMUX_PANE\" \":open \\\"%file:%line\\\"\" Enter'\nexit = true\n```\n\n#### Piping to scooter\n\nYou can pipe content from Helix to scooter, and it will write the updated contents back to the buffer in Helix. Select some contents in Helix, and then enter `:| scooter --print-on-exit \u003e/dev/tty` (i.e. press `:`, type or paste `| scooter --print-on-exit \u003e/dev/tty`, and then press enter). scooter will open, allowing you to find and replace on the contents: once you're done, the buffer will have been updated with any changes.\n\n### Neovim\n\nYou can integrate scooter with Neovim using either [ToggleTerm](https://github.com/akinsho/toggleterm.nvim) or [snacks.nvim](https://github.com/folke/snacks.nvim). Both configurations provide the following keymaps:\n* `\u003cleader\u003es` to open scooter, resuming an existing session if one exists.\n* `\u003cleader\u003er` to open scooter and search for the currently selected text in visual mode. (Note that the search text field will be disabled by default in scooter, but you can tweak this behaviour as explained [here](#pre-populating-search-fields)).\n* `e` to open files from scooter in Neovim, hiding the scooter window (which can be re-opened with `\u003cleader\u003es`).\n\nFirst, add the following to your [scooter config file](#configuration-options):\n\n```toml\n[editor_open]\ncommand = \"nvim --server $NVIM --remote-send '\u003ccmd\u003elua EditLineFromScooter(\\\"%file\\\", %line)\u003cCR\u003e'\"\n```\n\nThen, add this to your Neovim config:\n\n```lua\n\n-- REPLACE THIS: Integration-specific code here\n\nvim.keymap.set('n', '\u003cleader\u003es', open_scooter, { desc = 'Open scooter' })\nvim.keymap.set('v', '\u003cleader\u003er',\n    function()\n        local selection = vim.fn.getreg('\"')\n        vim.cmd('normal! \"ay')\n        open_scooter_with_text(vim.fn.getreg('a'))\n        vim.fn.setreg('\"', selection)\n    end,\n    { desc = 'Search selected text in scooter' })\n```\n\nFinally, replace the `-- REPLACE THIS: ...` line with one of the integration-specific code blocks below:\n\n#### Option 1: using snacks.nvim\n\nInstall snacks.nvim as per the instructions [here](https://github.com/folke/snacks.nvim#-installation).\n\n```lua\nlocal scooter_term = nil\n\n-- Called by scooter to open the selected file at the correct line from the scooter search list\n_G.EditLineFromScooter = function(file_path, line)\n    if scooter_term and scooter_term:buf_valid() then\n        scooter_term:hide()\n    end\n\n    local current_path = vim.fn.expand(\"%:p\")\n    local target_path = vim.fn.fnamemodify(file_path, \":p\")\n\n    if current_path ~= target_path then\n        vim.cmd.edit(vim.fn.fnameescape(file_path))\n    end\n\n    vim.api.nvim_win_set_cursor(0, { line, 0 })\nend\n\nlocal function is_terminal_running(term)\n    if not term or not term:buf_valid() then\n        return false\n    end\n    local channel = vim.fn.getbufvar(term.buf, 'terminal_job_id')\n    return channel and vim.fn.jobwait({channel}, 0)[1] == -1\nend\n\nlocal function open_scooter()\n    if is_terminal_running(scooter_term) then\n        scooter_term:toggle()\n    else\n        scooter_term = require(\"snacks\").terminal.open(\"scooter\", {\n            win = { position = \"float\" },\n        })\n    end\nend\n\nlocal function open_scooter_with_text(search_text)\n    if scooter_term and scooter_term:buf_valid() then\n        scooter_term:close()\n    end\n\n    local escaped_text = vim.fn.shellescape(search_text:gsub(\"\\r?\\n\", \" \"))\n    scooter_term = require(\"snacks\").terminal.open(\"scooter --fixed-strings --search-text \" .. escaped_text, {\n        win = { position = \"float\" },\n    })\nend\n```\n\n#### Option 2: using ToggleTerm\n\nInstall ToggleTerm as per the instructions [here](https://github.com/akinsho/toggleterm.nvim#installation), then add to your Neovim config:\n\n```lua\nlocal scooter_term = nil\n\n-- Called by scooter to open the selected file at the correct line from the scooter search list\n_G.EditLineFromScooter = function(file_path, line)\n    if scooter_term and scooter_term:is_open() then\n        scooter_term:close()\n    end\n\n    local current_path = vim.fn.expand(\"%:p\")\n    local target_path = vim.fn.fnamemodify(file_path, \":p\")\n\n    if current_path ~= target_path then\n        vim.cmd.edit(vim.fn.fnameescape(file_path))\n    end\n\n    vim.api.nvim_win_set_cursor(0, { line, 0 })\nend\n\nlocal function open_scooter()\n    if not scooter_term then\n        scooter_term = require(\"toggleterm.terminal\").Terminal:new({\n            cmd = \"scooter\",\n            direction = \"float\",\n            close_on_exit = true,\n            on_exit = function()\n                scooter_term = nil\n            end\n        })\n    end\n    scooter_term:open()\nend\n\nlocal function open_scooter_with_text(search_text)\n    if scooter_term and scooter_term:is_open() then\n        scooter_term:close()\n    end\n\n    local escaped_text = vim.fn.shellescape(search_text:gsub(\"\\r?\\n\", \" \"))\n    scooter_term = require(\"toggleterm.terminal\").Terminal:new({\n        cmd = \"scooter --fixed-strings --search-text \" .. escaped_text,\n        direction = \"float\",\n        close_on_exit = true,\n        on_exit = function()\n            scooter_term = nil\n        end\n    })\n    scooter_term:open()\nend\n```\n\n## Contributing\n\nContributions are very welcome! I'd be especially grateful for any contributions to add scooter to popular package managers. If you'd like to add a new feature, please create an issue first so we can discuss the idea, then create a PR with your changes.\n\n### Development\n\nOnce you've pulled down the repo, you can run scooter with:\n\n```sh\ncargo run --bin scooter\n```\n\nIf you make any changes to either the readme or config, you'll need to run the following to regenerate the docs:\n\n```sh\ncargo xtask readme\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnkh%2Fzcooter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnkh%2Fzcooter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnkh%2Fzcooter/lists"}