Fortran · 7146 bytes Raw Blame History
1 module hover_tooltip_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 implicit none
7 private
8
9 public :: hover_tooltip_t
10 public :: init_hover_tooltip, cleanup_hover_tooltip
11 public :: show_hover_tooltip, hide_hover_tooltip
12 public :: handle_hover_response
13 public :: is_hover_visible
14
15 integer, parameter :: MAX_WIDTH = 60
16 integer, parameter :: MAX_HEIGHT = 10
17
18 type :: hover_tooltip_t
19 character(len=:), allocatable :: content
20 logical :: visible = .false.
21
22 ! Position on screen
23 integer :: row = 0
24 integer :: col = 0
25 integer :: width = 0
26 integer :: height = 0
27 end type hover_tooltip_t
28
29 contains
30
31 subroutine init_hover_tooltip(tooltip)
32 type(hover_tooltip_t), intent(out) :: tooltip
33
34 tooltip%visible = .false.
35 tooltip%row = 0
36 tooltip%col = 0
37 tooltip%width = 0
38 tooltip%height = 0
39
40 if (allocated(tooltip%content)) deallocate(tooltip%content)
41 end subroutine init_hover_tooltip
42
43 subroutine cleanup_hover_tooltip(tooltip)
44 type(hover_tooltip_t), intent(inout) :: tooltip
45
46 if (allocated(tooltip%content)) deallocate(tooltip%content)
47 tooltip%visible = .false.
48 end subroutine cleanup_hover_tooltip
49
50 ! Handle LSP hover response and populate tooltip
51 subroutine handle_hover_response(tooltip, response)
52 type(hover_tooltip_t), intent(inout) :: tooltip
53 type(json_value_t), intent(in) :: response
54 type(json_value_t) :: contents, markup_content
55 character(len=:), allocatable :: hover_text, language, value
56
57 call cleanup_hover_tooltip(tooltip)
58
59 ! Check if response has contents
60 if (json_has_key(response, "contents")) then
61 contents = json_get_object(response, "contents")
62
63 ! LSP hover can return different formats:
64 ! 1. MarkupContent with kind and value
65 ! 2. MarkedString (deprecated but still used)
66 ! 3. Plain string
67 ! 4. Array of MarkedString
68
69 if (json_has_key(contents, "kind") .and. json_has_key(contents, "value")) then
70 ! MarkupContent format
71 hover_text = json_get_string(contents, "value")
72 else if (json_has_key(contents, "language") .and. json_has_key(contents, "value")) then
73 ! MarkedString with language
74 language = json_get_string(contents, "language")
75 value = json_get_string(contents, "value")
76 hover_text = language // ": " // value
77 else
78 ! Try as plain string
79 hover_text = json_get_string(response, "contents")
80 end if
81
82 ! Clean up markdown formatting for terminal display
83 if (len_trim(hover_text) > 0) then
84 ! Remove markdown code block markers if present
85 if (hover_text(1:3) == "```") then
86 ! Find end of first line
87 block
88 integer :: start_pos, end_pos
89 start_pos = index(hover_text, char(10))
90 if (start_pos > 0) then
91 end_pos = index(hover_text, "```", back=.true.)
92 if (end_pos > start_pos) then
93 hover_text = hover_text(start_pos+1:end_pos-1)
94 end if
95 end if
96 end block
97 end if
98
99 tooltip%content = hover_text
100 call calculate_tooltip_dimensions(tooltip)
101 end if
102 end if
103 end subroutine handle_hover_response
104
105 subroutine calculate_tooltip_dimensions(tooltip)
106 type(hover_tooltip_t), intent(inout) :: tooltip
107 integer :: content_len, lines, i, line_start, line_len, max_line_len
108
109 if (.not. allocated(tooltip%content)) return
110
111 content_len = len(tooltip%content)
112 lines = 1
113 max_line_len = 0
114 line_start = 1
115
116 ! Count lines and find max line length
117 do i = 1, content_len
118 if (tooltip%content(i:i) == char(10)) then ! newline
119 line_len = i - line_start
120 if (line_len > max_line_len) max_line_len = line_len
121 lines = lines + 1
122 line_start = i + 1
123 end if
124 end do
125
126 ! Check last line
127 line_len = content_len - line_start + 1
128 if (line_len > max_line_len) max_line_len = line_len
129
130 tooltip%width = min(max_line_len + 4, MAX_WIDTH) ! +4 for borders and padding
131 tooltip%height = min(lines + 2, MAX_HEIGHT) ! +2 for borders
132 end subroutine calculate_tooltip_dimensions
133
134 subroutine show_hover_tooltip(tooltip, row, col)
135 type(hover_tooltip_t), intent(inout) :: tooltip
136 integer, intent(in) :: row, col
137 integer :: display_row, i, line_start, line_end
138 character(len=256) :: line
139
140 if (.not. allocated(tooltip%content)) return
141 if (len_trim(tooltip%content) == 0) return
142
143 tooltip%row = row
144 tooltip%col = col
145 tooltip%visible = .true.
146
147 ! Draw top border
148 call terminal_move_cursor(tooltip%row, tooltip%col)
149 call terminal_write("┌" // repeat("─", tooltip%width - 2) // "┐")
150
151 ! Draw content lines
152 display_row = tooltip%row + 1
153 line_start = 1
154
155 do i = 1, len(tooltip%content)
156 if (tooltip%content(i:i) == char(10) .or. i == len(tooltip%content)) then
157 if (i == len(tooltip%content) .and. tooltip%content(i:i) /= char(10)) then
158 line_end = i
159 else
160 line_end = i - 1
161 end if
162
163 call terminal_move_cursor(display_row, tooltip%col)
164 write(line, '(a,a,a)') "│ ", &
165 tooltip%content(line_start:line_end), " "
166 call terminal_write(line(1:min(len_trim(line), tooltip%width - 1)))
167 call terminal_move_cursor(display_row, tooltip%col + tooltip%width - 1)
168 call terminal_write("│")
169
170 display_row = display_row + 1
171 line_start = i + 1
172
173 if (display_row - tooltip%row >= tooltip%height - 1) exit
174 end if
175 end do
176
177 ! Draw bottom border
178 call terminal_move_cursor(display_row, tooltip%col)
179 call terminal_write("└" // repeat("─", tooltip%width - 2) // "┘")
180
181 end subroutine show_hover_tooltip
182
183 subroutine hide_hover_tooltip(tooltip)
184 type(hover_tooltip_t), intent(inout) :: tooltip
185 tooltip%visible = .false.
186 end subroutine hide_hover_tooltip
187
188 function is_hover_visible(tooltip) result(visible)
189 type(hover_tooltip_t), intent(in) :: tooltip
190 logical :: visible
191
192 visible = tooltip%visible
193 end function is_hover_visible
194
195 end module hover_tooltip_module