• Stars
    star
    1,965
  • Rank 22,606 (Top 0.5 %)
  • Language
    Lua
  • License
    Apache License 2.0
  • Created 11 months ago
  • Updated about 1 month ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Navigate your code with search labels, enhanced character motions and Treesitter integration

⚑flash.nvim

flash.nvim lets you navigate your code with search labels, enhanced character motions, and Treesitter integration.

Search Integration Standalone Jump
f, t, F, T Treesitter

✨ Features

  • πŸ” Search Integration: integrate flash.nvim with your regular search using / or ?. Labels appear next to the matches, allowing you to quickly jump to any location. Labels are guaranteed not to exist as a continuation of the search pattern.
  • ⌨️ type as many characters as you want before using a jump label.
  • ⚑ Enhanced f, t, F, T motions
  • 🌳 Treesitter Integration: all parents of the Treesitter node under your cursor are highlighted with a label for quick selection of a specific Treesitter node.
  • 🎯 Jump Mode: a standalone jumping mode similar to search
  • πŸ”Ž Search Modes: exact, search (regex), and fuzzy search modes
  • πŸͺŸ Multi Window jumping
  • 🌐 Remote Actions: perform motions in remote locations
  • ⚫ dot-repeatable jumps
  • πŸ“‘ highly extensible: check the examples

πŸ“‹ Requirements

  • Neovim >= 0.8.0 (needs to be built with LuaJIT)

πŸ“¦ Installation

Install the plugin with your preferred package manager:

lazy.nvim:

{
  "folke/flash.nvim",
  event = "VeryLazy",
  ---@type Flash.Config
  opts = {},
  -- stylua: ignore
  keys = {
    { "s", mode = { "n", "o", "x" }, function() require("flash").jump() end, desc = "Flash" },
    { "S", mode = { "n", "o", "x" }, function() require("flash").treesitter() end, desc = "Flash Treesitter" },
    { "r", mode = "o", function() require("flash").remote() end, desc = "Remote Flash" },
    { "R", mode = { "o", "x" }, function() require("flash").treesitter_search() end, desc = "Treesitter Search" },
    { "<c-s>", mode = { "c" }, function() require("flash").toggle() end, desc = "Toggle Flash Search" },
  },
}

⚠️ When creating the keymaps manually either use a lua function like function() require("flash").jump() end as the rhs, or a string like <cmd>lua require("flash").jump()<cr>. DO NOT use :lua, since that will break dot-repeat

βš™οΈ Configuration

flash.nvim is highly configurable. Please refer to the default settings below.

Default Settings
{
  -- labels = "abcdefghijklmnopqrstuvwxyz",
  labels = "asdfghjklqwertyuiopzxcvbnm",
  search = {
    -- search/jump in all windows
    multi_window = true,
    -- search direction
    forward = true,
    -- when `false`, find only matches in the given direction
    wrap = true,
    ---@type Flash.Pattern.Mode
    -- Each mode will take ignorecase and smartcase into account.
    -- * exact: exact match
    -- * search: regular search
    -- * fuzzy: fuzzy search
    -- * fun(str): custom function that returns a pattern
    --   For example, to only match at the beginning of a word:
    --   mode = function(str)
    --     return "\\<" .. str
    --   end,
    mode = "exact",
    -- behave like `incsearch`
    incremental = false,
    -- Excluded filetypes and custom window filters
    ---@type (string|fun(win:window))[]
    exclude = {
      "notify",
      "cmp_menu",
      "noice",
      "flash_prompt",
      function(win)
        -- exclude non-focusable windows
        return not vim.api.nvim_win_get_config(win).focusable
      end,
    },
    -- Optional trigger character that needs to be typed before
    -- a jump label can be used. It's NOT recommended to set this,
    -- unless you know what you're doing
    trigger = "",
    -- max pattern length. If the pattern length is equal to this
    -- labels will no longer be skipped. When it exceeds this length
    -- it will either end in a jump or terminate the search
    max_length = false, ---@type number|false
  },
  jump = {
    -- save location in the jumplist
    jumplist = true,
    -- jump position
    pos = "start", ---@type "start" | "end" | "range"
    -- add pattern to search history
    history = false,
    -- add pattern to search register
    register = false,
    -- clear highlight after jump
    nohlsearch = false,
    -- automatically jump when there is only one match
    autojump = false,
    -- You can force inclusive/exclusive jumps by setting the
    -- `inclusive` option. By default it will be automatically
    -- set based on the mode.
    inclusive = nil, ---@type boolean?
    -- jump position offset. Not used for range jumps.
    -- 0: default
    -- 1: when pos == "end" and pos < current position
    offset = nil, ---@type number
  },
  label = {
    -- allow uppercase labels
    uppercase = true,
    -- add any labels with the correct case here, that you want to exclude
    exclude = "",
    -- add a label for the first match in the current window.
    -- you can always jump to the first match with `<CR>`
    current = true,
    -- show the label after the match
    after = true, ---@type boolean|number[]
    -- show the label before the match
    before = false, ---@type boolean|number[]
    -- position of the label extmark
    style = "overlay", ---@type "eol" | "overlay" | "right_align" | "inline"
    -- flash tries to re-use labels that were already assigned to a position,
    -- when typing more characters. By default only lower-case labels are re-used.
    reuse = "lowercase", ---@type "lowercase" | "all" | "none"
    -- for the current window, label targets closer to the cursor first
    distance = true,
    -- minimum pattern length to show labels
    -- Ignored for custom labelers.
    min_pattern_length = 0,
    -- Enable this to use rainbow colors to highlight labels
    -- Can be useful for visualizing Treesitter ranges.
    rainbow = {
      enabled = false,
      -- number between 1 and 9
      shade = 5,
    },
    -- With `format`, you can change how the label is rendered.
    -- Should return a list of `[text, highlight]` tuples.
    ---@class Flash.Format
    ---@field state Flash.State
    ---@field match Flash.Match
    ---@field hl_group string
    ---@field after boolean
    ---@type fun(opts:Flash.Format): string[][]
    format = function(opts)
      return { { opts.match.label, opts.hl_group } }
    end,
  },
  highlight = {
    -- show a backdrop with hl FlashBackdrop
    backdrop = true,
    -- Highlight the search matches
    matches = true,
    -- extmark priority
    priority = 5000,
    groups = {
      match = "FlashMatch",
      current = "FlashCurrent",
      backdrop = "FlashBackdrop",
      label = "FlashLabel",
    },
  },
  -- action to perform when picking a label.
  -- defaults to the jumping logic depending on the mode.
  ---@type fun(match:Flash.Match, state:Flash.State)|nil
  action = nil,
  -- initial pattern to use when opening flash
  pattern = "",
  -- When `true`, flash will try to continue the last search
  continue = false,
  -- Set config to a function to dynamically change the config
  config = nil, ---@type fun(opts:Flash.Config)|nil
  -- You can override the default options for a specific mode.
  -- Use it with `require("flash").jump({mode = "forward"})`
  ---@type table<string, Flash.Config>
  modes = {
    -- options used when flash is activated through
    -- a regular search with `/` or `?`
    search = {
      -- when `true`, flash will be activated during regular search by default.
      -- You can always toggle when searching with `require("flash").toggle()`
      enabled = true,
      highlight = { backdrop = false },
      jump = { history = true, register = true, nohlsearch = true },
      search = {
        -- `forward` will be automatically set to the search direction
        -- `mode` is always set to `search`
        -- `incremental` is set to `true` when `incsearch` is enabled
      },
    },
    -- options used when flash is activated through
    -- `f`, `F`, `t`, `T`, `;` and `,` motions
    char = {
      enabled = true,
      -- dynamic configuration for ftFT motions
      config = function(opts)
        -- autohide flash when in operator-pending mode
        opts.autohide = vim.fn.mode(true):find("no") and vim.v.operator == "y"

        -- disable jump labels when enabled and when using a count
        opts.jump_labels = opts.jump_labels and vim.v.count == 0

        -- Show jump labels only in operator-pending mode
        -- opts.jump_labels = vim.v.count == 0 and vim.fn.mode(true):find("o")
      end,
      -- hide after jump when not using jump labels
      autohide = false,
      -- show jump labels
      jump_labels = false,
      -- set to `false` to use the current line only
      multi_line = true,
      -- When using jump labels, don't use these keys
      -- This allows using those keys directly after the motion
      label = { exclude = "hjkliardc" },
      -- by default all keymaps are enabled, but you can disable some of them,
      -- by removing them from the list.
      -- If you rather use another key, you can map them
      -- to something else, e.g., { [";"] = "L", [","] = H }
      keys = { "f", "F", "t", "T", ";", "," },
      ---@alias Flash.CharActions table<string, "next" | "prev" | "right" | "left">
      -- The direction for `prev` and `next` is determined by the motion.
      -- `left` and `right` are always left and right.
      char_actions = function(motion)
        return {
          [";"] = "next", -- set to `right` to always go right
          [","] = "prev", -- set to `left` to always go left
          -- clever-f style
          [motion:lower()] = "next",
          [motion:upper()] = "prev",
          -- jump2d style: same case goes next, opposite case goes prev
          -- [motion] = "next",
          -- [motion:match("%l") and motion:upper() or motion:lower()] = "prev",
        }
      end,
      search = { wrap = false },
      highlight = { backdrop = true },
      jump = { register = false },
    },
    -- options used for treesitter selections
    -- `require("flash").treesitter()`
    treesitter = {
      labels = "abcdefghijklmnopqrstuvwxyz",
      jump = { pos = "range" },
      search = { incremental = false },
      label = { before = true, after = true, style = "inline" },
      highlight = {
        backdrop = false,
        matches = false,
      },
    },
    treesitter_search = {
      jump = { pos = "range" },
      search = { multi_window = true, wrap = true, incremental = false },
      remote_op = { restore = true },
      label = { before = true, after = true, style = "inline" },
    },
    -- options used for remote flash
    remote = {
      remote_op = { restore = true, motion = true },
    },
  },
  -- options for the floating window that shows the prompt,
  -- for regular jumps
  prompt = {
    enabled = true,
    prefix = { { "⚑", "FlashPromptIcon" } },
    win_config = {
      relative = "editor",
      width = 1, -- when <=1 it's a percentage of the editor width
      height = 1,
      row = -1, -- when negative it's an offset from the bottom
      col = 0, -- when negative it's an offset from the right
      zindex = 1000,
    },
  },
  -- options for remote operator pending mode
  remote_op = {
    -- restore window views and cursor position
    -- after doing a remote operation
    restore = false,
    -- For `jump.pos = "range"`, this setting is ignored.
    -- `true`: always enter a new motion when doing a remote operation
    -- `false`: use the window's cursor position and jump target
    -- `nil`: act as `true` for remote windows, `false` for the current window
    motion = false,
  },
}

πŸš€ Usage

  • Treesitter: require("flash").treesitter(opts?) opens flash in Treesitter mode
    • use a jump label, or use ; and , to increase/decrease the selection
  • regular search: search as you normally do, but enhanced with jump labels
  • f, t, F, T motions:
    • After typing f{char} or F{char}, you can repeat the motion with f or go to the previous match with F to undo a jump.
    • Similarly, after typing t{char} or T{char}, you can repeat the motion with t or go to the previous match with T.
    • You can also go to the next match with ; or previous match with ,
    • Any highlights clear automatically when moving, changing buffers, or pressing <esc>.
  • toggle Search: require("flash").toggle(boolean?)
    • toggles flash on or off while using regular search
  • Treesitter Search: require("flash").treesitter_search(opts?) opens flash in Treesitter Search mode
    • combination of Treesitter and Search modes
    • do something like yR
    • you can now start typing a search pattern.
    • arround your matches, all the surrounding Treesitter nodes will be labeled.
    • select a label to perform the operator on the new selection
  • remote: require("flash").remote(opts?) opens flash in remote mode
    • equivalent to:
      require("flash").jump({
        remote_op = {
          restore = true,
          motion = true,
        },
      })
    • this is only useful in operator pending mode.
    • For example, press yr to start yanking and open flash
      • select a label to set the cursor position
      • perform any motion, like iw or even start flash Treesitter with S
      • the yank will be performed on the new selection
      • you'll be back in the original window / position
    • You can also configure the remote_op options by default, so that ys, behaves like yr for remote operations
      require("flash").jump({
        remote_op = {
          restore = true,
          motion = nil,
        },
      })
  • jump: require("flash").jump(opts?) opens flash with the given options
    • type any number of characters before typing a jump label
  • VS Code: some functionality is changed/disabled when running flash in VS Code:
    • prompt is disabled
    • highlights are set to different defaults that will actually work in VS Code
    • search.multi_window is disabled, since VS Code has problems with vim.api.nvim_set_current_win

πŸ“‘ API

The options for require("flash").jump(opts?), are the same as those in the config section, but can additionally have the following fields:

  • matcher: a custom function that generates matches for a given window
  • labeler: a custom function to label matches

You can also add labels in the matcher function and then set labeler to an empty function labeler = function() end

Type Definitions
type FlashMatcher = (win: number, state: FlashState) => FlashMatch[];
type FlashLabeler = (matches: FlashMatch[], state: FlashState) => void;

interface FlashMatch {
  win: number;
  pos: [number, number]; // (1,0)-indexed
  end_pos: [number, number]; // (1,0)-indexed
  label?: string | false; // set to false to never show a label for this match
  highlight?: boolean; // override opts.highlight.matches for this match
}

// Check the code for the full definition
// of Flash.State at `lua/flash/state.lua`
type FlashState = {};

πŸ’‘ Examples

Forward search only
require("flash").jump({
  search = { forward = true, wrap = false, multi_window = false },
})
Backward search only
require("flash").jump({
  search = { forward = false, wrap = false, multi_window = false },
})
Show diagnostics at target, without changing cursor position
require("flash").jump({
  action = function(match, state)
    vim.api.nvim_win_call(match.win, function()
      vim.api.nvim_win_set_cursor(match.win, match.pos)
      vim.diagnostic.open_float()
    end)
    state:restore()
  end,
})

-- More advanced example that also highlights diagnostics:
require("flash").jump({
  matcher = function(win)
    ---@param diag Diagnostic
    return vim.tbl_map(function(diag)
      return {
        pos = { diag.lnum + 1, diag.col },
        end_pos = { diag.end_lnum + 1, diag.end_col - 1 },
      }
    end, vim.diagnostic.get(vim.api.nvim_win_get_buf(win)))
  end,
  action = function(match, state)
    vim.api.nvim_win_call(match.win, function()
      vim.api.nvim_win_set_cursor(match.win, match.pos)
      vim.diagnostic.open_float()
    end)
    state:restore()
  end,
})
Match beginning of words only
require("flash").jump({
  search = {
    mode = function(str)
      return "\\<" .. str
    end,
  },
})
Initialize flash with the word under the cursor
require("flash").jump({
  pattern = vim.fn.expand("<cword>"),
})
Jump to a line
require("flash").jump({
  search = { mode = "search", max_length = 0 },
  label = { after = { 0, 0 } },
  pattern = "^"
})
Select any word
require("flash").jump({
  pattern = ".", -- initialize pattern with any char
  search = {
    mode = function(pattern)
      -- remove leading dot
      if pattern:sub(1, 1) == "." then
        pattern = pattern:sub(2)
      end
      -- return word pattern and proper skip pattern
      return ([[\<%s\w*\>]]):format(pattern), ([[\<%s]]):format(pattern)
    end,
  },
  -- select the range
  jump = { pos = "range" },
})
f, t, F, T with labels

Use the options below:

{
  modes = {
    char = {
      jump_labels = true
    }
  }
}
Telescope integration

This will allow you to use s in normal mode and <c-s> in insert mode, to jump to a label in Telescope results.

{
    "nvim-telescope/telescope.nvim",
    optional = true,
    opts = function(_, opts)
      local function flash(prompt_bufnr)
        require("flash").jump({
          pattern = "^",
          label = { after = { 0, 0 } },
          search = {
            mode = "search",
            exclude = {
              function(win)
                return vim.bo[vim.api.nvim_win_get_buf(win)].filetype ~= "TelescopeResults"
              end,
            },
          },
          action = function(match)
            local picker = require("telescope.actions.state").get_current_picker(prompt_bufnr)
            picker:set_selection(match.pos[1] - 1)
          end,
        })
      end
      opts.defaults = vim.tbl_deep_extend("force", opts.defaults or {}, {
        mappings = {
          n = { s = flash },
          i = { ["<c-s>"] = flash },
        },
      })
    end,
  }
Continue last search
require("flash").jump({continue = true})
2-char jump, similar to mini.jump2d or HopWord (hop.nvim)
local Flash = require("flash")

---@param opts Flash.Format
local function format(opts)
  -- always show first and second label
  return {
    { opts.match.label1, "FlashMatch" },
    { opts.match.label2, "FlashLabel" },
  }
end

Flash.jump({
  search = { mode = "search" },
  label = { after = false, before = { 0, 0 }, uppercase = false, format = format },
  pattern = [[\<]],
  action = function(match, state)
    state:hide()
    Flash.jump({
      search = { max_length = 0 },
      highlight = { matches = false },
      label = { format = format },
      matcher = function(win)
        -- limit matches to the current label
        return vim.tbl_filter(function(m)
          return m.label == match.label and m.win == win
        end, state.results)
      end,
      labeler = function(matches)
        for _, m in ipairs(matches) do
          m.label = m.label2 -- use the second label
        end
      end,
    })
  end,
  labeler = function(matches, state)
    local labels = state:labels()
    for m, match in ipairs(matches) do
      match.label1 = labels[math.floor((m - 1) / #labels) + 1]
      match.label2 = labels[(m - 1) % #labels + 1]
      match.label = match.label1
    end
  end,
})

🌈 Highlights

Group Default Description
FlashBackdrop Comment backdrop
FlashMatch Search search matches
FlashCurrent IncSearch current match
FlashLabel Substitute jump label

πŸ“¦ Alternatives

More Repositories

1

lazy.nvim

πŸ’€ A modern plugin manager for Neovim
Lua
11,573
star
2

tokyonight.nvim

πŸ™ A clean, dark Neovim theme written in Lua, with support for lsp, treesitter and lots of plugins. Includes additional themes for Kitty, Alacritty, iTerm and Fish.
Lua
5,173
star
3

trouble.nvim

🚦 A pretty diagnostics, references, telescope results, quickfix and location list to help you solve all the trouble your code is causing.
Lua
4,726
star
4

which-key.nvim

πŸ’₯ Create key bindings that stick. WhichKey is a lua plugin for Neovim 0.5 that displays a popup with possible keybindings of the command you started typing.
Lua
4,441
star
5

noice.nvim

πŸ’₯ Highly experimental plugin that completely replaces the UI for messages, cmdline and the popupmenu.
Lua
3,677
star
6

todo-comments.nvim

βœ… Highlight, list and search todo comments in your projects
Lua
2,711
star
7

neodev.nvim

πŸ’» Neovim setup for init.lua and plugin development with full signature help, docs and completion for the nvim lua API.
Lua
1,935
star
8

zen-mode.nvim

🧘 Distraction-free coding for Neovim
Lua
1,442
star
9

ultra-runner

πŸƒβ›° Ultra fast monorepo script runner and build tool
TypeScript
1,188
star
10

twilight.nvim

πŸŒ… Twilight is a Lua plugin for Neovim 0.5 that dims inactive portions of the code you're editing using TreeSitter.
Lua
1,130
star
11

esbuild-runner

⚑️ Super-fast on-the-fly transpilation of modern JS, TypeScript and JSX using esbuild
TypeScript
701
star
12

dot

β˜•οΈ My Dot Files
Lua
670
star
13

edgy.nvim

Easily create and manage predefined window layouts, bringing a new edge to your workflow
Lua
665
star
14

neoconf.nvim

πŸ’Ό Neovim plugin to manage global and project-local settings
Lua
605
star
15

persistence.nvim

πŸ’Ύ Simple session management for Neovim
Lua
533
star
16

lsp-colors.nvim

🌈 Plugin that creates missing LSP diagnostics highlight groups for color schemes that don't yet support the Neovim 0.5 builtin LSP client.
Lua
434
star
17

devmoji

πŸ€– πŸš€ ✨ Emojify your conventional commits with Devmoji
TypeScript
286
star
18

styler.nvim

Simple Neovim plugin to set a different colorscheme per filetype.
Lua
247
star
19

drop.nvim

🍁 Fun little plugin that can be used as a screensaver and on your dashboard
Lua
190
star
20

polydock

✨ A shiny and hackable application dock
TypeScript
179
star
21

paint.nvim

Easily add additional highlights to your buffers
Lua
152
star
22

vscode-monorepo-workspace

πŸ“¦βœ¨Manage monorepos with multi-root workspaces. Supports Lerna, Yarn, Pnpm, Rushjs and recursive package directories.
TypeScript
138
star
23

splashcii

Simple cli tool that shows a random ascii art from https://www.asciiur.com/
TypeScript
35
star
24

semantic-release-commit-filter

πŸ“¦πŸ•΅οΈβ€β™€οΈ Semantic-release plugin that filters git commits based on the current working directory
JavaScript
25
star
25

zmk-config

Folke's ZMK config
C++
21
star
26

lovelace-styler

Custom styling for lovelace cards
TypeScript
18
star
27

folke

7
star
28

phpintel

Accurate autocompletion / code checking / .... framework for PHP
PHP
3
star
29

adventofcode

πŸŽ… πŸŽ„ β›„ ✨ Solutions for Advent of Code 2020 in TypeScript
TypeScript
2
star
30

chess

C#
2
star
31

adventofcode2019

Advent of Code 2019 in TypeScript
TypeScript
2
star