Laika/shell/src/sterm.c

302 lines
8.1 KiB
C

#include "sterm.h"
#include "lmem.h"
#include "scmd.h"
#define KEY_ESCAPE 0x001b
#define KEY_ENTER 0x000a
#define KEY_BACKSPACE 0x007f
#define KEY_UP 0x0105
#define KEY_DOWN 0x0106
#define KEY_LEFT 0x0107
#define KEY_RIGHT 0x0108
#define cursorForward(x) printf("\033[%dC", (x))
#define cursorBackward(x) printf("\033[%dD", (x))
#define clearLine() printf("\033[2K")
/* =================================[[ DEPRECATED ARRAY API ]]================================== */
/*
this whole target needs to be rewritten, so these macros have been embedded here until a
rewrite can be done. this is the only section of the entire codebase that relies too heavily
on these to quickly exchange into the vector api equivalent. there's technically a memory leak
here since the array is never free'd, but since the array is expected to live the entire
lifetime of the program it's safe to leave as-is for now.
*/
#define laikaM_growarray(type, buf, needed, count, capacity) \
if (count + needed >= capacity || buf == NULL) { \
capacity = (capacity + needed) * GROW_FACTOR; \
buf = (type *)laikaM_realloc(buf, sizeof(type) * capacity); \
}
/* moves array elements above indx down by numElem, removing numElem elements at indx */
#define laikaM_rmvarray(buf, count, indx, numElem) \
do { \
int _i, _sz = ((count - indx) - numElem); \
for (_i = 0; _i < _sz; _i++) \
buf[indx + _i] = buf[indx + numElem + _i]; \
count -= numElem; \
} while (0);
/* moves array elements above indx up by numElem, inserting numElem elements at indx */
#define laikaM_insertarray(buf, count, indx, numElem) \
do { \
int _i; \
for (_i = count; _i > indx; _i--) \
buf[_i] = buf[_i - 1]; \
count += numElem; \
} while (0);
struct termios orig_termios;
char *cmd = NULL, *prompt = "$> ";
int cmdCount = 0, cmdCap = 4, cmdCursor = 0;
void shellT_conioTerm(void)
{
struct termios new_termios;
/* take two copies - one for now, one for later */
tcgetattr(0, &orig_termios);
memcpy(&new_termios, &orig_termios, sizeof(new_termios));
/* register cleanup handler, and set the new terminal mode */
atexit(shellT_resetTerm);
new_termios.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &new_termios);
}
void shellT_resetTerm(void)
{
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
}
const char *shellT_getForeColor(TERM_COLOR col)
{
switch (col) {
case TERM_BLACK:
return "\033[30m";
break;
case TERM_RED:
return "\033[31m";
break;
case TERM_GREEN:
return "\033[32m";
break;
case TERM_YELLOW:
return "\033[33m";
break;
case TERM_BLUE:
return "\033[34m";
break;
case TERM_MAGENTA:
return "\033[35m";
break;
case TERM_CYAN:
return "\033[36m";
break;
case TERM_WHITE:
return "\033[37m";
break;
case TERM_BRIGHT_BLACK:
return "\033[90m";
break;
case TERM_BRIGHT_RED:
return "\033[91m";
break;
case TERM_BRIGHT_GREEN:
return "\033[92m";
break;
case TERM_BRIGHT_YELLOW:
return "\033[93m";
break;
case TERM_BRIGHT_BLUE:
return "\033[94m";
break;
case TERM_BRIGHT_MAGENTA:
return "\033[95m";
break;
case TERM_BRIGHT_CYAN:
return "\033[96m";
break;
case TERM_BRIGHT_WHITE:
default:
return "\033[97m";
break;
}
}
void shellT_printf(const char *format, ...)
{
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
fflush(stdout);
}
/* waits for input for timeout. returns true if input is ready to be read, false if no events */
bool shellT_waitForInput(int timeout)
{
struct timeval tv;
fd_set fds;
/* setup stdin file descriptor */
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
/* wait for read events on STDIN_FILENO for timeout period */
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
return select(1, &fds, NULL, NULL, &tv) > 0;
}
int shellT_readRawInput(uint8_t *buf, size_t max)
{
return read(STDIN_FILENO, buf, max);
}
void shellT_writeRawOutput(uint8_t *buf, size_t sz)
{
write(STDOUT_FILENO, buf, sz);
fflush(stdout);
}
void shellT_getTermSize(int *col, int *row)
{
struct winsize ws;
ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
*col = ws.ws_col;
*row = ws.ws_row;
}
char shellT_getch(void)
{
int r;
char in;
if ((r = shellT_readRawInput((uint8_t *)&in, 1)) > 0) {
return in;
} else {
return r;
}
}
int shellT_kbesc(void)
{
int c;
/* if no event waiting, it's KEY_ESCAPE */
if (!shellT_waitForInput(0))
return KEY_ESCAPE;
if ((c = shellT_getch()) == '[') {
switch (shellT_getch()) {
case 'A':
c = KEY_UP;
break;
case 'B':
c = KEY_DOWN;
break;
case 'C':
c = KEY_RIGHT;
break;
case 'D':
c = KEY_LEFT;
break;
default:
c = 0;
break;
}
} else {
c = 0;
}
/* unrecognized key? consume until there's no event */
if (c == 0) {
while (shellT_waitForInput(0))
shellT_getch();
}
return c;
}
int shellT_kbget(void)
{
char c = shellT_getch();
return (c == KEY_ESCAPE) ? shellT_kbesc() : c;
}
void shellT_printPrompt(void)
{
clearLine();
shellT_printf("\r%s%.*s", prompt, cmdCount, (cmd ? cmd : ""));
if (cmdCount > cmdCursor)
cursorBackward(cmdCount - cmdCursor);
fflush(stdout);
}
void shellT_setPrompt(char *_prompt)
{
prompt = _prompt;
}
/* covers every non-controller related ascii characters (ignoring the extended character range) */
bool isAscii(int c)
{
return c >= ' ' && c <= '~';
}
void shellT_addChar(tShell_client *client, int c)
{
int i;
switch (c) {
case KEY_BACKSPACE:
if (cmdCursor > 0) {
laikaM_rmvarray(cmd, cmdCount, (cmdCursor - 1), 1);
cmdCursor--;
shellT_printPrompt();
}
break;
case KEY_LEFT:
if (cmdCursor > 0) {
cursorBackward(1);
--cmdCursor;
fflush(stdout);
}
break;
case KEY_RIGHT:
if (cmdCursor < cmdCount) {
cursorForward(1);
cmdCursor++;
fflush(stdout);
}
break;
case KEY_ENTER:
if (cmdCount > 0) {
cmd[cmdCount] = '\0';
cmdCount = 0;
cmdCursor = 0;
shellS_runCmd(client, cmd);
shellT_printPrompt();
}
break;
case KEY_UP:
case KEY_DOWN:
break; /* ignore these */
default:
/* we only want to accept valid input, just ignore non-ascii characters */
if (!isAscii(c))
return;
laikaM_growarray(char, cmd, 1, cmdCount, cmdCap);
laikaM_insertarray(cmd, cmdCount, cmdCursor, 1);
cmd[cmdCursor++] = c;
shellT_printPrompt();
}
}