1b279d4d10
e2e Tests / e2e (macos-latest) (push) Waiting to run
e2e Tests / e2e (windows-latest) (push) Waiting to run
Nix CI / check (push) Waiting to run
Python CI / Python bindings (macos-latest) (push) Waiting to run
Python CI / Python bindings (windows-latest) (push) Waiting to run
Build & Publish / Build Neovim aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Neovim x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Waiting to run
Build & Publish / Release (push) Blocked by required conditions
Build & Publish / Publish Python wheels to PyPI (push) Blocked by required conditions
Build & Publish / Publish Rust crates (push) Blocked by required conditions
Build & Publish / Publish npm packages (push) Blocked by required conditions
Rust CI / Test (macos-latest) (push) Waiting to run
Rust CI / Test (windows-latest) (push) Waiting to run
Rust CI / Fuzz Tests (macos-latest) (push) Waiting to run
Rust CI / Fuzz Tests (windows-latest) (push) Waiting to run
Build & Publish / Build MCP aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Waiting to run
Lua CI / lua-language-server type check (push) Failing after 1s
Lua CI / luacheck lint (push) Failing after 1s
Python CI / Python bindings (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Neovim aarch64-linux-android (push) Failing after 3s
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI aarch64-linux-android (push) Failing after 1s
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Failing after 3s
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Failing after 2s
Rust CI / Fuzz Tests (ubuntu-latest) (push) Failing after 1s
Rust CI / Build i686-unknown-linux-gnu (push) Failing after 1s
Rust CI / cargo fmt (push) Failing after 1s
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Failing after 4s
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python sdist (push) Failing after 0s
Rust CI / Test (ubuntu-latest) (push) Failing after 1s
Rust CI / cargo clippy (push) Failing after 1s
Spelling / Spell Check with Typos (push) Failing after 1s
Stylua / Check lua files using Stylua (push) Failing after 1s
e2e Tests / e2e (ubuntu-latest) (push) Failing after 4s
e2e Tests / e2e (alpine-musl) (push) Successful in 58m54s
501 lines
18 KiB
Lua
501 lines
18 KiB
Lua
local M = {}
|
|
|
|
local conf = require('fff.conf')
|
|
local layout = require('fff.layout')
|
|
local preview = require('fff.file_picker.preview')
|
|
local list_separator = require('fff.list_separator')
|
|
local picker_ui_state = require('fff.picker_ui.picker_ui_state')
|
|
|
|
-- Parent module reference (set by picker_ui.lua during initialization).
|
|
-- Allows ui_creator functions to call back into the main picker module.
|
|
---@type table
|
|
local P = nil
|
|
|
|
function M.init(parent_module) P = parent_module end
|
|
|
|
-- Convenience alias
|
|
local S = picker_ui_state.state
|
|
|
|
local function resolve_winhl(kind)
|
|
local hl = S.config.hl
|
|
local winhl = hl.winhl
|
|
local default_winhl = string.format('Normal:%s,FloatBorder:%s,FloatTitle:%s', hl.normal, hl.border, hl.title)
|
|
|
|
if winhl == nil then return default_winhl end
|
|
if type(winhl) == 'string' then return winhl end
|
|
if type(winhl) == 'table' then return winhl[kind] or default_winhl end
|
|
return default_winhl
|
|
end
|
|
|
|
local function open_preview(win_cfg)
|
|
if not win_cfg then return end
|
|
if S.preview_win and vim.api.nvim_win_is_valid(S.preview_win) then return end
|
|
|
|
if not (S.preview_buf and vim.api.nvim_buf_is_valid(S.preview_buf)) then
|
|
S.preview_buf = vim.api.nvim_create_buf(false, true)
|
|
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = S.preview_buf })
|
|
vim.api.nvim_buf_set_name(S.preview_buf, 'fffile preview')
|
|
vim.api.nvim_set_option_value('buftype', 'nofile', { buf = S.preview_buf })
|
|
vim.api.nvim_set_option_value('filetype', 'fff_preview', { buf = S.preview_buf })
|
|
vim.api.nvim_set_option_value('modifiable', false, { buf = S.preview_buf })
|
|
end
|
|
|
|
S.preview_win = vim.api.nvim_open_win(S.preview_buf, false, win_cfg)
|
|
|
|
local win_hl = resolve_winhl('preview')
|
|
|
|
vim.api.nvim_set_option_value('wrap', false, { win = S.preview_win })
|
|
vim.api.nvim_set_option_value('cursorline', false, { win = S.preview_win })
|
|
vim.api.nvim_set_option_value('cursorlineopt', vim.o.cursorlineopt, { win = S.preview_win })
|
|
vim.api.nvim_set_option_value(
|
|
'number',
|
|
S.mode == 'grep' or (conf.get().preview and conf.get().preview.line_numbers or false),
|
|
{ win = S.preview_win }
|
|
)
|
|
vim.api.nvim_set_option_value('relativenumber', false, { win = S.preview_win })
|
|
vim.api.nvim_set_option_value('signcolumn', 'no', { win = S.preview_win })
|
|
vim.api.nvim_set_option_value('foldcolumn', '0', { win = S.preview_win })
|
|
vim.api.nvim_set_option_value('winhighlight', win_hl, { win = S.preview_win })
|
|
|
|
preview.set_preview_window(S.preview_win)
|
|
end
|
|
|
|
local function close_preview()
|
|
if S.preview_win and vim.api.nvim_win_is_valid(S.preview_win) then vim.api.nvim_win_close(S.preview_win, true) end
|
|
S.preview_win = nil
|
|
|
|
if S.preview_buf and vim.api.nvim_buf_is_valid(S.preview_buf) then
|
|
preview.clear_buffer(S.preview_buf)
|
|
vim.api.nvim_buf_delete(S.preview_buf, { force = true })
|
|
end
|
|
S.preview_buf = nil
|
|
S.last_preview_file = nil
|
|
S.last_preview_location = nil
|
|
end
|
|
|
|
local function set_keymap(mode, keys, handler, opts)
|
|
local normalized_keys
|
|
|
|
if type(keys) == 'string' then
|
|
normalized_keys = { keys }
|
|
elseif type(keys) == 'table' then
|
|
normalized_keys = keys
|
|
else
|
|
normalized_keys = {}
|
|
end
|
|
|
|
for _, key in ipairs(normalized_keys) do
|
|
vim.keymap.set(mode, key, handler, opts)
|
|
end
|
|
end
|
|
|
|
local function handle_mouse_click_or_fallback(action, fallback)
|
|
local pos = vim.fn.getmousepos()
|
|
|
|
if P.state.active and pos.winid == S.list_win then
|
|
local item_idx = S.line_to_item[pos.line]
|
|
if not item_idx then return '' end
|
|
|
|
vim.schedule(function()
|
|
if not P.state.active then return end
|
|
if not S.filtered_items[item_idx] then return end
|
|
|
|
if S.cursor ~= item_idx then
|
|
S.cursor = item_idx
|
|
P.render_list()
|
|
if S.mode == 'grep' or S.suggestion_source == 'grep' then
|
|
P.update_preview_smart()
|
|
else
|
|
P.update_preview()
|
|
end
|
|
P.update_status()
|
|
end
|
|
|
|
if action then P.select(action) end
|
|
end)
|
|
return ''
|
|
end
|
|
|
|
return fallback
|
|
end
|
|
|
|
local function move_list_cursor(direction)
|
|
if not P.state.active then return end
|
|
|
|
local items = S.filtered_items
|
|
if #items == 0 then return end
|
|
|
|
local wrap_around = S.config and S.config.wrap_around or false
|
|
local new_cursor = S.cursor + direction
|
|
|
|
if wrap_around then
|
|
if new_cursor < 1 then
|
|
new_cursor = #items
|
|
elseif new_cursor > #items then
|
|
new_cursor = 1
|
|
end
|
|
else
|
|
new_cursor = math.max(1, math.min(new_cursor, #items))
|
|
end
|
|
|
|
if new_cursor ~= S.cursor then
|
|
S.cursor = new_cursor
|
|
P.render_list()
|
|
if S.mode == 'grep' or S.suggestion_source == 'grep' then
|
|
P.update_preview_smart()
|
|
else
|
|
P.update_preview()
|
|
end
|
|
P.update_status()
|
|
end
|
|
end
|
|
|
|
function M.create_ui()
|
|
local config = S.config
|
|
if not config then return false end
|
|
|
|
-- Prompt editing should behave consistently even if the user has :set paste.
|
|
S.restore_paste = (function()
|
|
if not vim.o.paste then return false end
|
|
vim.o.paste = false
|
|
return true
|
|
end)()
|
|
|
|
if not S.ns_id then
|
|
S.ns_id = vim.api.nvim_create_namespace('fff_picker_status')
|
|
list_separator.init(S.ns_id)
|
|
end
|
|
|
|
local computed_layout = layout.compute(config, conf.preview_enabled(config))
|
|
local win_configs = computed_layout.win_configs
|
|
local debug_enabled_in_preview = computed_layout.debug_enabled
|
|
|
|
S.layout = computed_layout.layout
|
|
S.preview_visible = computed_layout.preview_visible
|
|
|
|
S.input_buf = vim.api.nvim_create_buf(false, true)
|
|
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = S.input_buf })
|
|
|
|
S.list_buf = vim.api.nvim_create_buf(false, true)
|
|
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = S.list_buf })
|
|
|
|
if debug_enabled_in_preview then
|
|
S.file_info_buf = vim.api.nvim_create_buf(false, true)
|
|
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = S.file_info_buf })
|
|
else
|
|
S.file_info_buf = nil
|
|
end
|
|
|
|
S.list_win = vim.api.nvim_open_win(S.list_buf, false, win_configs.list)
|
|
if debug_enabled_in_preview and win_configs.file_info then
|
|
S.file_info_win = vim.api.nvim_open_win(S.file_info_buf, false, win_configs.file_info)
|
|
else
|
|
S.file_info_win = nil
|
|
end
|
|
|
|
if S.preview_visible then open_preview(win_configs.preview) end
|
|
|
|
S.input_win = vim.api.nvim_open_win(S.input_buf, false, win_configs.input)
|
|
|
|
M.setup_buffers()
|
|
M.setup_windows()
|
|
M.setup_keymaps()
|
|
|
|
vim.api.nvim_set_current_win(S.input_win)
|
|
|
|
P.update_results_sync()
|
|
P.clear_preview()
|
|
P.update_status()
|
|
|
|
return true
|
|
end
|
|
|
|
function M.setup_buffers()
|
|
vim.api.nvim_buf_set_name(S.input_buf, 'fffile search')
|
|
vim.api.nvim_buf_set_name(S.list_buf, 'fffiles list')
|
|
|
|
vim.api.nvim_set_option_value('buftype', 'prompt', { buf = S.input_buf })
|
|
vim.api.nvim_set_option_value('filetype', 'fff_input', { buf = S.input_buf })
|
|
|
|
vim.fn.prompt_setprompt(S.input_buf, S.config.prompt)
|
|
|
|
-- Changing the contents of the input buffer will trigger Neovim to guess the language
|
|
-- in order to provide syntax highlighting. This makes sure that it's always off.
|
|
vim.api.nvim_create_autocmd('Syntax', {
|
|
buffer = S.input_buf,
|
|
callback = function() vim.api.nvim_set_option_value('syntax', '', { buf = S.input_buf }) end,
|
|
})
|
|
|
|
vim.api.nvim_set_option_value('buftype', 'nofile', { buf = S.list_buf })
|
|
vim.api.nvim_set_option_value('filetype', 'fff_list', { buf = S.list_buf })
|
|
vim.api.nvim_set_option_value('modifiable', false, { buf = S.list_buf })
|
|
|
|
if S.file_info_buf then
|
|
vim.api.nvim_set_option_value('buftype', 'nofile', { buf = S.file_info_buf })
|
|
vim.api.nvim_set_option_value('filetype', 'fff_file_info', { buf = S.file_info_buf })
|
|
vim.api.nvim_set_option_value('modifiable', false, { buf = S.file_info_buf })
|
|
end
|
|
end
|
|
|
|
function M.setup_windows()
|
|
local prompt_win_hl = resolve_winhl('prompt')
|
|
local list_win_hl = resolve_winhl('list')
|
|
local file_info_win_hl = resolve_winhl('file_info')
|
|
|
|
vim.api.nvim_set_option_value('wrap', false, { win = S.input_win })
|
|
vim.api.nvim_set_option_value('cursorline', false, { win = S.input_win })
|
|
vim.api.nvim_set_option_value('number', false, { win = S.input_win })
|
|
vim.api.nvim_set_option_value('relativenumber', false, { win = S.input_win })
|
|
vim.api.nvim_set_option_value('signcolumn', 'no', { win = S.input_win })
|
|
vim.api.nvim_set_option_value('foldcolumn', '0', { win = S.input_win })
|
|
vim.api.nvim_set_option_value('winhighlight', prompt_win_hl, { win = S.input_win })
|
|
|
|
vim.api.nvim_set_option_value('wrap', false, { win = S.list_win })
|
|
vim.api.nvim_set_option_value('cursorline', false, { win = S.list_win })
|
|
vim.api.nvim_set_option_value('number', false, { win = S.list_win })
|
|
vim.api.nvim_set_option_value('relativenumber', false, { win = S.list_win })
|
|
vim.api.nvim_set_option_value('signcolumn', 'yes:1', { win = S.list_win })
|
|
vim.api.nvim_set_option_value('foldcolumn', '0', { win = S.list_win })
|
|
vim.api.nvim_set_option_value('winhighlight', list_win_hl, { win = S.list_win })
|
|
|
|
if S.file_info_win and vim.api.nvim_win_is_valid(S.file_info_win) then
|
|
vim.api.nvim_set_option_value('wrap', false, { win = S.file_info_win })
|
|
vim.api.nvim_set_option_value('cursorline', false, { win = S.file_info_win })
|
|
vim.api.nvim_set_option_value('number', false, { win = S.file_info_win })
|
|
vim.api.nvim_set_option_value('relativenumber', false, { win = S.file_info_win })
|
|
vim.api.nvim_set_option_value('signcolumn', 'no', { win = S.file_info_win })
|
|
vim.api.nvim_set_option_value('foldcolumn', '0', { win = S.file_info_win })
|
|
vim.api.nvim_set_option_value('winhighlight', file_info_win_hl, { win = S.file_info_win })
|
|
end
|
|
|
|
local picker_group = vim.api.nvim_create_augroup('fff_picker_focus', { clear = true })
|
|
|
|
local function is_picker_window(win)
|
|
if not win or not vim.api.nvim_win_is_valid(win) then return false end
|
|
|
|
local picker_windows = { S.input_win, S.list_win }
|
|
if S.preview_win then table.insert(picker_windows, S.preview_win) end
|
|
if S.file_info_win then table.insert(picker_windows, S.file_info_win) end
|
|
|
|
for _, picker_win in ipairs(picker_windows) do
|
|
if picker_win and vim.api.nvim_win_is_valid(picker_win) and win == picker_win then return true end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
vim.api.nvim_create_autocmd('WinLeave', {
|
|
group = picker_group,
|
|
callback = function()
|
|
if not P.state.active then return end
|
|
|
|
local leaving_win = vim.api.nvim_get_current_win()
|
|
|
|
if not is_picker_window(leaving_win) then return end
|
|
|
|
vim.schedule(function()
|
|
if not P.state.active then return end
|
|
|
|
local new_win = vim.api.nvim_get_current_win()
|
|
|
|
if not is_picker_window(new_win) then P.close() end
|
|
end)
|
|
end,
|
|
desc = 'Close picker when focus leaves picker windows',
|
|
})
|
|
|
|
vim.api.nvim_create_autocmd('VimResized', {
|
|
group = picker_group,
|
|
callback = function()
|
|
if not P.state.active then return end
|
|
vim.schedule(function()
|
|
if not P.state.active then return end
|
|
P.relayout()
|
|
end)
|
|
end,
|
|
desc = 'Re-layout picker on terminal resize',
|
|
})
|
|
end
|
|
|
|
function M.setup_keymaps()
|
|
local keymaps = S.config.keymaps
|
|
local input_opts = { buffer = S.input_buf, noremap = true, silent = true }
|
|
local list_opts = { buffer = S.list_buf, noremap = true, silent = true }
|
|
|
|
vim.keymap.set('i', '<C-w>', function()
|
|
local col = vim.fn.col('.') - 1
|
|
local line = vim.fn.getline('.')
|
|
local prompt_len = #S.config.prompt
|
|
if col <= prompt_len then return '' end
|
|
local text_part = line:sub(prompt_len + 1, col)
|
|
local after_cursor = line:sub(col + 1)
|
|
local new_text = text_part:gsub('%S*%s*$', '')
|
|
local new_line = S.config.prompt .. new_text .. after_cursor
|
|
local new_col = prompt_len + #new_text
|
|
vim.fn.setline('.', new_line)
|
|
vim.fn.cursor(vim.fn.line('.'), new_col + 1)
|
|
return ''
|
|
end, input_opts)
|
|
|
|
set_keymap({ 'n', 'i' }, keymaps.move_up, P.move_up, input_opts)
|
|
set_keymap({ 'n', 'i' }, keymaps.move_down, P.move_down, input_opts)
|
|
set_keymap('i', keymaps.cycle_previous_query, P.recall_query_from_history, input_opts)
|
|
set_keymap('i', keymaps.cycle_forward_query, P.cycle_forward_query, input_opts)
|
|
set_keymap('n', 'j', P.move_down, input_opts)
|
|
set_keymap('n', 'k', P.move_up, input_opts)
|
|
set_keymap('n', 'q', P.close, input_opts)
|
|
set_keymap('n', keymaps.focus_list, M.focus_list_win, input_opts)
|
|
set_keymap('n', keymaps.focus_preview, M.focus_preview_win, input_opts)
|
|
|
|
if keymaps.grep_jump_to_next_file then
|
|
set_keymap({ 'i', 'n' }, keymaps.grep_jump_to_next_file, function() P.grep_jump_to_next_file() end, input_opts)
|
|
end
|
|
if keymaps.grep_jump_to_prev_file then
|
|
set_keymap({ 'i', 'n' }, keymaps.grep_jump_to_prev_file, function() P.grep_jump_to_prev_file() end, input_opts)
|
|
end
|
|
|
|
if S.config.prompt_vim_mode then
|
|
set_keymap('n', keymaps.close, P.close, input_opts)
|
|
set_keymap('i', '<C-c>', P.close, input_opts)
|
|
|
|
-- cc/S clear the whole line, wiping the prompt buffer's prompt and leaving
|
|
-- a stray icon behind. Reset to just the prompt and re-enter insert instead.
|
|
local function clear_query_line()
|
|
local prompt = S.config.prompt
|
|
vim.api.nvim_set_option_value('modifiable', true, { buf = S.input_buf })
|
|
vim.api.nvim_buf_set_lines(S.input_buf, 0, -1, false, { prompt })
|
|
vim.schedule(function()
|
|
if S.input_win and vim.api.nvim_win_is_valid(S.input_win) then
|
|
vim.api.nvim_win_set_cursor(S.input_win, { 1, #prompt })
|
|
vim.cmd('startinsert!')
|
|
end
|
|
end)
|
|
end
|
|
-- remap=true so existing user mappings of cc/S still resolve
|
|
local clear_opts = vim.tbl_extend('force', input_opts, { noremap = false, remap = true })
|
|
set_keymap('n', 'cc', clear_query_line, clear_opts)
|
|
set_keymap('n', 'S', clear_query_line, clear_opts)
|
|
else
|
|
set_keymap({ 'i', 'n' }, keymaps.close, P.close, input_opts)
|
|
end
|
|
|
|
set_keymap({ 'i', 'n' }, keymaps.select, P.select, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.select_split, function() P.select('split') end, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.select_vsplit, function() P.select('vsplit') end, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.select_tab, function() P.select('tab') end, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.preview_scroll_up, P.scroll_preview_up, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.preview_scroll_down, P.scroll_preview_down, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.toggle_debug, P.toggle_debug, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.toggle_select, P.toggle_select, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.send_to_quickfix, P.send_to_quickfix, input_opts)
|
|
set_keymap({ 'i', 'n' }, keymaps.cycle_grep_modes, P.cycle_grep_modes, input_opts)
|
|
|
|
local input_mouse_opts = vim.tbl_extend('force', input_opts, { expr = true, replace_keycodes = true })
|
|
set_keymap(
|
|
{ 'i', 'n' },
|
|
'<LeftMouse>',
|
|
function() return handle_mouse_click_or_fallback(nil, '<LeftMouse>') end,
|
|
input_mouse_opts
|
|
)
|
|
set_keymap(
|
|
{ 'i', 'n' },
|
|
'<2-LeftMouse>',
|
|
function() return handle_mouse_click_or_fallback('edit', '<2-LeftMouse>') end,
|
|
input_mouse_opts
|
|
)
|
|
|
|
-- List buffer
|
|
set_keymap('n', keymaps.close, P.close, list_opts)
|
|
set_keymap('n', 'q', P.close, list_opts)
|
|
set_keymap('n', 'j', function() move_list_cursor(1) end, list_opts)
|
|
set_keymap('n', 'k', function() move_list_cursor(-1) end, list_opts)
|
|
set_keymap('n', 'i', M.focus_input_win, list_opts)
|
|
set_keymap('n', keymaps.focus_preview, M.focus_preview_win, list_opts)
|
|
set_keymap('n', keymaps.select, P.select, list_opts)
|
|
set_keymap('n', keymaps.select_split, function() P.select('split') end, list_opts)
|
|
set_keymap('n', keymaps.select_vsplit, function() P.select('vsplit') end, list_opts)
|
|
set_keymap('n', keymaps.select_tab, function() P.select('tab') end, list_opts)
|
|
set_keymap('n', keymaps.preview_scroll_up, P.scroll_preview_up, list_opts)
|
|
set_keymap('n', keymaps.preview_scroll_down, P.scroll_preview_down, list_opts)
|
|
set_keymap('n', keymaps.toggle_debug, P.toggle_debug, list_opts)
|
|
set_keymap('n', keymaps.toggle_select, P.toggle_select, list_opts)
|
|
set_keymap('n', keymaps.send_to_quickfix, P.send_to_quickfix, list_opts)
|
|
|
|
local list_mouse_opts = vim.tbl_extend('force', list_opts, { expr = true, replace_keycodes = true })
|
|
set_keymap(
|
|
'n',
|
|
'<LeftMouse>',
|
|
function() return handle_mouse_click_or_fallback(nil, '<LeftMouse>') end,
|
|
list_mouse_opts
|
|
)
|
|
set_keymap(
|
|
'n',
|
|
'<2-LeftMouse>',
|
|
function() return handle_mouse_click_or_fallback('edit', '<2-LeftMouse>') end,
|
|
list_mouse_opts
|
|
)
|
|
|
|
-- Preview buffer
|
|
if S.preview_buf then
|
|
local preview_opts = { buffer = S.preview_buf, noremap = true, silent = true }
|
|
|
|
set_keymap('n', keymaps.close, P.close, preview_opts)
|
|
set_keymap('n', 'q', P.close, preview_opts)
|
|
set_keymap('n', 'i', M.focus_input_win, preview_opts)
|
|
set_keymap('n', keymaps.focus_list, M.focus_list_win, preview_opts)
|
|
set_keymap('n', keymaps.select, P.select, preview_opts)
|
|
set_keymap('n', keymaps.select_split, function() P.select('split') end, preview_opts)
|
|
set_keymap('n', keymaps.select_vsplit, function() P.select('vsplit') end, preview_opts)
|
|
set_keymap('n', keymaps.select_tab, function() P.select('tab') end, preview_opts)
|
|
set_keymap('n', keymaps.toggle_debug, P.toggle_debug, preview_opts)
|
|
set_keymap('n', keymaps.toggle_select, P.toggle_select, preview_opts)
|
|
set_keymap('n', keymaps.send_to_quickfix, P.send_to_quickfix, preview_opts)
|
|
end
|
|
|
|
vim.api.nvim_buf_attach(S.input_buf, false, {
|
|
on_lines = function()
|
|
vim.schedule(function() P.on_input_change() end)
|
|
end,
|
|
})
|
|
|
|
if S.config.prompt_vim_mode then
|
|
vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, {
|
|
buffer = S.input_buf,
|
|
callback = function()
|
|
local prompt_len = #S.config.prompt
|
|
if vim.fn.col('.') <= prompt_len then vim.fn.cursor(vim.fn.line('.'), prompt_len + 1) end
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
|
|
function M.focus_list_win()
|
|
if not P.state.active then return end
|
|
if not S.list_win or not vim.api.nvim_win_is_valid(S.list_win) then return end
|
|
|
|
vim.cmd('stopinsert')
|
|
vim.api.nvim_set_current_win(S.list_win)
|
|
end
|
|
|
|
function M.focus_preview_win()
|
|
if not P.state.active then return end
|
|
if not S.preview_win or not vim.api.nvim_win_is_valid(S.preview_win) then return end
|
|
|
|
vim.cmd('stopinsert')
|
|
vim.api.nvim_set_current_win(S.preview_win)
|
|
end
|
|
|
|
function M.focus_input_win()
|
|
if not P.state.active then return end
|
|
if not S.input_win or not vim.api.nvim_win_is_valid(S.input_win) then return end
|
|
|
|
vim.api.nvim_set_current_win(S.input_win)
|
|
vim.api.nvim_win_call(S.input_win, function() vim.cmd('startinsert!') end)
|
|
end
|
|
|
|
-- Expose open/close preview for relayout in the main module
|
|
M.open_preview = open_preview
|
|
M.close_preview = close_preview
|
|
|
|
return M
|