Repository navigation
Expand file tree
/
Copy pathinline_input.lua
More file actions
167 lines (147 loc) · 4.69 KB
/
Copy pathinline_input.lua
File metadata and controls
167 lines (147 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
local M = {}
---@class InlineInputOpts
---@field win integer -- window to anchor against
---@field row integer -- 0-indexed row in that window's buffer
---@field col integer -- 0-indexed col in that window's buffer
---@field title? string -- window border title
---@field initial_text? string
---@field on_submit fun(text: string)
---@field on_cancel fun()
---@field on_leave? fun(text: string)
---Open a floating, prompt-buffer-backed text input anchored at a specific
---(row, col) inside an existing window's buffer, so it visually appears
---"inline" at that position rather than as a separate cmdline prompt.
---@param opts InlineInputOpts
---@return { close: fun(), win: integer, buf: integer }
function M.open(opts)
local anchor = vim.fn.screenpos(opts.win, opts.row + 1, opts.col + 1)
local width = math.max(1, math.min(50, vim.o.columns - anchor.col - 1))
local col_shift = math.max(0, anchor.col + width + 1 - vim.o.columns)
local row_shift = math.max(0, anchor.row + 3 - vim.o.lines)
local max_height = math.max(1, vim.o.lines - anchor.row + row_shift - 2)
local initial_lines = opts.initial_text and vim.split(opts.initial_text, '\n', { plain = true }) or nil
local buf = vim.api.nvim_create_buf(false, true)
vim.bo[buf].buftype = 'prompt'
vim.bo[buf].bufhidden = 'wipe'
vim.fn.prompt_setprompt(buf, '')
if initial_lines then
vim.api.nvim_buf_set_lines(buf, 0, 1, false, initial_lines)
end
local win = vim.api.nvim_open_win(buf, true, {
relative = 'win',
win = opts.win,
bufpos = { opts.row, opts.col },
row = 1 - row_shift,
col = -col_shift,
width = width,
height = 1,
style = 'minimal',
border = 'rounded',
title = opts.title and (' ' .. opts.title .. ' ') or nil,
title_pos = opts.title and 'left' or nil,
zindex = 60,
})
vim.wo[win].wrap = true
local function resize_height()
if not vim.api.nvim_win_is_valid(win) then
return
end
local line_count = vim.api.nvim_buf_line_count(buf)
local height = vim.api.nvim_win_text_height(win, { start_row = 0, end_row = line_count - 1 }).all
vim.api.nvim_win_set_config(win, { height = math.min(max_height, math.max(1, height)) })
end
resize_height()
local closed = false
local win_closed_autocmd
local function delete_win_closed_autocmd()
if win_closed_autocmd then
pcall(vim.api.nvim_del_autocmd, win_closed_autocmd)
win_closed_autocmd = nil
end
end
local function close()
if closed then
return
end
closed = true
delete_win_closed_autocmd()
if vim.api.nvim_win_is_valid(win) then
vim.api.nvim_win_close(win, true)
end
if vim.api.nvim_win_is_valid(opts.win) then
vim.api.nvim_set_current_win(opts.win)
end
vim.schedule(function()
pcall(vim.cmd.stopinsert)
end)
end
local function cancel_with_draft()
local text = table.concat(vim.api.nvim_buf_get_lines(buf, 0, -1, false), '\n')
close()
if opts.on_leave then
opts.on_leave(text)
end
opts.on_cancel()
end
vim.fn.prompt_setcallback(buf, function(text)
close()
if text ~= '' then
opts.on_submit(text)
else
opts.on_cancel()
end
end)
vim.keymap.set('i', '<C-c>', function()
cancel_with_draft()
end, { buffer = buf, silent = true, nowait = true })
vim.keymap.set('n', '<Esc>', function()
cancel_with_draft()
end, { buffer = buf, silent = true, nowait = true })
vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, {
buffer = buf,
callback = function()
vim.schedule(resize_height)
end,
})
win_closed_autocmd = vim.api.nvim_create_autocmd('WinClosed', {
pattern = { tostring(opts.win), tostring(win) },
callback = function(event)
if closed then
return
end
if tonumber(event.match) == opts.win then
cancel_with_draft()
return
end
closed = true
delete_win_closed_autocmd()
if vim.api.nvim_win_is_valid(opts.win) then
vim.api.nvim_set_current_win(opts.win)
end
vim.schedule(function()
pcall(vim.cmd.stopinsert)
end)
opts.on_cancel()
end,
})
vim.api.nvim_create_autocmd('WinLeave', {
buffer = buf,
callback = function()
if not closed then
cancel_with_draft()
end
end,
})
vim.schedule(function()
if vim.api.nvim_win_is_valid(win) then
vim.api.nvim_set_current_win(win)
vim.cmd.startinsert()
if initial_lines then
local last_line = initial_lines[#initial_lines]
vim.api.nvim_win_set_cursor(win, { #initial_lines, vim.fn.strlen(last_line) })
end
end
end)
return { close = close, win = win, buf = buf }
end
return M