forked from sudo-tee/opencode.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnavigation.lua
More file actions
204 lines (175 loc) · 5.37 KB
/
Copy pathnavigation.lua
File metadata and controls
204 lines (175 loc) · 5.37 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
local M = {}
local state = require('opencode.state')
local renderer = require('opencode.ui.renderer')
local output_window = require('opencode.ui.output_window')
function M.goto_message_by_id(message_id)
require('opencode.ui.ui').focus_output()
local windows = state.windows or {}
local win = windows.output_win
local buf = windows.output_buf
if not win or not buf then
return
end
local rendered_msg = renderer.get_rendered_message(message_id)
if not rendered_msg or not rendered_msg.line_start then
return
end
vim.api.nvim_win_set_cursor(win, { rendered_msg.line_start + 1, 0 })
end
function M.goto_next_message()
require('opencode.ui.ui').focus_output()
local windows = state.windows or {}
local win = windows.output_win
local buf = windows.output_buf
if not win or not buf then
return
end
local current_line = vim.api.nvim_win_get_cursor(win)[1]
local next_message = renderer.get_next_rendered_message(current_line)
if next_message and next_message.line_start then
vim.api.nvim_win_set_cursor(win, { next_message.line_start + 1, 0 })
return
end
local line_count = vim.api.nvim_buf_line_count(buf)
vim.api.nvim_win_set_cursor(win, { line_count, 0 })
end
function M.goto_prev_message()
require('opencode.ui.ui').focus_output()
local windows = state.windows or {}
local win = windows.output_win
local buf = windows.output_buf
if not win or not buf then
return
end
local current_line = vim.api.nvim_win_get_cursor(win)[1]
local previous_message = renderer.get_prev_rendered_message(current_line)
if previous_message and previous_message.line_start then
vim.api.nvim_win_set_cursor(win, { previous_message.line_start + 1, 0 })
return
end
vim.api.nvim_win_set_cursor(win, { 1, 0 })
end
---@param raw string
local function resolve_path(raw)
if vim.uv.fs_stat(raw) then
return raw
end
local absolute = vim.fn.fnamemodify(raw, ':p')
if vim.uv.fs_stat(absolute) then
return absolute
end
local found = vim.fn.findfile(raw, '.;')
if found ~= '' then
return found
end
end
---Resolve file and line number at cursor position in the output buffer.
---@return { path: string, line: number? }?
function M.resolve_file_at_cursor()
local windows = state.windows or {}
local win = windows.output_win
local buf = windows.output_buf
if not win or not buf or not vim.api.nvim_win_is_valid(win) then
return nil
end
local cursor = vim.api.nvim_win_get_cursor(win)
local line_num = cursor[1]
local line = vim.api.nvim_buf_get_lines(buf, line_num - 1, line_num, false)[1]
if not line then
return nil
end
-- 1. Check for markdown-style file links: [`path`](path)
local path = line:match('%[`([^`]+)%`%]%([^%)]+%)')
if path then
return { path = path }
end
-- 2. Check for file:// style links: `file://path/to/file.lua:line`
local f_path, f_line = line:match('`file://([^:`]+):?(%d*)`')
if f_path then
return { path = f_path, line = tonumber(f_line) }
end
-- 3. Check for action lines: **icon tool** `path`
path = line:match('%*%*.-%*%*%s+`([^`]+)`')
if path then
return { path = path }
end
-- 4. Check for diff hunk: look for the nearest file path upwards
local file_path = nil
for i = line_num, 1, -1 do
local l = vim.api.nvim_buf_get_lines(buf, i - 1, i, false)[1]
if l then
local p = l:match('%[`([^`]+)%`%]%([^%)]+%)') or l:match('%*%*.-%*%*%s+`([^`]+)`')
if p then
file_path = p
break
end
end
end
if not file_path then
return nil
end
-- Check if we are on a diff line with a line number in the gutter
local ns = output_window.namespace
local extmarks = vim.api.nvim_buf_get_extmarks(buf, ns, { line_num - 1, 0 }, { line_num - 1, -1 }, { details = true })
local ln ---@type number?
for _, extmark in ipairs(extmarks) do
local details = extmark[4]
if details and details.virt_text then
for _, vt in ipairs(details.virt_text) do
local val = tonumber(vim.trim(vt[1]))
if val then
ln = val
break
end
end
end
if ln then
break
end
end
return { path = file_path, line = ln }
end
---Open a file in the current window without triggering BufRead/BufNew autocmds.
---Falls back to :edit if the file isn't loaded in any buffer yet.
---@param path string
local function open_silent(path)
local escaped = vim.fn.fnameescape(path)
if not pcall(vim.cmd, 'buffer ' .. escaped) then
pcall(vim.cmd, 'edit ' .. escaped)
end
end
local function open_at(win, path, line)
if not win or not vim.api.nvim_win_is_valid(win) then
return
end
vim.api.nvim_set_current_win(win)
open_silent(path)
if line then
local buf = vim.api.nvim_win_get_buf(win)
local line_count = vim.api.nvim_buf_line_count(buf)
line = math.min(line, line_count)
pcall(vim.api.nvim_win_set_cursor, win, { line, 0 })
end
end
local function best_target_win()
local w = state.last_code_win_before_opencode
if w and vim.api.nvim_win_is_valid(w) then
return w
end
local alt = vim.fn.win_getid(vim.fn.winnr('#'))
if alt ~= 0 and vim.api.nvim_win_is_valid(alt) then
return alt
end
end
function M.jump_to_file_at_cursor()
local resolved = M.resolve_file_at_cursor()
if not resolved then
return
end
local path = resolve_path(resolved.path)
if not path then
return
end
open_at(best_target_win(), path, resolved.line)
end
return M