checkmate.nvim

bngarrenMIT更新放缓

在 Neovim 里用 Markdown 管理待办事项的插件,支持任务列表、状态切换和查询,适合习惯纯文本记事的开发者。

A full-featured, Markdown-based Neovim todo plugin

v0.12.1发布于 2026-04-25 16:33 UTC378star8fork同步于 2026-11-07 07:33 UTC

怎么上手

由 README 提炼,执行前请确认
{
  "bngarren/checkmate.nvim",
  ft = "markdown",
  opts = {}
}
硬件要求按 README 说明

README 未说明,仅要求 Neovim 0.10 或更高版本

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

基于 Markdown 的 Neovim 待办插件,让你在编辑器里直接创建、切换和归档任务

在 Neovim 里用 Markdown 管理待办事项的插件,支持任务列表、状态切换和查询,适合习惯纯文本记事的开发者。

核心能力

  • 用 Markdown 复选框语法创建和切换待办
  • 支持 @tag(value) 元数据注解并可自定义
  • 支持自定义状态如进行中、搁置、部分完成
  • 自动归档已完成项并统计完成数量与百分比
  • 集成 snacks.nvim、mini.pick、telescope 选择器

容易踩的坑

仅在 markdown filetype 下激活,无扩展名文件需手动 :set filetype=markdown;默认只对 todo、TODO、todo.md、TODO.md 及 .todo 扩展名文件生效,其他文件需配置 files;预发布版本建议在 lazy spec 中加 version 锁定小版本

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.12.1
  1. v0.12.1最新正式版2026-04-25 · 2026-04-25 16:33 UTC

    0.12.1 (2026-04-25)

    Bug Fixes

    • properly handle symlinks during write (#226) (79636d1)
  2. v0.12.02025-11-15 · 2025-11-15 20:59 UTC
    查看更新说明

    0.12.0 (2025-11-15)

    ⚠ BREAKING CHANGES

    • API return values standardized Public API functions now return consistent boolean values indicating whether an action was queued/scheduled, not whether the buffer was immediately updated. Since many updates occur asynchronously or within transactions, the return value was often ambiguous. This is only breaking if your configuration or plugin code was explicitly depending on previous return values (rare).
    • allow create to use position opt in visual mode (#207) This augments the existing behavior of the create API. Visual-mode create previously only converted non-todo lines into todos. It now supports inserting new todos above/below the selection—matching normal-mode behavior.
    • Keymap configuration no longer merges with defaults The keys table now replaces defaults instead of merging. This avoids duplicated mappings and surprising behavior, but requires copying defaults manually when you want to modify only a subset.
    • Metadata callbacks (e.g., on_add, on_remove, on_change) now receive the public checkmate.Todo type rather than the internal checkmate.TodoItem type This could break existing callbacks, but should not change capabilities as most functionality is still present on the Todo class.
    • ui.picker no longer accepts a function Users should now customize pickers via picker_opts or by providing a custom_picker function to APIs that support it. If this limits your workflow, please report an issue.
    • Hard deprecation (removal) of config's todo_markers (previously soft deprecated).

    Features

    Upgraded Picker System

    A major feature of this release is a brand new picker integration. We continue to provide out-of-the-box support for popular external pickers: snacks.nvim, mini.pick, and telescope. Checkmate will auto-detect an installed picker and/or allow extensive picker customization. (#220) (0dd9e91)

    • Added a checkmate.PickerOpts type that can used for per-call picker customization, allowing backend-specific overrides.
    • Refined the design for a custom_picker function utilized by some APIs (e.g. select_metadata_value, select_todo)
      • Your function receives Checkmate context
      • You drive the picker
      • You call the provided complete() callback to finalize behavior

    For example, this allows a user to call their own external picker to populate a list of values that can be used to update a metadata tag, e.g., a snacks Files picker. In other word, this allows you to leverage the power of dedicated picker plugins to update Checkmate todos.

    This also allows you to implement your own picker, e.g. fzf-lua, with Checkmate data passed in.

    Example: using a snacks. files picker to update a metadata value:

    require("checkmate").select_metadata_value({
      custom_picker = function(ctx, complete)
        require("snacks").picker.files({
          confirm = function(picker, item)
            if item then
              vim.schedule(function()
                complete(item.text)
              end)
            end
            picker:close()
          end,
        })
      end,
    })
    
    • Add opts to select_metadata_value that allows:
      • Using a position opt to locate the metadata rather than default which is cursor position (#212) (57ea1d0)
      • Customizing the picker via picker_opts (see checkmate.PickerOpts type)
      • The user to pass a bespoke picker implementation via custom_picker function that receives the metadata context and a complete() callback (#205) ([0b70ee6](h
  3. v0.11.22025-10-20 · 2025-10-20 14:25 UTC
    查看更新说明

    0.11.2 (2025-10-11)

    Bug Fixes

    • metadata: remove picker highlight and animation (#199) (5ffa4af)
  4. v0.11.12025-10-10 · 2025-10-10 18:28 UTC
    查看更新说明

    0.11.1 (2025-10-10)

    Bug Fixes

    • config: improve config validation and checkhealth (#191) (cd9948f)
    • highlights: adds nil check to Normal group 'fg' to address #196 (#197) (4aeb383)
    • typo in select_metadata_value() api (#195) (9d73cde)
  5. v0.11.02025-10-01 · 2025-10-01 11:42 UTC
    查看更新说明

    0.11.0 (2025-10-01)

    ⚠ BREAKING CHANGES

    • api: changes intended behavior of archive to include all "completed" state type (#186). Previously, archive functionality only collected todos with the "checked" state. However, with the introduction of custom todo states and state_types, this change now aligns with user expectations, i.e. all "completed" type todos are collected for archival.

    Major Features

    • add remove API that converts todo lines back to regular text (#172) ([8f84322]. (https://github.com/bngarren/checkmate.nvim/commit/8f843227f180148f1ce699c8c1e9b2ebf6002d85)). This includes options for preserve_list_marker and remove_metadata (both true by default).
    • improve create API and add 'list continuation' feature (#170) (882b34d). This is a major upgrade todo creation capabilities, expanding the create API for more customization. Additionally, this release adds "todo list continuation" feature which allows for the automatic creation of new todo lines in insert mode. By default <CR> creates a new todo line underneath and <S-CR> creates a nested todo. These list_continuation opts, including the insert mode keymapping are configurable, allowing users to integrate with other list continuation plugins, e.g. auto-pairs.

    Minor Features

    • highlights: improve performance, especially for Insert mode and large buffers (#176) (fd26dc8)
    • update get_todo API (#174) (af9fd70). This also addresses an off-by-one bug in get_todo position suggested in #183.

    Bug Fixes

    • remove() todo bug when metadata with on_remove handler is removed (#177) (5f41aab)
    • add 'redraw' before deferred highlighting to keep UI snappy (#181) (73da29d)
    • api: bugs with process_buffer triggering, autocmds and debouncing (d8f4c59)
    • api: changes intended behavior of archive to include all "completed" state type (#186) (7760aa0)
    • refactored and fixed archive system (#187) (ba6132b)
    • various bug fixes, some refactoring (#184) (d8f4c59)

    Performance Improvements

    • improve transaction callbacks (#178) (602ead8)
    • small fixes to debug profiler (#179) (ba8b813)
    • various fixes/refactors for e

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Get stuff done

    A Markdown-based todo/task plugin for Neovim.

    Features

    • Create and toggle Markdown todos
    • Customizable markers and styling
    • Compatible with popular Neovim Markdown plugins (e.g., render-markdown, markview)
    • Metadata e.g. @tag(value) annotations with extensive customization
      • e.g. @started, @done, @priority, @your-custom-tag
    • Todo completion counts/percentage
    • Smart toggling behavior
    • Archive (reorganize) completed todos
    • Todo templates with LuaSnip snippet integration
    • Custom todo states
      • More than just "checked" and "unchecked", e.g. "partial", "in-progress", "on-hold"
    • Automatic todo creation (list continuation in insert mode)
    • Out-of-the-box picker integration with support for snacks.nvim, mini.pick, telescope
    Check out the wiki for additional documentation and recipes, including:

    https://github.com/user-attachments/assets/d5fa2fc8-085a-4cee-9763-a392d543347e

    Table of Contents

    Installation

    Requirements

    • Neovim 0.10 or higher

    Using lazy.nvim

    {
      "bngarren/checkmate.nvim",
      ft = "markdown", -- Lazy loads for Markdown files matching patterns in 'files'
      opts = {
        -- files = { "*.md" }, -- any .md file (instead of defaults)
      },
    }
    

    If you'd like stable-ish version during pre-release, can add a minor version to the lazy spec:

    {
      version = "~0.12.0" -- pins to minor 0.12.x
    }
    

    Usage

    1. Open or Create a Todo File

    Checkmate automatically activates when you open a Markdown file that matches your configured file name patterns.

    Default patterns:

    • todo or TODO (exact filename)
    • todo.md or TODO.md
    • Files with .todo extension (e.g., project.todo, work.todo.md)

    [!NOTE] Checkmate only activates for files with the "markdown" filetype. Files without extensions need their filetype set to markdown (:set filetype=markdown)

    You can customize which files activate Checkmate using the files configuration option:

    files = {
      "*.md",              -- Any markdown file (basename matching)
      "**/todo.md",        -- 'todo.md' anywhere in directory tree
      "project/todo.md",   -- Any path ending with 'project/todo.md'
      "/absolute/path.md", -- Exact absolute path match
    }
    

    Patterns support Unix-style globs including *, **, ?, [abc], and {foo,bar}

    2. Create Todos

    • Use the mapped key (default: <leader>Tn) or the :Checkmate create command
    • Or manually using Markdown syntax:
    - [ ] Unchecked todo
    - [x] Checked todo
    

    (These will automatically convert when you leave insert mode!)

    3. Manage Your Tasks

    • Toggle items with :Checkmate toggle (default: <leader>Tt)
    • Check items with :Checkmate check (default: <leader>Tc)
    • Uncheck items with :Checkmate uncheck (default: <leader>Tu)
    • Cycle to other custom states with :Checkmate cycle_next (default: <leader>T=) and :Checkmate cycle_previous (default <leader>T-)
    • Select multiple items in visual mode and use the same commands
    • Archive completed todos with :Checkmate archive (default: <leader>Ta)
    • Search for your todos with :Checkmate select_todo or corresponding API

    Enhance your todos with custom metadata with quick keymaps!

    Your buffer is saved as regular Markdown which means it's compatible with any Markdown editor!

    Commands

    User commands

    :Checkmate [subcommand]

    subcommandDescription
    archiveArchive all completed todo items in the buffer. This extracts them and moves them to a bottom section. See api archive() and Archiving section.
    checkMark the todo item under the cursor as checked. See api check()
    createIn normal mode, converts the current line into a todo (or if already a todo, creates a sibling below). In visual mode, converts each selected line into a todo. In insert mode, creates a new todo on the next line and keeps you in insert mode. For more advanced placement, indentation, and state options, see the create(opts) API.
    cycle_nextCycle a todo's state to the next available. See api cycle()
    cycle_previousCycle a todo's state to the previous. See api cycle()
    lintLint this buffer for Checkmate formatting issues. Runs automatically on InsertLeave and TextChanged. See api lint() and Linting section.
    metadata addAdd a metadata tag to the todo under the cursor or within the selection. Usage: :Checkmate metadata add <key> [value]. See api add_metadata(key, value) and Metadata section.
    metadata jump_nextMove the cursor to the next metadata tag for the todo item under the cursor. See api jump_next_metadata()
    metadata jump_previousMove the cursor to the previous metadata tag for the todo item under the cursor. See api jump_previous_metadata()
    metadata removeRemove a specific metadata tag from the todo under the cursor or within the selection. Usage: :Checkmate metadata remove <key>. See api remove_metadata(key)
    metadata select_valueSelect a value from the 'choices' option for the metadata tag under the cursor. See api select_metadata_value()
    metadata toggleToggle a metadata tag on/off for the todo under the cursor or within the selection. Usage: :Checkmate metadata toggle <key> [value]. See api toggle_metadata(key, value)
    removeConvert a todo line back to regular text. See api remove(opts). By default, will preserve the list item marker and remove any metadata. This can be configured via opts.
    remove_all_metadataRemove all metadata tags from the todo under the cursor or within the selection. See api remove_all_metadata()
    select_todoOpen a picker to select a todo from the current buffer. See api select_todo(), which allows for filtering and picker customization.
    toggleToggle the todo item under the cursor (normal mode) or all todo items within the selection (visual mode). See api toggle(). Without a parameter, toggles between unchecked and checked. To change to custom states, use the api toggle(target_state) or the cycle_* commands.
    uncheckMark the todo item under the cursor as unchecked. See api uncheck()

    Config

    For config definitions/annotations, see here.

    Defaults

    ---@type checkmate.Config
    return {
      enabled = true,
      notify = true,
      -- Default file matching:
      --  - Any `todo` or `TODO` file, including with `.md` extension
      --  - Any `.todo` extension (can be ".todo" or ".todo.md")
      -- To activate Checkmate, the filename must match AND the filetype must be "markdown"
      files = {
        "todo",
        "TODO",
        "todo.md",
        "TODO.md",
        "*.todo",
        "*.todo.md",
      },
      log = {
        level = "warn",
        use_file = true,
      },
      -- Default keymappings
      keys = {
        ["<leader>Tt"] = {
          rhs = "<cmd>Checkmate toggle<CR>",
          desc = "Toggle todo item",
          modes = { "n", "v" },
        },
        ["<leader>Tc"] = {
          rhs = "<cmd>Checkmate check<CR>",
          desc = "Set todo item as checked (done)",
          modes = { "n", "v" },
        },
        ["<leader>Tu"] = {
          rhs = "<cmd>Checkmate uncheck<CR>",
          desc = "Set todo item as unchecked (not done)",
          modes = { "n", "v" },
        },
        ["<leader>T="] = {
          rhs = "<cmd>Checkmate cycle_next<CR>",
          desc = "Cycle todo item(s) to the next state",
          modes = { "n", "v" },
        },
        ["<leader>T-"] = {
          rhs = "<cmd>Checkmate cycle_previous<CR>",
          desc = "Cycle todo item(s) to the previous state",
          modes = { "n", "v" },
        },
        ["<leader>Tn"] = {
          rhs = "<cmd>Checkmate create<CR>",
          desc = "Create todo item",
          modes = { "n", "v" },
        },
        ["<leader>Tr"] = {
          rhs = "<cmd>Checkmate remove<CR>",
          desc = "Remove todo marker (convert to text)",
          modes = { "n", "v" },
        },
        ["<leader>TR"] = {
          rhs = "<cmd>Checkmate remove_all_metadata<CR>",
          desc = "Remove all metadata from a todo item",
          modes = { "n", "v" },
        },
        ["<leader>Ta"] = {
          rhs = "<cmd>Checkmate archive<CR>",
          desc = "Archive checked/completed todo items (move to bottom section)",
          modes = { "n" },
        },
        ["<leader>TF"] = {
          rhs = "<cmd>Checkmate select_todo<CR>",
          desc = "Open a picker to select a todo from the current buffer",
          modes = { "n" },
        },
        ["<leader>Tv"] = {
          rhs = "<cmd>Checkmate metadata select_value<CR>",
          desc = "Update the value of a metadata tag under the cursor",
          modes = { "n" },
        },
        ["<leader>T]"] = {
          rhs = "<cmd>Checkmate metadata jump_next<CR>",
          desc = "Move cursor to next metadata tag",
          modes = { "n" },
        },
        ["<leader>T["] = {
          rhs = "<cmd>Checkmate metadata jump_previous<CR>",
          desc = "Move cursor to previous metadata tag",
          modes = { "n" },
        },
      },
      default_list_marker = "-",
      ui = {},
      todo_states = {
        -- we don't need to set the `markdown` field for `unchecked` and `checked` as these can't be overriden
        ---@diagnostic disable-next-line: missing-fields
        unchecked = {
          marker = "□",
          order = 1,
        },
        ---@diagnostic disable-next-line: missing-fields
        checked = {
          marker = "✔",
          order = 2,
        },
      },
      style = {}, -- override defaults
      enter_insert_after_new = true, -- Should enter INSERT mode after `:Checkmate create` (new todo)
      list_continuation = {
        enabled = true,
        split_line = true,
        keys = {
          ["<CR>"] = function()
            require("checkmate").create({
              position = "below",
              indent = false,
            })
          end,
          ["<S-CR>"] = function()
            require("checkmate").create({
              position = "below",
              indent = true,
            })
          end,
        },
      },
      smart_toggle = {
        enabled = true,
        include_cycle = false,
        check_down = "direct_children",
        uncheck_down = "none",
        check_up = "direct_children",
        uncheck_up = "direct_children",
      },
      show_todo_count = true,
      todo_count_position = "eol",
      todo_count_recursive = true,
      use_metadata_keymaps = true,
      metadata = {
        -- Example: A @priority tag that has dynamic color based on the priority value
        priority = {
          style = function(context)
            local value = context.value:lower()
            if value == "high" then
              return { fg = "#ff5555", bold = true }
            elseif value == "medium" then
              return { fg = "#ffb86c" }
            elseif value == "low" then
              return { fg = "#8be9fd" }
            else -- fallback
              return { fg = "#8be9fd" }
            end
          end,
          get_value = function()
            return "medium" -- Default priority
          end,
          choices = function()
            return { "low", "medium", "high" }
          end,
          key = "<leader>Tp",
          sort_order = 10,
          jump_to_on_insert = "value",
          select_on_insert = true,
        },
        -- Example: A @started tag that uses a default date/time string when added
        started = {
          aliases = { "init" },
          style = { fg = "#9fd6d5" },
          get_value = function()
            return tostring(os.date("%m/%d/%y %H:%M"))
          end,
          key = "<leader>Ts",
          sort_order = 20,
        },
        -- Example: A @done tag that also sets the todo item state when it is added and removed
        done = {
          aliases = { "completed", "finished" },
          style = { fg = "#96de7a" },
          get_value = function()
            return tostring(os.date("%m/%d/%y %H:%M"))
          end,
          key = "<leader>Td",
          on_add = function(todo)
            require("checkmate").set_todo_state(todo, "checked")
          end,
          on_remove = function(todo)
            require("checkmate").set_todo_state(todo, "unchecked")
          end,
          sort_order = 30,
        },
      },
      archive = {
        heading = {
          title = "Archive",
          level = 2, -- e.g. ##
        },
        parent_spacing = 0, -- no extra lines between archived todos
        newest_first = true,
      },
      linter = {
        enabled = true,
      },
    }
    

    Keymapping

    Default keymaps can be disabled by setting keys = false.

    The keys table overrides the defaults (does not merge). If you want some custom and some defaults, you need to copy the defaults into your own keys table.

    Keymaps should be defined as a dict-like table or a sequence of {rhs, desc?, modes?}.

    keys = {
      ["<leader>Ta"] = {
          rhs = "<cmd>Checkmate archive<CR>",
          desc = "Archive todos",
          modes = { "n" },
      },
    }
    

    or

    keys = {
      ["<leader>Ta"] = {"<cmd>Checkmate archive<CR>", "Archive todos", {"n"} }
    }
    

    The rhs parameter follows :h vim.keymap.set() and can be a string or Lua function.

    Metadata define their own keymaps (for toggling behavior) within the metadata definition. To disable the registration of these keys, set use_metadata_keymaps to false.

    Styling

    Default styles are calculated based on the current colorscheme. This attempts to provide reasonable out-of-the-box defaults based on colorscheme-defined hl groups and contrast ratios.

    Individual styles can still be overriden using the style option and passing a 'highlight definition map' according to :h nvim_set_hl() and vim.api.keyset.highlight for the desired highlight group (see below).

    Checkmate highlighting can be completely disabled by setting style to false.

    Highlight groups

    hl_groupdescription
    CheckmateListMarkerUnorderedUnordered list markers, e.g. -,*, and +. (Only those associated with a todo)
    CheckmateListMarkerOrderedOrdered list markers, e.g. 1., 2). (Only those associated with a todo)
    CheckmateUncheckedMarkerUnchecked todo marker, e.g. □. See todo_states marker option
    CheckmateUncheckedMainContentThe main content of an unchecked todo (typically the first paragraph)
    CheckmateUncheckedAdditionalContentAdditional content for an unchecked todo (subsequent paragraphs, list items, etc.)
    CheckmateCheckedMarkerChecked todo marker, e.g. ✔. See todo_states marker option
    CheckmateCheckedMainContentThe main content of a checked todo (typically the first paragraph)
    CheckmateCheckedAdditionalContentAdditional content for a checked todo (subsequent paragraphs, list items, etc.)
    CheckmateTodoCountIndicatorThe todo count indicator, e.g. 1/4, shown on the todo line, if enabled. See show_todo_count option

    Metadata highlights are prefixed with CheckmateMeta_ and keyed with the tag name and style.

    Main content versus Additional content

    Highlight groups with 'MainContent' refer to the todo item's first paragraph. 'AdditionalContent' refers to subsequent paragraphs, list items, etc.

    Example: Change the checked marker to a bold green
    opts = {
        style = {
            CheckmateCheckedMarker = { fg = "#7bff4f", bold = true}
        }
    }
    
    Example: Style a custom todo state

    Custom todo states will be styled following the same highlight group naming convention: e.g. Checkmate[State]Marker So, if you define a partial state:

    todo_states = {
      partial = {
        -- ...
      }
    }
    

    You can then style it:

    styles = {
      CheckmatePartialMarker = { fg = "#f0fc03" }
      CheckmatePartialMainContent = { fg = "#faffa1" }
    }
    

    State names will be converted to CamelCase when used in highlight group names. E.g. not_planned = NotPlanned

    Todo states

    Checkmate supports both standard GitHub-flavored Markdown states and custom states for more nuanced task management.

    Default states

    The standard states are checked and unchecked, which are always saved to disk as - [ ] and - [x] per the Github-flavored Markdown spec. You can customize their visual appearance with the todo_states marker opt:

    todo_states = {
      checked = {
        marker = "☒" -- how it appears in Neovim
      },
      unchecked = {
        marker = "☐" -- how it appears in Neovim
      }
    }
    

    Custom states

    Add custom states to track tasks more precisely. Each state needs:

    • marker: How it appears in Neovim (must be unique)
    • markdown: How it's saved to disk (must be unique) and optionally:
    • type: How it behaves in the task hierarchy
    todo_states = {
      -- Built-in states (cannot change markdown or type)
      unchecked = { marker = "□" },
      checked = { marker = "✔" },
    
      -- Custom states
      in_progress = {
        marker = "◐",
        markdown = ".",     -- Saved as `- [.]`
        type = "incomplete", -- Counts as "not done"
        order = 50,
      },
      cancelled = {
        marker = "✗",
        markdown = "c",     -- Saved as `- [c]`
        type = "complete",   -- Counts as "done"
        order = 2,
      },
      on_hold = {
        marker = "⏸",
        markdown = "/",     -- Saved as `- [/]`
        type = "inactive",   -- Ignored in counts
        order = 100,
      }
    }
    
    State types

    States may be one of three types that give the state semantic and functional meaning:

    TypeBehaviorExample States
    incompleteCounts as "not done"unchecked, in_progress, pending, future
    completeCounts as "done"checked, cancelled
    inactiveIgnored in calculationson_hold, not_planned

    [!WARNING] Custom states like - [.] or - [/] are not standard Markdown and may not be recognized by other apps.

    You can then cycle through a todo's states with :Checkmate cycle_next and :Checkmate cycle_previous or using the API, such as:

    require("checkmate").cycle()        -- Next state
    require("checkmate").cycle(true)    -- Previous state
    
    -- or to toggle to a specific state
    require("checkmate").toggle("on_hold")
    

    Todo count indicator

    Shows completion progress for todos with subtasks.

    It displays the number of complete / incomplete todos in a hierarchy. It counts the standard "checked" and "unchecked" states, as well as custom states based on their type (incomplete or complete). The "inactive" type is not included.

    Change the default display by passing a custom formatter

    Basic example
    -- Custom formatter that returns the % completed
    todo_count_formatter = function(completed, total)
      return string.format("[%.0f%%]", completed / total * 100)
    end,
    style = {
      CheckmateTodoCountIndicator = { fg = "#faef89" },
    },
    
    Progress bar example, see Wiki for code.
    Count all nested todo items

    If you want the todo count of a parent todo item to include all nested todo items, set the todo_count_recursive option.

    Smart Toggle

    Intelligently propagates a todo's state change through its hierarchy.

    When you toggle a todo item, it can automatically update related todos based on your configuration.

    [!NOTE] Smart toggle only propagates "unchecked" and "checked" states (the default/standard todo states). If custom todo states are used, they may influence parent completion but will not be changed themselves.

    How it works

    Smart toggle operates in two phases:

    1. Downward propagation: When toggling a parent, optionally propagate the state change to children
    2. Upward propagation: When toggling a child, optionally update the parent based on children states

    Configuration

    Smart toggle is enabled by default with sensible defaults. You can customize the behavior:

    opts = {
      smart_toggle = {
        enabled = true,
        check_down = "direct",        -- How checking a parent affects children
        uncheck_down = "none",        -- How unchecking a parent affects children
        check_up = "direct_children", -- When to auto-check parents
        uncheck_up = "direct_children", -- When to auto-uncheck parents
      }
    }
    

    Pickers

    Checkmate uses a picker for various API functions. To provide a reasonable, out-of-the-box experience, several popular external picker plugin backends are implemented:

    • snacks.nvim
    • mini.pick
    • telescope.nvim

    or, as a fallback native vim.ui.select

    Configuration

    Global configuration

    Set the default picker in the setup opts:

    ui = {
      picker = "snacks" -- or "mini", "telescope", or "native"
    }
    
    Per-call configuration

    Some API functions allow passing picker_opts:

    require("checkmate").select_metadata_value({
      picker_opts = {
        picker = "snacks",  -- force snacks for this call
        snacks = {
          layout = { preset = "dropdown" }
        }
      }
    })
    
    Custom pickers

    Some API functions support a custom_picker function that is intended to receive data related to that API and expects the complete(value) callback to finalize the Checkmate behavior.

    require("checkmate").select_metadata_value({
      custom_picker = function(ctx, complete)
        require("snacks").picker.files({
          confirm = function(picker, item)
            if item then
              vim.schedule(function()
                complete(item.text)
              end)
            end
            picker:close()
          end,
        })
      end,
    })
    

    Metadata

    Metadata tags allow you to add custom @tag(value) annotations to todo items.

    • Default tags:
      • @started - default value is the current date/time
      • @done - default value is the current date/time
      • @priority - "low" | "medium" (default) | "high"

    The default tags are not deeply merged in order to avoid unexpected behavior. If you wish to modify a default metadata, you should copy the default implementation.

    By configuring a metadata's choices option, you can populate your own lists of metadata values for powerful workflows, e.g. project file names, Git branches, PR's, issues, etc., team member names, external APIs, etc.

    Alternatively, you can call select_metadata_value() and pass a custom picker function and use the selected item to update your metadata!

    -- Update metadata under the cursor using a snacks.nvim picker
    require("checkmate").select_metadata_value({
      custom_picker = function(ctx, complete)
        require("snacks").picker.files({
          confirm = function(picker, item)
            if item then
              vim.schedule(function()
                complete(item.text)
              end)
            end
            picker:close()
          end,
        })
      end,
    })
    

    For in-depth guide and recipes for custom metadata, see the Wiki page.

    Archiving

    Allows you to easily reorganize the buffer by moving all completed todo items to a Markdown section beneath all other content. The remaining unchecked/incomplete todos are reorganized up top and spacing is adjusted.

    Archiving collects all todos with the "completed" state type, which includes the default "checked" state, but possibly others based on custom todo states.

    See Checkmate archive command or require("checkmate").archive()

    Current behavior (could be adjusted in the future): a completed todo item that is nested under an incomplete parent will not be archived. This prevents 'orphan' todos being separated from their parents. Similarly, a completed parent todo will carry all nested todos (completed and incomplete) when archived.

    Heading

    By default, a Markdown level 2 header (##) section named "Archive" is used. You can configure the archive section heading via config.archive.heading

    The following will produce an archive section labeled:

    #### Completed
    
    opts = {
      archive = {
        heading = {
          title = "Completed",
          level = 4
        }
      }
    }
    
    Spacing

    The amount of blank lines between each archived todo item can be customized via config.archive.parent_spacing

    E.g. parent_spacing = 0

    ## Archive
    
    - ✔ Update the dependencies
    - ✔ Refactor the User api
    - ✔ Add additional tests
    

    E.g. parent_spacing = 1

    ## Archive
    
    - ✔ Update the dependencies
    
    - ✔ Refactor the User api
    
    - ✔ Add additional tests
    

    Integrations

    Please see wiki for additional details/recipes.

    IntegrationCapable?
    render-markdown✅
    markview✅
    LuaSnip✅
    scratch buffer/floating window for quick todos, e.g. snacks.nvim✅

    Linting

    Checkmate uses a very limited custom linter in order require zero dependencies but attempt to warn the user of Markdown (CommonMark spec) formatting issues that could cause unexpected plugin behavior.

    The embedded linter is NOT a general-purpose Markdown linter and may interfere with other linting tools. Though, in testing with conform.nvim and prettier, I have not found any issues.

    Example

    ❌ misaligned list marker

    1. ☐ Parent todo item
    
    - ☐ Child todo item (indented only 2 spaces!)
    

    ✅ correctly aligned list marker

    1. ☐ Parent todo item
       - ☐ Child todo item (indented 3 spaces!)
    

    The CommonMark spec requires that nested list markers begin at the col of the first non-whitespace content after the parent list marker (which will be a different col for bullet list vs ordered list markers)

    If you feel comfortable with the nuances of Markdown list syntax, you can disable the linter (default is enabled) via config:

    {
      linter = {
        enabled = false
      }
    }
    

    Roadmap

    Planned features:

    • Metadata support - mappings for quick addition of metadata/tags such as @start, @done, @due, @priority, etc. with custom highlighting. Added v0.2.0

    • Sub-task counter - add a completed/total count (e.g. 1/4) to parent todo items. Added v0.3.0

    • Archiving - manually or automatically move completed items to the bottom of the document. Added v0.7.0

    • Smart toggling - toggle all children checked if a parent todo is checked. Toggle a parent checked if the last unchecked child is checked. Added v0.7.0

    • Metadata upgrade - callbacks, async support, jump to. Added v0.9.0

    • Custom todo states - support beyond binary "checked" and "unchecked", allowing for todos to be in custom states, e.g. pending, not-planned, on-hold, etc. Added v0.10.0

    • List (todo) continuation - automatically created new todo lines in insert mode, e.g. <CR> on a todo line will create a new todo below. Added v0.11.0

    • Todo search - Expose a get_todos that return lists of todos, based on search criteria that can be used to populate qflists and pickers. Add a select_todo that integrates this with a picker.

    • Better archive - generalize the archive functionality to move todos to specific buffer locations (or even different buffers/files). Provide config opt and API to specify which todo state type to act on (i.e. completed, incompleted, inactive). Integrate with picker to choose a new Markdown heading location to move todos.

    Contributing

    If you have feature suggestions or ideas, please feel free to open an issue on GitHub!

    Credits