Fortran · 10923 bytes Raw Blame History
1 module completion_popup_module
2 use iso_fortran_env, only: int32
3 use terminal_io_module, only: terminal_move_cursor, terminal_write
4 use json_module, only: json_value_t, json_get_string, &
5 json_get_object, json_has_key, &
6 json_array_size, json_get_array_element, &
7 json_get_array, json_get_number
8 implicit none
9 private
10
11 public :: completion_popup_t
12 public :: init_completion_popup, cleanup_completion_popup
13 public :: show_completion_popup, hide_completion_popup
14 public :: handle_completion_response
15 public :: navigate_completion_up, navigate_completion_down
16 public :: get_selected_completion, is_completion_visible
17
18 integer, parameter :: MAX_VISIBLE_ITEMS = 10
19 integer, parameter :: MAX_LABEL_WIDTH = 40
20
21 type :: completion_item_t
22 character(len=:), allocatable :: label ! Main text to display
23 character(len=:), allocatable :: kind ! Variable, Function, etc.
24 character(len=:), allocatable :: detail ! Additional info
25 character(len=:), allocatable :: insert_text ! Text to insert when selected
26 end type completion_item_t
27
28 type :: completion_popup_t
29 type(completion_item_t), allocatable :: items(:)
30 integer :: item_count = 0
31 integer :: selected_index = 1
32 integer :: scroll_offset = 0
33 logical :: visible = .false.
34
35 ! Position on screen
36 integer :: row = 0
37 integer :: col = 0
38 integer :: width = 0
39 integer :: height = 0
40 end type completion_popup_t
41
42 contains
43
44 subroutine init_completion_popup(popup)
45 type(completion_popup_t), intent(out) :: popup
46
47 popup%item_count = 0
48 popup%selected_index = 1
49 popup%scroll_offset = 0
50 popup%visible = .false.
51 popup%row = 0
52 popup%col = 0
53 popup%width = MAX_LABEL_WIDTH + 10
54 popup%height = 0
55
56 if (allocated(popup%items)) deallocate(popup%items)
57 end subroutine init_completion_popup
58
59 subroutine cleanup_completion_popup(popup)
60 type(completion_popup_t), intent(inout) :: popup
61 integer :: i
62
63 if (allocated(popup%items)) then
64 do i = 1, popup%item_count
65 if (allocated(popup%items(i)%label)) deallocate(popup%items(i)%label)
66 if (allocated(popup%items(i)%kind)) deallocate(popup%items(i)%kind)
67 if (allocated(popup%items(i)%detail)) deallocate(popup%items(i)%detail)
68 if (allocated(popup%items(i)%insert_text)) deallocate(popup%items(i)%insert_text)
69 end do
70 deallocate(popup%items)
71 end if
72
73 popup%item_count = 0
74 popup%visible = .false.
75 end subroutine cleanup_completion_popup
76
77 ! Handle LSP completion response and populate popup
78 subroutine handle_completion_response(popup, response)
79 type(completion_popup_t), intent(inout) :: popup
80 type(json_value_t), intent(in) :: response
81 type(json_value_t) :: items_array, item, text_edit
82 integer :: i, n_items, kind_num
83 character(len=:), allocatable :: label, kind_str, detail, insert_text
84
85 call cleanup_completion_popup(popup)
86
87 ! Check if response has items array
88 if (json_has_key(response, "items")) then
89 items_array = json_get_array(response, "items")
90 n_items = json_array_size(items_array)
91
92 ! Limit to max visible items for now
93 n_items = min(n_items, MAX_VISIBLE_ITEMS * 2)
94
95 if (n_items > 0) then
96 allocate(popup%items(n_items))
97 popup%item_count = n_items
98
99 do i = 1, n_items
100 item = json_get_array_element(items_array, i-1)
101
102 ! Extract completion item fields
103 label = json_get_string(item, "label")
104 if (len_trim(label) > 0) then
105 popup%items(i)%label = label
106 else
107 popup%items(i)%label = "Item " // char(48 + mod(i-1, 10))
108 end if
109
110 ! Get completion kind (number mapping to type)
111 if (json_has_key(item, "kind")) then
112 kind_num = int(json_get_number(item, "kind"))
113 select case(kind_num)
114 case(1)
115 popup%items(i)%kind = "Text"
116 case(2)
117 popup%items(i)%kind = "Method"
118 case(3)
119 popup%items(i)%kind = "Function"
120 case(4)
121 popup%items(i)%kind = "Constructor"
122 case(5)
123 popup%items(i)%kind = "Field"
124 case(6)
125 popup%items(i)%kind = "Variable"
126 case(7)
127 popup%items(i)%kind = "Class"
128 case(8)
129 popup%items(i)%kind = "Interface"
130 case(9)
131 popup%items(i)%kind = "Module"
132 case(10)
133 popup%items(i)%kind = "Property"
134 case(14)
135 popup%items(i)%kind = "Keyword"
136 case default
137 popup%items(i)%kind = ""
138 end select
139 else
140 popup%items(i)%kind = ""
141 end if
142
143 ! Get detail text if available
144 if (json_has_key(item, "detail")) then
145 detail = json_get_string(item, "detail")
146 popup%items(i)%detail = detail
147 else
148 popup%items(i)%detail = ""
149 end if
150
151 ! Get insert text or fall back to label
152 if (json_has_key(item, "insertText")) then
153 insert_text = json_get_string(item, "insertText")
154 popup%items(i)%insert_text = insert_text
155 else
156 popup%items(i)%insert_text = popup%items(i)%label
157 end if
158 end do
159
160 popup%selected_index = 1
161 popup%scroll_offset = 0
162 popup%height = min(popup%item_count, MAX_VISIBLE_ITEMS) + 2 ! +2 for borders
163 end if
164 end if
165 end subroutine handle_completion_response
166
167 subroutine show_completion_popup(popup, row, col)
168 type(completion_popup_t), intent(inout) :: popup
169 integer, intent(in) :: row, col
170 integer :: i, display_row, start_idx, end_idx
171 character(len=256) :: line
172
173 if (popup%item_count == 0) return
174
175 popup%row = row
176 popup%col = col
177 popup%visible = .true.
178
179 ! Calculate visible range
180 start_idx = popup%scroll_offset + 1
181 end_idx = min(popup%scroll_offset + MAX_VISIBLE_ITEMS, popup%item_count)
182
183 ! Draw top border
184 call terminal_move_cursor(popup%row, popup%col)
185 call terminal_write("┌" // repeat("─", popup%width - 2) // "┐")
186
187 ! Draw items
188 display_row = popup%row + 1
189 do i = start_idx, end_idx
190 call terminal_move_cursor(display_row, popup%col)
191
192 ! Format item line
193 if (i == popup%selected_index) then
194 ! Highlight selected item with > marker
195 write(line, '(a,a)') "│>", adjustl(popup%items(i)%label)
196 call terminal_write(line(1:min(len_trim(line), popup%width - 1)))
197 call terminal_move_cursor(display_row, popup%col + popup%width - 1)
198 call terminal_write("│")
199 else
200 write(line, '(a,a)') "│ ", adjustl(popup%items(i)%label)
201 call terminal_write(line(1:min(len_trim(line), popup%width - 1)))
202 call terminal_move_cursor(display_row, popup%col + popup%width - 1)
203 call terminal_write("│")
204 end if
205
206 display_row = display_row + 1
207 end do
208
209 ! Draw bottom border
210 call terminal_move_cursor(display_row, popup%col)
211 call terminal_write("└" // repeat("─", popup%width - 2) // "┘")
212
213 end subroutine show_completion_popup
214
215 subroutine hide_completion_popup(popup)
216 type(completion_popup_t), intent(inout) :: popup
217 popup%visible = .false.
218 end subroutine hide_completion_popup
219
220 subroutine navigate_completion_up(popup)
221 type(completion_popup_t), intent(inout) :: popup
222
223 if (.not. popup%visible .or. popup%item_count == 0) return
224
225 if (popup%selected_index > 1) then
226 popup%selected_index = popup%selected_index - 1
227
228 ! Adjust scroll if needed
229 if (popup%selected_index <= popup%scroll_offset) then
230 popup%scroll_offset = popup%selected_index - 1
231 end if
232 else
233 ! Wrap to bottom
234 popup%selected_index = popup%item_count
235 popup%scroll_offset = max(0, popup%item_count - MAX_VISIBLE_ITEMS)
236 end if
237 end subroutine navigate_completion_up
238
239 subroutine navigate_completion_down(popup)
240 type(completion_popup_t), intent(inout) :: popup
241
242 if (.not. popup%visible .or. popup%item_count == 0) return
243
244 if (popup%selected_index < popup%item_count) then
245 popup%selected_index = popup%selected_index + 1
246
247 ! Adjust scroll if needed
248 if (popup%selected_index > popup%scroll_offset + MAX_VISIBLE_ITEMS) then
249 popup%scroll_offset = popup%selected_index - MAX_VISIBLE_ITEMS
250 end if
251 else
252 ! Wrap to top
253 popup%selected_index = 1
254 popup%scroll_offset = 0
255 end if
256 end subroutine navigate_completion_down
257
258 function get_selected_completion(popup) result(text)
259 type(completion_popup_t), intent(in) :: popup
260 character(len=:), allocatable :: text
261
262 if (popup%visible .and. popup%item_count > 0 .and. &
263 popup%selected_index > 0 .and. popup%selected_index <= popup%item_count) then
264
265 if (allocated(popup%items(popup%selected_index)%insert_text)) then
266 text = popup%items(popup%selected_index)%insert_text
267 else
268 text = popup%items(popup%selected_index)%label
269 end if
270 else
271 text = ""
272 end if
273 end function get_selected_completion
274
275 function is_completion_visible(popup) result(visible)
276 type(completion_popup_t), intent(in) :: popup
277 logical :: visible
278
279 visible = popup%visible
280 end function is_completion_visible
281
282 end module completion_popup_module