Netrw


a meme about vim

Y'all like vim?

Programming is mostly thinking, followed by trying out a probably wrong idea, and then more thinking. It’s hard to demonstrate proficiency unless you’re a competitive programmer and can spit out algorithms faster than ChatGPT can hallucinate them.

I’m not a competitive programmer by any means, so to address my lack of flash and panache, I’ve become increasingly savvy at using vim.

I use vim btw. Well, no, not for Java. Or debugging. Also, it’s actually neovim.

What I lack in algorithmic finesse I make up for in a willingness to invest in skills employers will never, ever care about. Take for example, netrw, vim’s built-in file explorer.

To begin, choose an incantation to invoke:

# From the command line, in a directory
vi .

# While in vim
:Explore

Nice, now you’re in netrw.

Here’s a smattering of useful commands:

CmdDescription
iCycle through netrw’s display types
%Create a file
dCreate a directory
DDelete a file/directory
RRename a file/directory
-Navigate up a directory level
<CR>Enter a file, or descend a directory level (<CR> is vim for “Enter”)

Crazy! What a ride. On to customization.

Here are some settings I like:

-- in your init.lua

-- use tree mode by default
vim.g.netrw_liststyle = 3

-- 25% vertical split
vim.g.netrw_winsize = 25

-- The same keymap will both toggle open,
-- and toggle close a netrw vertical split
-- to the left of your working buffer
vim.keymap.set('n', '<leader>e', '<cmd>Lexplore<CR>',
  { desc = '[E]xplore Files (Toggle)' }
)

Enhance

When I’m using vim, my workflow usually looks something like this:

  • cd into a project directory
  • open vim in the project root via vi .
  • navigate to the file I want to edit via either telescope or :e path/to/file

Here’s where the workflow breaks down.

  1. open vi in blog/
  2. navigate to content/posts/post_1.md
  3. toggle open netrw using <leader>e

When I toggle netrw open, it shows me the tree in the cwd (project root), like so:

../
blog/
| .git/
| .github/
| archetypes/
| assets/
| content/
| data/
| i18n/
| layouts/
| node_modules/
| public/
| resources/
| scratch/
| static/

I like that it keeps my root context.

What would be even better, is that when I toggle open netrw, my root context is preserved, and the filetree is expanded to the current file, like so:


blog/ <-- still starts from the project root
| .git/
| .github/
| archetypes/
| assets/
| content/
| | about/
| | posts/
| | | post_1.md <-- expanded to current file
| | | post_2.md
| | | post_3.md
| | _index.md
| data/
| i18n/
| layouts/
| node_modules/
| public/
| resources/
| scratch/
| static/

So, here is a dubiously tested toggle function that opens netrw with the following settings:

  • tree view
  • directories expanded to the current file
  • current file highlighted/focused
  • buffer centered to highlight

Quite nice when you’re deeply nested in some directory.

local u = require 'custom.utils'

local netrw_toggler = function()
  -- @type boolean - Tracks if a netrw split has been opened.
  local toggled_open = false

  -- @type integer - Tracks the buffer number of the opened netrw split.
  local toggled_netrw_buf_num = -1

  -- @type integer - Holds an autocommand that sets toggle state.
  local toggler_group = vim.api.nvim_create_augroup('toggler_group', { clear = true })

  return function()
    -------------------
    -- EARLY RETURN  --
    -------------------

    local only_netrw_window = (vim.bo.ft == 'netrw' and #vim.api.nvim_list_wins() == 1)
    if only_netrw_window then
      vim.notify('Must have more than one window open to toggle Netrw', vim.log.levels.WARN)
      return
    end

    if toggled_open then
      assert(
        toggled_netrw_buf_num ~= -1,
        'Variable toggled_netrw_buf_num must be initalized to valid buffer number.'
      )

      vim.api.nvim_buf_delete(toggled_netrw_buf_num, {})

      assert(
        toggled_open == false,
        'The WinClosed autocommand on toggle_group should have set toggled_open=false.'
      )
      assert(
        #vim.api.nvim_get_autocmds { group = toggler_group } == 0,
        'The single autocommand on toggler_group should have been deleted after firing.'
      )
      return
    end

    ---------------------------
    -- BEFORE OPENING NETRW  --
    ---------------------------

    -- @type string - The buffer from which the cmd is launched.
    local cmd_buf = vim.api.nvim_buf_get_name(0)

    -- @type string
    local cmd_buf_parent_dir_path = vim.fs.dirname(cmd_buf) or '.'

    -- set last search as file name, so it is highlighted/focused upon netrw open
    vim.cmd [[:let @/=expand("%:t")]] -- [:t] 'tail' (last file), no path

    -- open netrw in left split
    vim.cmd('Lexplore ' .. cmd_buf_parent_dir_path)

    -------------------------
    -- AFTER OPENING NETRW --
    -------------------------

    -- set the variables captured by the closure
    toggled_open = true
    toggled_netrw_buf_num = vim.api.nvim_get_current_buf()

    -- expand the tree upwards for every parent directory til root is reached
    local root = u.get_project_root_dir() or vim.fn.getcwd()
    for dir in vim.fs.parents(cmd_buf) do
      if dir == root then
        break
      end
      vim.cmd "call netrw#Call('NetrwBrowseUpDir', 1)"
    end

    -- n<CR> will focus/highlight the last search, which was set above
    -- zz will center the buffer
    vim.cmd ':normal n<CR>zz'

    -- handle edge case: quitting netrw with command other than `<leader>e`
    vim.api.nvim_create_autocmd({ 'WinClosed' }, {
      group = toggler_group,
      buffer = toggled_netrw_buf_num, -- pin to the toggled buffer
      once = true, -- delete after firing, needed because of pin
      callback = function()
        toggled_open = false
      end,
    })
  end
end

-- netrw settings
vim.g.netrw_winsize = 25 -- 25% vertical split
vim.g.netrw_liststyle = 3 -- use tree mode by default

-- this will store the state for the toggling command
local toggler = netrw_toggler()
vim.keymap.set('n', '<leader>e', toggler, { desc = '[E]xpore Files Toggle' })

You’ll need something similar to this for the above to work (or just use the cwd).

-- @return string|nil
M.get_project_root_dir = function()
    return vim.fs.dirname(vim.fs.find({ '.git', '.editorconfig' }, {
      upward = true,
      stop = vim.loop.os_homedir(), -- non-inclusive, at home dir
      limit = 1, -- only care about first result, stop searching when found
    })[1])
end

Oh also, I lied.

I don’t really know how to use netrw that well. The above is pretty much it.

Anyways, I need to go tweak my .zshrc so that that my prompt uses a more subtle color palate. Should only take a couple of weeks.