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
12char *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
219char *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