/nvim-spectre

Find the enemy and replace them with dark power.

Primary LanguageLuaMIT LicenseMIT

nvim-spectre

A search panel for neovim.

Spectre find the enemy and replace them with dark power.

demo

Why Use Spectre?

  • Use regex in search
  • It can filter search by path glob (filetype)
  • It only searches when you leave Insert Mode, incsearch can be annoying when writing regex
  • Use one buffer and you can edit or move
  • A tool to replace text on project

Installation

Plug 'nvim-lua/plenary.nvim'
Plug 'nvim-pack/nvim-spectre'

You may also need to install the following:

MacOs

You may need run brew install gnu-sed.

Usage

vim.keymap.set('n', '<leader>S', '<cmd>lua require("spectre").open()<CR>', {
    desc = "Open Spectre"
})
vim.keymap.set('n', '<leader>sw', '<cmd>lua require("spectre").open_visual({select_word=true})<CR>', {
    desc = "Search current word"
})
vim.keymap.set('v', '<leader>sw', '<esc><cmd>lua require("spectre").open_visual()<CR>', {
    desc = "Search current word"
})
vim.keymap.set('n', '<leader>sp', '<cmd>lua require("spectre").open_file_search({select_word=true})<CR>', {
    desc = "Search on current file"
})

Use command: :Spectre

Warnings

  • Always commit your files before you replace text. nvim-spectre does not support undo directly.
  • Don't use your crazy vim skills to edit result text or UI or you may encounter strange behaviour.
  • You can use dd to toggle result items.
  • You need to use <Esc> not <C-c> to leave insert mode.

Regex Issues

  • The default regex uses vim's magic mode \v and no-ignore-case.
  • It has different regex sytax compared to the rg command and replace command sed so be careful when replacing text.
  • It has a different highlighting result because I use vim regex to highlight text so be careful but you can try to replace.

Replace

You can replace groups with \0-9 similar to vim and sed, if you run a replace command and don't see the change you may need to reload file with :e because sed is replace outside vim.

Customization

require('spectre').setup()

Change any settings if you don't like them. Don't just copy all as settings may change as the plugin is updated so it may be better use the default settings.

require('spectre').setup({

  color_devicons = true,
  open_cmd = 'vnew',
  live_update = false, -- auto execute search again when you write to any file in vim
  line_sep_start = '┌-----------------------------------------',
  result_padding = '¦  ',
  line_sep       = '└-----------------------------------------',
  highlight = {
      ui = "String",
      search = "DiffChange",
      replace = "DiffDelete"
  },
  mapping={
    ['toggle_line'] = {
        map = "dd",
        cmd = "<cmd>lua require('spectre').toggle_line()<CR>",
        desc = "toggle current item"
    },
    ['enter_file'] = {
        map = "<cr>",
        cmd = "<cmd>lua require('spectre.actions').select_entry()<CR>",
        desc = "goto current file"
    },
    ['send_to_qf'] = {
        map = "<leader>q",
        cmd = "<cmd>lua require('spectre.actions').send_to_qf()<CR>",
        desc = "send all item to quickfix"
    },
    ['replace_cmd'] = {
        map = "<leader>c",
        cmd = "<cmd>lua require('spectre.actions').replace_cmd()<CR>",
        desc = "input replace vim command"
    },
    ['show_option_menu'] = {
        map = "<leader>o",
        cmd = "<cmd>lua require('spectre').show_options()<CR>",
        desc = "show option"
    },
    ['run_current_replace'] = {
      map = "<leader>rc",
      cmd = "<cmd>lua require('spectre.actions').run_current_replace()<CR>",
      desc = "replace current line"
    },
    ['run_replace'] = {
        map = "<leader>R",
        cmd = "<cmd>lua require('spectre.actions').run_replace()<CR>",
        desc = "replace all"
    },
    ['change_view_mode'] = {
        map = "<leader>v",
        cmd = "<cmd>lua require('spectre').change_view()<CR>",
        desc = "change result view mode"
    },
    ['change_replace_sed'] = {
      map = "trs",
      cmd = "<cmd>lua require('spectre').change_engine_replace('sed')<CR>",
      desc = "use sed to replace"
    },
    ['change_replace_oxi'] = {
      map = "tro",
      cmd = "<cmd>lua require('spectre').change_engine_replace('oxi')<CR>",
      desc = "use oxi to replace"
    },
    ['toggle_live_update']={
      map = "tu",
      cmd = "<cmd>lua require('spectre').toggle_live_update()<CR>",
      desc = "update change when vim write file."
    },
    ['toggle_ignore_case'] = {
      map = "ti",
      cmd = "<cmd>lua require('spectre').change_options('ignore-case')<CR>",
      desc = "toggle ignore case"
    },
    ['toggle_ignore_hidden'] = {
      map = "th",
      cmd = "<cmd>lua require('spectre').change_options('hidden')<CR>",
      desc = "toggle search hidden"
    },
    ['resume_last_search'] = {
      map = "<leader>l",
      cmd = "<cmd>lua require('spectre').resume_last_search()<CR>",
      desc = "resume last search before close"
    },
    -- you can put your mapping here it only use normal mode
  },
  find_engine = {
    -- rg is map with finder_cmd
    ['rg'] = {
      cmd = "rg",
      -- default args
      args = {
        '--color=never',
        '--no-heading',
        '--with-filename',
        '--line-number',
        '--column',
      } ,
      options = {
        ['ignore-case'] = {
          value= "--ignore-case",
          icon="[I]",
          desc="ignore case"
        },
        ['hidden'] = {
          value="--hidden",
          desc="hidden file",
          icon="[H]"
        },
        -- you can put any rg search option you want here it can toggle with
        -- show_option function
      }
    },
    ['ag'] = {
      cmd = "ag",
      args = {
        '--vimgrep',
        '-s'
      } ,
      options = {
        ['ignore-case'] = {
          value= "-i",
          icon="[I]",
          desc="ignore case"
        },
        ['hidden'] = {
          value="--hidden",
          desc="hidden file",
          icon="[H]"
        },
      },
    },
  },
  replace_engine={
      ['sed']={
          cmd = "sed",
          args = nil,
          options = {
            ['ignore-case'] = {
              value= "--ignore-case",
              icon="[I]",
              desc="ignore case"
            },
          }
      },
      -- call rust code by nvim-oxi to replace
      ['oxi'] = {
        cmd = 'oxi',
        args = {},
        options = {
          ['ignore-case'] = {
            value = "i",
            icon = "[I]",
            desc = "ignore case"
          },
        }
      }
  },
  default = {
      find = {
          --pick one of item in find_engine
          cmd = "rg",
          options = {"ignore-case"}
      },
      replace={
          --pick one of item in replace_engine
          cmd = "sed"
      }
  },
  replace_vim_cmd = "cdo",
  is_open_target_win = true, --open file on opener window
  is_insert_mode = false  -- start open panel on is_insert_mode
})

Custom Functions

-- if you want to get items from spectre panel you can use some of the
-- following functions to get data from spectre.
require('spectre.actions').get_current_entry()
require('spectre.actions').get_all_entries()
require('spectre.actions').get_state()

-- write your custom open function
require('spectre').open({
  is_insert_mode = true,
  cwd = "~/.config/nvim",
  search_text="test",
  replace_text="test",
  path="lua/**/*.lua",
  is_close = false, -- close an exists instance of spectre and open new
})
-- you can use all variables above on command line
-- for example: Spectre % is_insert_mode=true cwd=~/.config/nvim
-- in this example `%` will expand to current file.

Replace Method

There are two replace methods sed and oxi.

Sed oxi
group number by '\0' group number by '${0}'
use vim to highlight on UI use rust to highlight on UI
use sed to replace use rust to replace
run sed command call rust code directly by nvim-oxi

Install oxi:

  • you will need to install cargo and run the command: build.sh nvim-oxi

  • set default replace command to "oxi" on setup()

require('spectre').setup({
    default = {
        replace = {
            cmd = "oxi"
       }
    }
  )

Sponsors

Thanks to everyone who sponsors my projects and makes continued development and maintenance possible!

FAQ

  • How can I add a custom status line? windline
    require('windline').add_status(
        require('spectre.state_utils').status_line()
    )
  • Why is it called Spectre?

I wanted to call it Search Panel but this name is not cool.

I got the name of a hero on a game.

Spectre has a skill to find enemy on global map so I use it:)