Skip to content

A Drawer plugin for Neovim. AKA "persistent split" / "persistent window".

Notifications You must be signed in to change notification settings

mikew/nvim-drawer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nvim-drawer

A Drawer plugin for Neovim.

Ever wanted your terminal or file explorer to be at the bottom, have the same height, and appear on all tabs at a consistent size? And when you just want to hide it, you don't want to have to do that across all tabs?

Then this plugin is for you.

Installation

{
  'mikew/nvim-drawer',
  opts = {},
  config = function(_, opts)
    local drawer = require('nvim-drawer')
    drawer.setup(opts)

    -- See usage and examples below.
  end
}

Features

Features.-.Walkthrough.mp4
  • Attach to any side of the screen.
  • Floating drawers.
  • Automatically claim buffers.
  • Size is consistent across tabs.
  • Open/close state is consistent across tabs.
  • Drawers can be zoomed to take up the whole screen.
  • Drawers remember what buffer they were editing.
  • Has a tab system.
  • When the last non-drawer is closed in a tab, the tab (or vim) is closed.
  • Simple API.
  • Uses buffers and is very flexible.

About

At its core, nvim-drawer just creates and hides windows and tries really hard to keep them consistent across tabs. You could also call a "drawer" a persistent window, or a persistent split.

Since windows in vim require a buffer, nvim-drawer creates a scratch buffer for you.

Usage

First, you need to create a drawer via create_drawer:

local drawer = require('nvim-drawer')

drawer.create_drawer({
  size = 15,
  position = 'bottom',
})

When opened, this drawer will be at the bottom of the screen, 15 lines tall, editing a scratch buffer.

This doesn't do much, you get a nice scratch space, but to get the most out of it, you need to use the API and add some key mappings.

Your drawer has methods like ...

  • open(): Open the drawer.
  • close(): Close the drawer.
  • toggle(): Toggle the drawer.
  • focus(): Focus the drawer.
  • go(): Go to a different tab.

... and callbacks like:

  • on_did_create_buffer: Called after a buffer is created.
  • on_did_open_window: Called after a drawer is opened.
  • on_did_close: Called after a drawer is closed.

Examples

Terminal

Examples.-.Terminal.mp4
local drawer = require('nvim-drawer')

drawer.create_drawer({
  size = 15,
  position = 'below',

  -- Automatically claim any opened terminals.
  does_own_buffer = function(context)
    return context.bufname:match('term://') ~= nil
  end,

  on_vim_enter = function(event)
    -- Open the drawer on startup.
    event.instance.open({
      focus = false,
    })

    -- Example keymaps:
    -- C-`: focus the drawer.
    -- <leader>tn: open a new terminal.
    -- <leader>tt: go to the next terminal.
    -- <leader>tT: go to the previous terminal.
    -- <leader>tz: zoom the terminal.
    vim.keymap.set('n', '<C-`>', function()
      event.instance.focus_or_toggle()
    end)
    vim.keymap.set('t', '<C-`>', function()
      event.instance.focus_or_toggle()
    end)
    vim.keymap.set('n', '<leader>tn', function()
      event.instance.open({ mode = 'new' })
    end)
    vim.keymap.set('n', '<leader>tt', function()
      event.instance.go(1)
    end)
    vim.keymap.set('n', '<leader>tT', function()
      event.instance.go(-1)
    end)
    vim.keymap.set('n', '<leader>tz', function()
      event.instance.toggle_zoom()
    end)
  end,

  -- When a new buffer is created, switch it to a terminal.
  on_did_create_buffer = function()
    vim.fn.termopen(os.getenv('SHELL'))
  end,

  -- Remove some UI elements.
  on_did_open_buffer = function()
    vim.opt_local.number = false
    vim.opt_local.signcolumn = 'no'
    vim.opt_local.statuscolumn = ''
  end,

  -- Scroll to the end when changing tabs.
  on_did_open = function()
    vim.cmd('$')
  end,
})

nvim-tree

Examples.-.nvim-tree.mp4
local drawer = require('nvim-drawer')

drawer.create_drawer({
  size = 40,
  position = 'right',
  should_reuse_previous_bufnr = false,
  should_close_on_bufwipeout = false,

  on_vim_enter = function(event)
    --- Open the drawer on startup.
    event.instance.open({
      focus = false,
    })

    --- Example mapping to toggle.
    vim.keymap.set('n', '<leader>e', function()
      event.instance.focus_or_toggle()
    end)
  end,

  --- Ideally, we would just call this here and be done with it, but
  --- mappings in nvim-tree don't seem to apply when re-using a buffer in
  --- a new tab / window.
  on_did_create_buffer = function()
    local nvim_tree_api = require('nvim-tree.api')
    nvim_tree_api.tree.open({ current_window = true })
  end,

  --- This gets the tree to sync when changing tabs.
  on_did_open = function()
    local nvim_tree_api = require('nvim-tree.api')
    nvim_tree_api.tree.reload()

    vim.opt_local.number = false
    vim.opt_local.signcolumn = 'no'
    vim.opt_local.statuscolumn = ''
  end,

  --- Cleans up some things when closing the drawer.
  on_did_close = function()
    local nvim_tree_api = require('nvim-tree.api')
    nvim_tree_api.tree.close()
  end,
})

nvim-spectre

Screen.Recording.2024-09-08.at.7.22.04.PM.mov.mp4
local drawer = require('nvim-drawer')

drawer.create_drawer({
  position = 'below',
  size = 30,

  does_own_window = function(context)
    return context.bufname:match('spectre') ~= nil
  end,

  on_vim_enter = function(event)
    vim.keymap.set('n', '<leader>S', function()
      -- If the drawer has never been opened, call spectre. Once its
      -- window opens, it will be claimed by the drawer, and we can use
      -- the drawer API afterwards.
      if
        #vim.tbl_keys(event.instance.state.windows_and_buffers) == 0
      then
        require('spectre').toggle()
      else
        event.instance.focus_or_toggle()
      end
    end)
  end,

  -- Remove some UI elements.
  on_did_open_buffer = function()
    vim.opt_local.number = false
    vim.opt_local.signcolumn = 'no'
    vim.opt_local.statuscolumn = ''
  end,
})

NOTES.md / .plan

Examples.-.Notes.mp4
local drawer = require('nvim-drawer')

drawer.create_drawer({
  position = 'float',
  -- Technically unused when using `position = 'float'`.
  size = 40,

  win_config = {
    anchor = 'NC',
    margin = 2,
    border = 'rounded',
    width = '100%',
    height = 10,
  },

  -- Automatically claim any opened NOTES.md file.
  does_own_buffer = function(context)
    return context.bufname:match('NOTES.md') ~= nil
  end,

  on_vim_enter = function(event)
    vim.keymap.set('n', '<leader>nn', function()
      event.instance.focus_or_toggle()
    end)
    vim.keymap.set('n', '<leader>nz', function()
      event.instance.toggle_zoom()
    end)
  end,

  on_did_create_buffer = function()
    vim.cmd('edit NOTES.md')
  end,
})

API

API.md

Alternatives

About

A Drawer plugin for Neovim. AKA "persistent split" / "persistent window".

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published