Bookmarks in Neovim

I go through many files when I work, especially when debugging or tracing behaviors. I created a bookmark system in Neovim to quickly jump between files. This system differs from the default Neovim system because it focuses on marking files and lines, not just positions within the same file.

How It Works

The system records the file path and the current line in a text file. I use the <leader>o key to open all marked files. I use the <leader>m key to mark a file. I also use the :Clearmarks command to clear all bookmarks when I finish a task or start a new one.

Implementation

The Mark_point function writes the file path and current line to the end of a text file. It creates this file if it does not exist.

function Mark_point()
  local home = os.getenv("HOME")
  local file_marks = home .. "/marks.txt"

  local file = vim.fn.expand('%:p') -- full path
  local line = vim.fn.line('.')     -- current line number

  local mark = file .. " +" .. line

  local file_handle = io.open(file_marks, "a")
  if file_handle then
    file_handle:write(mark .. "\n")
    file_handle:close()
    print("Marked: " .. mark)
    return
  end
  print("Error opening bookmark file: " .. file_marks)
end

The Open_mark function reads the bookmark file, opens the file, and jumps to the marked line. It first goes to the beginning of the file with gg and then to the desired line with G.

function Open_mark(marks_file)
  local file = io.open(marks_file, "r")
  if not file then
    -- print("Error opening bookmark file: " .. marks_file)
    return
  end

  for line in file:lines() do
    local file_path, line_number = string.match(
        line, "^(.-)%s+%+(%d+)$")
    vim.cmd('edit ' .. file_path)
    vim.cmd('normal gg')
    vim.cmd('normal ' .. line_number .. 'G')
  end
  file:close()
end

I created the Open_marks function to open marked files in the user directory and the current directory.

function Open_marks()
  local path = os.getenv("HOME")
  local marks_file = path .. "/marks.txt"
  Open_mark(marks_file)

  path = vim.fn.getcwd()
  marks_file = path .. "/marks.txt"
  Open_mark(marks_file)
end

The Clear_marks function removes the bookmark files from the user directory and the current directory.

function Clear_marks()
  local path = os.getenv("HOME")
  local marks_file = path .. "/marks.txt"
  os.remove(marks_file)

  path = vim.fn.getcwd()
  marks_file = path .. "/marks.txt"
  os.remove(marks_file)
end

I set up shortcut keys and a Neovim command to call the functions.

vim.api.nvim_set_keymap(
  'n', '<leader>o', ':lua Open_marks()<CR>',
  { noremap = true, silent = true }
)
vim.api.nvim_set_keymap(
  'n', '<leader>m', ':lua Mark_point()<CR>',
  { noremap = true, silent = true }
)
vim.api.nvim_command(
  'command! Clearmarks lua Clear_marks()'
)

Complete source code

Bookmark File

I store the bookmarks in a simple text file, which makes it easy to create scripts that work with those bookmarks without having to open Neovim. Below is a Bash example:

#!/usr/bin/env bash

INITIAL_QUERY="${*:-}"

MARKS_FILES=("$HOME/marks.txt")
[ -f "./marks.txt" ] && \
    MARKS_FILES+=("./marks.txt")

IFS='+' read -r file_path line_number <<< "$(
    cat "${MARKS_FILES[@]}" | \
    fzf --ansi \
        --delimiter '+' \
        --preview 'bat --color=always {1} --highlight-line {2}' \
        --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
        --query "$INITIAL_QUERY"
)"

[ -n "$file_path" ] && {
    file_path=$(echo "$file_path" | xargs)
    line_number=$(echo "$line_number" | xargs)
    "$EDITOR" "$file_path" "+$line_number"
    echo "$file_path" "+$line_number"
}

Source code

This script reads the bookmark file, lets you select a file, and opens it in Neovim at the marked line. It uses fzf for user interaction and bat for file preview.

Conclusion

I prefer small tools that perform specific tasks efficiently. They’re easy to combine or modify, making my everyday workflow more productive and pleasant.

Until next time!

References

Cesar Gimenes

Última modificação