Improved readline function to support <- and ->
- readline function now uses raw terminal input mode (if in interactive session) - readline function supports cursor movement to the left and to the right and inline editing (also unicode support)
This commit is contained in:
parent
8285e148e1
commit
1101c53bf6
2 changed files with 185 additions and 13 deletions
|
|
@ -11,7 +11,7 @@ Simple shell for Unix-like systems written in C, that has a funny name (for germ
|
||||||
- ~cd~ builtin command (~cd~ without arguments moves you to ~$HOME~)
|
- ~cd~ builtin command (~cd~ without arguments moves you to ~$HOME~)
|
||||||
- ~exit~ builtin command
|
- ~exit~ builtin command
|
||||||
- ~CTRL+C~ stops running command
|
- ~CTRL+C~ stops running command
|
||||||
- Custom ~readline~ function
|
- Custom ~readline~ function with support for cursor moving (<-, ->) and inline editing
|
||||||
- Run scripts with ~arsh /path/to/script~ or by putting ~#!/usr/bin/env arsh~ at the first line of a script and making it executable
|
- Run scripts with ~arsh /path/to/script~ or by putting ~#!/usr/bin/env arsh~ at the first line of a script and making it executable
|
||||||
- Run commands with ~arsh -c 'echo $PATH'~ to launch something quickly with ~arsh~.
|
- Run commands with ~arsh -c 'echo $PATH'~ to launch something quickly with ~arsh~.
|
||||||
* Build
|
* Build
|
||||||
|
|
|
||||||
196
main.c
196
main.c
|
|
@ -8,6 +8,7 @@
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
constexpr size_t max_length = 1024;
|
constexpr size_t max_length = 1024;
|
||||||
constexpr size_t max_env_var_length = 256;
|
constexpr size_t max_env_var_length = 256;
|
||||||
|
|
@ -48,18 +49,10 @@ void* ccalloc(size_t size, size_t n)
|
||||||
|
|
||||||
static constexpr size_t max_line = 1024;
|
static constexpr size_t max_line = 1024;
|
||||||
static char string_buffer[max_line];
|
static char string_buffer[max_line];
|
||||||
char* readline(char* print, FILE* stream)
|
char* __readline_file(FILE* stream)
|
||||||
{
|
{
|
||||||
if(print)
|
|
||||||
{
|
|
||||||
printf("%s", print);
|
|
||||||
}
|
|
||||||
if(fgets(string_buffer, max_line, stream) == NULL)
|
if(fgets(string_buffer, max_line, stream) == NULL)
|
||||||
{
|
{
|
||||||
if (feof(stdin))
|
|
||||||
{
|
|
||||||
return NULL; // Ctrl+D
|
|
||||||
}
|
|
||||||
if(ferror(stream))
|
if(ferror(stream))
|
||||||
{
|
{
|
||||||
perror("fgets error");
|
perror("fgets error");
|
||||||
|
|
@ -78,6 +71,185 @@ char* readline(char* print, FILE* stream)
|
||||||
return string_buffer;
|
return string_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct termios orig_termios;
|
||||||
|
|
||||||
|
void disable_raw_mode()
|
||||||
|
{
|
||||||
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable_raw_mode()
|
||||||
|
{
|
||||||
|
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
atexit(disable_raw_mode);
|
||||||
|
|
||||||
|
struct termios raw = orig_termios;
|
||||||
|
|
||||||
|
raw.c_lflag &= ~(ECHO | ICANON);
|
||||||
|
raw.c_iflag &= ~(IXON | ICRNL);
|
||||||
|
raw.c_oflag &= ~(OPOST);
|
||||||
|
|
||||||
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t utf8_char_len(unsigned char c) {
|
||||||
|
if ((c & 0x80) == 0) return 1;
|
||||||
|
if ((c & 0xE0) == 0xC0) return 2;
|
||||||
|
if ((c & 0xF0) == 0xE0) return 3;
|
||||||
|
if ((c & 0xF8) == 0xF0) return 4;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t prev_char_start(char* buf, size_t pos) {
|
||||||
|
if (pos == 0) return 0;
|
||||||
|
|
||||||
|
pos--;
|
||||||
|
while (pos > 0 && ((buf[pos] & 0xC0) == 0x80)) {
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t next_char_start(char* buf, size_t len, size_t pos) {
|
||||||
|
if (pos >= len) return len;
|
||||||
|
return pos + utf8_char_len((unsigned char)buf[pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t read_utf8_char(int fd, char* out) {
|
||||||
|
unsigned char c;
|
||||||
|
if (read(fd, &c, 1) != 1) return -1;
|
||||||
|
|
||||||
|
size_t len = utf8_char_len(c);
|
||||||
|
out[0] = c;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < len; i++) {
|
||||||
|
if (read(fd, &out[i], 1) != 1)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t utf8_display_width(const char *buf, size_t len)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
size_t width = 0;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
unsigned char c = buf[i];
|
||||||
|
|
||||||
|
if ((c & 0x80) == 0) {
|
||||||
|
i += 1;
|
||||||
|
width += 1;
|
||||||
|
}
|
||||||
|
else if ((c & 0xE0) == 0xC0) {
|
||||||
|
i += 2;
|
||||||
|
width += 1;
|
||||||
|
}
|
||||||
|
else if ((c & 0xF0) == 0xE0) {
|
||||||
|
i += 3;
|
||||||
|
width += 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
i += 4;
|
||||||
|
width += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* __readline_interactive(char* prompt)
|
||||||
|
{
|
||||||
|
enable_raw_mode();
|
||||||
|
static char buffer[max_length];
|
||||||
|
size_t cursor = 0;
|
||||||
|
size_t len = 0;
|
||||||
|
ssize_t swrite(int fd, const char* str)
|
||||||
|
{
|
||||||
|
return write(fd, str, strlen(str));
|
||||||
|
}
|
||||||
|
void render_line(char *prompt, char *buf, size_t len, size_t cursor)
|
||||||
|
{
|
||||||
|
swrite(STDOUT_FILENO, "\r");
|
||||||
|
swrite(STDOUT_FILENO, prompt);
|
||||||
|
|
||||||
|
write(STDOUT_FILENO, buf, len);
|
||||||
|
|
||||||
|
swrite(STDOUT_FILENO, "\x1b[K");
|
||||||
|
size_t prompt_width = utf8_display_width(prompt, strlen(prompt));
|
||||||
|
size_t cell_cursor = utf8_display_width(buf, cursor);
|
||||||
|
char seq[64];
|
||||||
|
snprintf(seq, sizeof(seq), "\r\x1b[%zuC", prompt_width + cell_cursor);
|
||||||
|
swrite(STDOUT_FILENO, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
render_line(prompt, buffer, len, cursor);
|
||||||
|
while (1) {
|
||||||
|
char utf8[4];
|
||||||
|
ssize_t clen = read_utf8_char(STDIN_FILENO, utf8);
|
||||||
|
if (clen <= 0) continue;
|
||||||
|
|
||||||
|
if (utf8[0] == '\x1b') {
|
||||||
|
char seq[2];
|
||||||
|
if (read(STDIN_FILENO, &seq[0], 1) != 1) continue;
|
||||||
|
if (read(STDIN_FILENO, &seq[1], 1) != 1) continue;
|
||||||
|
|
||||||
|
if (seq[0] == '[') {
|
||||||
|
switch (seq[1])
|
||||||
|
{
|
||||||
|
case 'A': break; // Arrow up
|
||||||
|
case 'B': break; // Arrow down
|
||||||
|
case 'C': // Arrow right
|
||||||
|
cursor = next_char_start(buffer, len, cursor);
|
||||||
|
break;
|
||||||
|
case 'D': // Arrow left
|
||||||
|
cursor = prev_char_start(buffer, cursor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (utf8[0] == '\r') { // enter
|
||||||
|
buffer[len] = '\0';
|
||||||
|
swrite(STDOUT_FILENO, "\r\n");
|
||||||
|
break;
|
||||||
|
} else if (utf8[0] == 127) { // backspace
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
if (cursor > 0) {
|
||||||
|
size_t prev = prev_char_start(buffer, cursor);
|
||||||
|
memmove(buffer + prev, buffer + cursor, len - cursor);
|
||||||
|
len -= (cursor - prev);
|
||||||
|
cursor = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(len < sizeof(buffer))
|
||||||
|
{
|
||||||
|
memmove(buffer + cursor + clen, buffer + cursor, len - cursor);
|
||||||
|
memcpy(buffer + cursor, utf8, clen);
|
||||||
|
cursor += clen;
|
||||||
|
len += clen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render_line(prompt, buffer, len, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_raw_mode();
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* readline(char* prompt, FILE* stream)
|
||||||
|
{
|
||||||
|
if (isatty(fileno(stream)))
|
||||||
|
{
|
||||||
|
return __readline_interactive(prompt);
|
||||||
|
}
|
||||||
|
return __readline_file(stream);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Struct to represent a command and its arguments.
|
* Struct to represent a command and its arguments.
|
||||||
*/
|
*/
|
||||||
|
|
@ -737,7 +909,7 @@ char* prettify_pwd(char* pwd)
|
||||||
{
|
{
|
||||||
if (!pwd)
|
if (!pwd)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Internal Error: pwd can't be null");
|
fprintf(stderr, "\nInternal Error: pwd can't be null\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
char* home = get_home();
|
char* home = get_home();
|
||||||
|
|
@ -817,7 +989,7 @@ char* generate_ps1_prompt()
|
||||||
size_t len = strlen(data);
|
size_t len = strlen(data);
|
||||||
if(j + i - 1 - start + len >= sizeof(prompt_buf))
|
if(j + i - 1 - start + len >= sizeof(prompt_buf))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Out of memory(1) for prompt: %d >= %d", j + i - start + len, sizeof(prompt_buf));
|
fprintf(stderr, "\nOut of memory(1) for prompt: %d >= %d\n", j + i - start + len, sizeof(prompt_buf));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
size_t start_len = min_size_t(sizeof(prompt_buf) - j, i - start - 1);
|
size_t start_len = min_size_t(sizeof(prompt_buf) - j, i - start - 1);
|
||||||
|
|
@ -825,7 +997,7 @@ char* generate_ps1_prompt()
|
||||||
j += start_len;
|
j += start_len;
|
||||||
if(j + len >= sizeof(prompt_buf))
|
if(j + len >= sizeof(prompt_buf))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Out of memory(2) for prompt: %d >= %d", j + len, sizeof(prompt_buf));
|
fprintf(stderr, "\nOut of memory(2) for prompt: %d >= %d\n", j + len, sizeof(prompt_buf));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
memcpy(prompt_buf + j, data, len);
|
memcpy(prompt_buf + j, data, len);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue