Skip to content

Clipboard manager neovim plugin with telescope integration

Notifications You must be signed in to change notification settings

mitchthorson/nvim-neoclip.lua

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

83 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

nvim-neoclip.lua

This is a story about Bob ๐Ÿ‘ท.

Bob loves vim โค๏ธ.

Bob likes to yank ยฉ๏ธ.

Bob knows about registers but sometimes forgets them ยฎ๏ธ.

This is what happens to Bob everyday ๐Ÿšง:

  • Bob yanks some line. ๐Ÿ˜€
  • Bob yanks another line. ๐Ÿค”
  • Bob realises he actually wanted the first. ๐Ÿ™
  • But it is gone and Bob is now sad. ๐Ÿ˜ข

Don't be like Bob, use neoclip! ๐ŸŽ‰

neoclip is a clipboard manager for neovim inspired by for example clipmenu. It records everything that gets yanked in your vim session (up to a limit which is by default 1000 entries but can be configured). You can then select an entry in the history using telescope or fzf-lua which then gets populated in a register of your choice.

If you're on latest nightly (works if :echo exists('##RecordingLeave') returns 1) neoclip will also keep track of any recorded macro (opt-out) which you can search for using telescope, put back in a register or simply replay.

That's it!

Oh, some more things, you can define an optional filter if you don't want some things to be saved and custom actions to take.

Hold on, neoclip optionally also supports persistent history between sessions powered by sqlite.lua.

neoclip

Installation

use {
  "AckslD/nvim-neoclip.lua",
  requires = {
    -- you'll need at least one of these
    -- {'nvim-telescope/telescope.nvim'},
    -- {'ibhagwan/fzf-lua'},
  }
  config = function()
    require('neoclip').setup()
  end,
}

When require('neoclip').setup() is called, only the autocommand (for TextYankPost event) is setup to save yanked things. This means that telescope is not required at this point if you lazy load it.

If you want to use persistent history between sessions you also need sqlite.lua installed, for example by:

use {
  "AckslD/nvim-neoclip.lua",
  requires = {
    {'tami5/sqlite.lua', module = 'sqlite'},
    -- you'll need at least one of these
    -- {'nvim-telescope/telescope.nvim'},
    -- {'ibhagwan/fzf-lua'},
  }
  config = function()
    require('neoclip').setup()
  end,
}

Configuration

You can configure neoclip by passing a table to setup (all are optional). The following are the defaults and the keys are explained below:

use {
  "AckslD/nvim-neoclip.lua",
  config = function()
    require('neoclip').setup({
      history = 1000,
      enable_persistent_history = false,
      db_path = vim.fn.stdpath("data") .. "/databases/neoclip.sqlite3",
      filter = nil,
      preview = true,
      default_register = '"',
      default_register_macros = 'q',
      enable_macro_history = true,
      content_spec_column = false,
      on_paste = {
        set_reg = false,
      },
      on_replay = {
        set_reg = false,
      },
      keys = {
        telescope = {
          i = {
            select = '<cr>',
            paste = '<c-p>',
            paste_behind = '<c-k>',
            replay = '<c-q>',
            custom = {},
          },
          n = {
            select = '<cr>',
            paste = 'p',
            paste_behind = 'P',
            replay = 'q',
            custom = {},
          },
        },
        fzf = {
          select = 'default',
          paste = 'ctrl-p',
          paste_behind = 'ctrl-k',
          custom = {},
        },
      },
    })
  end,
}
  • history: The max number of entries to store (default 1000).
  • enable_persistent_history: If set to true the history is stored on VimLeavePre using sqlite.lua and lazy loaded when querying.
  • db_path: The path to the sqlite database to store history if enable_persistent_history=true. Defaults to vim.fn.stdpath("data") .. "/databases/neoclip.sqlite3 which on my system is ~/.local/share/nvim/databases/neoclip.sqlite3
  • filter: A function to filter what entries to store (default all are stored). This function filter should return true (include the yanked entry) or false (don't include it) based on a table as the only argument, which has the following keys:
    • event: The event from TextYankPost (see :help TextYankPost for which keys it contains).
    • filetype: The filetype of the buffer where the yank happened.
    • buffer_name: The name of the buffer where the yank happened.
  • preview: Whether to show a preview (default) of the current entry or not. Useful for for example multiline yanks. When yanking the filetype is recorded in order to enable correct syntax highlighting in the preview. NOTE: in order to use the dynamic title showing the type of content and number of lines you need to configure telescope with the dynamic_preview_title = true option.
  • default_register: What register to use by default when not specified (e.g. Telescope neoclip). Can be a string such as '"' (single register) or a table of strings such as {'"', '+', '*'}.
  • default_register_macros: What register to use for macros by default when not specified (e.g. Telescope macroscope).
  • enable_macro_history: If true (default) any recorded macro will be saved, see macros.
  • content_spec_colunm: Can be set to true (default false) to use instead of the preview. It will only show the type and number of lines next to the first line of the entry.
  • on_paste:
    • set_reg: if the register should be populated when pressing the key to paste directly.
  • on_replay:
    • set_reg: if the register should be populated when pressing the key to replay a recorded macro.
  • keys: keys to use for the different pickers (telescope and fzf-lua). With telescope normal key-syntax is supported and both insert i and normal mode n. With fzf-lua only insert mode is supported and fzf-style key-syntax needs to be used. You can also use the custom entry to specify custom actions to take on certain key-presses, see below for more details. NOTE: these are only set in the telescope buffer and you need to setup your own keybindings to for example open telescope.

See screenshot section below for how the settings above might affect the looks.

Custom actions

You can specify custom actions in the keys entry in the settings. For example you can do:

require('neoclip').setup({
  ...
  keys = {
    ...
    n = {
      ...
      custom = {
        ['<space>'] = function(opts)
          print(vim.inspect(opts))
        end,
      },
    },
  },
})

which when pressing <space> in normal mode will print something like:

{
  register_names = { '"' },
  entry = {
    contents = { "which when pressing `<space>` in normal mode will print something like:" },
    filetype = "markdown",
    regtype = "l"
  }
}

to do your custom action and also populate a register and/or paste you can call neoclips built-in handlers, such as:

require('neoclip').setup({
  ...
  keys = {
    ...
    n = {
      ...
      custom = {
        ['<space>'] = function(opts)
          -- do your stuff
          -- ...
          local handlers = require('neoclip.handlers')
          -- optionally set the registers with the entry
          -- handlers.set_registers(opts.register_names, opts.entry)
          -- optionally paste entry
          -- handlers.paste(opts.entry, 'p')
          -- optionally paste entry behind
          -- handlers.paste(opts.entry, 'P')
        end,
      },
    },
  },
})

Usage

Yanks

Yank all you want and then do:

:Telescope neoclip

if using telescope or

:lua require('neoclip.fzf')()

if using fzf-lua, which will show you a history of the yanks that happened in the current session. If you pick (default <cr>) one this will then replace the current " (unnamed) register.

If you instead want to directly paste it you can press by default <c-p> in insert mode and p in normal. Paste behind is by default <c-k> and P respectively.

If you want to replace another register with an entry from the history you can do for example:

:Telescope neoclip a

if using telescope or

:lua require('neoclip.fzf')('a')

if using fzf-lua, which will replace register a. The register [0-9a-z] and default (") are supported.

The following special registers are support:

  • ": Telescope neoclip unnamed
  • *: Telescope neoclip star
  • +: Telescope neoclip plus

and Telescope neoclip (and Telescope neoclip default) will use what you set default_register in the setup.

You can also specify more registers to populate in a single command with the extra keyword argument which supports registers separated by comma, for example:

:Telescope neoclip a extra=star,plus,b

if using telescope or

:lua require('neoclip.fzf')({'a', 'star', 'plus', 'b'})

if using fzf-lua.

Macros

If enable_macro_history is set to true (default) in the setup then any recorded macro will be stored and can later be accessed using:

:Telescope macroscope

or equivalently (which is probably the better way if you're lazy loading telescope):

:lua require('telescope').extensions.macroscope.default()

The same arguments are supported as for the neoclip extension.

NOTE: This feature requires latest nightly and in particular this PR. You can check that your neovim supports this by checking that :echo exists('##RecordingLeave') returns 1. If not then everything will work normally except that no macro will be saved in the history of neoclip.

Start/stop

If you temporarily don't want neoclip to record anything you can use the following calls:

  • :lua require('neoclip').start()
  • :lua require('neoclip').stop()
  • :lua require('neoclip').toggle()

Tips

  • If you lazy load telescope with packer with for example the key module = telescope, then it's better to use e.g. :lua require('telescope').extensions.neoclip.default() than :Telescope neoclip (or :lua require('telescope').extensions.neoclip['<reg>']() over :Telescope neoclip <reg>) for keybindings since it will properly load telescope before calling the extension.
  • If you don't want to store pure whitespace yanks you could specify a filter as:
    local function is_whitespace(line)
      return vim.fn.match(line, [[^\s*$]]) ~= -1
    end
    
    local function all(tbl, check)
      for _, entry in ipairs(tbl) do
        if not check(entry) then
          return false
        end
      end
      return true
    end
    
    require('neoclip').setup{
      ...
      filter = function(data)
        return not all(data.event.regcontents, is_whitespace)
      end,
      ...
    }

Troubleshooting

  • For some plugin managers it seems necessary to do
    :lua require('telescope').load_extension('neoclip')
    
    before being able to call :Telescope neoclip (packer does not seem to need this). However, :lua require('telescope').extensions.neoclip.default() seems to work without having to load. It also seems that calling through lua seems necessary to play well with the (optional) persistent history if you're using vim-plug, see discussion here for details. If you find out what is causing this, I'd be very happy to know :)
  • If using packer, don't forget to PackerCompile after adding the plugin.

Thanks

  • Thanks @cdown for the inspiration with clipmenu.
  • Thanks @fdschmidt93 for help understanding some telescope concepts.
  • Thanks @ibhagwan for providing the code example to support fzf-lua.

Screenshots

preview = true and content_spec_column = false

preview

preview = false and content_spec_column = true

content_spec_column

preview = false and content_spec_column = false

clean

About

Clipboard manager neovim plugin with telescope integration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Lua 99.1%
  • Makefile 0.9%