Переместить курсор в программе C
Я хотел бы переместить курсор вперед и назад в программе C. Я читаю всю строку в цикле, но мне бы хотелось, чтобы при нажатии клавиши курсора курсор на экране меняет положение, не блокируя цикл. Я попробовал getwch()
, но он блокирует вызывающего абонента, пока он не будет нажат. Я ищу поведение, подобное запросу bash. Я читаю код, похожий на этот:
while (TRUE) {
printf("%s", PROMPT);
fgets(input, 1024, stdin);
do_something(input);
}
Я пытаюсь, чтобы функция выше работала как readline(PROMPT)
в библиотеке readline.h
Ответы
Ответ 1
Использование termios
и консольных кодов (совместим с VT100 - не переносимо):
#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 0x0108
static 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;
}
Ответ 2
Простой пример с использованием escape-последовательностей ANSI:
#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;
}
Чтобы сделать очень полезный, терминал нужно поместить в канонический режим с termios.h и/или curses.h/ncurses.h. Таким образом, обратное пространство key code может быть сразу поймано и откликаться, и буфер, нарисованный для экранирования соответственно. Вот пример того, как установить терминал в канонический режим с помощью 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);
Другим вариантом может быть использование библиотеки readline()
или editline()
. Чтобы использовать библиотеку readline, укажите -lreadline для своего компилятора. Следующий фрагмент кода можно скомпилировать с помощью
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;
}
Ответ 3
Перемещает курсор вправо и влево. останавливает ввод на новой строке или слишком много символов
#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 30
static 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;
}
Ответ 4
Управляющие последовательности ANSI позволяют перемещать курсор по экрану по желанию. Это может быть полезно для полноэкранных пользовательских интерфейсов, сгенерированных программой, запущенной под оболочкой, но также может использоваться в подсказках. Это не будет работать на эмуляторах терминалов, которые не принимают коды сохранения и восстановления курсора. Более подробную информацию об управляющих последовательностях для перемещения курсора см. В разделе перемещение курсора.
Если вам требуется более портативное решение, используйте curses, это библиотека управления терминалом, которая управляет отображением приложения на терминалах символьных ячеек для Unix-подобных систем. Библиотека curses в исполняющей системе отправляет правильные управляющие символы на основе типа терминала. Используйте int wmove(WINDOW* win, int y, int x)
в ncurses, чтобы переместить курсор на новую позицию. Подробнее о том, как программировать под ncurses, см. NCurses-programing-howto.