Usermode/libreadline - Fix backspace
[tpg/acess2.git] / Usermode / Libraries / libreadline.so_src / main.c
1 /*
2  * Acess2 Library Suite
3  * - Readline
4  * 
5  * Text mode entry with history
6  */
7 #include <readline.h>
8 #include <acess/sys.h>
9 #include <acess/devices/pty.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <errno.h>
14
15 #define STDIN_FD        0
16 #define STDOUT_FD       1
17
18 // Size of the read() buffer
19 // - 32 should be pleanty
20 #define READ_BUFFER_SIZE        32
21
22 // === STRUCTURES ===
23 struct sReadline
24 {
25          int    UseHistory;     // Boolean
26         
27         // TODO: Command Completion
28         
29         // History
30          int    NumHistory;
31         char    **History;
32         
33         // Internal Flags
34         char    *OutputValue;   //!< Pointer (owned by history) to output value
35         
36         // Command Buffer
37          int    BufferSize;     // Allocated size of the buffer
38          int    BufferUsed;     // Offset of first free byte in the buffer
39          int    BufferWritePos; // Cursor location
40         char    *CurBuffer;     // Current translated command (pointer to a entry in history)
41         
42         // 
43          int    HistoryPos;
44         
45         // Read Buffer
46         char    ReadBuffer[READ_BUFFER_SIZE];   //!< Buffer for read()
47          int    ReadBufferLen;
48 };
49
50 // === PROTOTYPES ===
51  int    SoMain();
52 tReadline       *Readline_Init(int bUseHistory);
53  int    Readline_int_ParseCharacter(tReadline *Info, char *Input);
54 char    *Readline_NonBlock(tReadline *Info);
55 char    *Readline(tReadline *Info);
56
57 // === GLOBALS ===
58
59 // === CODE ===
60 int SoMain()
61 {
62         return 0;
63 }
64
65 tReadline *Readline_Init(int bUseHistory)
66 {
67         tReadline       *ret = calloc( 1, sizeof(tReadline) );
68         ret->UseHistory = bUseHistory;
69         ret->BufferSize = 0;
70         
71         ret->History = malloc( 1 * sizeof(*ret->History) );
72         ret->History[0] = NULL;
73         ret->NumHistory = 1;
74         
75         return ret;
76 }
77
78 /**
79  */
80 char *Readline_NonBlock(tReadline *Info)
81 {
82          int    len, i;
83         
84         // Read as much as possible (appending to remaining data)
85         len = _SysRead(STDIN_FD, Info->ReadBuffer, READ_BUFFER_SIZE - 1 - Info->ReadBufferLen);
86         if( len <= 0 )  return NULL;
87         Info->ReadBuffer[Info->ReadBufferLen + len] = '\0';
88         
89         // Parse the data we have
90         for( i = 0; i < len; )
91         {
92                 int size = Readline_int_ParseCharacter(Info, Info->ReadBuffer+i);
93                 if( size <= 0 ) break;  // Error, skip the rest?
94                 i += size;
95         }
96         
97         // Move the unused data to the start of the buffer
98         memcpy(Info->ReadBuffer, &Info->ReadBuffer[Info->ReadBufferLen + i], len - i);
99         Info->ReadBufferLen = len - i;
100         
101         // Is the command finished?
102         if( Info->OutputValue ) {
103                 char    *ret = Info->OutputValue;
104                 Info->OutputValue = NULL;       // Mark as no command pending
105                 return ret;     // Return the string (now the caller's responsibility)
106         }
107         
108         // Return NULL when command is still being edited
109         errno = EAGAIN;
110         return NULL;
111 }
112
113 char *Readline(tReadline *Info)
114 {
115         char    *ret;
116
117         // stty -echo,canon
118         struct ptymode  mode = {.InputMode = 0, .OutputMode = 0};
119         _SysIOCtl(STDIN_FD, PTY_IOCTL_SETMODE, &mode);
120         
121         while( NULL == (ret = Readline_NonBlock(Info)) && errno == EAGAIN );
122
123         // stty +echo,canon
124         mode.InputMode = PTYIMODE_CANON|PTYIMODE_ECHO;
125         _SysIOCtl(STDIN_FD, PTY_IOCTL_SETMODE, &mode);
126
127         return ret;
128 }
129
130 int Readline_int_AddToHistory(tReadline *Info, const char *String)
131 {
132         void    *tmp;
133         
134         // History[#-1] = CurBuffer (always)
135         if( !Info->History ) {
136                 // Realy shouldn't happen, but just in case
137                 Info->History = malloc( sizeof(char*) );
138                 Info->NumHistory = 1;
139         }
140         
141         // Don't add duplicates
142         if( Info->NumHistory >= 2 && strcmp( Info->History[ Info->NumHistory-2 ], String ) == 0 )
143         {
144                 return 0;
145         }
146         
147         // Duplicate over the current
148         Info->History[ Info->NumHistory-1 ] = strdup(String);
149         
150         Info->NumHistory ++;
151         
152         tmp = realloc( Info->History, Info->NumHistory * sizeof(char*) );
153         if( tmp == NULL )       return -1;
154         Info->History = tmp;
155                                 
156         // Zero the new current
157         Info->History[ Info->NumHistory-1 ] = NULL;
158         
159         return 0;
160 }
161
162 int Readline_int_ParseCharacter(tReadline *Info, char *Input)
163 {
164          int    ofs = 0;
165         char    ch;
166         
167         if( Input[ofs] == 0 )   return 0;
168         
169         // Read In Command Line
170         ch = Input[ofs++];
171         
172         if(ch == '\n')
173         {
174 //              printf("\n");
175                 if(Info->CurBuffer)
176                 {       
177                         // Cap String
178                         Info->CurBuffer[Info->BufferUsed] = '\0';
179                         
180                         if( Info->UseHistory )
181                                 Readline_int_AddToHistory(Info, Info->CurBuffer);
182                         Info->OutputValue = strdup(Info->CurBuffer);
183                 }
184                 else
185                         Info->OutputValue = strdup("");
186                 
187                 // Save and reset
188                 Info->BufferSize = 0;
189                 Info->BufferUsed = 0;
190                 Info->BufferWritePos = 0;
191                 Info->CurBuffer = 0;
192                 Info->HistoryPos = Info->NumHistory - 1;
193                 
194                 return 1;
195         }
196         
197         switch(ch)
198         {
199         // Control characters
200         case '\x1B':
201                 ch = Input[ofs++];      // Read control character
202                 switch(ch)
203                 {
204                 //case 'D':     if(pos) pos--;  break;
205                 //case 'C':     if(pos<len)     pos++;  break;
206                 case '[':
207                         ch = Input[ofs++];      // Read control character
208                         switch(ch)
209                         {
210                         case 'A':       // Up
211                                 {
212                                          int    oldLen = Info->BufferUsed;
213                                          int    pos;
214                                         if( Info->HistoryPos <= 0 )     break;
215                                         
216                                         // Move to the beginning of the line
217                                         pos = oldLen;
218                                         while(pos--)    _SysWrite(STDOUT_FD, "\x1B[D", 3);
219                                         
220                                         // Update state
221                                         Info->CurBuffer = Info->History[--Info->HistoryPos];
222                                         Info->BufferSize = Info->BufferUsed = strlen(Info->CurBuffer);
223                                         
224                                         _SysWrite(STDOUT_FD, Info->CurBuffer, Info->BufferUsed);
225                                         Info->BufferWritePos = Info->BufferUsed;
226                                         
227                                         // Clear old characters (if needed)
228                                         if( oldLen > Info->BufferWritePos ) {
229                                                 pos = oldLen - Info->BufferWritePos;
230                                                 while(pos--)    _SysWrite(STDOUT_FD, " ", 1);
231                                                 pos = oldLen - Info->BufferWritePos;
232                                                 while(pos--)    _SysWrite(STDOUT_FD, "\x1B[D", 3);
233                                         }
234                                 }
235                                 break;
236                         case 'B':       // Down
237                                 {
238                                          int    oldLen = Info->BufferUsed;
239                                          int    pos;
240                                         if( Info->HistoryPos >= Info->NumHistory - 1 )  break;
241                                         
242                                         // Move to the beginning of the line
243                                         pos = oldLen;
244                                         while(pos--)    _SysWrite(STDOUT_FD, "\x1B[D", 3);
245                                         
246                                         // Update state
247                                         Info->CurBuffer = Info->History[Info->HistoryPos++];
248                                         Info->BufferSize = Info->BufferUsed = strlen(Info->CurBuffer);
249                                         
250                                         // Write new line
251                                         _SysWrite(STDOUT_FD, Info->CurBuffer, Info->BufferUsed);
252                                         Info->BufferWritePos = Info->BufferUsed;
253                                         
254                                         // Clear old characters (if needed)
255                                         if( oldLen > Info->BufferWritePos ) {
256                                                 pos = oldLen - Info->BufferWritePos;
257                                                 while(pos--)    _SysWrite(STDOUT_FD, " ", 1);
258                                                 pos = oldLen - Info->BufferWritePos;
259                                                 while(pos--)    _SysWrite(STDOUT_FD, "\x1B[D", 3);
260                                         }
261                                 }
262                                 break;
263                         case 'D':       // Left
264                                 if(Info->BufferWritePos == 0)   break;
265                                 Info->BufferWritePos --;
266                                 _SysWrite(STDOUT_FD, "\x1B[D", 3);
267                                 break;
268                         case 'C':       // Right
269                                 if(Info->BufferWritePos == Info->BufferUsed)    break;
270                                 Info->BufferWritePos ++;
271                                 _SysWrite(STDOUT_FD, "\x1B[C", 3);
272                                 break;
273                         }
274                         break;
275                 case '\0':
276                         ofs --;
277                         break;
278                 }
279                 break;
280         
281         // Backspace
282         case '\b':
283                 if(Info->BufferWritePos <= 0)   break;  // Protect against underflows
284                 // Write the backsapce
285                 _SysWrite(STDOUT_FD, "\b \b", 3);
286                 if(Info->BufferWritePos == Info->BufferUsed)    // Simple case: End of string
287                 {
288                         Info->BufferUsed --;
289                         Info->BufferWritePos --;
290                 }
291                 else
292                 {
293                         // Have to delete the character, and reposition the text
294                         char    buf[7] = "\x1B[000D";
295                          int    delta = Info->BufferUsed - Info->BufferWritePos + 1;
296                         buf[2] += (delta/100) % 10;
297                         buf[3] += (delta/10) % 10;
298                         buf[4] += (delta) % 10;
299                         // Write everything save for the deleted character
300                         _SysWrite(STDOUT_FD,
301                                 &Info->CurBuffer[Info->BufferWritePos],
302                                 Info->BufferUsed - Info->BufferWritePos
303                                 );
304                         ch = ' ';       _SysWrite(STDOUT_FD, &ch, 1);   ch = '\b';      // Clear old last character
305                         _SysWrite(STDOUT_FD, buf, 7);   // Update Cursor
306                         // Alter Buffer
307                         memmove(&Info->CurBuffer[Info->BufferWritePos-1],
308                                 &Info->CurBuffer[Info->BufferWritePos],
309                                 Info->BufferUsed-Info->BufferWritePos
310                                 );
311                         Info->BufferWritePos --;
312                         Info->BufferUsed --;
313                 }
314                 break;
315         
316         // Tab
317         case '\t':
318                 //TODO: Implement Tab-Completion
319                 //Currently just ignore tabs
320                 break;
321         
322         default:                
323                 // Expand Buffer
324                 if(Info->BufferUsed + 1 > Info->BufferSize)
325                 {
326                         Info->BufferSize += 256;
327                         Info->CurBuffer = Info->History[Info->HistoryPos]
328                                 = realloc(Info->CurBuffer, Info->BufferSize);
329                         if(!Info->CurBuffer)    return 0;
330                 }
331                 
332                 // Editing inside the buffer
333                 if(Info->BufferWritePos < Info->BufferUsed) {
334                         char    buf[7] = "\x1B[000D";
335                          int    delta = Info->BufferUsed - Info->BufferWritePos;
336                         buf[2] += (delta/100) % 10;
337                         buf[3] += (delta/10) % 10;
338                         buf[4] += (delta) % 10;
339                         _SysWrite(STDOUT_FD, &ch, 1);   // Print new character
340                         _SysWrite(STDOUT_FD,
341                                 &Info->CurBuffer[Info->BufferWritePos],
342                                 Info->BufferUsed - Info->BufferWritePos
343                                 );
344                         _SysWrite(STDOUT_FD, buf, 7);   // Update Cursor
345                         // Move buffer right
346                         memmove(
347                                 &Info->CurBuffer[Info->BufferWritePos+1],
348                                 &Info->CurBuffer[Info->BufferWritePos],
349                                 Info->BufferUsed - Info->BufferWritePos
350                                 );
351                 }
352                 // Simple append
353                 else {
354                         _SysWrite(STDOUT_FD, &ch, 1);
355                 }
356                 Info->CurBuffer[ Info->BufferWritePos ++ ] = ch;
357                 Info->BufferUsed ++;
358                 break;
359         }
360         
361         return ofs;
362 }

UCC git Repository :: git.ucc.asn.au