CuVoodoo STM32F1 firmware template
terminal.c
Go to the documentation of this file.
1 /* This program is free software: you can redistribute it and/or modify
2  * it under the terms of the GNU General Public License as published by
3  * the Free Software Foundation, either version 3 of the License, or
4  * (at your option) any later version.
5  *
6  * This program is distributed in the hope that it will be useful,
7  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9  * GNU General Public License for more details.
10  *
11  * You should have received a copy of the GNU General Public License
12  * along with this program. If not, see <http://www.gnu.org/licenses/>.
13  *
14  */
21 /* standard libraries */
22 #include <stdint.h> // standard integer types
23 #include <stdbool.h> // boolean type
24 #include <stdlib.h> // standard utilities
25 #include <string.h> // string utilities
26 
27 /* own libraries */
28 #include "global.h" // global definitions
29 #include "terminal.h" // own definitions
30 #include "print.h" // printing utilities
31 
32 char* terminal_prefix = NULL;
33 void (*terminal_process)(char* line) = NULL;
34 
36 static char terminal_buffer[1024] = {0};
38 static uint16_t terminal_end = 0;
40 static uint16_t terminal_pos = 0;
42 static uint16_t terminal_line = 0;
44 static bool terminal_last = true;
46 static bool terminal_insert = true;
48 static char escape_code[8] = {0};
50 static uint8_t escape_pos = 0;
51 
55 static bool terminal_remove_line(void)
56 {
57  if (0==terminal_line) { // be sure we are currently not on the first line
58  return false;
59  }
60  uint16_t line_end = strlen(&terminal_buffer[0]); // get end of line
61  if (terminal_end<=line_end) { // be sure there is a line to delete
62  return false;
63  }
64  for (uint16_t i=line_end+1; i<=terminal_end && i<LENGTH(terminal_buffer); i++) { // line buffer after the line to start
65  terminal_buffer[i-line_end-1] = terminal_buffer[i];
66  }
67  if (terminal_end>line_end+1) { // update buffer end
68  terminal_end -= line_end+1;
69  } else {
70  terminal_end = 0;
71  }
72  if (terminal_pos>line_end+1) { // update buffer position
73  terminal_pos -= line_end+1;
74  } else {
75  terminal_pos = 0;
76  }
77  if (terminal_line>line_end+1) { // update line position
78  terminal_line -= line_end+1;
79  } else {
80  terminal_line = 0;
81  }
82  if (0==terminal_line) { // update if we are on the last line
83  terminal_last = true;
84  }
85  return true;
86 }
87 
94 static void terminal_shift_line(uint16_t to_copy, uint16_t nb_copy, uint16_t to_shift)
95 {
96  char c = terminal_buffer[to_copy];
97  terminal_buffer[to_copy] = terminal_buffer[to_shift];
98  if (to_shift<terminal_end) { // characters to shift remain
99  terminal_shift_line(to_copy+1, nb_copy>0 ? nb_copy-1 : 0, to_shift+1); // shift next character
100  }
101  if (nb_copy>0) {
102  terminal_buffer[terminal_end-nb_copy] = c; // place back
103  }
104 }
105 
107 static void terminal_copy_line(void)
108 {
109  if (terminal_last) { // current line is already last line
110  return; // do nothing
111  }
112  uint16_t line_len = strlen(&terminal_buffer[terminal_line]);
113  while (terminal_end>=LENGTH(terminal_buffer)-line_len-1 && terminal_remove_line()); // delete line if not enough space
114  if (terminal_end<LENGTH(terminal_buffer)-line_len-1) { // there is enough space to copy the line
115  for (uint16_t i=0; i<line_len; i++) { // copy line to end of buffer
116  terminal_buffer[terminal_end+i+1] = terminal_buffer[terminal_line+i]; // copy character
117  }
118  terminal_pos += (terminal_end+1-terminal_line); // update current position
119  terminal_line = terminal_end+1; // update line position
120  terminal_end += line_len+1; // update buffer end position
121  terminal_buffer[terminal_end] = '\0'; // ensure end is terminated
122  } else if (0==terminal_line) { // shift (first) line to end of buffer
123  terminal_shift_line(0, line_len, line_len+1); // shift line
124  terminal_line = terminal_end-line_len; // update line position
125  terminal_pos += terminal_line; // update current position
126  // terminal_end did not change
127  }
128  terminal_last = true; // now we are on the last line
129 }
130 
132 static void terminal_process_escape(void)
133 {
134  if (escape_pos<2) { // the escape code must have at least 2 bytes (C1 and final)
135  return;
136  }
137  switch (escape_code[0]) { // process escape code according to C1
138  case '[': // CSI - Control Sequence Introducer
139  switch (escape_code[escape_pos-1]) { // process CSI code
140  case 'A': // CUU - cursor up
141  {
142  uint16_t n = 1; // number of cells to move
143  if (escape_pos>2) { // number of cells provided
144  escape_code[escape_pos-1] = '\0'; // terminate string
145  n = atoi(&escape_code[1]); // get number of cells
146  }
147  while (n--) { // go up number of line
148  if (0==terminal_line) { // stop if we are already at the top line
149  break;
150  }
151  uint16_t terminal_line_new=0; // new line start
152  for (uint16_t pos=0; pos<terminal_line-1 && pos<LENGTH(terminal_buffer); pos++) { // find for the last line before the current
153  if ('\0'==terminal_buffer[pos]) { // new line found
154  terminal_line_new = pos+1; // save new line
155  }
156  }
157  if (terminal_pos==terminal_line+strlen(&terminal_buffer[terminal_line])) { // if the position is the end of the current line
158  terminal_pos=terminal_line_new+strlen(&terminal_buffer[terminal_line_new]); // set position to end of new line
159  } else {
160  terminal_pos -= (terminal_line-terminal_line_new); // move position to new line
161  if (terminal_pos>terminal_line_new+strlen(&terminal_buffer[terminal_line_new])) { // position is outside of line
162  terminal_pos = terminal_line_new+strlen(&terminal_buffer[terminal_line_new]); // set new position to end of line
163  }
164  }
165  terminal_line = terminal_line_new; // set new line start
166  terminal_last = (terminal_end==terminal_line+strlen(&terminal_buffer[terminal_line])); // check if we are on the last line
167  }
168  }
169  break;
170  case 'B': // CUD - cursor down
171  {
172  uint16_t n = 1; // number of cells to move
173  if (escape_pos>2) { // number of cells provided
174  escape_code[escape_pos-1] = '\0'; // terminate string
175  n = atoi(&escape_code[1]); // get number of cells
176  }
177  while (n--) { // go down number of line
178  if (terminal_last) { // stop if we are already at the last line
179  break;
180  }
181  uint16_t terminal_line_new = terminal_line+strlen(&terminal_buffer[terminal_line])+1; // line start for the next line
182  if (terminal_pos==terminal_line+strlen(&terminal_buffer[terminal_line])) { // if the position is the end of the current line
183  terminal_pos=terminal_line_new+strlen(&terminal_buffer[terminal_line_new]); // set position to end of new line
184  } else {
185  terminal_pos += (terminal_line_new-terminal_line); // move position to new line
186  if (terminal_pos>terminal_line_new+strlen(&terminal_buffer[terminal_line_new])) { // position is outside of line
187  terminal_pos = terminal_line_new+strlen(&terminal_buffer[terminal_line_new]); // set new position to end of line
188  }
189  }
190  terminal_line = terminal_line_new; // set new line start
191  terminal_last = (terminal_end==terminal_line+strlen(&terminal_buffer[terminal_line])); // check if we are on the last line
192  }
193  }
194  break;
195  case 'C': // CUF - cursor forward
196  {
197  uint16_t n = 1; // number of cells to move
198  if (escape_pos>2) { // number of cells provided
199  escape_code[escape_pos-1] = '\0'; // terminate string
200  n = atoi(&escape_code[1]); // get number of cells
201  }
202  while (n--) { // go right number of moves
203  if (terminal_pos>=terminal_line+strlen(&terminal_buffer[terminal_line])) { // stop if we are already at the end
204  break;
205  }
206  terminal_pos++;
207  }
208  }
209  break;
210  case 'D': // CUB - cursor back
211  {
212  uint16_t n = 1; // number of cells to move
213  if (escape_pos>2) { // number of cells provided
214  escape_code[escape_pos-1] = '\0'; // terminate string
215  n = atoi(&escape_code[1]); // get number of cells
216  }
217  while (n--) { // go left number of moves
218  if (terminal_pos<=terminal_line) { // stop if we are already at the beginning
219  break;
220  }
221  terminal_pos--;
222  }
223  }
224  break;
225  case '~': // special key
226  if (3==escape_pos) { // we only expect one parameter bytes
227  switch (escape_code[1]) {
228  case '2': // insert
229  terminal_insert = !terminal_insert; // toggle insert/replace mode
230  break;
231  case '3': // delete
232  if (!terminal_last) { // we are not editing the last line
233  terminal_copy_line(); // make current line the last line
234  }
235  if (terminal_pos<terminal_end) { // be sure we are not at the end of the line, where there is nothing to erase
236  for (uint16_t i=terminal_pos; i<terminal_end && i<LENGTH(terminal_buffer)-1; i++) { // delete character by shifting end of line
237  terminal_buffer[i] = terminal_buffer[i+1]; // shift character
238  }
239  terminal_end--; // shorten line
240  }
241  break;
242  case '7': // home
243  terminal_pos = terminal_line; // set position to beginning of line
244  break;
245  case '8': // end
246  terminal_pos = terminal_line+strlen(&terminal_buffer[terminal_line]); // set position to end of line
247  break;
248  case '5': // page up
249  if (terminal_line>0) {
250  uint16_t terminal_line_new = 0; // set to first line
251  if (terminal_pos==terminal_line+strlen(&terminal_buffer[terminal_line])) { // if the position is the end of the current line
252  terminal_pos=terminal_line_new+strlen(&terminal_buffer[terminal_line_new]); // set position to end of new line
253  } else {
254  terminal_pos -= (terminal_line-terminal_line_new); // move position to new line
255  if (terminal_pos>terminal_line_new+strlen(&terminal_buffer[terminal_line_new])) { // position is outside of line
256  terminal_pos = terminal_line_new+strlen(&terminal_buffer[terminal_line_new]); // set new position to end of line
257  }
258  }
259  terminal_line = terminal_line_new; // set new line start
260  terminal_last = (terminal_end==terminal_line+strlen(&terminal_buffer[terminal_line])); // check if we are on the last line
261  }
262  break;
263  case '6': // page down
264  if (!terminal_last) { // stop if we are already at the last line
265  uint16_t terminal_line_new = terminal_line; // start last line search
266  for (uint16_t i=terminal_line_new; i<terminal_end-1; i++) { // search for last line
267  if ('\0'==terminal_buffer[i]) { // end of line found
268  terminal_line_new = i+1; // set new line start
269  }
270  }
271  if (terminal_pos==terminal_line+strlen(&terminal_buffer[terminal_line])) { // if the position is the end of the current line
272  terminal_pos=terminal_line_new+strlen(&terminal_buffer[terminal_line_new]); // set position to end of new line
273  } else {
274  terminal_pos += (terminal_line_new-terminal_line); // move position to new line
275  if (terminal_pos>terminal_line_new+strlen(&terminal_buffer[terminal_line_new])) { // position is outside of line
276  terminal_pos = terminal_line_new+strlen(&terminal_buffer[terminal_line_new]); // set new position to end of line
277  }
278  }
279  terminal_line = terminal_line_new; // set new line start
280  terminal_last = (terminal_end==terminal_line+strlen(&terminal_buffer[terminal_line])); // check if we are on the last line
281  }
282  break;
283  }
284  }
285  break;
286  default: // we don't handle other codes
287  break; // do nothing
288  }
289  break;
290  default: // we don't handle this code
291  break; // do nothing
292  }
293 }
294 
296 static void terminal_print_line(void)
297 {
298  printf("\r\x1b[K%s%s%s", terminal_prefix ? terminal_prefix : "", ": ", &terminal_buffer[terminal_line]); // erase line and display last one
299  if (terminal_pos<terminal_line+strlen(&terminal_buffer[terminal_line])) { // cursor is not at the end of the line
300  printf("\x1b[%uD", terminal_line+strlen(&terminal_buffer[terminal_line])-terminal_pos); // set position by going back
301  }
302 }
303 
304 void terminal_setup(void) {
305  // reset all buffers
306  terminal_buffer[0] = '\0';
307  terminal_end = 0;
308  terminal_pos = 0;
309  terminal_line = 0;
310  terminal_last = true;
311  escape_pos = 0;
312  // start prompt
313  terminal_send(0);
314 }
315 
316 void terminal_send(volatile char c)
317 {
318  static char newline_last = 0; // last new linefeed or carrier return character received
319 
320  if (escape_pos) { // currently receiving an escape code
321  if (1==escape_pos) { // we received C0 and expected C1
322  if (c>=0x40 && c<=0x5f) { // data is in the right range
323  escape_code[0] = c; // save C1
324  escape_pos++; // got to next position
325  } else { // this is not a C1 code
326  escape_pos = 0; // stop saving the escape code
327  terminal_send(c); // process data as non-escape code
328  }
329  } else { // after C1 we expect a parameters, intermediate, or final byte
330  if (c>=0x20 && c<=0x3f) { // received parameter (0x30-0x3f) or intermediate (0x20-0x2f) byte
331  if (escape_pos<=LENGTH(escape_code)) {
332  escape_code[escape_pos-1] = c; // save parameter byte
333  escape_pos++; // go to next position
334  }
335  } else if (c>=0x40 && c<=0x7f) { // received final byte
336  if (escape_pos<=LENGTH(escape_code)) {
337  escape_code[escape_pos-1] = c; // save final byte
338  }
339  terminal_process_escape(); // process escape code since we received the final byte
340  escape_pos = 0; // stop saving the escape code
341  terminal_print_line(); // print current line since if might have changed
342  } else { // this is not a expected byte
343  escape_pos = 0; // stop saving the escape code
344  terminal_send(c); // process data as non-escape code
345  }
346  }
347  } else {
348  if (0==c) { // string end received
349  terminal_print_line(); // only update line
350  } else if (0x1b==c) { // receiving new escape code (ESC)
351  escape_pos = 1; // start filling buffer
352  } else if (0x03==c) { // received CRTL+C
353  if (!terminal_last) { // we are not on the last line
354  uint16_t terminal_line_new = terminal_line; // start last line search
355  for (uint16_t i=terminal_line_new; i<terminal_end-1; i++) { // search for last line
356  if ('\0'==terminal_buffer[i]) { // end of line found
357  terminal_line_new = i+1; // set new line start
358  }
359  }
360  terminal_line = terminal_line_new; // set new line start
361  terminal_last = true; // remember we are on the last line
362  }
363  if (terminal_line==terminal_end) { // line is empty
364  // nothing to do
365  } else {
366  // do not process current line
367  while (terminal_end>=LENGTH(terminal_buffer)-1 && terminal_remove_line()); // delete line if not enough space
368  if (terminal_end<LENGTH(terminal_buffer)-1) { // now there is space
369  terminal_end++; // shift end to new line
370  } else { // the current last line takes all the space -> erase it
371  terminal_end = 0; // update end
372  }
373  terminal_buffer[terminal_end] = '\0'; // ensure end is terminated
374  }
375  terminal_pos = terminal_end; // set position to new line
376  terminal_line = terminal_end; // update line position
377  printf("\n"); // go to new line
378  terminal_print_line(); // print current empty line
379  } else { // all other bytes do some line editing
380  if (!terminal_last) { // we are not editing the last line
381  terminal_copy_line(); // make current line the last line
382  }
383  if ('\r'== c || '\n'==c) { // line finished
384  if ('\r'==newline_last && '\n'==c) { // windows newline received
385  // this newline has already been handled before
386  } else {
387  printf("\n"); // print new line
388  if (terminal_process) {
389  (*terminal_process)(&terminal_buffer[terminal_line]); // process line
390  }
391  if (strlen(&terminal_buffer[terminal_line])>0) { // only store non-empty line
392  while (terminal_end>=LENGTH(terminal_buffer)-1 && terminal_remove_line()); // delete line if not enough space
393  if (terminal_end<LENGTH(terminal_buffer)-1) { // now there is space
394  terminal_end++; // shift end to new line
395  } else { // the current last line takes all the space -> erase it
396  terminal_end = 0; // update end
397  }
398  terminal_buffer[terminal_end] = '\0'; // ensure end is terminated
399  terminal_pos = terminal_end; // set position to new line
400  terminal_line = terminal_pos; // update line position
401  }
402  }
403  newline_last = c; // remember last character
404  } else if (0x7f==c) { // backspace
405  if (terminal_pos>terminal_line) { // we are not at the beginning of the line
406  for (uint16_t i=terminal_pos-1; i<terminal_end && i<LENGTH(terminal_buffer)-1; i++) { // delete character by shifting end of line
407  terminal_buffer[i] = terminal_buffer[i+1]; // shift character
408  }
409  terminal_end--; // shorten line
410  terminal_pos--; // move position back
411  }
412  } else if (terminal_insert) {
413  while (terminal_end>=LENGTH(terminal_buffer)-1 && terminal_remove_line()); // delete line if not enough space
414  if (terminal_end<LENGTH(terminal_buffer)-1) { // there is space to move the end
415  for (int16_t i=terminal_end; i>=terminal_pos; i--) { // shift buffer
417  }
418  terminal_buffer[terminal_pos++] = c; // insert new character
419  terminal_buffer[++terminal_end] = '\0'; // update end
420  }
421  } else { // replace current character
422  while (terminal_pos==terminal_end && terminal_end>=LENGTH(terminal_buffer)-1 && terminal_remove_line()); // delete line if not enough space
423  terminal_buffer[terminal_pos] = c; // replace current character
424  if (terminal_pos<LENGTH(terminal_buffer)-1) { // go to next character (if possible)
425  terminal_pos++;
426  }
427  if (terminal_pos>terminal_end) { // update end if it moved
428  terminal_end = terminal_pos; // move end
429  terminal_buffer[terminal_end] = '\0'; // ensure end is terminated
430  }
431  }
432  terminal_print_line(); // print current line
433  }
434  }
435 }
436 
static bool terminal_insert
currently inserting or replacing characters
Definition: terminal.c:46
void terminal_send(volatile char c)
send character to terminal
Definition: terminal.c:316
static bool terminal_last
is the current line the last one
Definition: terminal.c:44
global definitions and methods (API)
static void terminal_copy_line(void)
copy current line to last line
Definition: terminal.c:107
terminal prompt interface (API)
static void terminal_shift_line(uint16_t to_copy, uint16_t nb_copy, uint16_t to_shift)
shift with rotate current characters to other position
Definition: terminal.c:94
static char terminal_buffer[1024]
buffer to store user input and keep history
Definition: terminal.c:36
static void terminal_print_line(void)
print current line and set position
Definition: terminal.c:296
void terminal_setup(void)
initialize terminal prompt
Definition: terminal.c:304
static uint16_t terminal_end
how much of the buffer is user
Definition: terminal.c:38
static uint16_t terminal_pos
current position in the buffer
Definition: terminal.c:40
static uint16_t terminal_line
start position or current line in the buffer
Definition: terminal.c:42
static char escape_code[8]
current escape code
Definition: terminal.c:48
static uint8_t escape_pos
current position in the escape code
Definition: terminal.c:50
#define LENGTH(x)
get the length of an array
Definition: global.h:26
static void terminal_process_escape(void)
process current escape code
Definition: terminal.c:132
static bool terminal_remove_line(void)
remove one line from buffer start and shift rest to start
Definition: terminal.c:55
void(* terminal_process)(char *line)
called when a line is entered
Definition: terminal.c:33
char * terminal_prefix
terminal prompt prefix
Definition: terminal.c:32