Argument list: The underrated (neo)vim 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
This bookmarking journey began with harpoon, a plugin built by a prolific YouTube content creator named Theprimeagen. 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?.
After that, I came across grapple. Grapple shares the same underlying concept as harpoon but is a step ahead. It provides a neat feature on top: Scope. 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 but not a plugin for 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:
-- 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:
-- 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:
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 ##:
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, {})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 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. 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, 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:
#!/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
fiThen, I set the script above as a git alias so that after a git merge with conflicts, I can run git conflicts to open all conflicted files into (neo)vim:
[alias]
conflicts = !git-resolveAfter 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:
#!/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 $@[alias]
merge-fix = !git-merge-fixAfter 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:
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>")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:
$ nvim *.py:argdo g/breakpoint()/d | upDo you have different ways of using the argument list? I would appreciate if you shared them in the comment section below. Happy coding