1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | |
3 | #include "readline/libreadline.h" |
4 | |
5 | #include <stdio.h> |
6 | #include <stdlib.h> |
7 | #include <string.h> |
8 | #include <unistd.h> |
9 | |
10 | #define LINE_BUFFER_SIZE 1024 |
11 | |
12 | char *readline(const char *prompt) |
13 | { |
14 | const size_t prompt_length = strlen(s: prompt); |
15 | printf(format: "%s" , prompt); |
16 | fflush(stream: stdout); |
17 | |
18 | char *line_buffer = malloc(LINE_BUFFER_SIZE); |
19 | if (!line_buffer) |
20 | return NULL; |
21 | |
22 | memset(dest: line_buffer, c: 0, LINE_BUFFER_SIZE); |
23 | |
24 | int insertion_point = 0; // index of the character that will be overwritten |
25 | int line_length = 0; // not including the null terminator |
26 | |
27 | while (true) |
28 | { |
29 | fflush(stream: stdout); |
30 | char c = getchar(); |
31 | |
32 | switch (c) |
33 | { |
34 | case 1: |
35 | insertion_point = 0; |
36 | const size_t pos = prompt_length + 1 + insertion_point; |
37 | printf(format: "\033[%zuG" , pos); |
38 | break; |
39 | case 5: |
40 | insertion_point = line_length; |
41 | const size_t pos2 = prompt_length + 1 + insertion_point; |
42 | printf(format: "\033[%zuG" , pos2); |
43 | break; |
44 | case '\r': |
45 | case '\n': |
46 | line_buffer[line_length] = '\0'; |
47 | fflush(stream: stdout); |
48 | return line_buffer; |
49 | case '\f': |
50 | printf(format: "\033[2J\033[H" ); // clear screen |
51 | printf(format: "%s" , prompt); |
52 | break; |
53 | case '\b': |
54 | if (insertion_point > 0) |
55 | { |
56 | // if at the end of the line, we can just move the insertion point |
57 | if (insertion_point == line_length) |
58 | { |
59 | insertion_point--; |
60 | line_length--; |
61 | printf(format: "\b \b" ); |
62 | } |
63 | else |
64 | { |
65 | // move the rest of the line to the right |
66 | for (int i = line_length; i > insertion_point; i--) |
67 | { |
68 | line_buffer[i] = line_buffer[i - 1]; |
69 | printf(format: "%c" , line_buffer[i]); |
70 | } |
71 | |
72 | // move the cursor forward |
73 | for (int i = insertion_point; i < line_length; i++) |
74 | printf(format: "\033[C" ); |
75 | |
76 | // clear the last character |
77 | printf(format: " \b" ); |
78 | |
79 | // move the insertion point back |
80 | insertion_point--; |
81 | line_length--; |
82 | } |
83 | } |
84 | break; |
85 | case 0x7f: // backspace |
86 | if (insertion_point > 0) |
87 | { |
88 | // if at the end of the line, we can just move the insertion point |
89 | if (insertion_point == line_length) |
90 | { |
91 | insertion_point--; |
92 | line_length--; |
93 | printf(format: "\b \b" ); // move cursor back, clear last character, move cursor back again |
94 | } |
95 | else |
96 | { |
97 | // move the rest of the line to the left |
98 | insertion_point--; |
99 | line_length--; |
100 | printf(format: "\b" ); // move cursor back |
101 | |
102 | for (int i = insertion_point; i < line_length; i++) |
103 | { |
104 | line_buffer[i] = line_buffer[i + 1]; |
105 | printf(format: "%c" , line_buffer[i]); |
106 | } |
107 | |
108 | // clear the last character |
109 | printf(format: " \b" ); |
110 | |
111 | // move the cursor back to the insertion point |
112 | for (int i = insertion_point; i < line_length; i++) |
113 | printf(format: "\b" ); |
114 | } |
115 | } |
116 | break; |
117 | |
118 | case 0x1b: // ESC |
119 | c = getchar(); |
120 | if (c == '[') // is an escape sequence |
121 | { |
122 | c = getchar(); |
123 | if (c == 'D') |
124 | { |
125 | if (insertion_point > 0) |
126 | insertion_point--; |
127 | else |
128 | printf(format: "\033[C" ); // move the cursor back to the insertion point |
129 | } |
130 | else if (c == 'C') |
131 | { |
132 | if (insertion_point < line_length) |
133 | insertion_point++; |
134 | else |
135 | printf(format: "\033[D" ); // move the cursor back to the insertion point |
136 | } |
137 | else if (c == 'A') |
138 | { |
139 | printf(format: "\033[B" ); // move the cursor back to the insertion point |
140 | } |
141 | else if (c == 'B') |
142 | { |
143 | printf(format: "\033[A" ); // move the cursor back to the insertion point |
144 | } |
145 | else if (c == '3') |
146 | { |
147 | c = getchar(); |
148 | if (c == '~') |
149 | { |
150 | if (insertion_point < line_length) |
151 | { |
152 | // move the rest of the line to the left |
153 | for (int i = insertion_point; i < line_length; i++) |
154 | { |
155 | line_buffer[i] = line_buffer[i + 1]; |
156 | printf(format: "%c" , line_buffer[i]); |
157 | } |
158 | |
159 | // clear the last character |
160 | printf(format: " \b" ); |
161 | |
162 | line_length--; |
163 | |
164 | // move the cursor back |
165 | for (int i = insertion_point; i < line_length; i++) |
166 | printf(format: "\b" ); |
167 | } |
168 | } |
169 | } |
170 | } |
171 | else |
172 | { |
173 | // ??? |
174 | } |
175 | break; |
176 | default: |
177 | if (line_length + 1 >= LINE_BUFFER_SIZE) |
178 | { |
179 | // buffer is full |
180 | puts(string: "readline: line too long" ); |
181 | abort(); |
182 | break; |
183 | } |
184 | |
185 | line_length++; |
186 | |
187 | // if we're not at the end of the line, we need to move the rest of the line |
188 | if (insertion_point < line_length) |
189 | { |
190 | // move the rest of the line to the right |
191 | for (int i = line_length; i > insertion_point; i--) |
192 | { |
193 | line_buffer[i] = line_buffer[i - 1]; |
194 | } |
195 | |
196 | // print the rest of the line |
197 | for (int i = insertion_point + 1; i < line_length; i++) |
198 | { |
199 | printf(format: "%c" , line_buffer[i]); |
200 | } |
201 | |
202 | // move the cursor back to the insertion point |
203 | for (int i = insertion_point + 1; i < line_length; i++) |
204 | { |
205 | printf(format: "\b" ); |
206 | } |
207 | } |
208 | |
209 | line_buffer[insertion_point] = c; |
210 | insertion_point++; |
211 | break; |
212 | } |
213 | } |
214 | |
215 | fflush(stream: stdout); |
216 | return "" ; |
217 | } |
218 | |
219 | char *get_line(fd_t fd) |
220 | { |
221 | char *line_buffer = malloc(LINE_BUFFER_SIZE); |
222 | if (!line_buffer) |
223 | return NULL; |
224 | |
225 | memset(dest: line_buffer, c: 0, LINE_BUFFER_SIZE); |
226 | |
227 | char c; |
228 | int i = 0; |
229 | while (i < LINE_BUFFER_SIZE - 1) |
230 | { |
231 | size_t readsz = read(fd, buffer: &c, size: 1); |
232 | |
233 | if (readsz == 0 && i == 0) |
234 | { |
235 | free(pointer: line_buffer); |
236 | return NULL; // EOF |
237 | } |
238 | |
239 | if (readsz != 1) |
240 | break; |
241 | |
242 | if (c == '\n') |
243 | break; |
244 | |
245 | line_buffer[i] = c; |
246 | i++; |
247 | } |
248 | |
249 | line_buffer[i] = '\0'; |
250 | return line_buffer; |
251 | } |
252 | |