Fortran · 5177 bytes Raw Blame History
1 ! ==============================================================================
2 ! Module: suggestions
3 ! Pure suggestion selection logic for autosuggestions (shadow text).
4 ! Separated from readline for testability — no I/O, no terminal ops.
5 ! ==============================================================================
6 module suggestions
7 implicit none
8 private
9
10 ! Buffer size — matches readline's MAX_LINE_LEN on Linux/gfortran
11 integer, parameter, public :: SUGGEST_BUF_LEN = 1024
12 integer, parameter, public :: MAX_SUGGEST_COMPLETIONS = 40
13
14 ! Suggestion source identifiers
15 integer, parameter, public :: SUGGEST_NONE = 0
16 integer, parameter, public :: SUGGEST_PATH = 1
17 integer, parameter, public :: SUGGEST_HISTORY = 2
18
19 type, public :: suggestion_result_t
20 character(len=SUGGEST_BUF_LEN) :: text = ''
21 integer :: length = 0
22 integer :: source = 0 ! SUGGEST_NONE, SUGGEST_PATH, SUGGEST_HISTORY
23 end type suggestion_result_t
24
25 public :: compute_path_suggestion, compute_history_suggestion
26
27 contains
28
29 ! --------------------------------------------------------------------------
30 ! Compute a path-based suggestion from completion results.
31 !
32 ! Fish-style: pick the FIRST prefix-matching completion and suggest its
33 ! full remainder. This works for both single and multiple completions,
34 ! and correctly handles paths ending in / (where common prefix = last_word).
35 ! --------------------------------------------------------------------------
36 function compute_path_suggestion(last_word, last_word_len, &
37 completions, num_completions) result(res)
38 character(len=*), intent(in) :: last_word
39 integer, intent(in) :: last_word_len
40 character(len=*), intent(in) :: completions(:)
41 integer, intent(in) :: num_completions
42 type(suggestion_result_t) :: res
43
44 integer :: i, j, comp_len
45 logical :: is_prefix
46
47 res%text = ''
48 res%length = 0
49 res%source = SUGGEST_NONE
50
51 if (last_word_len == 0 .or. num_completions == 0) return
52
53 ! Find the first completion that is a strict prefix extension of last_word
54 do i = 1, num_completions
55 comp_len = len_trim(completions(i))
56 if (comp_len <= last_word_len) cycle
57
58 ! Verify prefix match character-by-character
59 is_prefix = .true.
60 do j = 1, last_word_len
61 if (completions(i)(j:j) /= last_word(j:j)) then
62 is_prefix = .false.
63 exit
64 end if
65 end do
66
67 if (is_prefix) then
68 res%length = min(comp_len - last_word_len, SUGGEST_BUF_LEN)
69 do j = 1, res%length
70 res%text(j:j) = completions(i)(last_word_len + j : last_word_len + j)
71 end do
72 res%source = SUGGEST_PATH
73 return
74 end if
75 end do
76 end function compute_path_suggestion
77
78 ! --------------------------------------------------------------------------
79 ! Compute a history-based suggestion.
80 !
81 ! Searches history_lines backwards for the first entry that starts with
82 ! current_input. Returns the remaining suffix (truncated at newlines).
83 ! --------------------------------------------------------------------------
84 function compute_history_suggestion(current_input, input_len, &
85 history_lines, history_count) result(res)
86 character(len=*), intent(in) :: current_input
87 integer, intent(in) :: input_len
88 character(len=*), intent(in) :: history_lines(:)
89 integer, intent(in) :: history_count
90 type(suggestion_result_t) :: res
91
92 integer :: i, j, hist_len, remainder_len, newline_pos
93 logical :: matches
94
95 res%text = ''
96 res%length = 0
97 res%source = SUGGEST_NONE
98
99 if (input_len == 0 .or. history_count == 0) return
100
101 do i = history_count, 1, -1
102 hist_len = len_trim(history_lines(i))
103 if (hist_len > input_len) then
104 ! Check prefix match character-by-character
105 matches = .true.
106 do j = 1, input_len
107 if (history_lines(i)(j:j) /= current_input(j:j)) then
108 matches = .false.
109 exit
110 end if
111 end do
112
113 if (matches) then
114 ! Extract remainder
115 remainder_len = min(hist_len - input_len, SUGGEST_BUF_LEN)
116
117 ! Find first newline in remainder
118 newline_pos = 0
119 do j = 1, remainder_len
120 if (history_lines(i)(input_len + j : input_len + j) == char(10) .or. &
121 history_lines(i)(input_len + j : input_len + j) == char(13)) then
122 newline_pos = j
123 exit
124 end if
125 end do
126
127 if (newline_pos == 1) then
128 ! Newline is first char of remainder — no useful suggestion
129 cycle
130 end if
131
132 ! Truncate at newline if found
133 if (newline_pos > 1) then
134 remainder_len = newline_pos - 1
135 end if
136
137 ! Copy remainder character-by-character
138 res%text = ''
139 do j = 1, remainder_len
140 res%text(j:j) = history_lines(i)(input_len + j : input_len + j)
141 end do
142 res%length = remainder_len
143 res%source = SUGGEST_HISTORY
144 return
145 end if
146 end if
147 end do
148 end function compute_history_suggestion
149
150 end module suggestions
151