Skip to content

Commit f968a42

Browse files
committed
fix ANSI Escape sequences parsing and add support for line edit:
- HOME/END - DELETE - left/right by word
1 parent dcfd3b7 commit f968a42

File tree

1 file changed

+135
-8
lines changed

1 file changed

+135
-8
lines changed

libcli.c

Lines changed: 135 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,47 @@ static void cli_clear_line(int sockfd, char *cmd, int l, int cursor) {
10131013
memset((char *)cmd, 0, l);
10141014
}
10151015

1016+
// return new cursor position
1017+
static int cli_word_right(char *cmd, int l, int cursor) {
1018+
1019+
while (cursor < l && cmd[cursor] == ' ') {
1020+
cursor++;
1021+
}
1022+
1023+
while (cursor < l && cmd[cursor] != ' ') {
1024+
cursor++;
1025+
}
1026+
return cursor;
1027+
}
1028+
1029+
static int cli_word_left(char *cmd, int cursor) {
1030+
// like readline compare char before cursor
1031+
while (cursor > 0 && cmd[cursor-1] == ' ') {
1032+
cursor--;
1033+
}
1034+
1035+
while (cursor > 0 && cmd[cursor-1] != ' ') {
1036+
cursor--;
1037+
}
1038+
return cursor;
1039+
}
1040+
1041+
static int cli_move_cursor(struct cli_def *cli, int sockfd, char *cmd, int l, int cursor, int nc) {
1042+
while (cursor && cursor > nc) {
1043+
if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) {
1044+
_write(sockfd, "\b", 1);
1045+
}
1046+
cursor--;
1047+
}
1048+
while (cursor < l && cursor < nc) {
1049+
if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD) {
1050+
_write(sockfd, &cmd[cursor], 1);
1051+
}
1052+
cursor++;
1053+
}
1054+
return cursor;
1055+
}
1056+
10161057
void cli_reprompt(struct cli_def *cli) {
10171058
if (!cli) return;
10181059
cli->showprompt = 1;
@@ -1067,7 +1108,9 @@ static int show_prompt(struct cli_def *cli, int sockfd) {
10671108
}
10681109

10691110
int cli_loop(struct cli_def *cli, int sockfd) {
1070-
int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0, cursor = 0;
1111+
int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0, cursor = 0, nc;
1112+
char esc_buff[10] = {0};
1113+
int esc_pos = 0;
10711114
char *cmd = NULL, *oldcmd = 0;
10721115
char *username = NULL, *password = NULL;
10731116

@@ -1266,7 +1309,16 @@ int cli_loop(struct cli_def *cli, int sockfd) {
12661309

12671310
// Handle ANSI arrows
12681311
if (esc) {
1269-
if (esc == '[') {
1312+
if (esc == '[') { // 0x5b
1313+
1314+
// terminate ESC seq
1315+
if (c >= 0x40 && c <= 0x7E) {
1316+
esc = 0;
1317+
}
1318+
if (c >= 0x30 && c <= 0x3F && esc_pos < (int)sizeof(esc_buff) - 2) {
1319+
esc_buff[esc_pos++] = c;
1320+
}
1321+
12701322
// Remap to readline control codes
12711323
switch (c) {
12721324
case 'A': // Up
@@ -1278,23 +1330,79 @@ int cli_loop(struct cli_def *cli, int sockfd) {
12781330
break;
12791331

12801332
case 'C': // Right
1281-
c = CTRL('F');
1333+
if (strcmp(esc_buff, "1;5") == 0) {
1334+
nc = cli_word_right(cmd, l, cursor);
1335+
cursor = cli_move_cursor(cli, sockfd, cmd, l, cursor, nc);
1336+
c = 0;
1337+
}
1338+
else {
1339+
c = CTRL('F');
1340+
}
12821341
break;
12831342

12841343
case 'D': // Left
1285-
c = CTRL('B');
1344+
if (strcmp(esc_buff, "1;5") == 0) {
1345+
nc = cli_word_left(cmd, cursor);
1346+
cursor = cli_move_cursor(cli, sockfd, cmd, l, cursor, nc);
1347+
c = 0;
1348+
}
1349+
else {
1350+
c = CTRL('B');
1351+
}
1352+
break;
1353+
1354+
case 'H': // Home
1355+
c = CTRL('A');
1356+
break;
1357+
1358+
case 'F': // End
1359+
c = CTRL('E');
1360+
break;
1361+
1362+
case '~': {
1363+
// Delete, do not remap to EOF if l==0
1364+
if (strcmp(esc_buff, "3") == 0 && l) {
1365+
c = CTRL('D');
1366+
}
1367+
else {
1368+
c = 0;
1369+
}
12861370
break;
1371+
}
12871372

12881373
default:
12891374
c = 0;
12901375
}
12911376

1292-
esc = 0;
12931377
} else {
1294-
esc = (c == '[') ? c : 0;
1378+
1379+
switch (c) {
1380+
1381+
case 'b': // Left by word
1382+
nc = cli_word_left(cmd, cursor);
1383+
cursor = cli_move_cursor(cli, sockfd, cmd, l, cursor, nc);
1384+
break;
1385+
1386+
case 'f': // Right by word
1387+
nc = cli_word_right(cmd, l, cursor);
1388+
cursor = cli_move_cursor(cli, sockfd, cmd, l, cursor, nc);
1389+
break;
1390+
1391+
case '[':
1392+
esc = c;
1393+
continue;
1394+
1395+
default:
1396+
break;
1397+
}
1398+
esc = 0;
12951399
continue;
12961400
}
12971401
}
1402+
else {
1403+
memset(esc_buff, 0, sizeof(esc_buff));
1404+
esc_pos = 0;
1405+
}
12981406

12991407
if (c == 0) continue;
13001408
if (c == '\n') continue;
@@ -1304,7 +1412,7 @@ int cli_loop(struct cli_def *cli, int sockfd) {
13041412
break;
13051413
}
13061414

1307-
if (c == 27) {
1415+
if (c == 27) { // 0x1b ESC
13081416
esc = 1;
13091417
continue;
13101418
}
@@ -1421,7 +1529,26 @@ int cli_loop(struct cli_def *cli, int sockfd) {
14211529
if (c == CTRL('D')) {
14221530
if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) break;
14231531

1424-
if (l) continue;
1532+
if (l) {
1533+
1534+
if (cursor < l) {
1535+
_write(sockfd, cmd + cursor + 1, l - cursor - 1);
1536+
_write(sockfd, " ", 1);
1537+
1538+
// Move everything one char left
1539+
memmove(cmd + cursor, cmd + cursor + 1, l - cursor - 1);
1540+
1541+
l--;
1542+
1543+
// And reposition cursor
1544+
for (int i = l; i >= cursor; i--) _write(sockfd, "\b", 1);
1545+
1546+
// Set former last char to null
1547+
cmd[l] = 0;
1548+
}
1549+
1550+
continue;
1551+
}
14251552

14261553
l = -1;
14271554
break;

0 commit comments

Comments
 (0)