Fortran · 33100 bytes Raw Blame History
1 ! ==============================================================================
2 ! Module: completion
3 ! Purpose: Programmable completion system for fortsh (bash-compatible)
4 ! ==============================================================================
5 module completion
6 use shell_types
7 use iso_fortran_env, only: output_unit, error_unit
8 use iso_c_binding, only: c_ptr, c_null_char, c_associated
9 #ifdef USE_MEMORY_POOL
10 use string_pool
11 #endif
12 implicit none
13
14 ! Forward declarations for optional dependencies
15 ! variables module will be used in specific subroutines
16
17 ! Maximum number of completion specifications
18 ! REDUCED to avoid static storage (was 100 specs * 256KB = 25.6MB!)
19 integer, parameter :: MAX_COMPLETION_SPECS = 20 ! 20 specs max (was 100)
20 integer, parameter :: MAX_WORD_LIST = 50 ! 50 words per spec (was 1000!)
21 integer, parameter :: MAX_COMPLETIONS = 50 ! 50 completions (was 1000!)
22
23 ! Completion specification type
24 type :: completion_spec_t
25 character(len=256) :: command ! Command this spec applies to
26 character(len=256) :: word_list(MAX_WORD_LIST) ! Static word list (-W)
27 integer :: word_list_count
28 character(len=256) :: function_name ! Completion function (-F)
29 character(len=256) :: filter_pattern ! Filter pattern (-X)
30 character(len=256) :: prefix ! Prefix to add (-P)
31 character(len=256) :: suffix ! Suffix to add (-S)
32 logical :: use_default ! Use default completion (-o default)
33 logical :: use_dirnames ! Complete directory names (-o dirnames)
34 logical :: use_filenames ! Complete filenames (-o filenames)
35 logical :: nospace ! Don't add space after (-o nospace)
36 logical :: plusdirs ! Add directory completion (+o plusdirs)
37 logical :: nosort ! Don't sort results (-o nosort)
38 ! Built-in completers
39 logical :: builtin_alias ! Complete aliases (-A alias)
40 logical :: builtin_command ! Complete commands (-A command)
41 logical :: builtin_directory ! Complete directories (-A directory)
42 logical :: builtin_file ! Complete files (-A file)
43 logical :: builtin_function ! Complete functions (-A function)
44 logical :: builtin_hostname ! Complete hostnames (-A hostname)
45 logical :: builtin_variable ! Complete variables (-A variable)
46 logical :: builtin_user ! Complete usernames (-A user)
47 logical :: builtin_group ! Complete groups (-A group)
48 logical :: builtin_service ! Complete services (-A service)
49 logical :: builtin_export ! Complete exported vars (-A export)
50 logical :: builtin_keyword ! Complete shell keywords (-A keyword)
51 logical :: builtin_builtin ! Complete builtins (-A builtin)
52 logical :: is_active ! Whether this spec is active
53 end type completion_spec_t
54
55 ! Global completion specs storage
56 type(completion_spec_t), save :: completion_specs(MAX_COMPLETION_SPECS)
57 integer, save :: num_completion_specs = 0
58
59 ! Current completion context (set during completion)
60 type :: completion_context_t
61 character(len=:), allocatable :: comp_line ! Full command line
62 integer :: comp_point ! Cursor position
63 character(len=256) :: comp_words(50) ! Words in command line
64 integer :: comp_cword ! Index of word being completed
65 integer :: comp_word_count ! Total words
66 character(len=256) :: comp_word_prefix ! Word being completed
67 end type completion_context_t
68
69 type(completion_context_t), save :: current_comp_context
70
71 ! ===========================================================================
72 ! CALLBACK INTERFACE FOR FUNCTION EXECUTION
73 ! This allows the executor module to register itself without circular deps
74 ! ===========================================================================
75
76 ! Abstract interface for completion function execution callback
77 abstract interface
78 subroutine completion_func_executor_t(shell, func_name, command, word, prev_word)
79 import :: shell_state_t
80 type(shell_state_t), intent(inout) :: shell
81 character(len=*), intent(in) :: func_name
82 character(len=*), intent(in) :: command
83 character(len=*), intent(in) :: word
84 character(len=*), intent(in) :: prev_word
85 end subroutine completion_func_executor_t
86 end interface
87
88 ! Procedure pointer for function execution (set by executor at startup)
89 procedure(completion_func_executor_t), pointer, save :: completion_func_executor => null()
90
91 ! Public interface for callback registration
92 public :: register_completion_executor, completion_func_executor_t
93
94 contains
95
96 ! Initialize the completion system
97 subroutine init_completion_system()
98 integer :: i
99
100 num_completion_specs = 0
101 do i = 1, MAX_COMPLETION_SPECS
102 completion_specs(i)%is_active = .false.
103 completion_specs(i)%command = ''
104 completion_specs(i)%word_list_count = 0
105 completion_specs(i)%function_name = ''
106 completion_specs(i)%filter_pattern = ''
107 completion_specs(i)%prefix = ''
108 completion_specs(i)%suffix = ''
109 completion_specs(i)%use_default = .false.
110 completion_specs(i)%use_dirnames = .false.
111 completion_specs(i)%use_filenames = .false.
112 completion_specs(i)%nospace = .false.
113 completion_specs(i)%plusdirs = .false.
114 completion_specs(i)%nosort = .false.
115 completion_specs(i)%builtin_alias = .false.
116 completion_specs(i)%builtin_command = .false.
117 completion_specs(i)%builtin_directory = .false.
118 completion_specs(i)%builtin_file = .false.
119 completion_specs(i)%builtin_function = .false.
120 completion_specs(i)%builtin_hostname = .false.
121 completion_specs(i)%builtin_variable = .false.
122 completion_specs(i)%builtin_user = .false.
123 completion_specs(i)%builtin_group = .false.
124 completion_specs(i)%builtin_service = .false.
125 completion_specs(i)%builtin_export = .false.
126 completion_specs(i)%builtin_keyword = .false.
127 completion_specs(i)%builtin_builtin = .false.
128 end do
129 end subroutine init_completion_system
130
131 ! Register the completion function executor callback
132 ! Called by executor module at shell startup to avoid circular dependency
133 subroutine register_completion_executor(executor_proc)
134 procedure(completion_func_executor_t) :: executor_proc
135
136 completion_func_executor => executor_proc
137 end subroutine register_completion_executor
138
139 ! Register a new completion spec
140 function register_completion_spec(spec) result(success)
141 type(completion_spec_t), intent(in) :: spec
142 logical :: success
143 integer :: i, existing_idx
144
145 success = .false.
146 existing_idx = -1
147
148 ! Check if a spec already exists for this command
149 do i = 1, num_completion_specs
150 if (completion_specs(i)%is_active .and. &
151 trim(completion_specs(i)%command) == trim(spec%command)) then
152 existing_idx = i
153 exit
154 end if
155 end do
156
157 if (existing_idx > 0) then
158 ! Replace existing spec
159 completion_specs(existing_idx) = spec
160 completion_specs(existing_idx)%is_active = .true.
161 success = .true.
162 else if (num_completion_specs < MAX_COMPLETION_SPECS) then
163 ! Add new spec
164 num_completion_specs = num_completion_specs + 1
165 completion_specs(num_completion_specs) = spec
166 completion_specs(num_completion_specs)%is_active = .true.
167 success = .true.
168 end if
169 end function register_completion_spec
170
171 ! Get completion spec for a command
172 function get_completion_spec(command) result(spec)
173 character(len=*), intent(in) :: command
174 type(completion_spec_t) :: spec
175 integer :: i
176
177 ! Initialize result
178 spec%is_active = .false.
179 spec%command = ''
180 spec%word_list_count = 0
181
182 ! Search for matching spec
183 do i = 1, num_completion_specs
184 if (completion_specs(i)%is_active .and. &
185 trim(completion_specs(i)%command) == trim(command)) then
186 spec = completion_specs(i)
187 return
188 end if
189 end do
190 end function get_completion_spec
191
192 ! Remove completion spec for a command
193 function remove_completion_spec(command) result(success)
194 character(len=*), intent(in) :: command
195 logical :: success
196 integer :: i
197
198 success = .false.
199 do i = 1, num_completion_specs
200 if (completion_specs(i)%is_active .and. &
201 trim(completion_specs(i)%command) == trim(command)) then
202 completion_specs(i)%is_active = .false.
203 success = .true.
204 return
205 end if
206 end do
207 end function remove_completion_spec
208
209 ! Clear all completion specs
210 subroutine clear_completion_specs()
211 integer :: i
212 do i = 1, num_completion_specs
213 completion_specs(i)%is_active = .false.
214 end do
215 num_completion_specs = 0
216 end subroutine clear_completion_specs
217
218 ! List all registered completion specs
219 subroutine list_completion_specs(found)
220 logical, intent(out), optional :: found
221 integer :: i
222 logical :: found_any
223
224 found_any = .false.
225 do i = 1, num_completion_specs
226 if (completion_specs(i)%is_active) then
227 found_any = .true.
228 write(output_unit, '(a)', advance='no') 'complete'
229
230 ! Print word list if present
231 if (completion_specs(i)%word_list_count > 0) then
232 block
233 integer :: w
234 write(output_unit, '(a)', advance='no') " -W '"
235 do w = 1, completion_specs(i)%word_list_count
236 if (w > 1) write(output_unit, '(a)', advance='no') ' '
237 write(output_unit, '(a)', advance='no') trim(completion_specs(i)%word_list(w))
238 end do
239 write(output_unit, '(a)', advance='no') "'"
240 end block
241 end if
242
243 ! Print function if present
244 if (len_trim(completion_specs(i)%function_name) > 0) then
245 write(output_unit, '(a)', advance='no') ' -F ' // trim(completion_specs(i)%function_name)
246 end if
247
248 ! Print built-in completers
249 if (completion_specs(i)%builtin_command) then
250 write(output_unit, '(a)', advance='no') ' -A command'
251 end if
252 if (completion_specs(i)%builtin_file) then
253 write(output_unit, '(a)', advance='no') ' -A file'
254 end if
255 if (completion_specs(i)%builtin_directory) then
256 write(output_unit, '(a)', advance='no') ' -A directory'
257 end if
258 if (completion_specs(i)%builtin_variable) then
259 write(output_unit, '(a)', advance='no') ' -A variable'
260 end if
261 if (completion_specs(i)%builtin_function) then
262 write(output_unit, '(a)', advance='no') ' -A function'
263 end if
264 if (completion_specs(i)%builtin_alias) then
265 write(output_unit, '(a)', advance='no') ' -A alias'
266 end if
267 if (completion_specs(i)%builtin_builtin) then
268 write(output_unit, '(a)', advance='no') ' -A builtin'
269 end if
270 if (completion_specs(i)%builtin_keyword) then
271 write(output_unit, '(a)', advance='no') ' -A keyword'
272 end if
273 if (completion_specs(i)%builtin_hostname) then
274 write(output_unit, '(a)', advance='no') ' -A hostname'
275 end if
276 if (completion_specs(i)%builtin_user) then
277 write(output_unit, '(a)', advance='no') ' -A user'
278 end if
279
280 ! Print options
281 if (completion_specs(i)%use_default) then
282 write(output_unit, '(a)', advance='no') ' -o default'
283 end if
284 if (completion_specs(i)%use_dirnames) then
285 write(output_unit, '(a)', advance='no') ' -o dirnames'
286 end if
287 if (completion_specs(i)%use_filenames) then
288 write(output_unit, '(a)', advance='no') ' -o filenames'
289 end if
290 if (completion_specs(i)%nospace) then
291 write(output_unit, '(a)', advance='no') ' -o nospace'
292 end if
293 if (completion_specs(i)%nosort) then
294 write(output_unit, '(a)', advance='no') ' -o nosort'
295 end if
296
297 ! Print command name
298 write(output_unit, '(a)') ' ' // trim(completion_specs(i)%command)
299 end if
300 end do
301
302 if (present(found)) found = found_any
303 end subroutine list_completion_specs
304
305 ! Parse word list from -W argument
306 subroutine parse_word_list(word_list_str, spec)
307 character(len=*), intent(in) :: word_list_str
308 type(completion_spec_t), intent(inout) :: spec
309 integer :: i, start, pos, str_len
310 logical :: in_quotes
311 character(len=1) :: quote_char
312
313 spec%word_list_count = 0
314 i = 1
315 str_len = len_trim(word_list_str)
316
317 do while (i <= str_len .and. spec%word_list_count < MAX_WORD_LIST)
318 ! Skip leading spaces
319 do while (i <= str_len .and. word_list_str(i:i) == ' ')
320 i = i + 1
321 end do
322 if (i > str_len) exit
323
324 ! Start of word
325 start = i
326 in_quotes = .false.
327 quote_char = ' '
328
329 ! Find end of word
330 do while (i <= str_len)
331 if (.not. in_quotes) then
332 if (word_list_str(i:i) == '"' .or. word_list_str(i:i) == "'") then
333 in_quotes = .true.
334 quote_char = word_list_str(i:i)
335 else if (word_list_str(i:i) == ' ') then
336 exit
337 end if
338 else
339 ! Handle escaped quotes (backslash before quote char)
340 if (word_list_str(i:i) == '\' .and. i < str_len) then
341 ! Skip backslash and the next character (escaped)
342 i = i + 1
343 else if (word_list_str(i:i) == quote_char) then
344 in_quotes = .false.
345 end if
346 end if
347 i = i + 1
348 end do
349
350 ! Extract word (remove quotes if present)
351 spec%word_list_count = spec%word_list_count + 1
352 spec%word_list(spec%word_list_count) = word_list_str(start:i-1)
353
354 ! Strip surrounding quotes
355 pos = spec%word_list_count
356 if (len_trim(spec%word_list(pos)) >= 2) then
357 if ((spec%word_list(pos)(1:1) == '"' .and. &
358 spec%word_list(pos)(len_trim(spec%word_list(pos)):len_trim(spec%word_list(pos))) == '"') .or. &
359 (spec%word_list(pos)(1:1) == "'" .and. &
360 spec%word_list(pos)(len_trim(spec%word_list(pos)):len_trim(spec%word_list(pos))) == "'")) then
361 spec%word_list(pos) = spec%word_list(pos)(2:len_trim(spec%word_list(pos))-1)
362 end if
363 end if
364 end do
365 end subroutine parse_word_list
366
367 ! ===========================================================================
368 ! COMPLETION GENERATION FUNCTIONS
369 ! ===========================================================================
370
371 ! Generate completions from a word list
372 subroutine generate_word_list_completions(spec, prefix, completions, count)
373 type(completion_spec_t), intent(in) :: spec
374 character(len=*), intent(in) :: prefix
375 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
376 integer, intent(out) :: count
377 integer :: i, prefix_len, word_len, compare_len
378 logical :: matches
379 character(len=256) :: word, prefix_trimmed
380
381 count = 0
382 prefix_trimmed = trim(prefix)
383 prefix_len = len_trim(prefix_trimmed)
384
385 do i = 1, spec%word_list_count
386 word = trim(spec%word_list(i))
387 word_len = len_trim(word)
388
389 ! Check if word matches prefix
390 if (prefix_len == 0) then
391 matches = .true.
392 else if (word_len >= prefix_len) then
393 compare_len = min(prefix_len, word_len)
394 matches = (word(1:compare_len) == prefix_trimmed(1:compare_len))
395 else
396 matches = .false.
397 end if
398
399 if (matches .and. count < MAX_COMPLETIONS) then
400 count = count + 1
401
402 ! Apply prefix transformation
403 if (len_trim(spec%prefix) > 0) then
404 completions(count) = trim(spec%prefix) // word
405 else
406 completions(count) = word
407 end if
408
409 ! Apply suffix transformation
410 if (len_trim(spec%suffix) > 0) then
411 completions(count) = trim(completions(count)) // trim(spec%suffix)
412 end if
413 end if
414 end do
415
416 ! Sort completions unless nosort is set
417 if (.not. spec%nosort .and. count > 1) then
418 call sort_completions(completions, count)
419 end if
420 end subroutine generate_word_list_completions
421
422 ! Sort completions alphabetically (simple bubble sort)
423 subroutine sort_completions(completions, count)
424 character(len=256), intent(inout) :: completions(:)
425 integer, intent(in) :: count
426 integer :: i, j
427 character(len=256) :: temp
428 logical :: swapped
429
430 do i = 1, count - 1
431 swapped = .false.
432 do j = 1, count - i
433 if (lgt(trim(completions(j)), trim(completions(j+1)))) then
434 temp = completions(j)
435 completions(j) = completions(j+1)
436 completions(j+1) = temp
437 swapped = .true.
438 end if
439 end do
440 if (.not. swapped) exit
441 end do
442 end subroutine sort_completions
443
444 ! Check if a word matches a filter pattern
445 function matches_filter(word, filter_pattern) result(matches)
446 character(len=*), intent(in) :: word, filter_pattern
447 logical :: matches
448
449 ! For now, simple implementation - can be enhanced with glob patterns
450 if (len_trim(filter_pattern) == 0) then
451 matches = .true.
452 else
453 ! Simple prefix match
454 matches = (index(trim(word), trim(filter_pattern)) > 0)
455 end if
456 end function matches_filter
457
458 ! Apply filter to completions
459 subroutine filter_completions(completions, count, filter_pattern)
460 character(len=256), intent(inout) :: completions(:)
461 integer, intent(inout) :: count
462 character(len=*), intent(in) :: filter_pattern
463 integer :: i, write_pos
464
465 if (len_trim(filter_pattern) == 0) return
466
467 write_pos = 1
468 do i = 1, count
469 if (.not. matches_filter(completions(i), filter_pattern)) then
470 ! Keep completions that DON'T match (bash -X semantics removes matches)
471 if (write_pos /= i) then
472 completions(write_pos) = completions(i)
473 end if
474 write_pos = write_pos + 1
475 end if
476 end do
477
478 count = write_pos - 1
479 end subroutine filter_completions
480
481 ! Set up completion context for function-based completion
482 subroutine setup_completion_context(shell, comp_line, comp_point, comp_words, comp_cword)
483 use variables, only: set_shell_variable
484 type(shell_state_t), intent(inout) :: shell
485 character(len=*), intent(in) :: comp_line
486 integer, intent(in) :: comp_point, comp_cword
487 character(len=256), intent(in) :: comp_words(:)
488 character(len=32) :: point_str, cword_str
489 integer :: i
490
491 ! Set COMP_LINE - the current command line
492 call set_shell_variable(shell, 'COMP_LINE', trim(comp_line), len_trim(comp_line))
493
494 ! Set COMP_POINT - cursor position in the line
495 write(point_str, '(i15)') comp_point
496 call set_shell_variable(shell, 'COMP_POINT', trim(point_str), len_trim(point_str))
497
498 ! Set COMP_CWORD - index of the word containing cursor
499 write(cword_str, '(i15)') comp_cword
500 call set_shell_variable(shell, 'COMP_CWORD', trim(cword_str), len_trim(cword_str))
501
502 ! Set COMP_WORDS array (bash uses indexed array)
503 ! For now, we'll set COMP_WORDS as individual variables COMP_WORDS_0, COMP_WORDS_1, etc.
504 do i = 0, comp_cword
505 if (i <= size(comp_words)) then
506 write(point_str, '(a,i15)') 'COMP_WORDS_', i
507 call set_shell_variable(shell, trim(point_str), trim(comp_words(i+1)), len_trim(comp_words(i+1)))
508 end if
509 end do
510
511 ! Initialize empty COMPREPLY array
512 call set_shell_variable(shell, 'COMPREPLY', '', 0)
513 end subroutine setup_completion_context
514
515 ! Get completions from COMPREPLY array
516 subroutine get_compreply_results(shell, completions, count)
517 use variables, only: get_shell_variable, get_array_size, get_array_element
518 type(shell_state_t), intent(inout) :: shell
519 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
520 integer, intent(out) :: count
521 integer :: array_size, i
522 character(len=:), allocatable :: element
523
524 count = 0
525
526 ! Get COMPREPLY array size
527 array_size = get_array_size(shell, 'COMPREPLY')
528
529 if (array_size > 0) then
530 ! Read array elements
531 do i = 0, min(array_size - 1, MAX_COMPLETIONS - 1)
532 element = get_array_element(shell, 'COMPREPLY', i)
533 if (len_trim(element) > 0) then
534 count = count + 1
535 completions(count) = trim(element)
536 end if
537 end do
538 else
539 ! Fallback: try reading COMPREPLY as a space-separated string
540 element = get_shell_variable(shell, 'COMPREPLY')
541 if (len_trim(element) > 0) then
542 ! Parse space-separated values
543 call parse_space_separated(element, completions, count)
544 end if
545 end if
546 end subroutine get_compreply_results
547
548 ! Parse space-separated values into array
549 subroutine parse_space_separated(input, values, count)
550 character(len=*), intent(in) :: input
551 character(len=256), intent(out) :: values(MAX_COMPLETIONS)
552 integer, intent(out) :: count
553 integer :: i, start
554
555 count = 0
556 i = 1
557
558 do while (i <= len_trim(input) .and. count < MAX_COMPLETIONS)
559 ! Skip spaces
560 do while (i <= len_trim(input) .and. input(i:i) == ' ')
561 i = i + 1
562 end do
563 if (i > len_trim(input)) exit
564
565 ! Start of value
566 start = i
567 do while (i <= len_trim(input) .and. input(i:i) /= ' ')
568 i = i + 1
569 end do
570
571 ! Extract value
572 count = count + 1
573 values(count) = input(start:i-1)
574 end do
575 end subroutine parse_space_separated
576
577 ! Generate completions by calling a shell function
578 subroutine generate_function_completions(shell, spec, command, word_prefix, completions, count)
579 use parser, only: parse_pipeline
580 type(shell_state_t), intent(inout) :: shell
581 type(completion_spec_t), intent(in) :: spec
582 character(len=*), intent(in) :: command, word_prefix
583 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
584 integer, intent(out) :: count
585 character(len=:), allocatable :: function_call
586 character(len=256) :: comp_words(50)
587 integer :: comp_cword
588
589 count = 0
590
591 ! Build minimal context for now
592 ! In a real implementation, we'd parse the full command line
593 comp_words(1) = trim(command)
594 comp_words(2) = trim(word_prefix)
595 comp_cword = 1 ! Completing the second word
596
597 ! Set up completion context variables
598 call setup_completion_context(shell, trim(command) // ' ' // trim(word_prefix), &
599 len_trim(command) + 1 + len_trim(word_prefix), &
600 comp_words, comp_cword)
601
602 ! Build function call: function_name "command" "word" "prev_word"
603 ! For simplicity, we'll call with just command and word
604 function_call = trim(spec%function_name) // ' "' // trim(command) // '" "' // trim(word_prefix) // '" ""'
605
606 ! Execute the completion function via callback
607 ! The callback is registered by the executor module at shell startup
608 if (associated(completion_func_executor)) then
609 ! Clear COMPREPLY before calling the function
610 call clear_compreply(shell)
611
612 ! Call the completion function via the registered executor
613 ! The function is expected to populate COMPREPLY array
614 call completion_func_executor(shell, trim(spec%function_name), &
615 trim(command), trim(word_prefix), '')
616 end if
617
618 ! Get results from COMPREPLY (populated by the completion function)
619 call get_compreply_results(shell, completions, count)
620 end subroutine generate_function_completions
621
622 ! Clear the COMPREPLY array
623 subroutine clear_compreply(shell)
624 use variables, only: set_array_variable
625 type(shell_state_t), intent(inout) :: shell
626 character(len=1) :: empty_arr(1)
627
628 ! Clear the COMPREPLY array by setting it to empty
629 empty_arr(1) = ''
630 call set_array_variable(shell, 'COMPREPLY', empty_arr, 0)
631 end subroutine clear_compreply
632
633 ! ===========================================================================
634 ! BUILT-IN COMPLETERS
635 ! ===========================================================================
636
637 ! Complete file names
638 ! For now, use a simplified implementation. Full filesystem access
639 ! will be added in a future enhancement.
640 subroutine complete_files(prefix, completions, count)
641 character(len=*), intent(in) :: prefix
642 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
643 integer, intent(out) :: count
644
645 ! Simplified implementation - return empty for now
646 ! Phase 5 will integrate with readline's existing file completion
647 count = 0
648 completions = '' ! Initialize to silence warning
649 if (.false.) print *, prefix ! Silence unused warning
650 end subroutine complete_files
651
652 ! Complete directory names only
653 ! For now, use a simplified implementation
654 subroutine complete_directories(prefix, completions, count)
655 character(len=*), intent(in) :: prefix
656 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
657 integer, intent(out) :: count
658
659 ! Simplified implementation - return empty for now
660 ! Phase 5 will integrate with readline's existing directory completion
661 count = 0
662 completions = '' ! Initialize to silence warning
663 if (.false.) print *, prefix ! Silence unused warning
664 end subroutine complete_directories
665
666 ! Complete command names from PATH
667 ! Simplified implementation for Phase 4
668 subroutine complete_commands(shell, prefix, completions, count)
669 type(shell_state_t), intent(inout) :: shell
670 character(len=*), intent(in) :: prefix
671 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
672 integer, intent(out) :: count
673
674 ! Simplified implementation - return empty for now
675 ! Phase 5 will add full command completion
676 completions = '' ! Initialize to silence warning
677 if (.false.) print *, prefix, shell%cwd ! Silence unused warnings
678 count = 0
679 end subroutine complete_commands
680
681 ! Complete variable names
682 subroutine complete_variables(shell, prefix, completions, count)
683 type(shell_state_t), intent(inout) :: shell
684 character(len=*), intent(in) :: prefix
685 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
686 integer, intent(out) :: count
687 integer :: i
688 character(len=256) :: var_name
689
690 count = 0
691
692 ! Iterate through shell variables
693 do i = 1, shell%num_variables
694 if (count >= MAX_COMPLETIONS) exit
695
696 var_name = trim(shell%variables(i)%name)
697
698 ! Check if variable matches prefix
699 if (len_trim(prefix) == 0 .or. &
700 index(var_name, trim(prefix)) == 1) then
701 count = count + 1
702 completions(count) = var_name
703 end if
704 end do
705 end subroutine complete_variables
706
707 ! Complete shell keywords
708 subroutine complete_keywords(prefix, completions, count)
709 character(len=*), intent(in) :: prefix
710 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
711 integer, intent(out) :: count
712 character(len=20), parameter :: keywords(20) = [ &
713 'if ', 'then ', 'else ', 'elif ', 'fi ', &
714 'for ', 'while ', 'until ', 'do ', 'done ', &
715 'case ', 'esac ', 'in ', 'function', 'select ', &
716 'time ', 'coproc ', '[[ ', '! ', '{ ' ]
717 integer :: i
718
719 count = 0
720
721 do i = 1, size(keywords)
722 if (count >= MAX_COMPLETIONS) exit
723
724 if (len_trim(prefix) == 0 .or. &
725 index(trim(keywords(i)), trim(prefix)) == 1) then
726 count = count + 1
727 completions(count) = trim(keywords(i))
728 end if
729 end do
730 end subroutine complete_keywords
731
732 ! Complete builtin commands
733 subroutine complete_builtins(prefix, completions, count)
734 character(len=*), intent(in) :: prefix
735 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
736 integer, intent(out) :: count
737 character(len=20), parameter :: builtins(50) = [ &
738 'alias ', 'bg ', 'bind ', 'break ', 'builtin ', &
739 'cd ', 'command ', 'compgen ', 'complete ', 'continue ', &
740 'declare ', 'dirs ', 'disown ', 'echo ', 'enable ', &
741 'eval ', 'exec ', 'exit ', 'export ', 'fc ', &
742 'fg ', 'getopts ', 'hash ', 'help ', 'history ', &
743 'jobs ', 'kill ', 'let ', 'local ', 'logout ', &
744 'popd ', 'printf ', 'pushd ', 'pwd ', 'read ', &
745 'readonly ', 'return ', 'set ', 'shift ', 'shopt ', &
746 'source ', 'suspend ', 'test ', 'times ', 'trap ', &
747 'type ', 'ulimit ', 'umask ', 'unalias ', 'unset ' ]
748 integer :: i
749
750 count = 0
751
752 do i = 1, size(builtins)
753 if (count >= MAX_COMPLETIONS) exit
754
755 if (len_trim(prefix) == 0 .or. &
756 index(trim(builtins(i)), trim(prefix)) == 1) then
757 count = count + 1
758 completions(count) = trim(builtins(i))
759 end if
760 end do
761 end subroutine complete_builtins
762
763 ! Main entry point for generating completions for a command
764 subroutine generate_completions(command, word_prefix, completions, count, shell)
765 character(len=*), intent(in) :: command
766 character(len=*), intent(in) :: word_prefix
767 character(len=256), intent(out) :: completions(MAX_COMPLETIONS)
768 integer, intent(out) :: count
769 type(shell_state_t), intent(inout), optional :: shell
770 type(completion_spec_t) :: spec
771 character(len=256) :: temp_completions(MAX_COMPLETIONS)
772 integer :: temp_count, initial_count
773
774 count = 0
775
776 ! Get completion spec for this command
777 spec = get_completion_spec(command)
778 if (.not. spec%is_active) return
779
780 ! Priority 1: Function-based completion
781 if (len_trim(spec%function_name) > 0 .and. present(shell)) then
782 call generate_function_completions(shell, spec, command, word_prefix, completions, count)
783 if (count > 0) return
784 end if
785
786 ! Priority 2: Generate completions from word list
787 if (spec%word_list_count > 0) then
788 call generate_word_list_completions(spec, word_prefix, completions, count)
789 end if
790
791 ! Priority 3: Built-in completers
792 initial_count = count
793
794 if (spec%builtin_file .and. count < MAX_COMPLETIONS) then
795 call complete_files(word_prefix, temp_completions, temp_count)
796 call merge_completions(completions, count, temp_completions, temp_count)
797 end if
798
799 if (spec%builtin_directory .and. count < MAX_COMPLETIONS) then
800 call complete_directories(word_prefix, temp_completions, temp_count)
801 call merge_completions(completions, count, temp_completions, temp_count)
802 end if
803
804 if (spec%builtin_command .and. present(shell) .and. count < MAX_COMPLETIONS) then
805 call complete_commands(shell, word_prefix, temp_completions, temp_count)
806 call merge_completions(completions, count, temp_completions, temp_count)
807 end if
808
809 if (spec%builtin_variable .and. present(shell) .and. count < MAX_COMPLETIONS) then
810 call complete_variables(shell, word_prefix, temp_completions, temp_count)
811 call merge_completions(completions, count, temp_completions, temp_count)
812 end if
813
814 if (spec%builtin_keyword .and. count < MAX_COMPLETIONS) then
815 call complete_keywords(word_prefix, temp_completions, temp_count)
816 call merge_completions(completions, count, temp_completions, temp_count)
817 end if
818
819 if (spec%builtin_builtin .and. count < MAX_COMPLETIONS) then
820 call complete_builtins(word_prefix, temp_completions, temp_count)
821 call merge_completions(completions, count, temp_completions, temp_count)
822 end if
823
824 ! Apply filter if present
825 if (len_trim(spec%filter_pattern) > 0) then
826 call filter_completions(completions, count, spec%filter_pattern)
827 end if
828
829 ! Sort if we added any completions and nosort is not set
830 if (count > initial_count .and. .not. spec%nosort) then
831 call sort_completions(completions, count)
832 end if
833 end subroutine generate_completions
834
835 ! Merge temp completions into main list, avoiding duplicates
836 subroutine merge_completions(completions, count, temp_completions, temp_count)
837 character(len=256), intent(inout) :: completions(MAX_COMPLETIONS)
838 integer, intent(inout) :: count
839 character(len=256), intent(in) :: temp_completions(MAX_COMPLETIONS)
840 integer, intent(in) :: temp_count
841 integer :: i, j
842 logical :: is_duplicate
843
844 do i = 1, temp_count
845 if (count >= MAX_COMPLETIONS) exit
846
847 ! Check for duplicates
848 is_duplicate = .false.
849 do j = 1, count
850 if (trim(completions(j)) == trim(temp_completions(i))) then
851 is_duplicate = .true.
852 exit
853 end if
854 end do
855
856 if (.not. is_duplicate) then
857 count = count + 1
858 completions(count) = temp_completions(i)
859 end if
860 end do
861 end subroutine merge_completions
862
863 end module completion
864