Home

Awesome

Intro

nvim-deck : A plugin for displaying, filtering, and selecting items from customizable lists.

<!-- panvimdoc-ignore-start -->

Demo

<details> <summary>Open project files</summary> <video src="https://github.com/user-attachments/assets/69581231-6bb1-46fb-8320-ed4de9caa13e"></video> </details> <details> <summary>Grep and Replace</summary> <video src="https://github.com/user-attachments/assets/f038e149-f9ff-42ec-9114-9a84dfe88426"></video> </details> <details> <summary>Git integration</summary> <video src="https://github.com/user-attachments/assets/997bb045-d0b4-49f8-a09b-f1391c2a769b"></video> </details> <!-- panvimdoc-ignore-end -->

Concept

nvim-deck revolves around four core concepts:

Features

Why nvim-deck?

Setup

Here’s an example of how to set up nvim-deck:

local deck = require('deck')

-- Apply pre-defined easy settings.
-- For manual configuration, refer to the code in `deck/easy.lua`.
require('deck.easy').setup()

-- Set up buffer-specific key mappings for nvim-deck.
vim.api.nvim_create_autocmd('User', {
  pattern = 'DeckStart',
  callback = function(e)
    local ctx = e.data.ctx --[[@as deck.Context]]
    ctx.keymap('n', '<Esc>', function()
      ctx.set_preview_mode(false)
    end)
    ctx.keymap('n', '<Tab>', deck.action_mapping('choose_action'))
    ctx.keymap('n', '<C-l>', deck.action_mapping('refresh'))
    ctx.keymap('n', 'i', deck.action_mapping('prompt'))
    ctx.keymap('n', 'a', deck.action_mapping('prompt'))
    ctx.keymap('n', '@', deck.action_mapping('toggle_select'))
    ctx.keymap('n', '*', deck.action_mapping('toggle_select_all'))
    ctx.keymap('n', 'p', deck.action_mapping('toggle_preview_mode'))
    ctx.keymap('n', 'd', deck.action_mapping('delete'))
    ctx.keymap('n', '<CR>', deck.action_mapping('default'))
    ctx.keymap('n', 'o', deck.action_mapping('open'))
    ctx.keymap('n', 'O', deck.action_mapping('open_keep'))
    ctx.keymap('n', 's', deck.action_mapping('open_split'))
    ctx.keymap('n', 'v', deck.action_mapping('open_vsplit'))
    ctx.keymap('n', 'N', deck.action_mapping('create'))
    ctx.keymap('n', '<C-u>', deck.action_mapping('scroll_preview_up'))
    ctx.keymap('n', '<C-d>', deck.action_mapping('scroll_preview_down'))

    -- If you want to start the filter by default, call ctx.prompt() here
    ctx.prompt()
  end
})

-- Example key bindings for launching nvim-deck sources. (These mapping required `deck.easy` calls.)
vim.keymap.set('n', '<Leader>ff', '<Cmd>Deck files<CR>', { desc = 'Show recent files, buffers, and more' })
vim.keymap.set('n', '<Leader>gr', '<Cmd>Deck grep<CR>', { desc = 'Start grep search' })
vim.keymap.set('n', '<Leader>gi', '<Cmd>Deck git<CR>', { desc = 'Open git launcher' })
vim.keymap.set('n', '<Leader>he', '<Cmd>Deck helpgrep<CR>', { desc = 'Live grep all help tags' })

-- Show the latest deck context.
vim.keymap.set('n', '<Leader>;', function()
  local ctx = require('deck').get_history()[1]
  if ctx then
    ctx.show()
  end
end)

-- Do default action on next item.
vim.keymap.set('n', '<Leader>n', function()
  local ctx = require('deck').get_history()[1]
  if ctx then
    ctx.set_cursor(ctx.get_cursor() + 1)
    ctx.do_action('default')
  end
end)

Customization

!!! We strongly recommend using lua-language-server !!!

Appearance

You can customize your nvim-deck appearance.

  1. guicursor
require('deck').setup({
  guicursor = 'a:ver25',
})
  1. winhighlight
vim.api.nvim_create_autocmd('User', {
  group = misc.group,
  pattern = 'DeckShow',
  callback = function()
    vim.wo.winhighlight = 'CursorLine:Visual'
  end
})

Source

The source is typed as |deck.Source|. The source can be executed by deck.start().

The source can specify source-level actions, decorators, and previewers.

require('deck').start({
  name = 'my_source',
  execute = function(ctx)
    ctx.item({
      display_text = 'Hello, World!',
    })
    ctx.done()
  end,
  actions = {
    ...
  },
  decorators = {
    ...
  },
  previewers = {
    ...
  },
})

Action

The action is typed as |deck.Action| and can be registered below three different levels.

  1. Config Action : Provided by a start configuration.

This level can be registered by start_config.actions field.

require('deck').start(some_of_the_source, {
  actions = {
    {
      name = 'my_open',
      execute = function(ctx)
        -- some of the process.
      end
    }
  }
})
  1. Source Action : Provided by a source.

This level of actions can be registered by source.actions field.

require('deck').start({
  name = 'my_source',
  ...
  actions = {
    {
      name = 'my_open',
      execute = function(ctx)
        -- some of the process.
      end
    }
  }
})
  1. Global Action : Registered globally.

This level of actions can be registered by deck.register_action().

require('deck').register_action({
  name = 'my_open',
  resolve = function(ctx)
    -- Action is available only if there is exactly one action item with a filename.
    return #ctx.get_action_items() == 1 and ctx.get_action_items()[1].data.filename
  end,
  execute = function(ctx)
    -- Open the file.
    vim.cmd.edit(ctx.get_action_items()[1].data.filename)
  end
})

Note: The same name actions are choosen in the order of 1 -> 2 -> 3.

StartPreset

The start-preset is typed as |deck.StartPreset| and can be registered globally.

A start-preset in nvim-deck allows you to define shortcut command. In the below example, you can use :Deck recent command.

-- After registration, you can start the preset using the `:Deck recent` command.
require('deck').register_start_preset('recent', {
  require('deck').start({
    require('deck.builtin.source.recent_files')(),
    require('deck.builtin.source.buffers')(),
  })
})

Decorator

nvim-deck has decorator concept. It's designed to decorate the deck-buffer via nvim_buf_set_extmark. The below example shows how to create your own decorator.

--- This is example decorator.
--- To display the basename of the file and dirname as a comment.
--- This decorator highlight basename and make dirname less noticeable.
require('deck').register_decorator({
  name = 'basename_dirname',
  resolve = function(_, item)
    -- This decorator is available only if the item has a filename.
    return item.data.filename
  end,
  decorate = function(ctx, item, row)
    local dirname = vim.fn.fnamemodify(item.data.filename, ':~:h')
    local display_text = item.display_text
    local s, e = display_text:find(dirname, 1, true)
    if s then
      return {
        -- Hide the directory part (using conceal)
        {
          row = row,
          col = s - 1,
          end_row = row,
          end_col = e + 1,
          conceal = '',
          ephemeral = true,
        },
        -- Display the directory name as a comment at the end of the line
        {
          row = row,
          col = 0,
          virt_text = { { dirname, 'Comment' } },
          virt_text_pos = 'eol'
        }
      }
    end
    return {}
  end
})

Previewer

nvim-deck has previewer concept. It's designed to show the item preview.

require('deck').register_previewer({
  name = 'bat',
  resolve = function(_, item)
    return item.data.filename and vim.fn.filereadable(item.data.filename) == 1
  end,
  preview = function(_, item, env)
    vim.api.nvim_win_call(env.win, function()
      vim.fn.termopen(('bat --color=always %s'):format(item.data.filename))
    end)
  end
})

Built-in

Sources

<!-- auto-generate-s:source -->

buffers

Show buffers.

NameTypeDefaultDescription
ignore_pathsstring[]?[vim.fn.expand('%:p')]Ignore paths. The default value is intented to hide current buffer.
nofileboolean?falseIgnore nofile buffers.
deck.start(require('deck.builtin.source.buffers')({
  ignore_paths = { vim.fn.expand('%:p'):gsub('/$', '') },
  nofile = false,
}))

deck.actions

Show available actions from |deck.Context|

NameTypeDefaultDescription
context|deck.Context|
deck.start(require('deck.builtin.source.deck.actions')({
  context = context
}))

deck.history

Show deck.start history.

No options

deck.start(require('deck.builtin.source.deck.history')())

files

Show files under specified root directory.

NameTypeDefaultDescription
ignore_globsstring[]?[]Ignore glob patterns.
root_dirstringTarget root directory.
deck.start(require('deck.builtin.source.files')({
  root_dir = vim.fn.getcwd(),
  ignore_globs = { '**/node_modules/', '**/.git/' },
}))

git

Show git launcher.

NameTypeDefaultDescription
cwdstringTarget git root.
deck.start(require('deck.builtin.source.git.changeset')({
  cwd = vim.fn.getcwd(),
}))

git.branch

Show git branches

NameTypeDefaultDescription
cwdstringTarget git root.
deck.start(require('deck.builtin.source.git.branch')({
  cwd = vim.fn.getcwd()
}))

git.changeset

Show git changeset for specified revision.

NameTypeDefaultDescription
cwdstringTarget git root.
from_revstringFrom revision.
to_revstring?To revision. If you omit this option, it will be HEAD.
deck.start(require('deck.builtin.source.git.changeset')({
  cwd = vim.fn.getcwd(),
  from_rev = 'HEAD~3',
  to_rev = 'HEAD'
}))

git.log

Show git log.

NameTypeDefaultDescription
cwdstringTarget git root.
max_countinteger?Max count for log
deck.start(require('deck.builtin.source.git.log')({
  cwd = vim.fn.getcwd(),
}))

git.reflog

Show git reflog.

NameTypeDefaultDescription
cwdstringTarget git root.
max_countinteger?Max count for reflog
deck.start(require('deck.builtin.source.git.reflog')({
  cwd = vim.fn.getcwd(),
}))

git.remote

Show git remotes.

NameTypeDefaultDescription
cwdstringTarget git root.
deck.start(require('deck.builtin.source.git.remote')({
  cwd = vim.fn.getcwd(),
}))

git.status

Show git status.

NameTypeDefaultDescription
cwdstringTarget git root.
deck.start(require('deck.builtin.source.git.status')({
  cwd = vim.fn.getcwd(),
}))

grep

Grep files under specified root directory. (required ripgrep)

NameTypeDefaultDescription
root_dirstringTarget root directory.
patternstring?Grep pattern. If you omit this option, you must set dynamic option to true.
dynamicboolean?falseIf true, use dynamic pattern. If you set this option to false, you must set pattern option.
ignore_globsstring[]?[]Ignore glob patterns.
deck.start(require('deck.builtin.source.grep')({
  root_dir = vim.fn.getcwd(),
  pattern = vim.fn.input('grep: '),
  ignore_globs = { '**/node_modules/', '**/.git/' },
}))

helpgrep

Live grep all helptags. (required ripgrep)

No options

deck.start(require('deck.builtin.source.helpgrep')())

items

Listing any provided items.

NameTypeDefaultDescription
itemsstring[]|deck.ItemSpecifier[]Items to list.
deck.start(require('deck.builtin.source.items')({
  items = vim.iter(vim.api.nvim_list_bufs()):map(function(buf)
    return ('#%s'):format(buf)
  end):totable()
}))

recent_dirs

List recent directories.

NameTypeDefaultDescription
ignore_pathsstring[]?[]Ignore paths.
deck.start(require('deck.builtin.source.recent_dirs')({
  ignore_paths = { '**/node_modules/', '**/.git/' },
}))

recent_files

List recent files.

NameTypeDefaultDescription
ignore_pathsstring[]?[]Ignore paths.
deck.start(require('deck.builtin.source.recent_files')({
  ignore_paths = { '**/node_modules/', '**/.git/' },
}))
<!-- auto-generate-e:source -->

Actions

<!-- auto-generate-s:action --> <!-- auto-generate-e:action -->

Autocmd

<!-- auto-generate-s:autocmd --> <!-- auto-generate-e:autocmd -->

API

<!-- auto-generate-s:api --> <!-- panvimdoc-include-comment deck.action_mapping(mapping): fun(ctx: |deck.Context|) ~ --> <!-- panvimdoc-ignore-start -->

deck.action_mapping(mapping): fun(ctx: |deck.Context|)

<!-- panvimdoc-ignore-end -->

Create action mapping function for ctx.keymap.

NameTypeDescription
action_namesstring\string[]

 

<!-- panvimdoc-include-comment deck.alias_action(alias_name, alias_action_name): |deck.Action| ~ --> <!-- panvimdoc-ignore-start -->

deck.alias_action(alias_name, alias_action_name): |deck.Action|

<!-- panvimdoc-ignore-end -->

Create alias action.

NameTypeDescription
alias_namestringnew action name.
alias_action_namestringexisting action name.

 

<!-- panvimdoc-include-comment deck.get_actions(): |deck.Action|[] ~ --> <!-- panvimdoc-ignore-start -->

deck.get_actions(): |deck.Action|[]

<!-- panvimdoc-ignore-end -->

Get all registered actions.

No arguments  

<!-- panvimdoc-include-comment deck.get_decorators(): |deck.Decorator|[] ~ --> <!-- panvimdoc-ignore-start -->

deck.get_decorators(): |deck.Decorator|[]

<!-- panvimdoc-ignore-end -->

Get all registered decorators.

No arguments  

<!-- panvimdoc-include-comment deck.get_history(): |deck.Context|[] ~ --> <!-- panvimdoc-ignore-start -->

deck.get_history(): |deck.Context|[]

<!-- panvimdoc-ignore-end -->

Get all history (first history is latest).

No arguments  

<!-- panvimdoc-include-comment deck.get_previewers(): |deck.Previewer|[] ~ --> <!-- panvimdoc-ignore-start -->

deck.get_previewers(): |deck.Previewer|[]

<!-- panvimdoc-ignore-end -->

Get all registered previewers.

No arguments  

<!-- panvimdoc-include-comment deck.get_start_presets(): |deck.StartPreset|[] ~ --> <!-- panvimdoc-ignore-start -->

deck.get_start_presets(): |deck.StartPreset|[]

<!-- panvimdoc-ignore-end -->

Get all registered start presets.

No arguments  

<!-- panvimdoc-include-comment deck.register_action(action) ~ --> <!-- panvimdoc-ignore-start -->

deck.register_action(action)

<!-- panvimdoc-ignore-end -->

Register action.

NameTypeDescription
action|deck.Action|action to register.

 

<!-- panvimdoc-include-comment deck.register_decorator(decorator) ~ --> <!-- panvimdoc-ignore-start -->

deck.register_decorator(decorator)

<!-- panvimdoc-ignore-end -->

Register decorator.

NameTypeDescription
decorator|deck.Decorator|decorator to register.

 

<!-- panvimdoc-include-comment deck.register_previewer(previewer) ~ --> <!-- panvimdoc-ignore-start -->

deck.register_previewer(previewer)

<!-- panvimdoc-ignore-end -->

Register previewer.

NameTypeDescription
previewer|deck.Previewer|previewer to register.

 

<!-- panvimdoc-include-comment deck.register_start_preset(name, start_fn) ~ --> <!-- panvimdoc-ignore-start -->

deck.register_start_preset(name, start_fn)

<!-- panvimdoc-ignore-end -->

Register start_preset.

NameTypeDescription
namestringpreset name.
start_fnfun()Start function.

 

<!-- panvimdoc-include-comment deck.register_start_preset(start_preset) ~ --> <!-- panvimdoc-ignore-start -->

deck.register_start_preset(start_preset)

<!-- panvimdoc-ignore-end -->

Register start_preset.

NameTypeDescription
start_presetdeck.StartPreset|deck.StartPreset|

 

<!-- panvimdoc-include-comment deck.remove_actions(predicate) ~ --> <!-- panvimdoc-ignore-start -->

deck.remove_actions(predicate)

<!-- panvimdoc-ignore-end -->

Remove specific action.

NameTypeDescription
predicatefun(action: |deck.Action|): booleanPredicate function. If return true, remove action.

 

<!-- panvimdoc-include-comment deck.remove_decorators(predicate) ~ --> <!-- panvimdoc-ignore-start -->

deck.remove_decorators(predicate)

<!-- panvimdoc-ignore-end -->

Remove specific decorator.

NameTypeDescription
predicatefun(decorator: |deck.Decorator|): booleanPredicate function. If return true, remove decorator.

 

<!-- panvimdoc-include-comment deck.remove_previewers(predicate) ~ --> <!-- panvimdoc-ignore-start -->

deck.remove_previewers(predicate)

<!-- panvimdoc-ignore-end -->

Remove previewer.

NameTypeDescription
predicatefun(previewer: |deck.Previewer|): booleanPredicate function. If return true, remove previewer.

 

<!-- panvimdoc-include-comment deck.remove_start_presets(predicate) ~ --> <!-- panvimdoc-ignore-start -->

deck.remove_start_presets(predicate)

<!-- panvimdoc-ignore-end -->

Remove specific start_preset.

NameTypeDescription
predicatefun(start_preset: |deck.StartPreset|): booleanPredicate function. If return true, remove start_preset.

 

<!-- panvimdoc-include-comment deck.setup(config) ~ --> <!-- panvimdoc-ignore-start -->

deck.setup(config)

<!-- panvimdoc-ignore-end -->

Setup deck globally.

NameTypeDescription
configdeck.ConfigSpecifierSetup deck configuration.

 

<!-- panvimdoc-include-comment deck.start(sources, start_config): |deck.Context| ~ --> <!-- panvimdoc-ignore-start -->

deck.start(sources, start_config): |deck.Context|

<!-- panvimdoc-ignore-end -->

Start deck with given sources.

NameTypeDescription
sourcedeck.Source\deck.Source[]
start_configdeck.StartConfigSpecifierstart configuration.

 

<!-- auto-generate-e:api -->

Type

<!-- auto-generate-s:type -->
*deck.Action*
---@class deck.Action
---@field public name string
---@field public desc? string
---@field public hidden? boolean
---@field public resolve? deck.ActionResolveFunction
---@field public execute deck.ActionExecuteFunction
*deck.Config*
---@class deck.Config: deck.ConfigSpecifier
---@field public guicursor? string
---@field public max_history_size integer
---@field public default_start_config? deck.StartConfigSpecifier
*deck.Context*
---@class deck.Context
---@field id integer
---@field ns integer
---@field buf integer
---@field name string
---@field execute fun()
---@field is_visible fun(): boolean
---@field show fun()
---@field hide fun()
---@field prompt fun()
---@field scroll_preview fun(delta: integer)
---@field get_status fun(): deck.Context.Status
---@field get_cursor fun(): integer
---@field set_cursor fun(cursor: integer)
---@field get_query fun(): string
---@field set_query fun(query: string)
---@field set_selected fun(item: deck.Item, selected: boolean)
---@field get_selected fun(item: deck.Item): boolean
---@field set_select_all fun(select_all: boolean)
---@field get_select_all fun(): boolean
---@field set_preview_mode fun(preview_mode: boolean)
---@field get_preview_mode fun(): boolean
---@field get_items fun(): deck.Item[]
---@field get_cursor_item fun(): deck.Item?
---@field get_action_items fun(): deck.Item[]
---@field get_filtered_items fun(): deck.Item[]
---@field get_selected_items fun(): deck.Item[]
---@field get_actions fun(): deck.Action[]
---@field get_decorators fun(): deck.Decorator[]
---@field get_previewer fun(): deck.Previewer?
---@field get_revision fun(): deck.Context.Revision
---@field get_source_names fun(): string[]
---@field sync fun(option: { count: integer })
---@field keymap fun(mode: string, lhs: string, rhs: fun(ctx: deck.Context))
---@field do_action fun(name: string)
---@field dispose fun()
---@field disposed fun(): boolean
---@field on_show fun(callback: fun())
---@field on_hide fun(callback: fun())
---@field on_dispose fun(callback: fun()): fun()
*deck.Decoration*
---@class deck.Decoration
---@field public col? integer
---@field public end_col? integer
---@field public hl_group? string
---@field public virt_text? deck.VirtualText[]
---@field public virt_text_pos? 'eol' | 'overlay' | 'right_align' | 'inline'
---@field public virt_text_win_col? integer
---@field public virt_text_hide? boolean
---@field public virt_text_repeat_linebreak? boolean
---@field public virt_lines? deck.VirtualText[][]
---@field public virt_lines_above? boolean
---@field public ephemeral? boolean
---@field public priority? integer
---@field public sign_text? string
---@field public sign_hl_group? string
---@field public number_hl_group? string
---@field public line_hl_group? string
---@field public conceal? boolean
*deck.Decorator*
---@class deck.Decorator
---@field public name string
---@field public dynamic? boolean
---@field public resolve? deck.DecoratorResolveFunction
---@field public decorate deck.DecoratorDecorateFunction
*deck.ExecuteContext*
---@class deck.ExecuteContext
---@field public item fun(item: deck.ItemSpecifier)
---@field public done fun( )
---@field public get_query fun(): string
---@field public aborted fun(): boolean
---@field public on_abort fun(callback: fun())
*deck.Item*
---@class deck.Item: deck.ItemSpecifier
---@field public display_text string
---@field public data table
*deck.Previewer*
---@class deck.Previewer
---@field public name string
---@field public resolve? deck.PreviewerResolveFunction
---@field public preview deck.PreviewerPreviewFunction
*deck.Source*
---@class deck.Source
---@field public name string
---@field public dynamic? boolean
---@field public events? { Start?: fun(ctx: deck.Context), BufWinEnter?: fun(ctx: deck.Context) }
---@field public execute deck.SourceExecuteFunction
---@field public actions? deck.Action[]
---@field public decorators? deck.Decorator[]
---@field public previewers? deck.Previewer[]
*deck.StartConfig*
---@class deck.StartConfig: deck.StartConfigSpecifier
---@field public name string
---@field public view fun(): deck.View
---@field public matcher deck.Matcher
---@field public history boolean
---@field public performance { interrupt_interval: integer, interrupt_timeout: integer }
*deck.StartPreset*
---@class deck.StartPreset
---@field public name string
---@field public args? table<string|integer, { complete?: (fun(prefix: string):string[]), required?: boolean }>
---@field public start fun(args: table<string|integer, string>)
*deck.View*
---@class deck.View
---@field public get_win fun(): integer?
---@field public is_visible fun(ctx: deck.Context): boolean
---@field public show fun(ctx: deck.Context)
---@field public hide fun(ctx: deck.Context)
---@field public prompt fun(ctx: deck.Context)
---@field public scroll_preview fun(ctx: deck.Context, delta: integer)
---@field public render fun(ctx: deck.Context)
<!-- auto-generate-e:type -->