Git Product home page Git Product logo

searchbox.nvim's Introduction

Searchbox

Start your search from a more comfortable place, say the upper right corner?

Neovim in a terminal, displaying a wonderful searchbox

Here's demo of search and replace component, and also match_all search.

search-replace-demo.mp4

Getting Started

Make sure you have Neovim v0.5.1 or greater.

Dependencies

Installation

Use your favorite plugin manager. For example.

With vim-plug:

Plug 'MunifTanjim/nui.nvim'
Plug 'VonHeikemen/searchbox.nvim'

With packer:

use {
  'VonHeikemen/searchbox.nvim',
  requires = {
    {'MunifTanjim/nui.nvim'}
  }
}

With paq:

'VonHeikemen/searchbox.nvim';
'MunifTanjim/nui.nvim';

Types of search

There are four kinds of search:

  • incsearch: Highlights the nearest match of your query as you type.

  • match_all: Highlights all the matches in the buffer as you type. By default the highlights will disappear after you submit your search. If you want them to stay set the argument clear_matches to false (more on this later).

  • simple: Doesn't do anything as you type. No highlight, no moving the cursor around in realtime. It's only purpose is to execute a search.

  • replace: Starts a multi-step input to search and replace. First input allows you to enter a pattern (search term). Second input will ask for the string that will replace the previous pattern.

Usage

There is a command for each kind of search which can be used in a keybinding.

  • Lua Bindings

If using neovim 0.7 or greater.

vim.keymap.set('n', '<leader>s', ':SearchBoxIncSearch<CR>')

If you have neovim 0.6.1 or lower.

vim.api.nvim_set_keymap(
  'n',
  '<leader>s',
  ':SearchBoxIncSearch<CR>',
  {noremap = true}
)
  • Vimscript Bindings
nnoremap <leader>s :SearchBoxIncSearch<CR>

They are also exposed as lua functions, so the following is also valid.

:lua require('searchbox').incsearch()<CR>

Visual mode

To get proper support in visual mode you'll need to add visual_mode=true to the list of arguments.

In this mode the search area is limited to the range set by the selected text. Similar to what the substitute command does in this case :'<,'>s/this/that/g.

  • Lua Bindings
vim.keymap.set('x', '<leader>s', ':SearchBoxIncSearch visual_mode=true<CR>')
  • Vimscript Bindings
xnoremap <leader>s :SearchBoxIncSearch visual_mode=true<CR>

When using the lua api add <Esc> at the beginning of the binding.

<Esc>:lua require('searchbox').incsearch({visual_mode = true})<CR>

Search arguments

You can tweak the behaviour of the search if you pass any of these properties:

  • reverse: Look for matches above the cursor.
  • exact: Look for an exact match.
  • title: Set title for the popup window.
  • prompt: Set input prompt.
  • default_value: Set initial value for the input.
  • visual_mode: Search only in the recently selected text.
  • show_matches: If set to true it'll show number of matches in the input. If set to a string the pattern {total} will be replaced with the number of matches. If the pattern {match} is found it'll be replaced with the index of match under the cursor. You can set for example, show_matches='[M:{match} T:{total}]'. The default format of the message is [{match}/{total}].
  • modifier: Apply a "search modifier" at the beginning of the search pattern. It won't be visible in the search input. Possible values:
    • ignore-case: Make the search case insensitive. Applies the pattern \c.
    • case-sensitive: Make the search case sensitive. Applies the pattern \C.
    • no-magic: Act as if the option nomagic is used. Applies the pattern \M.
    • magic: Act as if the option magic is on. Applies the pattern \m.
    • very-magic: Anything that isn't alphanumeric has a special meaning. Applies the pattern \v.
    • very-no-magic: Only the backslash and the terminating character has special meaning. Applies the pattern \V.
    • plain: Is an alias for very-no-magic.
    • disabled: Is the default. Don't apply any modifier.
    • :: It acts as a prefix. Use it to add your own modifier to the search. Example, :\C\V will make the search very-no-magic and also case sensitive. See :help /magic to know more about possible patterns.

Other arguments are exclusive to one type of search.

For match_all:

  • clear_matches: Get rid of the highlight after the search is done.

For replace:

  • confirm: Ask the user to choose an action on each match. There are three possible values: off, native and menu. off disables the feature. native uses neovim's built-in confirm method. menu displays a list of possible actions below the match. Is worth mentioning menu will only show up if neovim's window is big enough, confirm type will fallback to "native" if it isn't.

Command Api

When using the command api the arguments are a space separated list of key/value pairs. The syntax for the arguments is this: key=value.

:SearchBoxMatchAll title=Match exact=true visual_mode=true<CR>

Because whitespace acts like a separator between the arguments if you want to use it as a value you need to escape it, or use a quoted argument. If you want to use Match All as a title, these are your options.

:SearchBoxMatchAll title="Match All"<CR>
" or
:SearchBoxMatchAll title='Match All'<CR>
:SearchBoxMatchAll title=Match\ All<CR>

Note that escaping is specially funny inside a lua string, so you might need to use \\.

Is worth mention that argument parsing is done manually inside the plugin. Complex escape sequences are not taken into account. Just \" and \' to avoid conflict in quoted arguments, and \ to escape whitespace in a string argument without quotes.

Not being able to use whitespace freely makes it difficult to use default_value with this api, that's why it gets a special treatment. There is no default_value argument, instead everything that follows the -- argument is considered part of the search term.

:SearchBoxMatchAll title=Match clear_matches=false -- I want to search this<CR>

In the example above I want to search this will become the initial value for the search input. This becomes useful when you want to use advance techniques to set the initial value of your search (I'll show you some examples later).

If you only going to set the initial value, meaning you're not going to use any of the other arguments, you can omit the --. This is valid, too.

:SearchBoxMatchAll I want to search this<CR>

Lua api

In this case you'll be using lua functions of the searchbox module instead of commands. The arguments can be provided as a lua table.

:lua require('searchbox').match_all({title='Match All', clear_matches=false, default_value='I want to search this'})<CR>

Examples

Make a reverse search, like the default ?:

:SearchBoxIncSearch reverse=true<CR>

Make the highlight of match_all stay after submit. They can be cleared manually with the command :SearchboxClear.

:SearchBoxMatchAll clear_matches=false<CR>

Move to the nearest exact match without any fuss.

:SearchBoxSimple modifier=case-sensitive exact=true<CR>

Add your own modifier to the search pattern. Here we apply case-sensitive and very-no-magic together. This makes it so we don't need to escape characters like * or ..

:SearchBoxIncSearch modifier=':\C\V'<CR>

Start a search and replace.

:SearchBoxReplace<CR>

Use the word under the cursor to begin search and replace. (Normal mode).

:SearchBoxReplace -- <C-r>=expand('<cword>')<CR><CR>

Look for the word under the cursor.

:SearchBoxMatchAll exact=true -- <C-r>=expand('<cword>')<CR><CR>

Use the selected text as a search term. (Visual mode):

Due to limitations on the input, it can't handle newlines well. So whatever you have selected, must be one line. The escape sequence \n can be use in the search term but will not be interpreted on the second input of search and replace.

y:SearchBoxReplace -- <C-r>"<CR>

Search and replace within the range of the selected text, and look for an exact match. (Visual mode)

:SearchBoxReplace exact=true visual_mode=true<CR>

Confirm every match of search and replace.

  • Normal mode:
:SearchBoxReplace confirm=menu<CR>
  • Visual mode:
:SearchBoxReplace confirm=menu visual_mode=true<CR>

Default keymaps

Inside the input you can use the following keymaps:

  • Alt + .: Writes the content of the last search in the input.
  • Enter: Submit input.
  • Esc: Closes input.
  • Ctrl + c: Close input.
  • Ctrl + y: Scroll up.
  • Ctrl + e: Scroll down.
  • Ctrl + b: Scroll page up.
  • Ctrl + f: Scroll page down.
  • Ctrl + g: Go to previous match.
  • Ctrl + l: Go to next match.

In the confirm menu (of search and replace):

  • y: Confirm replace.
  • n: Move to next match.
  • a: Replace all matches.
  • q: Quit menu.
  • l: Replace match then quit. Think of it as "the last replace".
  • Enter: Accept option.
  • Esc: Quit menu.
  • ctrl + c: Quit menu.
  • Tab: Next option.
  • shift + Tab: Previous option.
  • Down arrow: Next option.
  • Up arrow: Previous option.

The "native" confirm method:

  • y: Confirm replace.
  • n: Move to next match.
  • a: Replace all matches.
  • q: Quit menu.
  • l: Replace match then quit.

Configuration

If you want to change anything in the UI or add a "hook" you can use .setup().

This is the default configuration.

require('searchbox').setup({
  defaults = {
    reverse = false,
    exact = false,
    prompt = ' ',
    modifier = 'disabled',
    confirm = 'off',
    clear_matches = true,
    show_matches = false,
  },
  popup = {
    relative = 'win',
    position = {
      row = '5%',
      col = '95%',
    },
    size = 30,
    border = {
      style = 'rounded',
      text = {
        top = ' Search ',
        top_align = 'left',
      },
    },
    win_options = {
      winhighlight = 'Normal:Normal,FloatBorder:FloatBorder',
    },
  },
  hooks = {
    before_mount = function(input)
      -- code
    end,
    after_mount = function(input)
      -- code
    end,
    on_done = function(value, search_type)
      -- code
    end
  }
})
  • defaults is a "global config", allows you to set some properties for all inputs. So you don't have to declare them using the command api or the lua api.

  • popup is passed directly to nui.popup. You can check the valid keys in their documentation: popup.options

  • hooks must be functions. They will be executed during the "lifecycle" of the input.

before_mount and after_mount receive the instance of the input, so you can do anything with it.

on_done it's executed after the search is finished. In the case of a successful search it gets the value submitted and the type of search as arguments. When doing a search and replace, it gets executed after the last substitution takes place. In case the search was cancelled, the first argument is nil and the second argument is the type of search.

Custom keymaps

You can only modify the keymaps after the input is created, means you should do it in the after_mount hook.

These are the actions available in the input.

  • <Plug>(searchbox-close)
  • <Plug>(searchbox-scroll-up)
  • <Plug>(searchbox-scroll-down)
  • <Plug>(searchbox-scroll-page-up)
  • <Plug>(searchbox-scroll-page-down)
  • <Plug>(searchbox-prev-match)
  • <Plug>(searchbox-next-match)
  • <Plug>(searchbox-last-search)

Here is an example. If you wanted to "add support" for normal mode, you would do this.

after_mount = function(input)
  local opts = {buffer = input.bufnr}

  -- Esc goes to normal mode
  vim.keymap.set('i', '<Esc>', '<cmd>stopinsert<cr>', opts)

  -- Close the input in normal mode
  vim.keymap.set('n', '<Esc>', '<Plug>(searchbox-close)', opts)
  vim.keymap.set('n', '<C-c>', '<Plug>(searchbox-close)', opts)
end

If you are using Neovim 0.6.1 or lower replace vim.keymap.set with vim.api.nvim_buf_set_keymap.

vim.api.nvim_buf_set_keymap(input.bufnr, 'n', '<Esc>', '<Plug>(searchbox-close)', {noremap = false})

Caveats

It's very possible that I can't simulate every feature of the built-in search (/ and ?).

Alternatives

Contributing

Bug fixes are welcome. Everything else? Let's discuss it first.

If you want to improve the UI it will be better if you contribute to nui.nvim.

Support

If you find this tool useful and want to support my efforts, buy me a coffee โ˜•.

buy me a coffee

searchbox.nvim's People

Contributors

evccyr avatar uga-rosa avatar vonheikemen avatar younger-1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

searchbox.nvim's Issues

clear_matches does not work

when I try this:

local map = vim.api.nvim_set_keymap
map('n', '<leader>S', ":SearchBoxMatchAll clear_matches=true<CR>", {noremap = true})

I search something in the searchbox and after press enter, the highlight words still appearing

Cursor doesn't jump for out of focus search matches

If a cursor is say at the top and the search result is at the bottom(out of
screen's focus) then cursor doesn't jump to the search result until you press
return.

With the neovim's default / the cursor jumps to the search result.

Start in replace prompt [feature]

I wish we had option to start directly in replace popup - where searchbox input popup is automatically set to selection or word under cursor, and we do not have to confirm search term. In addition replace popup input could be automatically pre-filled with word under cursor.
Reason is - is user want to rename variable name under cursor - eg. var_xyz to var_xyzw then this feature would simplify whole procedure to only inputting : w
It would be great if this worked with confirm option.

[Feature request]: add hook to define mappings

I notice that your another awesome neovim plugin fine-cmdline provide a hook set_keymaps to makes non-recursive mappings in insert mode. It is very useful to customize the searchbox behaviour. I simulate what I want below:

require('searchbox').setup({
  hooks = {
    set_keymaps = function(imap)
       imap('<C-a>', require('searchbox').match_all())
       imap('<C-r>', require('searchbox').incsearch({reverse = true}))
       imap('<C-x>', require('searchbox').incsearch({exact = true))
    end
  }
})

Say I use global mapping <C-f> to trigger SearchBoxIncSearch. In the searchbox's input I can use local mapping <C-a> to trigger SearchBoxMatchAll or use <C-r> to search backward or use <C-x> to search an exact match.

Basiclly it allow users to define only one global mappings to trigger all SearchBox's functions.

[Question] Colorscheme integration

Hi! I would like to contribute some colorschemes that I use so that Searchbox integrates well, but I didn't find Seachbox's highlight groups. Could you help me with that?

Onedark

Kanagawa
image

Wrong postion confirm menu pop in replace mode

Hi, I'd like to replace a word with replace confirm mode, but the popup menu is in the wrong position, I can't see a word to replace.
Could you please support me to fix it?
image
image

Here is my configuration for the plugin:
local status_ok, searchbox = pcall(require, "searchbox")
if not status_ok then
return
end

searchbox.setup({
defaults = {
reverse = false,
exact = false,
prompt = '๏€‚ ',
modifier = 'disabled',
confirm = 'on',
clear_matches = true,
show_matches = false,
},
popup = {
relative = 'cursor',
position = {
row = 2,
col = 0,
},

size = 30,
border = {
  style = 'rounded',
  text = {
    top = ' Search ',
    top_align = 'center',
  },
},
win_options = {
  winhighlight = 'Normal:Normal,FloatBorder:Normal',
},

},
hooks = {
before_mount = function(input)
-- code
end,
after_mount = function(input)
-- code
end,
on_done = function(value, search_type)
-- code
end
}
})

Replace in visual mode not working as expected

Am I correct in assuming this should prepopulate the search box with the selected text, then allow you to replace it?

Replace an exact match in the selected text. (Needs to be mapped in visual mode)

<Esc><cmd>lua require("searchbox").replace({exact = true, visual_mode = true})<CR>

Currently, that mapping just launches the normal search and replace box from visual mode for me... I was hoping for something along the lines of the following mapping if possible!

map("v", "<leader>s", 'y<Esc>:%s/<C-R>"//g<Left><Left>', { noremap = true })

Awesome work so far!

IncSearch binding doesn't seem to work in visual mode

map("x", "/", "<Esc>:lua require('searchbox').incsearch({visual_mode = true})<CR>", defo)

Maybe I don't know the point of this binding but it doesn't seem to do anything.
It does perform IncSearch but the visual mode selection goes away so either
visual selection is not supposed to disappear or there's no point of this
keybinding?

Also the lua api binding above works the same after removing <Esc> ,i.e. it
clears the visual selection anyways.

Search highlighting broken in visual mode for first character/word in selected text

Mapping: vnoremap <leader>s <Esc><cmd>SearchBoxReplace confirm=menu visual_mode=true<CR>

Selected text:

Screenshot_20220203-231842


First character searched:

Screenshot_20220203-232016

( first 'l' is ignored. )


First word searched:

Screenshot_20220203-231904

( first 'local' is ignored. )


First word searched except first character:

Screenshot_20220203-231946

( works as intended )

Although it doesn't highlight the first character/word when searched, replacement does work.

Error with `match_all`

After the recent updates, I'm getting an error when I try and run require("searchbox").match_all()

E5108: Error executing lua: vim/shared.lua:0: after the second argument: expected table, got nil
stack traceback:
        [C]: in function 'error'
        vim/shared.lua: in function 'validate'
        vim/shared.lua: in function 'merge_config'
        .../pack/packer/start/searchbox.nvim/lua/searchbox/init.lua:92: in function 'match_all'
        /Users/Oli/.config/nvim/lua/Oli/core/mappings.lua:426: in function 'orig'
        ...pack/packer/start/legendary.nvim/lua/legendary/utils.lua:231: in function <...pack/packer/start/legendary.nv
im/lua/legendary/utils.lua:223>

I solved it with require("searchbox").match_all({})

But broke my keymappings which made me ๐Ÿ˜ข .

Small Visual mode search / replace issue.

This plugin is awesome, excellent job.

I just have a small issue with visual mode.

If the search parameter in either does not exist inside the selected block, I get a lua error. Would it be possible to catch that error and replace it with something cleaner? (for example "Search parameter not found" or something similar)

--Edit: Issue applies to normal mode as well. If the search pattern does not appear in the buffer the lua error pops up.

Also, would it be possible to somehow automatically add the selected text in the search field? As in, if only one word is selected, instead of treating it as a block, use it as a default_value and search the whole buffer.

Working alongside gruvbox/vimade

Not really an issue so feel free to close, just wanted to document some troubleshooting steps I had in case anyone finds useful:

  • Playing along with themes
    Some themes suggest using autocmd vimenter * ++nested colorscheme theme in your .vimrc, I found this broke the search highlighting of this plugin, so swapped back to colorscheme theme

  • Playing along with Vimade
    Vimade fades the background of the not active window, but I found this made the highlights not standout enough - I tried using the hook functionality to enable/disable, but I would need a "end of replace" hook. In the end I settled for a hacky solution, re-enabling Vimade 25 seconds after calling searchbox:

nnoremap <leader>/ :VimadeDisable<CR>
	\ <cmd>lua vim.defer_fn(function () vim.api.nvim_command('VimadeEnable') end, 25000)<CR>
	\ <cmd>lua require('searchbox').replace({})<CR>

match-all doesn't jump to the first match on netrw

vim.api.nvim_set_keymap('n', '/', '<cmd>lua require("searchbox").match_all()<CR>', { noremap = true })

I was trying the keymap above. It works exactly as expected in normal files (jumps to the first match after pressing enter), but in netrw it doesn't actually jump to the first match. Not sure if this is a feature/bug.

Wondering if there is some option I've missed.

Double confirm in a sample command

:SearchBoxReplace -- <C-r>=expand('<cword>')<CR><CR>

This is given in the documentation.

This is my mapping:
map("n", "gr", ":SearchBoxReplace -- <C-r>=expand('<cword>')<CR><CR>", defo)

It doesn't directly take me to with: but takes me to replace: so needing an extra manual <CR> which I don't think should happen.

This is nitpicking but you should add a discussions tab for such silly questions.

SearchBoxMatchAll triggers `undo`

Steps:

  • Open a buffer, do some modifications
  • :SearchBoxMatchAll, input a query then press <CR>

Result:
An undo has been made on the buffer.

nohl doesn't work

Why is nohl unable to clear the searchbox highlights? Is it intended?

Current situation is:
map("n", "<C-l>", ":nohl<CR>:SearchBoxClear<CR><C-l>", { noremap = true, silent = true })

Default_value + confirm='menu' error

After using this comand:
:lua require('searchbox').replace({default_value = vim.fn.expand('<cword>'), confirm = 'menu'})<CR>

On text (cursor is over 'numpy' :

if True:
    numpy as np
numpy as np
numpy as np 

Popup menu is displayed, confirm menu is displayed ok too, but then accepting first change with gives error:
image

It seems error is happening only when first numpy is indented once, and then next occurance of numpy is not indented.

Btw. small feature request - can replace happen from place where cursor is located, and not from to of file? Great plugin btw.

Integration with nvim-hlslens

Is there an easy way to integrate the search with nvim-hlslens?

I tried to use the hook on_done with: require('hlslens').start()

This doesn't work as expected. if I execute :lua require('hlslens').start() manually after finishing a search using searchbox it seems to work. Am I using the on_done hook wrong?

on_done = function(value, search_type)
    require('hlslens').start()
end,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.