Skip to content

Commit d4807b5

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

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
@@ -1016,6 +1016,47 @@ static void cli_clear_line(int sockfd, char *cmd, int l, int cursor) {
10161016
memset((char *)cmd, 0, l);
10171017
}
10181018

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

10721113
int cli_loop(struct cli_def *cli, int sockfd) {
1073-
int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0, cursor = 0;
1114+
int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0, cursor = 0, nc;
1115+
char esc_buff[10] = {0};
1116+
int esc_pos = 0;
10741117
char *cmd = NULL, *oldcmd = 0;
10751118
char *username = NULL, *password = NULL;
10761119

@@ -1269,7 +1312,16 @@ int cli_loop(struct cli_def *cli, int sockfd) {
12691312

12701313
// Handle ANSI arrows
12711314
if (esc) {
1272-
if (esc == '[') {
1315+
if (esc == '[') { // 0x5b
1316+
1317+
// terminate ESC seq
1318+
if (c >= 0x40 && c <= 0x7E) {
1319+
esc = 0;
1320+
}
1321+
if (c >= 0x30 && c <= 0x3F && esc_pos < (int)sizeof(esc_buff) - 2) {
1322+
esc_buff[esc_pos++] = c;
1323+
}
1324+
12731325
// Remap to readline control codes
12741326
switch (c) {
12751327
case 'A': // Up
@@ -1281,23 +1333,79 @@ int cli_loop(struct cli_def *cli, int sockfd) {
12811333
break;
12821334

12831335
case 'C': // Right
1284-
c = CTRL('F');
1336+
if (strcmp(esc_buff, "1;5") == 0) {
1337+
nc = cli_word_right(cmd, l, cursor);
1338+
cursor = cli_move_cursor(cli, sockfd, cmd, l, cursor, nc);
1339+
c = 0;
1340+
}
1341+
else {
1342+
c = CTRL('F');
1343+
}
12851344
break;
12861345

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

12911376
default:
12921377
c = 0;
12931378
}
12941379

1295-
esc = 0;
12961380
} else {
1297-
esc = (c == '[') ? c : 0;
1381+
1382+
switch (c) {
1383+
1384+
case 'b': // Left by word
1385+
nc = cli_word_left(cmd, cursor);
1386+
cursor = cli_move_cursor(cli, sockfd, cmd, l, cursor, nc);
1387+
break;
1388+
1389+
case 'f': // Right by word
1390+
nc = cli_word_right(cmd, l, cursor);
1391+
cursor = cli_move_cursor(cli, sockfd, cmd, l, cursor, nc);
1392+
break;
1393+
1394+
case '[':
1395+
esc = c;
1396+
continue;
1397+
1398+
default:
1399+
break;
1400+
}
1401+
esc = 0;
12981402
continue;
12991403
}
13001404
}
1405+
else {
1406+
memset(esc_buff, 0, sizeof(esc_buff));
1407+
esc_pos = 0;
1408+
}
13011409

13021410
if (c == 0) continue;
13031411
if (c == '\n') continue;
@@ -1307,7 +1415,7 @@ int cli_loop(struct cli_def *cli, int sockfd) {
13071415
break;
13081416
}
13091417

1310-
if (c == 27) {
1418+
if (c == 27) { // 0x1b ESC
13111419
esc = 1;
13121420
continue;
13131421
}
@@ -1424,7 +1532,26 @@ int cli_loop(struct cli_def *cli, int sockfd) {
14241532
if (c == CTRL('D')) {
14251533
if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD) break;
14261534

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

14291556
l = -1;
14301557
break;

0 commit comments

Comments
 (0)