• CGamesPlay 2 hours ago

    Since we're all giving replacements to this, nobody's mentioned my preferred one, so: use git add's patch mode. On a clean worktree, do the search/replace in bulk. Then use `git add --patch` to selectively add the good replacements and skip the bad ones. Finally, `git checkout -- .` to throw away all the bad ones. The nice part about this is that it's not much to remember. If you can make a global search and replace, and you can use `git add --patch` (which is useful loads of times), you can do a selective search and replace by combining them.

    As far as this actual tool: the demo GIF is way too fast-paced to show what's going on. A better demo would maybe search "ring", have 10 or so results instead of pages and pages, and show how you can unselect "spring" matches which were unintentionally caught.

    • sriram_malhar 2 hours ago

      You get all this and more with a direct perl one liner. Without the interactivity. I'd argue that if it is a lot of files, interactivity would be a pain. Also, since the original is preserved as a .bak file, one can be fearless about trying

         #change xxx to yyy in all html
         bash> perl -pi.bak -e 's/xxx/yyy/g' *.html
      
         #change xxx10 (say) to yyy10 in all html
         bash> perl -pi.bak -e 's/xxx(\d+)/yyy$1/g' *.html
      
         # Change x4 to yyyy, where the number of y's equals to the number after x.
         bash> perl -pi.bak -e 's/x(\d+)/"y" x $1/ge' *.
      
      The last example shows the /e operator, which evaluates an expression and uses the result as substitute, instead of a simple string.

      And finally, to exclude files, one can use a subshell. For example, suppose you want to change all html, but exclude undesirable.html..

         perl -pi.bak -e 's/x/y/g'  $(ls *.html | egrep -v undesirable)
      • herrington_d 4 hours ago

        Cool! is it possible to support structural search like ast-grep[1]? ast-grep has some interactive mode but it is nothing near Scooter.

        1: https://ast-grep.github.io/

        • sigmonsays 3 hours ago

          We're losing the art of bash ``` find -type f -iname '*.go' | xargs -r -n1 sed -i 's,foo,foobar,g' ```

          • tpoacher 4 hours ago

            Neat ... but I'd probably just do this by opening a "grep -l" list in nano for interactive replacement directly instead. Easy peasy.

            • aerzen 8 hours ago

              Cool.

              I assumed it uses ripgrep (or the underlying walkdir) because that's the established high-performance tool for this. But apparently not.

              • tomschafer 8 hours ago

                It uses https://docs.rs/ignore/latest/ignore/ to walk dirs while respecting ignore files

                • seritools 7 hours ago

                  (And `ignore` uses `walkdir` internally)

                  • burntsushi 7 hours ago

                    For single threaded use cases. For multi-threaded, it has its own parallel directory traversal. :-)

              • jmhobbs 6 hours ago

                Very cool! I currently use `sad` for this, if you're already an fzf user you should check it out.

                https://github.com/ms-jpq/sad

                • jmercouris 6 hours ago

                  Feels like we just keep making tools that already exist in Emacs.

                  • mway 6 hours ago

                    I dunno, seems reasonable to me that we might have nice things without requiring everyone to use emacs. (And for those who do use emacs, I guess you're ahead of the curve?)

                    • mananaysiempre 4 hours ago

                      On the other hand, it seems reasonable that we should be able to have nice things without giving up our editors. I know I’ve been spoiled by Kakoune’s cursors, but this feels like a tool that should work by spawning $EDITOR in the middle of its execution (or perhaps just having two phases and a control file). I don’t know if that’s actually possible with the current capabilities of $EDITORs (which are not Emacs). I just feel, in the darkest hour of the night which I spend reflecting on UIs, like it should be.

                    • alganet 2 hours ago

                      Nonsense, lots of people are doing text editors.

                    • doylemark 7 hours ago

                      nice! Find and replace across a codebase is one of the few times I open an IDE.

                      Being able to interactively ignore instances for replacement is great!

                      • Freak_NL 7 hours ago

                        Am I alone in initially thinking this was specifically for the fish shell because of this tool's name?

                        • darrenf 7 hours ago

                          Perhaps. I as a fish user thought “oh, like `string replace`”

                        • jph 6 hours ago

                          Excellent, thank you. I do this with sed & awk & sometimes an IDE, and scooter looks better in every way.

                          I'm adding scooter to my cargo install favorites:

                          https://github.com/sixarm/cargo-install-favorites

                          • matt3210 7 hours ago

                            Very nice, it might be a good alternative when I can't use vscode remote connections.

                            • eevilspock 7 hours ago

                              A Homebrew install option will help this take off on Macs. https://github.com/thomasschafer/scooter/issues/6

                              • rvz 5 hours ago

                                That was my first problem with trying to install this. But agree that it should be on Homebrew.

                              • rvz 5 hours ago

                                There was another comment about the difficulty in installing scooter and in the issues section, there are some requests to add more installation options.

                                https://github.com/thomasschafer/scooter/issues/6

                                Not everyone has the Rust toolchain installed on their machine. The `cargo install` installation directive needs to be discouraged.

                                • anthk 6 hours ago

                                  Similarly, on bash/ksh: set -o vi Ctrl-[ v (or ESC) set -o emacs Ctrl-x e

                                  • bloopernova 8 hours ago

                                    A useful feature of bash and zsh is the "edit command". The standard shortcut is "ctrl-x ctrl-e".

                                    It opens the current command line in $EDITOR, which often defaults to vim.

                                    • dmd 8 hours ago

                                      That is very useful. What does it have to do with this?

                                      • bloopernova 8 hours ago

                                        If you want to search and replace a command line, there's tools to do it in your favourite editor.

                                        • dmd 8 hours ago

                                          Ah, so you didn't click through and actually see what this tool is, you just read the title.

                                          • bloopernova 8 hours ago

                                            I did click through, but misinterpreted what it was doing. Apologies, I'm "multitasking".

                                    • agateau 8 hours ago

                                      Looks handy!

                                      • patatass 3 hours ago

                                        Couldn't find it in nixpkgs.

                                      • mg 6 hours ago

                                        You could also use vim in a loop. Say you want to replace "hello" in all files in the current dir with "world" and confirm every replace, then you would do:

                                            for f in $(grep -l 'hello' *); do vim -c ':%s/hello/world/gc | wq' "$f"; done
                                        
                                        Or if you want to use some more vim magic, this simpler command will do the same:

                                            vim -c "argdo %s/hello/world/gce | update" -c "qall" *
                                        
                                        "argdo" will do the replace command for each file, the additional e modifier in gce will ignore files that do not contain the search string and the second command "qall" is to quit vim after the work is done.
                                        • gurgeous 7 hours ago

                                          Also see the excellent https://github.com/your-tools/ruplacer.

                                          For more advanced needs, I have a custom thing called greprep that let's you make changes using your favorite editor. Workflow is like this:

                                            1. $ rg -n .... > /tmp/lines.txt
                                            2. (edit lines.txt in vscode)
                                            3. $ greprep /tmp/lines.txt to apply the changes
                                          • jmarcher 7 hours ago

                                            In Emacs, there is [helm-ag-edit](https://github.com/emacsorphanage/helm-ag) (but uses ripgrep if present). It's almost identical to your workflow, but all done inside the same app.

                                            1. helm-ag <pattern> # the search results are updated as you type 2. helm-ag-edit # edit the search result as regular text. Use multi-cursors, macros, whatever. 3. helm-ag-edit-save # commits the changes to the affected files

                                            All those commands have keybindings, so it's pretty fast. I'll often open up Emacs just to do that and then go back to my JetBrains IDE.

                                          • lopkeny12ko 8 hours ago

                                            [flagged]

                                            • dang 7 hours ago

                                              Can you please not post shallow dismissals of other people's work? This is in the site guidelines: https://news.ycombinator.com/newsguidelines.html.

                                              It's important, when people share something they've made on HN, that they don't run into this sort of bilious internet putdown.

                                              Edit - these are other examples of the same thing (i.e. the thing we don't want in HN threads, and which we'd appreciate if you'd not do any more of):

                                              https://news.ycombinator.com/item?id=41810426

                                              https://news.ycombinator.com/item?id=41224056

                                              • karanbhangui 8 hours ago

                                                "For a Linux user, you can already build such a system yourself quite trivially by getting an FTP account, mounting it locally with curlftpfs, and then using SVN or CVS on the mounted filesystem"

                                                https://news.ycombinator.com/item?id=8863

                                                • tomschafer 8 hours ago

                                                  Not affiliated, I just built a little tool to make my life easier and thought I'd share

                                                  • dang 7 hours ago

                                                    It's great and clearly the community appreciates it! I'll put Show HN in the title since that's the convention for sharing one's projects on HN (https://news.ycombinator.com/showhn.html).

                                                    Btw, do you want to include some text giving the backstory of how you came to work on this, and explaining what's different about it? that's also the convention. If you post it in a reply to this comment, I'll move your text to the top of the thread.

                                                    • sockaddr 8 hours ago

                                                      I think it's cool. Thanks for sharing

                                                  • oulipo 8 hours ago

                                                    I'm using this quickly put-together shell script called replace

                                                        #!/usr/bin/env bash
                                                        
                                                        # Function to escape special characters for sed
                                                        escape_sed_string() {
                                                            printf '%s\n' "$1" | gsed -e 's/[]\/$*.^[]/\\&/g'
                                                        }
                                                        
                                                        help() {
                                                          gum style --foreground cyan --italic "\
                                                        Usage (everything optional, you will be prompted):\n\
                                                        $0\n\
                                                          --ext .js --ext .ts\n\
                                                          --from \"source string\"\n\
                                                          --to \"replacement string\"\n\
                                                          --dir somePath"
                                                        }
                                                        
                                                        # Parse command line arguments
                                                        while [[ "$#" -gt 0 ]]; do
                                                            case $1 in
                                                                -h)
                                                                    help
                                                                    exit 0
                                                                    ;;
                                                                --help)
                                                                    help
                                                                    exit 0
                                                                    ;;
                                                                --ext) EXTENSIONS+=("$2"); shift ;;
                                                                --from) REPLACE_FROM="$2"; shift ;;
                                                                --to) REPLACE_TO="$2"; shift ;;
                                                                --dir) DIRECTORY="$2"; shift ;;
                                                                *) gum style --foreground red --bold "Unknown parameter: $1"; exit 1 ;;
                                                            esac
                                                            shift
                                                        done
                                                        
                                                        # Check for missing parameters and prompt using gum
                                                        if [ -z "${EXTENSIONS+set}" ]; then
                                                            EXTENSIONS=($(gum choose \
                                                                --no-limit \
                                                                --selected .ts,.mts,.tsx,.vue,.js,.cjs,.mjs \
                                                                .ts .mts .tsx .vue .js .cjs .mjs .txt .md .html .json))
                                                        fi
                                                        
                                                        # Exit if no extension is selected
                                                        if [ ${#EXTENSIONS[@]} -eq 0 ]; then
                                                            gum style --foreground red --bold " Error: No extensions selected. Exiting."
                                                            exit 1
                                                        fi
                                                        
                                                        if [ -z "${REPLACE_FROM+set}" ]; then
                                                            REPLACE_FROM=$(gum input --placeholder "Search string:")
                                                            if [ -z "${REPLACE_FROM}" ]; then
                                                                echo "No replace from string, exiting"
                                                                exit 1
                                                            fi
                                                        fi
                                                        if [ -z "${REPLACE_TO+set}" ]; then
                                                            REPLACE_TO=$(gum input --placeholder "Replace string:")
                                                        fi
                                                        if [ -z "${DIRECTORY+set}" ]; then
                                                            DIRECTORY="."
                                                        fi
                                                        
                                                        # Escape strings for sed
                                                        ESCAPED_FROM=$(escape_sed_string "$REPLACE_FROM")
                                                        ESCAPED_TO=$(escape_sed_string "$REPLACE_TO")
                                                        
                                                        # Run the replacement
                                                        for ext in "${EXTENSIONS[@]}"; do
                                                            gum style --foreground blue " Replacing ${ext} files..."
                                                            find "$DIRECTORY" -type f -name "*$ext" ! -path "*/node_modules/*" -exec gsed -i "s/$ESCAPED_FROM/$ESCAPED_TO/g" {} \;
                                                        done
                                                        
                                                        gum style --foreground green --bold " Replacement complete."
                                                    • JadeNB an hour ago

                                                      What is gum?