Fortran · 13497 bytes Raw Blame History
1 module replace_prompt_module
2 use iso_fortran_env, only: input_unit, output_unit
3 use terminal_io_module
4 use editor_state_module, only: editor_state_t, cursor_t
5 use text_buffer_module
6 use search_prompt_module, only: find_next_match, center_viewport_on_cursor
7 use renderer_module, only: render_screen
8 implicit none
9 private
10
11 public :: show_replace_prompt
12
13 contains
14
15 subroutine show_replace_prompt(editor, buffer)
16 type(editor_state_t), intent(inout) :: editor
17 type(buffer_t), intent(inout) :: buffer
18 character(len=256) :: find_buffer, replace_buffer
19 character(len=64) :: prompt
20 integer :: input_pos, ch
21 logical :: entering_find, entering_replace
22 integer :: replace_count
23 character(len=:), allocatable :: find_pattern, replace_text
24
25 ! Initialize allocatables to prevent "may be uninitialized" warning
26 allocate(character(len=0) :: find_pattern)
27 allocate(character(len=0) :: replace_text)
28
29 ! Initialize
30 find_buffer = ''
31 replace_buffer = ''
32 input_pos = 0
33 entering_find = .true.
34 entering_replace = .false.
35 replace_count = 0
36
37 ! Display initial prompt
38 prompt = 'Replace: '
39 call terminal_move_cursor(editor%screen_rows, 1)
40 call terminal_write(prompt)
41 call terminal_show_cursor()
42
43 ! Input loop for find pattern
44 do
45 ch = terminal_read_char()
46
47 if (ch == -1) then
48 cycle
49 else if (ch == 27) then ! ESC - cancel
50 exit
51 else if (ch == 10 .or. ch == 13) then ! Enter - proceed to replacement
52 if (input_pos > 0 .and. entering_find) then
53 allocate(character(len=input_pos) :: find_pattern)
54 find_pattern = find_buffer(1:input_pos)
55 entering_find = .false.
56 entering_replace = .true.
57 input_pos = 0
58
59 ! Update prompt for replacement
60 prompt = 'With: '
61 call terminal_move_cursor(editor%screen_rows, 1)
62 call terminal_write(repeat(' ', editor%screen_cols))
63 call terminal_move_cursor(editor%screen_rows, 1)
64 call terminal_write(prompt)
65 else if (entering_replace) then
66 allocate(character(len=input_pos) :: replace_text)
67 if (input_pos > 0) then
68 replace_text = replace_buffer(1:input_pos)
69 else
70 replace_text = ''
71 end if
72 exit
73 end if
74 else if (ch == 127 .or. ch == 8) then ! Backspace
75 if (input_pos > 0) then
76 input_pos = input_pos - 1
77 call terminal_move_cursor(editor%screen_rows, 1)
78 if (entering_find) then
79 call terminal_write(prompt // find_buffer(1:input_pos) // ' ')
80 else
81 call terminal_write(prompt // replace_buffer(1:input_pos) // ' ')
82 end if
83 call terminal_move_cursor(editor%screen_rows, len(prompt) + input_pos + 1)
84 end if
85 else if (ch >= 32 .and. ch <= 126) then ! Printable characters
86 if (input_pos < 256) then
87 input_pos = input_pos + 1
88 if (entering_find) then
89 find_buffer(input_pos:input_pos) = char(ch)
90 else
91 replace_buffer(input_pos:input_pos) = char(ch)
92 end if
93 call terminal_write(char(ch))
94 end if
95 end if
96 end do
97
98 ! Clean up prompt line
99 call terminal_hide_cursor()
100 call terminal_move_cursor(editor%screen_rows, 1)
101 call terminal_write(repeat(' ', editor%screen_cols))
102
103 ! If we have both patterns, show replace options
104 if (allocated(find_pattern) .and. allocated(replace_text)) then
105 call execute_replace(editor, buffer, find_pattern, replace_text, replace_count)
106
107 ! Show result
108 call terminal_move_cursor(editor%screen_rows, 1)
109 if (replace_count > 0) then
110 write(prompt, '(a,i0,a)') 'Replaced ', replace_count, ' occurrences'
111 else
112 prompt = 'No matches found'
113 end if
114 call terminal_write(trim(prompt))
115 call flush(output_unit)
116
117 ! Brief pause to show message
118 call sleep_ms(1500)
119
120 ! Clear message
121 call terminal_move_cursor(editor%screen_rows, 1)
122 call terminal_write(repeat(' ', editor%screen_cols))
123 end if
124
125 ! Clean up
126 if (allocated(find_pattern)) deallocate(find_pattern)
127 if (allocated(replace_text)) deallocate(replace_text)
128 end subroutine show_replace_prompt
129
130 subroutine execute_replace(editor, buffer, find_pattern, replace_text, replace_count)
131 type(editor_state_t), intent(inout) :: editor
132 type(buffer_t), intent(inout) :: buffer
133 character(len=*), intent(in) :: find_pattern, replace_text
134 integer, intent(out) :: replace_count
135 logical :: found
136 integer :: found_line, found_col
137 integer :: start_line, start_col
138 character(len=64) :: prompt
139 character :: response
140 integer :: ch
141 logical :: replace_all
142
143 replace_count = 0
144 replace_all = .false.
145 start_line = 1
146 start_col = 1
147
148 ! Find first match
149 call find_next_match(buffer, find_pattern, start_line, start_col, &
150 found, found_line, found_col)
151
152 do while (found)
153 ! Move cursor to match
154 editor%cursors(editor%active_cursor)%line = found_line
155 editor%cursors(editor%active_cursor)%column = found_col
156 editor%cursors(editor%active_cursor)%desired_column = found_col
157
158 ! Select the match
159 editor%cursors(editor%active_cursor)%has_selection = .true.
160 editor%cursors(editor%active_cursor)%selection_start_line = found_line
161 editor%cursors(editor%active_cursor)%selection_start_col = found_col
162 editor%cursors(editor%active_cursor)%column = found_col + len(find_pattern)
163
164 ! Update viewport and render
165 call center_viewport_on_cursor(editor)
166 call render_screen(buffer, editor)
167
168 if (.not. replace_all) then
169 ! Ask for confirmation
170 call terminal_move_cursor(editor%screen_rows, 1)
171 prompt = 'Replace? (y/n/a/q): '
172 call terminal_write(trim(prompt))
173 call terminal_show_cursor()
174
175 ! Get response
176 do
177 ch = terminal_read_char()
178 if (ch > 0) then
179 response = char(ch)
180 exit
181 end if
182 end do
183
184 call terminal_hide_cursor()
185 call terminal_move_cursor(editor%screen_rows, 1)
186 call terminal_write(repeat(' ', editor%screen_cols))
187
188 select case(response)
189 case('y', 'Y')
190 ! Replace this occurrence
191 call perform_replacement(buffer, editor%cursors(editor%active_cursor), &
192 find_pattern, replace_text)
193 replace_count = replace_count + 1
194
195 case('n', 'N')
196 ! Skip this occurrence
197
198 case('a', 'A')
199 ! Replace all remaining
200 replace_all = .true.
201 call perform_replacement(buffer, editor%cursors(editor%active_cursor), &
202 find_pattern, replace_text)
203 replace_count = replace_count + 1
204
205 case('q', 'Q', char(27)) ! q or ESC
206 ! Quit replacing
207 exit
208
209 case default
210 ! Invalid response, skip
211 end select
212 else
213 ! Replace without asking
214 call perform_replacement(buffer, editor%cursors(editor%active_cursor), &
215 find_pattern, replace_text)
216 replace_count = replace_count + 1
217 end if
218
219 ! Clear selection
220 editor%cursors(editor%active_cursor)%has_selection = .false.
221
222 ! Find next match (starting after the replacement)
223 start_line = editor%cursors(editor%active_cursor)%line
224 start_col = editor%cursors(editor%active_cursor)%column
225
226 call find_next_match(buffer, find_pattern, start_line, start_col, &
227 found, found_line, found_col)
228
229 ! Check if we've wrapped around to the beginning
230 if (found .and. found_line <= start_line .and. found_col < start_col) then
231 exit
232 end if
233 end do
234 end subroutine execute_replace
235
236 subroutine perform_replacement(buffer, cursor, find_pattern, replace_text)
237 type(buffer_t), intent(inout) :: buffer
238 type(cursor_t), intent(inout) :: cursor
239 character(len=*), intent(in) :: find_pattern, replace_text
240 character(len=:), allocatable :: line, new_line
241 integer :: col, i
242
243 ! Get current line
244 line = buffer_get_line(buffer, cursor%line)
245
246 ! Build new line with replacement
247 col = cursor%selection_start_col
248 allocate(character(len=len(line) - len(find_pattern) + len(replace_text)) :: new_line)
249
250 ! Copy part before match
251 if (col > 1) then
252 new_line(1:col-1) = line(1:col-1)
253 end if
254
255 ! Insert replacement text
256 if (len(replace_text) > 0) then
257 new_line(col:col+len(replace_text)-1) = replace_text
258 end if
259
260 ! Copy part after match
261 if (col + len(find_pattern) <= len(line)) then
262 new_line(col+len(replace_text):) = line(col+len(find_pattern):)
263 end if
264
265 ! Delete old line content
266 cursor%column = 1
267 do i = 1, len(line)
268 call buffer_delete_at_cursor(buffer, cursor)
269 end do
270
271 ! Insert new line content
272 do i = 1, len(new_line)
273 call buffer_insert_char(buffer, cursor, new_line(i:i))
274 cursor%column = cursor%column + 1
275 end do
276
277 ! Position cursor after replacement
278 cursor%column = col + len(replace_text)
279 cursor%desired_column = cursor%column
280
281 buffer%modified = .true.
282
283 if (allocated(line)) deallocate(line)
284 if (allocated(new_line)) deallocate(new_line)
285 end subroutine perform_replacement
286
287 subroutine buffer_delete_at_cursor(buffer, cursor)
288 type(buffer_t), intent(inout) :: buffer
289 type(cursor_t), intent(in) :: cursor
290 integer :: pos
291
292 pos = get_buffer_position(buffer, cursor%line, cursor%column)
293 if (pos > 0 .and. pos <= get_buffer_content_size(buffer)) then
294 call buffer_delete(buffer, pos, 1)
295 end if
296 end subroutine buffer_delete_at_cursor
297
298 subroutine buffer_insert_char(buffer, cursor, ch)
299 type(buffer_t), intent(inout) :: buffer
300 type(cursor_t), intent(in) :: cursor
301 character, intent(in) :: ch
302 integer :: pos
303
304 pos = get_buffer_position(buffer, cursor%line, cursor%column)
305 call buffer_insert(buffer, pos, ch)
306 end subroutine buffer_insert_char
307
308 function get_buffer_position(buffer, line, column) result(pos)
309 type(buffer_t), intent(in) :: buffer
310 integer, intent(in) :: line, column
311 integer :: pos
312 integer :: current_line, i
313 character :: ch
314
315 pos = 1
316 current_line = 1
317
318 do i = 1, get_buffer_content_size(buffer)
319 if (current_line == line .and. pos == column) then
320 return
321 end if
322
323 ch = buffer_get_char(buffer, i)
324 if (ch == char(10)) then
325 if (current_line == line) then
326 return
327 end if
328 current_line = current_line + 1
329 pos = 1
330 else if (current_line == line) then
331 pos = pos + 1
332 end if
333 end do
334
335 if (current_line == line) then
336 pos = i
337 else
338 pos = get_buffer_content_size(buffer) + 1
339 end if
340 end function get_buffer_position
341
342 function get_buffer_content_size(buffer) result(size)
343 type(buffer_t), intent(in) :: buffer
344 integer :: size
345
346 size = buffer%size - (buffer%gap_end - buffer%gap_start)
347 end function get_buffer_content_size
348
349 subroutine sleep_ms(milliseconds)
350 integer, intent(in) :: milliseconds
351 integer :: count_rate, count_start, count_end
352 real :: elapsed_time
353
354 call system_clock(count_rate=count_rate)
355 call system_clock(count=count_start)
356
357 do
358 call system_clock(count=count_end)
359 elapsed_time = real(count_end - count_start) / real(count_rate) * 1000.0
360 if (elapsed_time >= milliseconds) exit
361 end do
362 end subroutine sleep_ms
363
364 end module replace_prompt_module