From 29c4cfa569ee0a9bc11867b9720708cfc2231ff3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 31 Jan 2025 12:39:40 +0800 Subject: [PATCH] chore(flygrep): include flygrep.nvim --- .ci/detach_plugin.sh | 17 ++ .github/workflows/async.yml | 2 +- bundle/flygrep.nvim/README.md | 103 +++++++ bundle/flygrep.nvim/lua/flygrep.lua | 265 ++++++++++++++++++ bundle/flygrep.nvim/lua/flygrep/config.lua | 53 ++++ bundle/flygrep.nvim/lua/flygrep/highlight.lua | 13 + bundle/flygrep.nvim/plugin/flygrep.lua | 13 + 7 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 bundle/flygrep.nvim/README.md create mode 100644 bundle/flygrep.nvim/lua/flygrep.lua create mode 100644 bundle/flygrep.nvim/lua/flygrep/config.lua create mode 100644 bundle/flygrep.nvim/lua/flygrep/highlight.lua create mode 100644 bundle/flygrep.nvim/plugin/flygrep.lua diff --git a/.ci/detach_plugin.sh b/.ci/detach_plugin.sh index acd5c565d..560f70239 100755 --- a/.ci/detach_plugin.sh +++ b/.ci/detach_plugin.sh @@ -97,6 +97,23 @@ main () { _checkdir test _detact_bundle FlyGrep test/vimrc ;; + flygrep.nvim) + git clone https://github.com/wsdjeg/$1.git detach/$1 + cd detach/$1 + _detact LICENSE + _checkdir plugin + _detact_bundle flygrep.nvim plugin/flygrep.lua + _detact_bundle flygrep.nvim README.md + _checkdir lua/flygrep + _detact_bundle flygrep.nvim lua/flygrep.lua + _detact_bundle flygrep.nvim lua/flygrep/config.lua + _detact_bundle flygrep.nvim lua/flygrep/highlight.lua + _detact LICENSE + _detact .stylua.toml + _checkdir lua/spacevim/api/vim + _detact lua/spacevim/api/job.lua + _detact lua/spacevim/api/vim/highlight.lua + ;; dein-ui.vim) git clone https://github.com/wsdjeg/dein-ui.vim.git detach/$1 cd detach/$1 diff --git a/.github/workflows/async.yml b/.github/workflows/async.yml index 5a2fa081b..f794a4218 100644 --- a/.github/workflows/async.yml +++ b/.github/workflows/async.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - detachPlugin: ["FlyGrep.vim", "dein-ui.vim", "vim-todo", "iedit.vim", "scrollbar.vim", "vim-chat", "vim-cheat", "xmake.vim", "GitHub.vim", "JavaUnit.vim", "git.vim", "cpicker.nvim", "SourceCounter.vim", "vim-zettelkasten", "format.nvim", "record-key.nvim", "quickfix.nvim", "winbar.nvim"] + detachPlugin: ["FlyGrep.vim", "dein-ui.vim", "vim-todo", "iedit.vim", "scrollbar.vim", "vim-chat", "vim-cheat", "xmake.vim", "GitHub.vim", "JavaUnit.vim", "git.vim", "cpicker.nvim", "SourceCounter.vim", "vim-zettelkasten", "format.nvim", "record-key.nvim", "quickfix.nvim", "winbar.nvim", "flygrep.nvim"] runs-on: ubuntu-latest steps: - name: Checkout diff --git a/bundle/flygrep.nvim/README.md b/bundle/flygrep.nvim/README.md new file mode 100644 index 000000000..9fd9b1fc9 --- /dev/null +++ b/bundle/flygrep.nvim/README.md @@ -0,0 +1,103 @@ +# flygrep.nvim + +> _flygrep.nvim_ is a plugin to searching text in neovim floating window asynchronously + +[![](https://spacevim.org/img/build-with-SpaceVim.svg)](https://spacevim.org) +[![GPLv3 License](https://img.spacevim.org/license-GPLv3-blue.svg)](LICENSE) + +![Image](https://github.com/user-attachments/assets/49638d4c-4828-4d46-9c24-165102ef61a7) + + + +- [Intro](#intro) +- [Requirements](#requirements) +- [Install](#install) +- [Command](#command) +- [Configuration](#configuration) +- [Key Bindings](#key-bindings) +- [Feedback](#feedback) + + + +## Intro + +`flygrep.nvim` is a neovim plugin that can be used to search code asynchronously in real time. + + +## Requirements + +- [neovim](https://github.com/neovim/neovim): >= v0.10.0 +- [ripgrep](https://github.com/BurntSushi/ripgrep): If you are using other searching tool, you need to set command option of flygrep. + +## Install + +- use [vim-plug](https://github.com/junegunn/vim-plug) package manager + +``` +Plug 'wsdjeg/flygrep.nvim' +``` + +## Command + +- `:FlyGrep`: open flygrep in current directory + +## Configuration + +```lua +require('flygrep').setup({ + color_templete = { + a = { + fg = '#2c323c', + bg = '#98c379', + ctermfg = 16, + ctermbg = 114, + bold = true, + }, + b = { + fg = '#abb2bf', + bg = '#3b4048', + ctermfg = 145, + ctermbg = 16, + bold = false, + }, + }, + timeout = 200, + command = { + execute = 'rg', + default_opts = { + '--no-heading', + '--color=never', + '--with-filename', + '--line-number', + '--column', + '-g', + '!.git', + }, + recursive_opt = {}, + expr_opt = { '-e' }, + fixed_string_opt = { '-F' }, + default_fopts = { '-N' }, + smart_case = { '-S' }, + ignore_case = { '-i' }, + hidden_opt = { '--hidden' }, + }, + matched_higroup = 'IncSearch', +}) +``` + +## Key Bindings + +| Key bindings | descretion | +| ------------ | ---------------------------------- | +| `` | open cursor item | +| `` | next item | +| `` | previous item | +| `` | open item in split window | +| `` | open item in vertical split window | +| `` | open item in new tabpage | + +## Feedback + +The development of this plugin is in [`SpaceVim/bundle/flygrep.nvim`](https://github.com/SpaceVim/SpaceVim/tree/master/bundle/flygrep.nvim) directory. + +If you encounter any bugs or have suggestions, please file an issue in the [issue tracker](https://github.com/SpaceVim/SpaceVim/issues) diff --git a/bundle/flygrep.nvim/lua/flygrep.lua b/bundle/flygrep.nvim/lua/flygrep.lua new file mode 100644 index 000000000..c436fde31 --- /dev/null +++ b/bundle/flygrep.nvim/lua/flygrep.lua @@ -0,0 +1,265 @@ +local M = {} +local conf = require('flygrep.config') +local job = require('spacevim.api.job') +local ok, cmp = pcall(require, 'cmp') +if not ok then + vim.cmd('doautocmd InsertEnter') + ok, cmp = pcall(require, 'cmp') +end + +local grep_timer_id = -1 +local grep_input = '' +local search_jobid = -1 +local search_hi_id = -1 +local fix_string = false + +-- all buffers +local result_bufid = -1 +local result_winid = -1 +local prompt_bufid = -1 +local prompt_winid = -1 + +local prompt_count_id +local extns = vim.api.nvim_create_namespace('floatgrep_ext') + +local function update_result_count() + local count = vim.api.nvim_buf_line_count(result_bufid) + local line = vim.api.nvim_win_get_cursor(result_winid)[1] + prompt_count_id = vim.api.nvim_buf_set_extmark(prompt_bufid, extns, 0, 0, { + id = prompt_count_id, + virt_text = { { string.format('%d/%d', line, count), 'Comment' } }, + virt_text_pos = 'right_align', + }) +end + +local function build_grep_command() + local cmd = {conf.command.execute} + for _, v in ipairs(conf.command.default_opts) do + table.insert(cmd, v) + end + if fix_string then table.insert(cmd, conf.command.fixed_string_opt) else table.insert(cmd, conf.command.expr_opt) end + table.insert(cmd, grep_input) + table.insert(cmd, '.') + return cmd +end + +local function grep_timer(t) + vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, {}) + if prompt_count_id then + pcall(vim.api.nvim_buf_del_extmark, prompt_bufid, extns, prompt_count_id) + prompt_count_id = nil + end + search_jobid = job.start(build_grep_command(), { + on_stdout = function(id, data) + if id == search_jobid and vim.api.nvim_buf_is_valid(prompt_bufid) and vim.api.nvim_win_is_valid(prompt_winid) then + if vim.fn.getbufline(result_bufid, 1)[1] == '' then + vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, data) + else + vim.api.nvim_buf_set_lines(result_bufid, -1, -1, false, data) + end + update_result_count() + end + end, + }) +end + +local function build_prompt_title() + local t = {} + table.insert(t, { ' FlyGrep ', 'FlyGrep_a' }) + table.insert(t, { '', 'FlyGrep_a_FlyGrep_b' }) + if not fix_string then + table.insert(t, { ' expr ', 'FlyGrep_b' }) + else + table.insert(t, { ' string ', 'FlyGrep_b' }) + end + table.insert(t, { '', 'FlyGrep_b' }) + table.insert(t, { ' ' .. vim.fn.getcwd() .. ' ', 'FlyGrep_b' }) + table.insert(t, { '', 'FlyGrep_b_Normal' }) + -- return {{}, {}, {}} + return t +end + +local function toggle_fix_string() + fix_string = not fix_string + vim.cmd('doautocmd TextChangedI') + local conf = vim.api.nvim_win_get_config(prompt_winid) + conf.title = build_prompt_title() + vim.api.nvim_win_set_config(prompt_winid, conf) +end + +local function open_win() + require('flygrep.highlight').def_higroup() + -- 窗口位置 + -- 宽度: columns 的 80% + local screen_width = math.floor(vim.o.columns * 0.8) + -- 起始位位置: lines * 10%, columns * 10% + local start_col = math.floor(vim.o.columns * 0.1) + local start_row = math.floor(vim.o.lines * 0.1) + -- 整体高度:lines 的 80% + local screen_height = math.floor(vim.o.lines * 0.8) + + prompt_bufid = vim.api.nvim_create_buf(false, true) + prompt_winid = vim.api.nvim_open_win(prompt_bufid, true, { + relative = 'editor', + width = screen_width, + height = 1, + col = start_col, + row = start_row + screen_height - 3, + focusable = true, + border = 'rounded', + title = build_prompt_title(), + title_pos = 'left', + -- noautocmd = true, + }) + + vim.api.nvim_set_option_value( + 'winhighlight', + 'NormalFloat:Normal,FloatBorder:WinSeparator', + { win = prompt_winid } + ) + vim.api.nvim_set_option_value('number', false, { win = prompt_winid }) + vim.api.nvim_set_option_value('relativenumber', false, { win = prompt_winid }) + vim.api.nvim_set_option_value('cursorline', false, { win = prompt_winid }) + vim.api.nvim_set_option_value('signcolumn', 'yes', { win = prompt_winid }) + vim.api.nvim_buf_set_extmark(prompt_bufid, extns, 0, 0, { + sign_text = '>', + sign_hl_group = 'Error', + }) + + result_bufid = vim.api.nvim_create_buf(false, true) + result_winid = vim.api.nvim_open_win(result_bufid, false, { + relative = 'editor', + width = screen_width, + height = screen_height - 5, + col = start_col, + row = start_row, + focusable = false, + border = 'rounded', + -- title = 'Result', + -- title_pos = 'center', + -- noautocmd = true, + }) + vim.api.nvim_set_option_value( + 'winhighlight', + 'NormalFloat:Normal,FloatBorder:WinSeparator', + { win = result_winid } + ) + vim.api.nvim_set_option_value('cursorline', true, { win = result_winid }) + cmp.setup.buffer({ + completion = { + autocomplete = false, + }, + }) + vim.cmd('noautocmd startinsert') + + local augroup = vim.api.nvim_create_augroup('floatgrep', { + clear = true, + }) + + vim.api.nvim_create_autocmd({ 'TextChangedI' }, { + group = augroup, + buffer = prompt_bufid, + callback = function(ev) + grep_input = vim.api.nvim_buf_get_lines(prompt_bufid, 0, 1, false)[1] + if grep_input ~= '' then + pcall(vim.fn.matchdelete, search_hi_id, result_winid) + pcall(vim.fn.timer_stop, grep_timer_id) + search_hi_id = vim.fn.matchadd(conf.matched_higroup, grep_input, 10, -1, { window = result_winid }) + grep_timer_id = vim.fn.timer_start(conf.timeout, grep_timer, { ['repeat'] = 1 }) + else + pcall(vim.fn.matchdelete, search_hi_id, result_winid) + pcall(vim.fn.timer_stop, grep_timer_id) + job.stop(search_jobid) + search_jobid = 0 + vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, {}) + end + end, + }) + + -- 使用 Esc/C-c 关闭整个界面 + for _, k in ipairs({ '', '' }) do + vim.keymap.set('i', k, function() + vim.cmd('noautocmd stopinsert') + vim.api.nvim_win_close(prompt_winid, true) + vim.api.nvim_win_close(result_winid, true) + end, { buffer = prompt_bufid }) + end + + -- 搜索结果行转换成文件名、光标位置 + local function get_file_pos(line) + local filename = vim.fn.fnameescape(vim.fn.split(line, [[:\d\+:]])[1]) + local linenr = vim.fn.str2nr(string.sub(vim.fn.matchstr(line, [[:\d\+:]]), 2, -2)) + local colum = vim.fn.str2nr(string.sub(vim.fn.matchstr(line, [[\(:\d\+\)\@<=:\d\+:]]), 2, -2)) + return filename, linenr, colum + end + -- 使用回车键打开光标所在的搜索结果,同时关闭界面 + local function open_item(cmd) + vim.cmd('noautocmd stopinsert') + -- 获取搜索结果光表行 + local line_number = vim.api.nvim_win_get_cursor(result_winid)[1] + local filename, linenr, colum = + get_file_pos(vim.api.nvim_buf_get_lines(result_bufid, line_number - 1, line_number, false)[1]) + vim.api.nvim_win_close(prompt_winid, true) + vim.api.nvim_win_close(result_winid, true) + vim.cmd(cmd .. ' ' .. filename) + vim.api.nvim_win_set_cursor(0, { linenr, colum }) + end + vim.keymap.set('i', '', function() + open_item('edit') + end, { buffer = prompt_bufid }) + vim.keymap.set('i', '', function() + open_item('vsplit') + end, { buffer = prompt_bufid }) + vim.keymap.set('i', '', function() + open_item('split') + end, { buffer = prompt_bufid }) + vim.keymap.set('i', '', function() + open_item('tabedit') + end, { buffer = prompt_bufid }) + + -- 避免使用 jk 切换到 normal 模式 + -- https://github.com/neovim/neovim/discussions/32208 + -- vim.keymap.del('i', 'jk', {buffer = prompt_bufid}) + if vim.fn.hasmapto('j', 'i') == 1 then + vim.keymap.set('i', 'j', 'j', { + nowait = true, + buffer = prompt_bufid, + }) + end + + -- 使用 Tab/Shift-Tab 上下移动搜素结果 + vim.keymap.set('i', '', function() + local line_number = vim.api.nvim_win_get_cursor(result_winid)[1] + pcall(vim.api.nvim_win_set_cursor, result_winid, { line_number + 1, 0 }) + update_result_count() + end, { buffer = prompt_bufid }) + + vim.keymap.set('i', '', function() + local line_number = vim.api.nvim_win_get_cursor(result_winid)[1] + pcall(vim.api.nvim_win_set_cursor, result_winid, { line_number - 1, 0 }) + update_result_count() + end, { buffer = prompt_bufid }) + vim.keymap.set('i', '', function() + toggle_fix_string() + update_result_count() + end, { buffer = prompt_bufid }) + + -- 高亮文件名及位置 + vim.fn.matchadd( + 'Comment', + [[\([A-Z]:\)\?[^:]*:\d\+:\(\d\+:\)\?]], + 11, + -1, + { window = result_winid } + ) +end + +function M.open() + open_win() +end + +function M.setup(conf) + require('flygrep.config').setup(conf) +end + +return M diff --git a/bundle/flygrep.nvim/lua/flygrep/config.lua b/bundle/flygrep.nvim/lua/flygrep/config.lua new file mode 100644 index 000000000..cbecfbdea --- /dev/null +++ b/bundle/flygrep.nvim/lua/flygrep/config.lua @@ -0,0 +1,53 @@ +local M = {} + +M.color_templete = { + a = { + fg = '#2c323c', + bg = '#98c379', + ctermfg = 16, + ctermbg = 114, + bold = true, + }, + b = { + fg = '#abb2bf', + bg = '#3b4048', + ctermfg = 145, + ctermbg = 16, + bold = false, + }, +} + +M.command = { + execute = 'rg', + default_opts = { + '--no-heading', + '--color=never', + '--with-filename', + '--line-number', + '--column', + '-g', + '!.git', + }, + expr_opt = '-e', + fixed_string_opt = '-F', + default_fopts = { '-N' }, + smart_case = '-S', + ignore_case = '-i', + hidden_opt = '--hidden', +} + +M.timeout = 200 + +M.matched_higroup = 'IncSearch' + +M.setup = function(conf) + if type(conf) ~= 'table' then + return + end + + M.color_templete = conf.color_templete or M.color_templete + + M.timeout = conf.timeout or M.timeout +end + +return M diff --git a/bundle/flygrep.nvim/lua/flygrep/highlight.lua b/bundle/flygrep.nvim/lua/flygrep/highlight.lua new file mode 100644 index 000000000..4ed3d3be7 --- /dev/null +++ b/bundle/flygrep.nvim/lua/flygrep/highlight.lua @@ -0,0 +1,13 @@ +local M = {} + +local color_templete = require('flygrep.config').color_templete +local hi = require('spacevim.api.vim.highlight') + +function M.def_higroup() + vim.api.nvim_set_hl(0, 'FlyGrep_a', color_templete.a) + vim.api.nvim_set_hl(0, 'FlyGrep_b', color_templete.b) + hi.hi_separator('FlyGrep_a', 'FlyGrep_b') + hi.hi_separator('FlyGrep_b', 'Normal') +end + +return M diff --git a/bundle/flygrep.nvim/plugin/flygrep.lua b/bundle/flygrep.nvim/plugin/flygrep.lua new file mode 100644 index 000000000..03294dc3f --- /dev/null +++ b/bundle/flygrep.nvim/plugin/flygrep.lua @@ -0,0 +1,13 @@ +--============================================================================= +-- flygrep.lua +-- Copyright 2025 Eric Wong +-- Author: Eric Wong < wsdjeg@outlook.com > +-- URL: https://github.com/wsdjeg/flygrep.nvim +-- License: GPLv3 +--============================================================================= + +vim.api.nvim_create_user_command('FlyGrep', function(opt) + require('flygrep').open() +end, {}) + +require('flygrep').setup()