emojis.nvim · Editing · vimdoc

:help emojis

Emoji operations for Neovim

doc/emojis.txt — rendered from the plugin's own vimdoc

*emojis.txt*  Emoji operations for Neovim                            *emojis.nvim*

Author:   Stefan Bartl
Version:  0.3.0

CONTENTS *emojis-contents*

  1. Introduction .............. |emojis-intro|
  2. Requirements .............. |emojis-requirements|
  3. Installation .............. |emojis-installation|
  4. Configuration ............. |emojis-config|
  5. The :Emojis command ....... |:Emojis|
     5.1 Actions ............... |emojis-actions|
     5.2 Scopes ................ |emojis-scopes|
  6. Space-collapse behaviour .. |emojis-clear-spaces|
  7. Tab completion ............ |emojis-completion|
  8. Lua API ................... |emojis-api|
  9. Health check .............. |emojis-health|
 10. Bindings .................. |emojis-bindings|
 11. Architecture .............. |emojis-architecture|
 12. Quick-insert overlay ...... |emojis-overlay|
 13. Emoji checkboxes .......... |emojis-checkboxes|
 14. Unicode toolkit ........... |emojis-unicode|

1. INTRODUCTION *emojis-intro*

emojis.nvim provides a single :Emojis command to remove, count, list,
replace, or insert emojis across several scopes — the current line, the visual
selection, the whole buffer, or the entire project (via ripgrep).

Cross-platform. Emoji detection uses a pure UTF-8 byte tokenizer; no external
library is required. Requires lib.nvim — the :Emojis command is registered
via lib.nvim.bindings.usercmd.composer.

2. REQUIREMENTS *emojis-requirements*

  - Neovim 0.9 or later
  - ripgrep (rg) — only for the cwd scope
  - telescope.nvim or fzf-lua — optional; live-search picker for `:Emojis
    insert (picker.engine = "auto"), falls back to vim.ui.select`
  - lib.nvim (StefanBartl/lib.nvim) — required; the :Emojis command is
    registered via lib.nvim.bindings.usercmd.composer, with no fallback
    (notify/map specifically still degrade to a native fallback if
    somehow absent at that call site, but the command layer itself does not)

3. INSTALLATION *emojis-installation*

lazy.nvim:
  {
    "StefanBartl/emojis.nvim",
    dependencies = { "StefanBartl/lib.nvim" }, -- required
    cmd = "Emojis",
    opts = {},
  }
packer.nvim:
  use {
    "StefanBartl/emojis.nvim",
    requires = { "StefanBartl/lib.nvim" }, -- required
    config = function()
      require("emojis").setup()
    end,
  }
vim-plug:
  Plug 'StefanBartl/lib.nvim' " required
  Plug 'StefanBartl/emojis.nvim'

  lua require("emojis").setup()

4. CONFIGURATION *emojis-config*

  require("emojis").setup({
    default_scope = "%",        -- scope used when none is given
    command       = "Emojis",   -- user-command name

    picks = {                    -- insert-picker entries: { glyph, label }
      { "✅", "check" }, { "⚠️", "warning" }, --[[ … ]]
    },

    names = {                    -- codepoint -> :name: for replace
      [0x2705] = ":white_check_mark:",
      [0x26A0] = ":warning:",
    },

    search = {                   -- cwd search (ripgrep)
      cmd = "rg",
      extra_args = { "--no-heading", "--line-number", "--with-filename", "--color=never" },
      no_ignore = false,          -- true -> --no-ignore (also search gitignored files)
    },

    keymaps = {                   -- opt-in preset keymaps
      preset = false,
    },

    wrap = {                      -- marker for the wrap action
      prefix = "[[",
      suffix = "]]",
    },

    preview = {                   -- opt-in highlight before clear/replace
      enable = false,
      duration_ms = 150,
      hl_group = "IncSearch",
    },

    picker = {                    -- insert-picker engine
      engine = "auto",            -- "auto" | "telescope" | "fzf-lua" | "select"
    },

    overlay = {                   -- quick-insert overlay (`:Emojis overlay`)
      mode = "grid",               -- "grid" | "grid_keys" | "list"
      frecency = true,             -- reorder picks by recorded usage
      columns = 5,
      limit = 20,
      title = " Emojis ",
      theme = "rounded",           -- any ui.kit theme arg
      -- picks = { { "✅", "white_check_mark" }, … } -- replaces the default list
    },

    checkbox = {                  -- emoji checkbox cycles (`:Emojis toggle [set]`)
      default_set = "",            -- "" = search every set below; or e.g. "status"
      sets = {
        checkbox = { "🔲", "✅" },
        status   = { "🔴", "🟡", "🟢" },
        review   = { "👍", "👎" },
      },
      order = { "checkbox", "status", "review" }, -- search order when default_set = ""
    },
  })

default_scope

  Scope applied when the second argument is omitted. One of word, line,
  visual, %, cwd. Default: "%".

command

  Name of the registered user command. Default: "Emojis".

picks

  Array of { glyph, label } entries shown by :Emojis insert. The default
  60+-entry catalog also derives names (one shared label per glyph, see
  config/DEFAULTS.lua), so overriding picks alone does not affect names.

names

  Map of Unicode codepoint to replacement text used by `:Emojis
  replace/unreplace. Unknown emojis fall back to :U+XXXX:`.

search.cmd

  External search binary for the cwd scope. Default: "rg".

search.extra_args

  Arguments passed before the pattern and path. Default: ripgrep flags for
  no-heading, line-number, with-filename, no-color.

search.no_ignore

  When true, passes --no-ignore so the cwd scope also searches
  gitignored files. Default: false.

keymaps.preset

  When true, binds the opt-in preset keymaps (<C-e>, <leader>ec,
  <leader>el) and labels the <leader>e group in which-key if installed.
  Default: false. See |emojis-bindings|.

wrap.prefix, wrap.suffix

  Text inserted before/after each emoji by the wrap action. Default:
  "[["/"]]".

preview.enable

  When true, briefly highlights the emojis about to be mutated before
  clear/replace runs (extmarks, preview.hl_group), blocking for
  preview.duration_ms. Default: false.

preview.duration_ms

  How long the highlight is shown before the buffer is mutated. Default:
  150.

preview.hl_group

  Highlight group used for the preview extmarks. Default: "IncSearch".

picker.engine

  Insert-picker engine: "auto" tries telescope.nvim then fzf-lua (both
  optional), falling back to vim.ui.select; "telescope"/"fzf-lua" force
  one (still falling back if not installed); "select" always uses
  vim.ui.select. Default: "auto".

overlay.mode

  Default interaction mode for :Emojis overlay: "grid" (hjkl/arrows +
  <CR>), "grid_keys" (one hotkey per cell), or "list" (delegates to
  kit.chooser). Default: "grid". See |emojis-overlay|.

overlay.frecency

  When true, every insertion (overlay and insert picker alike) is
  recorded and overlay.picks is reordered most-used-first with a 30-day
  recency half-life. Default: true. See |emojis-overlay|.

overlay.columns

  Grid width in grid/grid_keys mode. Default: 5.

overlay.limit

  Maximum number of cells shown. Default: 20.

overlay.title

  Float title. Default: " Emojis ".

overlay.theme

  Any ui.kit theme argument: a preset name ("minimal",
  "rounded", "solid", "double", "ascii") or an override table.
  Default: "rounded".

overlay.picks

  { glyph, label } entries for the overlay grid. Unlike most options, this
  **replaces** the default list rather than merging into it.

checkbox.sets

  Table of named glyph cycles used by :Emojis toggle [set]. A set you
  redefine replaces the default's states rather than merging into it. The
  defaults (checkbox, status, review) are deliberately disjoint.

checkbox.default_set

  Set used when :Emojis toggle is called with no set argument. Empty
  string (default) searches every set in checkbox.order.

checkbox.order

  Search order across sets when checkbox.default_set = "", and the
  tie-break for a glyph appearing in more than one set. Sets not listed here
  are still searched, appended in name-sorted order.

5. THE :Emojis COMMAND *:Emojis*

  :Emojis [action] [scope]
  :[range]Emojis [action]
With no arguments, :Emojis is equivalent to :Emojis clear %.

An explicit Vim range (:'<,'>Emojis, :10,20Emojis) always overrides the
scope keyword.

Built via lib.nvim.bindings.usercmd.composer: one route per action, forwarding to
the same dispatch function as before (unchanged). An unrecognized action now
reports composer's own usage block (every registered action, one per line)
instead of the old plain-string error.

5.1 Actions *emojis-actions*

clear

  Remove every emoji in scope. Collapses surrounding spaces — see
  |emojis-clear-spaces|. Default action.

replace

  Replace each emoji with its :name: placeholder (or :U+XXXX: fallback).

unreplace

  Replace :name:/:U+XXXX: placeholders back with their emoji — the
  inverse of |emojis-actions| replace. Unrecognized :...: tokens are left
  untouched.

list

  Collect every emoji in scope into the quickfix list and :copen.

count

  Count emojis in scope and report via a notification.

insert

  Open a picker (telescope.nvim/fzf-lua if available per picker.engine,
  else vim.ui.select) and insert the chosen emoji at the cursor. The
  scope argument is ignored for this action.

first

  Move the cursor to the first emoji in the buffer. The scope argument is
  ignored; the buffer is not modified.

next

  Move the cursor to the next emoji after the cursor, wrapping to the top of
  the buffer if none is found below. The scope argument is ignored; the
  buffer is not modified.

wrap

  Surround each emoji with config.wrap.prefix/config.wrap.suffix (default
  [[/]]), without removing it — e.g. for downstream machine processing.

overlay

  Open the quick-insert overlay (config.overlay). The scope argument is
  instead an interaction mode: grid|`grid_keys`|list. See
  |emojis-overlay|.

toggle

  Cycle the emoji checkbox glyph found on the cursor line (or every line in
  a range/visual selection) one step through a configured
  config.checkbox.sets cycle. The scope argument is instead a set name.
  See |emojis-checkboxes|.

unicode

  Unicode toolkit for any character, not just emoji: name|`search`|
  table|digraphs. The scope argument is instead a sub-action. See
  |emojis-unicode|.

5.2 Scopes *emojis-scopes*

%       Whole current buffer (default).
line    The current cursor line.
word    The whitespace-delimited run of text containing the cursor byte
        column — not the whole line. Errors if the cursor sits on
        whitespace. Only actions operating on a single line
        (clear/replace/list/count) narrow to this sub-range.
visual  The lines of the last / current visual selection.
cwd     All files under the working directory, searched asynchronously with
        ripgrep. list/count report results directly; clear/replace
        first show the same matches, then ask for confirmation (default:
        cancel) before mutating every matched file. Run :Emojis list cwd
        first as a dry-run preview. Buffers with unsaved changes are skipped
        rather than clobbered. Arguments after cwd are passed to ripgrep as
        extra --glob filters, e.g. :Emojis count cwd *.md.

6. SPACE-COLLAPSE BEHAVIOUR *emojis-clear-spaces*

When clear removes an emoji (or a run of adjacent emojis) that had a single
space on both sides, the result keeps exactly one space instead of leaving
two:

  " 🚀 "       ->  " "
  "a 🚀 b"     ->  "a b"
  " 🚀🔥 "     ->  " "
  "a🚀b"       ->  "ab"
Emojis carrying a Variation-Selector-16 (e.g. ⚠️) are treated as a single
emoji grapheme — counted once and replaced as one placeholder.

7. TAB COMPLETION *emojis-completion*

:Emojis completes the action at the first argument and the scope at the
second:

  :Emojis <Tab>          clear  count  first  insert  list  next  overlay
                         replace  toggle  unicode  unreplace  wrap
  :Emojis clear <Tab>    word  line  visual  %  cwd
  :Emojis overlay <Tab>  grid  grid_keys  list
  :Emojis toggle <Tab>   <configured config.checkbox.sets names>
  :Emojis unicode <Tab>  name  search  table  digraphs

8. LUA API *emojis-api*

  local emojis = require("emojis")
setup({opts})                                                *emojis.setup()*
  Configure and activate. Idempotent.

clear()                                                      *emojis.clear()*
  Clear emojis from the whole current buffer.

count()                                                      *emojis.count()*
  Count emojis in the whole current buffer.

insert()                                                    *emojis.insert()*
  Open the insert picker at the cursor.

overlay({mode})                                            *emojis.overlay()*
  Open the quick-insert overlay. mode is optional:
  "grid"|`"grid_keys"`|"list", defaulting to config.overlay.mode.

toggle({set}, {dir})                                        *emojis.toggle()*
  Cycle the checkbox glyph on the cursor line, or every line in the current
  visual range. set is optional (defaults to config.checkbox.default_set;
  an explicit empty string does not override a non-empty default_set).

checkbox_add({set})                                    *emojis.checkbox_add()*
  Add a checkbox glyph to the cursor line / visual range if it lacks one.

checkbox_remove({set})                              *emojis.checkbox_remove()*
  Remove the checkbox glyph from the cursor line / visual range.

cascade_groups({set})                                *emojis.cascade_groups()*
  Return config.checkbox.sets in cascade.nvim's cycle.groups format, so
  the same glyph vocabulary drives both plugins. Pure data function — never
  require("cascade") itself, safe to call whether or not cascade.nvim is
  installed.

ops()                                                          *emojis.ops()*
  Return the pure operations module (clear/count/list/replace) that works on
  string arrays without touching the Neovim API. Useful for scripting/tests:
    local ops = require("emojis").ops()
    local cleaned, removed = ops.clear({ " 🚀 done" })  -- { " done" }, 1
The |emojis-unicode| toolkit is reachable the same way, via
require("emojis.unicode") — see |emojis-unicode| and docs/api.md.

9. HEALTH CHECK *emojis-health*

  :checkhealth emojis
Checks:
  - Neovim >= 0.9
  - lib.nvim.bindings.usercmd.composer available (required for the :Emojis
    command layer, no fallback)
  - vim.ui.select available (insert picker)
  - ripgrep on PATH (cwd scope)
  - vim.system available (async search)
  - plugin loaded (guard flag set)
  - which-key found (optional; labels the preset's <leader>e group)
  - keymaps.preset enabled or not

10. BINDINGS *emojis-bindings*

Full cheatsheet of every keymap, user command, and autocommand: see
docs/BINDINGS.md in the repository. Only the :Emojis command is always
registered; the preset keymaps (<C-e>, <leader>ee, <leader>et,
<leader>ec, <leader>el) require |emojis-config| keymaps.preset = true
and are labelled under the <leader>e group in which-key if it is installed
(optional, no hard dependency). emojis.nvim defines no autocommands.

  <C-e>          n, i    Insert picker at the cursor
  <leader>ee     n       Quick-insert overlay
  <leader>et     n, x    Toggle emoji checkbox (line / visual range)
  <leader>ec     n       Count emojis in the buffer
  <leader>el     n       List emojis in the buffer -> quickfix

11. ARCHITECTURE *emojis-architecture*

  plugin/emojis.lua          Load guard
  lua/emojis/
    init.lua                 Public API, setup()
    @types.lua               LuaLS type definitions
    config/
      DEFAULTS.lua           Immutable default configuration
      init.lua               Merge + access to active config
    util/
      notify.lua             Prefixed notify wrapper (via util/lib.lua)
      lib.lua                Soft bridge to lib.nvim (notify/map), with fallback
    core/
      patterns.lua           Pure UTF-8 emoji tokenizer (graphemes incl. VS16)
      ops.lua                Pure clear/count/list/replace operations
      scope.lua              Scope (+ range) -> buffer line range
      insert.lua             Shared insert helper (picker + overlay), records frecency
      checkbox.lua           Pure line-scoped checkbox find/cycle
    bindings/
      init.lua               Orchestrates usrcmds/keymaps/autocmds
      usrcmds.lua            Registers :Emojis (via commands.lua)
      keymaps.lua            Opt-in preset keymaps (keymaps.preset); also the which-key group label
      autocmds.lua           Empty (no autocmds by design)
    overlay/
      init.lua               Quick-insert overlay (grid/grid_keys/list modes)
      frecency.lua           Usage tracking (stdpath("data")/emojis.nvim/frecency.json)
    actions.lua              Buffer-facing handlers (edit/list/count)
    nav.lua                  Cursor navigation (first/next)
    picker.lua               Insert picker (vim.ui.select)
    search.lua               Async cwd search (ripgrep)
    commands.lua             :Emojis dispatch + tab completion
    health.lua               checkhealth provider

12. QUICK-INSERT OVERLAY *emojis-overlay*

  :Emojis overlay [grid|grid_keys|list]
A small float holding the ~20 emojis a developer actually reaches for
(config.overlay.picks), ordered by how often you use them. Unlike
insert (full catalog), the overlay is meant to be opened and dismissed in
a second or two. Bound to <leader>ee when keymaps.preset = true.

The second argument is an interaction mode, not a scope. Omit it to use
config.overlay.mode:

  grid          2D grid; hjkl/arrows move, <CR> inserts (default)
  grid_keys     Same grid, plus a direct hotkey per cell (one keypress inserts)
  list          One glyph per row with its shortcode, via lib.nvim's kit chooser

<Esc> or q closes without inserting.

Every insertion — from the overlay and the insert picker — is recorded
to stdpath("data")/emojis.nvim/frecency.json and reorders
config.overlay.picks (most-used-first, 30-day recency half-life). Sorting
only ever reorders the configured set; it never adds or removes entries.
overlay.frecency = false disables both the reordering and the file write;
clear the history at any time with require("emojis.overlay.frecency").reset().

13. EMOJI CHECKBOXES *emojis-checkboxes*

  :Emojis toggle [set]
  :[range]Emojis toggle [set]
Cycles an emoji "checkbox" glyph one step through a configured
config.checkbox.sets cycle, e.g. 🔲 1. Hallo -> ✅ 1. Hallo -> back
again. The glyph is found anywhere on the line (line-scoped, not
cursor-scoped), so the cursor can sit at the end of the text being written.

Unlike the other actions, the second argument is a set name, not a scope —
and the scope is always a line range (explicit Vim range, or the cursor
line/visual selection), never word, %, or cwd. Bound to <leader>et
(normal and visual mode) when keymaps.preset = true.

require("emojis").cascade_groups() returns config.checkbox.sets in
cascade.nvim's cycle.groups format, so the same glyph vocabulary drives
both cascade's cursor-precise <C-y> cycling and emojis.nvim's line-scoped
:Emojis toggle — see |emojis.cascade_groups()|.

The pure logic in core/* is isolated from every API/UI layer and is therefore
unit-testable on plain string arrays (see TESTS/).

14. UNICODE TOOLKIT *emojis-unicode*

  :Emojis unicode name [reg [type]]
  :Emojis unicode search[!] <query>
  :Emojis unicode table
  :Emojis unicode digraphs
A chrisbra/unicode.vim replacement, for any Unicode character.

name

  Report the character under the cursor: codepoint (hex/dec), glyph, name,
  and any digraph that produces it. With reg, also save one
  representation into that register; type picks which — value|`hex`|
  name|`html`|digraph|regex (default name). reg = "=" is refused:
  the expression register evaluates its contents as Vimscript the next
  time anything reads @=, and the name text can come from
  downloaded/cached data.

search[!]

  Find characters by name substring (case-insensitive), or by an exact
  U+xxxx/0xNNNN/decimal value. Results open in |vim.ui.select()|;
  picking one reports it. With !, picking one inserts the glyph at the
  cursor instead.

table

  Open a scratch buffer listing the whole loaded name table, one line per
  character.

digraphs

  Open a scratch buffer listing every digraph Neovim itself knows
  (|digraph_getlist()|) — needs no download.

The name lookup has two tiers. A glyph already in config.picks/
config.names resolves instantly, no network involved. Anything else is
looked up in the Unicode Character Database's own UnicodeData.txt,
downloaded once per machine via curl into
stdpath("cache")/emojis/UnicodeData.txt and cached there. Without curl
on $PATH, or before the first successful download, name (for an
uncatalogued character), search, and table report why and do nothing
else; digraphs is unaffected. A codepoint inside a large contiguous UCD
block (CJK Unified Ideographs, Hangul Syllables, Tangut, ...) gets a
synthesized "PREFIX-HEX" name, e.g. CJK UNIFIED IDEOGRAPH-4E2D.

The download writes to a temp file and is renamed into place only once it
passes a size check; a cached file that turns out corrupt or incomplete is
deleted and reported rather than trusted, so retrying the command is
enough to recover.