Skip to content

Commit

Permalink
Merge pull request #3 from mistweaverco/feat/live-search
Browse files Browse the repository at this point in the history
feat: add live-search
  • Loading branch information
gorillamoe authored Aug 7, 2024
2 parents 5b15fd6 + 5c7d63f commit 7e0849b
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 8 deletions.
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ require('lazy').setup({
{
'mistweaverco/tafuta.nvim',
-- Make sure this matches the command you want to use and the command pass to setup
-- as user_command_prompt and user_command_cursor
-- as user_command_prompt, user_command_cursor and user_command_live
-- e.g. if you want to use `:Rg` then the cmd should be `Rg`
-- If you don't want to use a command, you can omit this option completely
cmd = { "Tf", "Tfc" },
cmd = { "Tf", "Tfc", "Tfl" },
config = function()
-- Setup is required, even if you don't pass any options
require('tafuta').setup({
Expand All @@ -54,7 +54,8 @@ require('lazy').setup({
-- If you don't want a command, you can set it to `nil`
user_command_prompt = "Tf",
user_command_cursor = "Tfc",
-- rg options, a lua table of options to pass to rg,
user_command_live = "Tfl",
-- rg options, a lua table of options to pass to rg as default,
-- e.g. { "--hidden", "--no-ignore" }
-- Default: nil
-- See `rg --help` for more options
Expand Down Expand Up @@ -128,3 +129,26 @@ or via calling a lua function:
```lua
require('tafuta').cursor()
```

#### Live search

You can also search with live results via:

> [!NOTE]
> You need to escape all spaces in the live search
```
:Tfl
```

or via calling a lua function:

```lua
require('tafuta').live()
```

> [!TIP]
> You can also pass flags to the live search
> Just prepend your flags to the command like so:
> `--hidden --no-ignore --smart-case Search\ for\ word`
2 changes: 2 additions & 0 deletions lua/tafuta/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ M.defaults = {
user_command_prompt = "Tf",
-- The user command to search for the word under the cursor e.g. `:Tfc`
user_command_cursor = "Tfc",
-- The user command to run the live search e.g. `:Tfl`
user_command_live = "Tfl",
-- The ripgrep options to use when searching, e.g. `{"--hidden", "--no-ignore-vcs"}`
rg_options = nil,
}
Expand Down
87 changes: 82 additions & 5 deletions lua/tafuta/init.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,72 @@
local GLOBALS = require("tafuta.globals")
local CONFIG = require("tafuta.config")
local UI = require("tafuta.ui")
local UTILS = require("tafuta.utils")
local M = {}

local rg_installed = vim.fn.executable("rg") == 1

local function focus_quickfix()
-- Get the list of all windows
local windows = vim.api.nvim_list_wins()
for _, win in ipairs(windows) do
-- Check if the window is a quickfix list
if vim.fn.getwininfo(win)[1].quickfix == 1 then
-- Focus on the quickfix list window
vim.api.nvim_set_current_win(win)
break
end
end
end

function M.live()
UI.input({
on_confirm = function(textstring)
if textstring == "" then
return
end
focus_quickfix()
end,
on_change = function(textstring)
local t = UTILS.split_args(textstring)
M.run(t, { live = true })
end,
}, {
title = "Live-Search",
})
end

local async_run_live = vim.schedule_wrap(function(res)
local code = res.code
if code == 0 then
local matches = vim.split(res.stdout, "\n", { trimempty = true })
if vim.tbl_isempty(matches) then
vim.notify("❌ no match", vim.log.levels.INFO, { title = "Tafuta" })
return
end
vim.fn.setqflist({}, "r", {
title = "Tafuta results (" .. #matches .. ")",
lines = matches,
})
-- Save the current window ID
local current_win = vim.api.nvim_get_current_win()
-- Open the quickfix list
vim.cmd("copen")
-- Return focus to the previously focused window
vim.api.nvim_set_current_win(current_win)
elseif code == 1 then
vim.fn.setqflist({}, "r", {
title = "Tafuta results",
lines = {},
})
else
vim.fn.setqflist({}, "r", {
title = "Tafuta results",
lines = {},
})
end
end)

local async_run = vim.schedule_wrap(function(res)
local code = res.code
if code == 0 then
Expand Down Expand Up @@ -38,7 +101,7 @@ local function get_word_under_cursor()
end
end

local search = function(search_opts)
local search = function(search_opts, opts)
-- the search query is the last item in the table
local search_query = search_opts[#search_opts]
-- search flags are all the items in the table except the last one
Expand All @@ -51,13 +114,18 @@ local search = function(search_opts)
table.insert(search_command, option)
end
table.insert(search_command, search_query)
vim.system(search_command, { text = true }, async_run)
if opts and opts.live then
vim.system(search_command, { text = true }, async_run_live)
else
vim.system(search_command, { text = true }, async_run)
end
end

M.setup = function(config)
CONFIG.setup(config)
local user_command_prompt = CONFIG.get().user_command_prompt
local user_command_cursor = CONFIG.get().user_command_cursor
local user_command_live = CONFIG.get().user_command_live
if user_command_prompt ~= nil then
vim.api.nvim_create_user_command(user_command_prompt, function(a)
if a.args == "" then
Expand All @@ -78,9 +146,18 @@ M.setup = function(config)
desc = "Search for the word under the cursor",
})
end
if user_command_live ~= nil then
vim.api.nvim_create_user_command(user_command_live, function()
M.live()
end, {
nargs = 0,
desc = "Live search",
})
end
end

M.run = function(search_opts)
M.run = function(search_opts, opts)
opts = opts or {}
if not rg_installed then
vim.notify("❌ ripgrep not found on the system", vim.log.levels.WARN, { title = "Tafuta" })
return
Expand All @@ -89,7 +166,7 @@ M.run = function(search_opts)
vim.notify("❌ no search query provided", vim.log.levels.INFO, { title = "Tafuta" })
return
end
search(search_opts)
search(search_opts, opts)
end

M.cursor = function()
Expand All @@ -98,7 +175,7 @@ M.cursor = function()
vim.notify("❌ no word under cursor", vim.log.levels.INFO, { title = "Tafuta" })
return
end
search({ word })
M.run({ word })
end

M.version = function()
Expand Down
94 changes: 94 additions & 0 deletions lua/tafuta/ui/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
local M = {}

-- input: create a floating window with a prompt
-- @param opts table: options for the prompt
-- @param opts.prompt string: the prompt text
-- @param opts.default string: the default text
-- @param opts.on_confirm function: callback when the user confirms the input
-- @param opts.on_change function: callback when the input changes
-- @param win_opts table: options for the floating window
-- @param win_opts.relative string: the relative position of the window
-- @param win_opts.row number: the row position of the window
-- @param win_opts.col number: the column position of the window
-- @param win_opts.width number: the width of the window
-- @param win_opts.height number: the height of the window
-- @param win_opts.focusable boolean: whether the window is focusable
-- @param win_opts.style string: the style of the window
-- @param win_opts.border string: the border of the window
-- @return nil
M.input = function(opts, win_opts)
-- create a "prompt" buffer that will be deleted once focus is lost
local buf = vim.api.nvim_create_buf(false, false)
vim.bo[buf].buftype = "prompt"
vim.bo[buf].bufhidden = "wipe"

local default_text = opts.default or ""
local on_confirm = opts.on_confirm or function() end

-- defer the on_confirm callback so that it is
-- executed after the prompt window is closed
local deferred_callback = function(input)
vim.defer_fn(function()
on_confirm(input)
end, 10)
end

-- set prompt and callback (CR) for prompt buffer
vim.fn.prompt_setprompt(buf, "")
vim.fn.prompt_setcallback(buf, deferred_callback)

-- set some keymaps: CR confirm and exit, ESC in normal mode to abort
vim.keymap.set({ "i", "n" }, "<CR>", "<CR><Esc>:close!<CR>:stopinsert<CR>", {
silent = true,
buffer = buf,
})
vim.keymap.set("n", "<esc>", "<cmd>close!<CR>", {
silent = true,
buffer = buf,
})

-- if on_change is provided, create an autocmd to call it
if opts.on_change then
vim.api.nvim_create_autocmd("TextChangedI", {
buffer = buf,
callback = function()
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
local text = table.concat(lines, "\n")
opts.on_change(text)
end,
})
end

local default_win_opts = {
title = win_opts.title or "Input",
title_pos = "center",
relative = "editor",
row = vim.o.lines / 2 - 1,
col = vim.o.columns / 2 - 25,
width = 50,
height = 1,
focusable = true,
style = "minimal",
border = "rounded",
}

win_opts = vim.tbl_deep_extend("force", default_win_opts, win_opts)

-- adjust window width so that there is always space
-- for prompt + default text plus a little bit
win_opts.width = #default_text + 5 < win_opts.width and win_opts.width or #default_text + 5

-- open the floating window pointing to our buffer and show the prompt
local win = vim.api.nvim_open_win(buf, true, win_opts)
vim.api.nvim_win_set_option(win, "winhighlight", "Search:None")

vim.cmd("startinsert")

-- set the default text (needs to be deferred after the prompt is drawn)
vim.defer_fn(function()
vim.api.nvim_buf_set_text(buf, 0, 0, 0, 0, { default_text })
vim.cmd("startinsert!") -- bang: go to end of line
end, 5)
end

return M
33 changes: 33 additions & 0 deletions lua/tafuta/utils/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
local M = {}

M.split_args = function(input)
local args = {}
local current_arg = ""
local escape = false

for i = 1, #input do
local char = input:sub(i, i)

if escape then
current_arg = current_arg .. char
escape = false
elseif char == "\\" then
escape = true
elseif char == " " then
if #current_arg > 0 then
table.insert(args, current_arg)
current_arg = ""
end
else
current_arg = current_arg .. char
end
end

if #current_arg > 0 then
table.insert(args, current_arg)
end

return args
end

return M

0 comments on commit 7e0849b

Please sign in to comment.