Move the cursor in a C program
Using termios
and console-codes (VT100 compatible - not portable):
#include <stdio.h>#include <string.h>#include <termios.h>#include <unistd.h>#define cursorforward(x) printf("\033[%dC", (x))#define cursorbackward(x) printf("\033[%dD", (x))#define KEY_ESCAPE 0x001b#define KEY_ENTER 0x000a#define KEY_UP 0x0105#define KEY_DOWN 0x0106#define KEY_LEFT 0x0107#define KEY_RIGHT 0x0108static struct termios term, oterm;static int getch(void);static int kbhit(void);static int kbesc(void);static int kbget(void);static int getch(void){ int c = 0; tcgetattr(0, &oterm); memcpy(&term, &oterm, sizeof(term)); term.c_lflag &= ~(ICANON | ECHO); term.c_cc[VMIN] = 1; term.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &term); c = getchar(); tcsetattr(0, TCSANOW, &oterm); return c;}static int kbhit(void){ int c = 0; tcgetattr(0, &oterm); memcpy(&term, &oterm, sizeof(term)); term.c_lflag &= ~(ICANON | ECHO); term.c_cc[VMIN] = 0; term.c_cc[VTIME] = 1; tcsetattr(0, TCSANOW, &term); c = getchar(); tcsetattr(0, TCSANOW, &oterm); if (c != -1) ungetc(c, stdin); return ((c != -1) ? 1 : 0);}static int kbesc(void){ int c; if (!kbhit()) return KEY_ESCAPE; c = getch(); if (c == '[') { switch (getch()) { case 'A': c = KEY_UP; break; case 'B': c = KEY_DOWN; break; case 'C': c = KEY_LEFT; break; case 'D': c = KEY_RIGHT; break; default: c = 0; break; } } else { c = 0; } if (c == 0) while (kbhit()) getch(); return c;}static int kbget(void){ int c; c = getch(); return (c == KEY_ESCAPE) ? kbesc() : c;}int main(void){ int c; while (1) { c = kbget(); if (c == KEY_ENTER || c == KEY_ESCAPE || c == KEY_UP || c == KEY_DOWN) { break; } else if (c == KEY_RIGHT) { cursorbackward(1); } else if (c == KEY_LEFT) { cursorforward(1); } else { putchar(c); } } printf("\n"); return 0;}
A simple example using ANSI escape sequences:
#include <stdio.h>int main(){ char *string = "this is a string"; char input[1024] = { 0 }; printf("%s", string); /* move the cursor back 5 spaces */ printf("\033[D"); printf("\033[D"); printf("\033[D"); printf("\033[D"); printf("\033[D"); fgets(input, 1024, stdin); return 0;}
To do very much useful the terminal needs to be put into canonical mode with termios.h and/or curses.h/ncurses.h. This way the backspace key code can be caught and responded to immediately and the buffer drawn to screen accordingly. Here is an example of how to set the terminal into canonical mode with tcsetattr()
:
struct termios info;tcgetattr(0, &info);info.c_lflag &= ~ICANON;info.c_cc[VMIN] = 1;info.c_cc[VTIME] = 0;tcsetattr(0, TCSANOW, &info);
Another option might be to use the readline()
or editline()
library. To use the readline library specify -lreadline to your compiler. The following code snippet can be compiled with
cc -lreadline some.c -o some#include <stdio.h>#include <readline/readline.h>#include <readline/history.h>int main(){ char *inpt; int i = 0; while ( i < 10 ) { inpt = readline("Enter text: "); add_history(inpt); printf("%s", inpt); printf("\n"); ++i; } return 0;
}
Moves cursor right and left. stops input on newline or too many characters
#include <stdio.h>#include <string.h>#include <termios.h>#include <unistd.h>#include <ctype.h>#include <sys/select.h>#include <sys/ioctl.h>#define ESC 27#define INSERT 50#define DELETE 51#define PGUP 53#define PGDN 54#define ARROWRIGHT 67#define ARROWLEFT 68#define END 70#define HOME 72#define OTHER 79#define BRACKETLEFT 91#define TILDE 126#define BACKSPACE 127#define SIZE 30static const int STDIN = 0;int kbhit(void){ int bytesWaiting; ioctl(STDIN, FIONREAD, &bytesWaiting); return bytesWaiting;}int main ( ) { char input[SIZE] = {'\0'}; int insert = 0; int each = 0; int end = 0; int to = 0; int ch = 0; int row = 0; int col = 0; struct termios oldattr, newattr; //set terminal tcgetattr( STDIN, &oldattr ); newattr = oldattr; newattr.c_lflag &= ~( ICANON | ECHO ); tcsetattr( STDIN, TCSANOW, &newattr ); setbuf(stdin, NULL); printf ( "\033[2J");//clear screen printf ( "\033[25;1H");//move cursor to row 25 col 1 printf ( "OVW"); printf ( "\033[9;1H");//move cursor to row 9 col 1 printf ( "enter your text ");//prompt //printf ( "%s", input); printf ( "\033[9;17H");//move cursor to row 9 col 17 col = 17; row = 9; while ( ( ch = getchar ()) != '\n') { if ( isprint( ch)) { if ( insert && each < end && end < SIZE-3) { //expand end++; for ( to = end; to >= each; to--) { input[to + 1] = input[to]; } printf ( "\033[9;17H");//move cursor to row 9 col 12 printf ( "\033[K");//erase to end of line printf ( "%s", input); } printf ( "\033[%d;%dH", row, col); printf ( "%c", ch); input[each] = ch; each++; if ( each > end) { end = each; } col++; if ( each == end) { input[each] = '\0'; } if ( each >= SIZE-1) { break; } continue; } if ( ch == BACKSPACE) { if ( each) { each--; col--; //contract for ( to = each; to <= end; to++) { input[to] = input[to + 1]; } end--; printf ( "\033[9;17H");//move cursor to row 1 col 7 printf ( "\033[K");//erase to end of line printf ( "%s", input); printf ( "\033[%d;%dH", row, col); } } if ( ch == ESC) { if ( !kbhit ( )) { continue; } ch = getchar ( ); if ( ch == OTHER) { ch = getchar ( ); if ( ch == HOME) { col -= each; each = 0; printf ( "\033[%d;%dH", row, col); ch = getchar ( ); } if ( ch == END) { col += end - each; each = end; printf ( "\033[%d;%dH", row, col); ch = getchar ( ); } } if ( ch == BRACKETLEFT) { ch = getchar ( ); if ( ch == INSERT) { ch = getchar ( ); if ( ch == TILDE) { insert = !insert; printf ( "\033[25;1H");//move cursor to row 25 col 1 if ( insert) { printf ( "INS"); } else { printf ( "OVW"); } printf ( "\033[%d;%dH", row, col); } } if ( ch == DELETE) { ch = getchar ( ); if ( ch == TILDE) { //contract for ( to = each; to <= end; to++) { input[to] = input[to + 1]; } end--; printf ( "\033[9;17H");//move cursor to row 10 col 1 printf ( "\033[K");//erase to end of line printf ( "%s", input); printf ( "\033[%d;%dH", row, col); } } if ( ch == ARROWRIGHT) { if ( each < end) { printf ( "\033[C");//cursor right each++; col++; } } if ( ch == ARROWLEFT) { if ( each) { printf ( "\033[D");//cursor left each--; col--; } } } else { ungetc ( ch, stdin); } } } printf ( "\n\ninput was [%s]\n", input); printf ( "\n\nbye\n"); //restore terminal tcsetattr( STDIN, TCSANOW, &oldattr ); return 0;}