Skip to content

Instantly share code, notes, and snippets.

@aristidebm
Created May 6, 2025 18:25
Show Gist options
  • Select an option

  • Save aristidebm/aebc2d90c5fae67fa5c000a24a35e9a0 to your computer and use it in GitHub Desktop.

Select an option

Save aristidebm/aebc2d90c5fae67fa5c000a24a35e9a0 to your computer and use it in GitHub Desktop.

Revisions

  1. aristidebm created this gist May 6, 2025.
    151 changes: 151 additions & 0 deletions arglist.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,151 @@
    # Argument list: The underrated [(neo)vim](https://neovim.io/) list

    The argument list is so powerful that I wonder why we as vimmers are underusing it. Below are some useful use cases that I have found for the (neo)vim argument list
    ## Arguments list for bookmarking

    This bookmarking journey began with [harpoon](https://github.com/ThePrimeagen/harpoon/tree/harpoon2), a plugin built by a prolific YouTube content creator named [Theprimeagen](https://www.youtube.com/playlist?list=PLm323Lc7iSW_wuxqmKx_xxNtJC_hJbQ7R). The main idea is to store commonly used files needed to achieve the task at hand into a buffer for fast access. For more information, check [Why harpoon?](https://www.youtube.com/watch?v=Qnos8aApa9g&t=12s).

    After that, I came across [grapple](https://github.com/cbochs/grapple.nvim). Grapple shares the same underlying concept as harpoon but is a step ahead. It provides a neat feature on top: [Scope](https://github.com/cbochs/grapple.nvim?tab=readme-ov-file#scopes). I really like the `git_branch` one, as it lets you bookmark your files per branch. So if, for example, you are working on `branch A` and for some reason you have to move to `branch B`, with that scope activated, when you come back to `branch A`, all the files you have bookmarked are there waiting for you.

    So why move away from all these plugins if they are working well for me? The answer is **context overhead**.

    With these plugins, I have bound `<C-j>`, `<C-k>`, `<C-l>`, and `<C-;>` to pick the first four files in the bookmark. When I am in a hurry, I usually don't know which file is attached to which key and mess up every time. So I needed to find a solution. An acceptable solution is to be able to fuzzy find my bookmarks. Grapple has a plugin for [telescope](https://github.com/nvim-telescope/telescope.nvim/) but not a plugin for [fzf-lua](https://github.com/ibhagwan/fzf-lua). Since I am a huge fan of **fzf-lua**, I needed to find a solution that can leverage its potential, thus the solution described below.

    I use `<leader>a` to add an item to the argument list, and have bound it as follows, so that I can keep the insertion order:

    ```lua
    -- Arguments list management command
    nnoremap("<leader>a", ":$argadd | argdedupe <CR>")
    ```

    I use `<C-j>`, `<C-k>`, `<C-l>`, and `<C-;>` to pick the first four elements in the argument list in that order:

    ```lua
    -- It is 0-based index but the first index always corresponds
    -- to the first argument passed to nvim cli command and usually
    -- it is the folder name (since I am used to nvim .)
    nnoremap("<C-j>", ":execute 'edit' argv(1)<CR>")
    nnoremap("<C-k>", ":execute 'edit' argv(2)<CR>")
    nnoremap("<C-l>", ":execute 'edit' argv(3)<CR>")
    nnoremap("<C-;>", ":execute 'edit' argv(4)<CR>")
    ```

    When I mess up with the `<C-*>` keys above, I can launch a fuzzy finder on my argument list by typing `<leader>pa` and have bound it as follows:

    ```lua
    vim.keymap.set("n", "<leader>pa", require("fzf-lua").args, { desc = "[P]roject [A]rgs" })
    ```

    To clear my argument list, I have created a custom command that clears every entry but directories. This is useful because I usually launch (neo)vim with an argument pointing to a directory (mainly `nvim .`) and don't want the current directory to be deleted from the argument list. If you want to remove everything from the argument list, (neo)vim covers this use case with `:argdelete ##`:

    ```lua
    vim.api.nvim_create_user_command("ClearArglist", function()
    local arglist = vim.fn.argv()
    for i = #arglist, 1, -1 do
    if vim.fn.isdirectory(arglist[i]) == 1 then
    --- We don't want to remove the directory added to args list
    --- when launching vim with nvim <directory>
    else
    vim.cmd("argdelete " .. arglist[i])
    end
    end

    end, {})
    ```

    ## Argument list for conflicts files grabbing

    Previously when I had merge conflicts while merging a branch into another, I had to go through the output of `git merge <branch>` to know which files had conflicts in them. When you are a [django](https://www.djangoproject.com/) user, with multiple apps each having `django.po`, it is not a pleasant experience to go through all these files by scanning the `git merge` output when you have conflicts in them. So I needed to find a better way of doing this. Here is where it began.

    I first came across [git mergetool](https://git-scm.com/docs/git-mergetool). I tried it, but it did not suit my needs because the mergetool must be opened and closed for each conflicted file. I needed something that would let me go through conflicts as fast as possible, fix conflicts, and stage all the changes at the end.

    Since the previous solution did not suit my needs and I knew what I was looking for experience-wise and also knew git has a hooking system, I naturally searched for a hook that could be executed when a merge fails. I went through [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks), but unfortunately there wasn't such a hook available. So I needed another solution. I asked myself if there were a way to grab all conflicted files after a merge fails, so I could pass those files directly to `(neo)vim`. It turns out that git provides such a utility: `git diff --name-only --diff-filter=U -z`. Thus, the solution described below.

    I created a bash script that can grab all conflicted files and pass them to `(neo)vim` as arguments with the aid of [perplexity](https://www.perplexity.ai/):

    ```bash
    #!/usr/bin/env bash

    function resolve() {
    # Initialize an empty array to hold conflicted filenames
    local files=()

    # Read NULL-separated filenames from git diff output safely into the array
    while IFS= read -r -d '' file; do
    files+=("$file") # Append each filename to the array
    done < <(git diff --name-only --diff-filter=U -z)

    # Check if the array is not empty (i.e., there are conflicted files)
    if (( ${#files[@]} )); then
    # Open all conflicted files in Neovim, passing each as a separate argument
    nvim --clean "${files[@]}"
    fi
    }

    # Run resolve if the function is invoked as a script
    if [[ "${#BASH_SOURCE[@]}" -eq 1 ]]; then
    resolve
    fi
    ```

    Then, I set the script above as a [git alias](https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases) so that after a `git merge` with conflicts, I can run `git conflicts` to open all conflicted files into `(neo)vim`:

    ```ini
    [alias]
    conflicts = !git-resolve
    ```

    After the above was working great, I decided to merge these two steps (`git merge` && `git conflicts`) into one, thus the `git merge-fix` alias defined as follows:

    ```bash
    #!/usr/bin/env bash

    function merge_fix() {
    local bsh="$HOME/.local/bin/bash-scripts/"
    source "$bsh/git-resolve"

    # Merge as usual
    git merge "$@"
    # Retrieve the process exit_code
    exit_code=$?

    if [ $exit_code -ne 0 ]; then
    # The merging process has failed,
    # resolve conflicts if any.
    resolve
    # exit $status
    fi
    }

    merge_fix $@
    ```

    ```ini
    [alias]
    merge-fix = !git-merge-fix
    ```

    After fixing conflicts inside one file, I just have to hit `]a` to go to the next one. Here are other keybindings that I use when in a merge conflicts fixing session:

    ```lua
    vim.keymap.set("n", "[a", ":previous<CR>")
    vim.keymap.set("n", "]a", ":next<CR>")
    vim.keymap.set("n", "[A", ":first<CR>")
    vim.keymap.set("n", "]A", ":last<CR>")
    ```

    ## Argument list for batch processing

    This is the most widely known and used approach. It allows you to perform batch processing on files present inside the argument list. I often use it to remove all `breakpoint()` calls that I have set inside Python files before publishing to the upstream:

    ```bash
    $ nvim *.py
    ```

    ```vim
    :argdo g/breakpoint()/d | up
    ```

    ## Conclusion

    Do you have different ways of using the argument list? I would appreciate if you shared them in the comment section below. Happy coding