Fortran · 13524 bytes Raw Blame History
1 module code_actions_panel_module
2 use iso_fortran_env, only: int32
3 use terminal_io_module, only: terminal_move_cursor, terminal_write
4 implicit none
5 private
6
7 public :: code_actions_panel_t, code_action_t
8 public :: init_code_actions_panel, cleanup_code_actions_panel
9 public :: show_code_actions_panel, hide_code_actions_panel
10 public :: toggle_code_actions_panel
11 public :: is_code_actions_panel_visible, code_actions_panel_handle_key
12 public :: set_code_actions, clear_code_actions
13 public :: get_selected_action, render_code_actions_panel
14
15 ! Code action type
16 type :: code_action_t
17 character(len=:), allocatable :: title
18 character(len=:), allocatable :: kind ! quickfix, refactor, etc.
19 character(len=:), allocatable :: command
20 logical :: is_preferred = .false.
21 ! Store the full action JSON for applying later
22 character(len=:), allocatable :: action_json
23 end type code_action_t
24
25 ! Code actions panel (offcanvas on right side)
26 type :: code_actions_panel_t
27 logical :: visible = .false.
28 integer :: width = 45 ! Panel width in columns
29 integer :: selected_index = 1
30 integer :: scroll_offset = 0
31
32 ! Actions data
33 type(code_action_t), allocatable :: actions(:)
34 integer :: num_actions = 0
35 end type code_actions_panel_t
36
37 contains
38
39 subroutine init_code_actions_panel(panel)
40 type(code_actions_panel_t), intent(out) :: panel
41
42 panel%visible = .false.
43 panel%width = 45
44 panel%selected_index = 1
45 panel%scroll_offset = 0
46 panel%num_actions = 0
47 end subroutine init_code_actions_panel
48
49 subroutine cleanup_code_actions_panel(panel)
50 type(code_actions_panel_t), intent(inout) :: panel
51 integer :: i
52
53 if (allocated(panel%actions)) then
54 do i = 1, panel%num_actions
55 if (allocated(panel%actions(i)%title)) deallocate(panel%actions(i)%title)
56 if (allocated(panel%actions(i)%kind)) deallocate(panel%actions(i)%kind)
57 if (allocated(panel%actions(i)%command)) deallocate(panel%actions(i)%command)
58 if (allocated(panel%actions(i)%action_json)) deallocate(panel%actions(i)%action_json)
59 end do
60 deallocate(panel%actions)
61 end if
62
63 panel%num_actions = 0
64 panel%selected_index = 1
65 panel%scroll_offset = 0
66 end subroutine cleanup_code_actions_panel
67
68 subroutine set_code_actions(panel, actions, num_actions)
69 type(code_actions_panel_t), intent(inout) :: panel
70 type(code_action_t), intent(in) :: actions(:)
71 integer, intent(in) :: num_actions
72 integer :: i
73
74 ! Clear existing actions
75 call cleanup_code_actions_panel(panel)
76
77 if (num_actions > 0) then
78 allocate(panel%actions(num_actions))
79 panel%num_actions = num_actions
80
81 do i = 1, num_actions
82 if (allocated(actions(i)%title)) then
83 allocate(character(len=len(actions(i)%title)) :: panel%actions(i)%title)
84 panel%actions(i)%title = actions(i)%title
85 end if
86
87 if (allocated(actions(i)%kind)) then
88 allocate(character(len=len(actions(i)%kind)) :: panel%actions(i)%kind)
89 panel%actions(i)%kind = actions(i)%kind
90 end if
91
92 if (allocated(actions(i)%command)) then
93 allocate(character(len=len(actions(i)%command)) :: panel%actions(i)%command)
94 panel%actions(i)%command = actions(i)%command
95 end if
96
97 if (allocated(actions(i)%action_json)) then
98 allocate(character(len=len(actions(i)%action_json)) :: panel%actions(i)%action_json)
99 panel%actions(i)%action_json = actions(i)%action_json
100 end if
101
102 panel%actions(i)%is_preferred = actions(i)%is_preferred
103 end do
104 end if
105
106 panel%selected_index = 1
107 panel%scroll_offset = 0
108 end subroutine set_code_actions
109
110 subroutine clear_code_actions(panel)
111 type(code_actions_panel_t), intent(inout) :: panel
112 call cleanup_code_actions_panel(panel)
113 end subroutine clear_code_actions
114
115 subroutine show_code_actions_panel(panel)
116 type(code_actions_panel_t), intent(inout) :: panel
117 panel%visible = .true.
118 panel%selected_index = 1
119 panel%scroll_offset = 0
120 end subroutine show_code_actions_panel
121
122 subroutine hide_code_actions_panel(panel)
123 type(code_actions_panel_t), intent(inout) :: panel
124 panel%visible = .false.
125 end subroutine hide_code_actions_panel
126
127 subroutine toggle_code_actions_panel(panel)
128 type(code_actions_panel_t), intent(inout) :: panel
129 panel%visible = .not. panel%visible
130 if (panel%visible) then
131 panel%selected_index = 1
132 panel%scroll_offset = 0
133 end if
134 end subroutine toggle_code_actions_panel
135
136 function is_code_actions_panel_visible(panel) result(visible)
137 type(code_actions_panel_t), intent(in) :: panel
138 logical :: visible
139 visible = panel%visible
140 end function is_code_actions_panel_visible
141
142 subroutine render_code_actions_panel(panel, screen_rows, screen_cols)
143 type(code_actions_panel_t), intent(in) :: panel
144 integer, intent(in) :: screen_rows, screen_cols
145 integer :: start_col, row, i, max_content_lines
146 character(len=256) :: line_buffer
147 character(len=10) :: kind_icon
148 character(len=10) :: kind_color
149
150 if (.not. panel%visible) return
151
152 ! Calculate panel position (right side)
153 start_col = screen_cols - panel%width + 1
154 if (start_col < 1) start_col = 1
155
156 ! Initialize line buffer
157 line_buffer = repeat(' ', len(line_buffer))
158
159 ! Draw panel header
160 row = 1
161 call terminal_move_cursor(row, start_col)
162 call terminal_write(char(27) // '[48;5;236m') ! Dark background
163 write(line_buffer, '(A,I0,A)') ' Code Actions (', panel%num_actions, ') '
164 call terminal_write(char(27) // '[1m' // trim(line_buffer) // char(27) // '[0m')
165
166 ! Pad header to width
167 call terminal_move_cursor(row, start_col + len_trim(line_buffer))
168 call terminal_write(char(27) // '[48;5;236m' // repeat(' ', panel%width - len_trim(line_buffer)) // char(27) // '[0m')
169
170 ! Separator
171 row = row + 1
172 call terminal_move_cursor(row, start_col)
173 call terminal_write(char(27) // '[48;5;236m' // repeat('-', panel%width) // char(27) // '[0m')
174
175 ! Content area
176 row = row + 1
177 max_content_lines = screen_rows - 4
178
179 ! Display "No code actions" if empty
180 if (panel%num_actions == 0) then
181 call terminal_move_cursor(row, start_col)
182 call terminal_write(char(27) // '[48;5;235m' // char(27) // '[90m')
183 line_buffer = ' No code actions available'
184 call pad_to_width(line_buffer, panel%width)
185 call terminal_write(line_buffer(1:panel%width))
186 call terminal_write(char(27) // '[0m')
187
188 ! Fill remaining lines
189 do i = row + 1, screen_rows - 1
190 call terminal_move_cursor(i, start_col)
191 call terminal_write(char(27) // '[48;5;235m' // repeat(' ', panel%width) // char(27) // '[0m')
192 end do
193 return
194 end if
195
196 ! Render code actions
197 do i = 1, min(panel%num_actions, max_content_lines)
198 call terminal_move_cursor(row, start_col)
199 line_buffer = repeat(' ', len(line_buffer))
200
201 ! Get icon and color based on kind
202 call get_action_display(panel%actions(i)%kind, kind_icon, kind_color)
203
204 ! Highlight selected item
205 if (i == panel%selected_index) then
206 call terminal_write(char(27) // '[48;5;240m') ! Highlight background
207 else
208 call terminal_write(char(27) // '[48;5;235m') ! Normal background
209 end if
210
211 ! Format: " [icon] Title "
212 if (i <= 9) then
213 write(line_buffer, '(A1,I1,A,A,A,A)') ' ', i, '. ', trim(kind_icon), ' ', trim(panel%actions(i)%title)
214 else
215 write(line_buffer, '(A,A,A,A)') ' ', trim(kind_icon), ' ', trim(panel%actions(i)%title)
216 end if
217
218 ! Add preferred indicator
219 if (panel%actions(i)%is_preferred) then
220 line_buffer = trim(line_buffer) // ' *'
221 end if
222
223 ! Truncate if too long
224 if (len_trim(line_buffer) > panel%width - 1) then
225 line_buffer = line_buffer(1:panel%width - 4) // '...'
226 end if
227
228 ! Pad to width
229 call pad_to_width(line_buffer, panel%width)
230
231 ! Apply kind color to icon
232 call terminal_write(trim(kind_color))
233 call terminal_write(line_buffer(1:panel%width))
234 call terminal_write(char(27) // '[0m')
235
236 row = row + 1
237 end do
238
239 ! Fill remaining lines with empty background
240 do i = row, screen_rows - 1
241 call terminal_move_cursor(i, start_col)
242 call terminal_write(char(27) // '[48;5;235m' // repeat(' ', panel%width) // char(27) // '[0m')
243 end do
244
245 ! Footer with hints
246 call terminal_move_cursor(screen_rows - 1, start_col)
247 call terminal_write(char(27) // '[48;5;236m' // char(27) // '[90m')
248 line_buffer = ' j/k:Navigate Enter:Apply Esc:Close'
249 call pad_to_width(line_buffer, panel%width)
250 call terminal_write(line_buffer(1:panel%width))
251 call terminal_write(char(27) // '[0m')
252
253 end subroutine render_code_actions_panel
254
255 subroutine get_action_display(kind, icon, color)
256 character(len=*), intent(in), optional :: kind
257 character(len=10), intent(out) :: icon
258 character(len=10), intent(out) :: color
259
260 icon = ''
261 color = ''
262
263 if (.not. present(kind)) return
264 if (len_trim(kind) == 0) return
265
266 select case(trim(kind))
267 case('quickfix')
268 icon = '[fix]'
269 color = char(27) // '[33m' ! Yellow
270 case('refactor')
271 icon = '[ref]'
272 color = char(27) // '[36m' ! Cyan
273 case('refactor.extract')
274 icon = '[ext]'
275 color = char(27) // '[36m'
276 case('refactor.inline')
277 icon = '[inl]'
278 color = char(27) // '[36m'
279 case('refactor.rewrite')
280 icon = '[rw]'
281 color = char(27) // '[36m'
282 case('source')
283 icon = '[src]'
284 color = char(27) // '[32m' ! Green
285 case('source.organizeImports', 'source.organizeImports.ruff')
286 icon = '[org]'
287 color = char(27) // '[32m'
288 case('source.fixAll', 'source.fixAll.ruff')
289 icon = '[all]'
290 color = char(27) // '[32m'
291 case default
292 icon = ''
293 color = char(27) // '[37m' ! White
294 end select
295 end subroutine get_action_display
296
297 subroutine pad_to_width(buffer, width)
298 character(len=*), intent(inout) :: buffer
299 integer, intent(in) :: width
300 integer :: current_len, i
301
302 current_len = len_trim(buffer)
303 do i = current_len + 1, width
304 if (i <= len(buffer)) buffer(i:i) = ' '
305 end do
306 end subroutine pad_to_width
307
308 function code_actions_panel_handle_key(panel, key) result(handled)
309 type(code_actions_panel_t), intent(inout) :: panel
310 character(len=*), intent(in) :: key
311 logical :: handled
312 integer :: num
313
314 handled = .false.
315 if (.not. panel%visible) return
316
317 select case(trim(key))
318 case('j', 'down')
319 if (panel%selected_index < panel%num_actions) then
320 panel%selected_index = panel%selected_index + 1
321 end if
322 handled = .true.
323
324 case('k', 'up')
325 if (panel%selected_index > 1) then
326 panel%selected_index = panel%selected_index - 1
327 end if
328 handled = .true.
329
330 case('1':'9')
331 ! Quick select by number
332 read(key, '(I1)') num
333 if (num <= panel%num_actions) then
334 panel%selected_index = num
335 end if
336 handled = .true.
337
338 case('enter')
339 ! Action will be applied by caller
340 handled = .true.
341
342 case('escape', 'esc', 'q')
343 panel%visible = .false.
344 handled = .true.
345 end select
346 end function code_actions_panel_handle_key
347
348 function get_selected_action(panel, action_json) result(has_action)
349 type(code_actions_panel_t), intent(in) :: panel
350 character(len=:), allocatable, intent(out) :: action_json
351 logical :: has_action
352
353 has_action = .false.
354
355 if (panel%selected_index > 0 .and. panel%selected_index <= panel%num_actions) then
356 if (allocated(panel%actions(panel%selected_index)%action_json)) then
357 allocate(character(len=len(panel%actions(panel%selected_index)%action_json)) :: action_json)
358 action_json = panel%actions(panel%selected_index)%action_json
359 has_action = .true.
360 end if
361 end if
362 end function get_selected_action
363
364 end module code_actions_panel_module
365