mirror of https://github.com/bsnes-emu/bsnes.git
1021 lines
29 KiB
C
1021 lines
29 KiB
C
#include "console.h"
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
|
|
#define ESC(x) "\x1B" x
|
|
#define CSI(x) ESC("[" x)
|
|
#define SGR(x) CSI(x "m")
|
|
|
|
static bool initialized = false;
|
|
typedef struct listent_s listent_t;
|
|
|
|
struct listent_s {
|
|
listent_t *prev;
|
|
listent_t *next;
|
|
char content[];
|
|
};
|
|
|
|
typedef struct {
|
|
listent_t *first;
|
|
listent_t *last;
|
|
} fifo_t;
|
|
|
|
static fifo_t lines;
|
|
static fifo_t history;
|
|
|
|
static void remove_entry(fifo_t *fifo, listent_t *entry)
|
|
{
|
|
if (fifo->last == entry) {
|
|
fifo->last = entry->prev;
|
|
}
|
|
if (fifo->first == entry) {
|
|
fifo->first = entry->next;
|
|
}
|
|
if (entry->next) {
|
|
entry->next->prev = entry->prev;
|
|
}
|
|
if (entry->prev) {
|
|
entry->prev->next = entry->next;
|
|
}
|
|
free(entry);
|
|
}
|
|
|
|
static void add_entry(fifo_t *fifo, const char *content)
|
|
{
|
|
size_t length = strlen(content);
|
|
listent_t *entry = malloc(sizeof(*entry) + length + 1);
|
|
entry->next = NULL;
|
|
entry->prev = fifo->last;
|
|
memcpy(entry->content, content, length);
|
|
entry->content[length] = 0;
|
|
if (fifo->last) {
|
|
fifo->last->next = entry;
|
|
}
|
|
fifo->last = entry;
|
|
if (!fifo->first) {
|
|
fifo->first = entry;
|
|
}
|
|
}
|
|
|
|
static listent_t *reverse_find(listent_t *entry, const char *string, bool exact)
|
|
{
|
|
while (entry) {
|
|
if (exact && strcmp(entry->content, string) == 0) {
|
|
return entry;
|
|
}
|
|
if (!exact && strstr(entry->content, string)) {
|
|
return entry;
|
|
}
|
|
entry = entry->prev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool is_term(void)
|
|
{
|
|
if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) return false;
|
|
#ifdef _WIN32
|
|
if (AllocConsole()) {
|
|
FreeConsole();
|
|
return false;
|
|
}
|
|
|
|
unsigned long input_mode, output_mode;
|
|
|
|
GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode);
|
|
GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode);
|
|
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT);
|
|
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFO before = {0,};
|
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &before);
|
|
|
|
printf(SGR("0"));
|
|
|
|
CONSOLE_SCREEN_BUFFER_INFO after = {0,};
|
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &after);
|
|
|
|
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode);
|
|
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode);
|
|
|
|
|
|
if (before.dwCursorPosition.X != after.dwCursorPosition.X ||
|
|
before.dwCursorPosition.Y != after.dwCursorPosition.Y) {
|
|
printf("\r \r");
|
|
return false;
|
|
}
|
|
return true;
|
|
#else
|
|
return getenv("TERM");
|
|
#endif
|
|
}
|
|
|
|
static unsigned width, height;
|
|
|
|
static char raw_getc(void)
|
|
{
|
|
#ifdef _WIN32
|
|
char c;
|
|
unsigned long ret;
|
|
ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &ret, NULL);
|
|
#else
|
|
ssize_t ret;
|
|
char c;
|
|
|
|
do {
|
|
ret = read(STDIN_FILENO, &c, 1);
|
|
} while (ret == -1 && errno == EINTR);
|
|
#endif
|
|
return ret == 1? c : EOF;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#pragma clang diagnostic ignored "-Wmacro-redefined"
|
|
#include <Windows.h>
|
|
|
|
static void update_size(void)
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
|
|
width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
|
|
height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
|
|
}
|
|
|
|
static unsigned long input_mode, output_mode;
|
|
|
|
static void cleanup(void)
|
|
{
|
|
printf(CSI("!p")); // reset
|
|
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode);
|
|
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode);
|
|
fflush(stdout);
|
|
}
|
|
|
|
static bool initialize(void)
|
|
{
|
|
if (!is_term()) return false;
|
|
update_size();
|
|
if (width == 0 || height == 0) {
|
|
return false;
|
|
}
|
|
|
|
static bool once = false;
|
|
if (!once) {
|
|
atexit(cleanup);
|
|
GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode);
|
|
GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode);
|
|
once = true;
|
|
}
|
|
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT);
|
|
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
|
|
|
printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
|
|
|
|
fflush(stdout);
|
|
initialized = true;
|
|
return true;
|
|
}
|
|
#else
|
|
#include <sys/ioctl.h>
|
|
|
|
static void update_size(void)
|
|
{
|
|
struct winsize winsize;
|
|
ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize);
|
|
width = winsize.ws_col;
|
|
height = winsize.ws_row;
|
|
}
|
|
|
|
static void terminal_resized(int ignored)
|
|
{
|
|
update_size();
|
|
}
|
|
|
|
#include <termios.h>
|
|
static struct termios terminal;
|
|
|
|
|
|
static void cleanup(void)
|
|
{
|
|
printf(CSI("!p")); // reset
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal);
|
|
fflush(stdout);
|
|
}
|
|
|
|
static bool initialize(void)
|
|
{
|
|
if (!is_term()) return false;
|
|
update_size();
|
|
if (width == 0 || height == 0) {
|
|
return false;
|
|
}
|
|
|
|
static bool once = false;
|
|
if (!once) {
|
|
atexit(cleanup);
|
|
signal(SIGWINCH, terminal_resized);
|
|
tcgetattr(STDIN_FILENO, &terminal);
|
|
#ifdef _WIN32
|
|
_setmode(STDIN_FILENO, _O_TEXT);
|
|
#endif
|
|
once = true;
|
|
}
|
|
struct termios raw_terminal;
|
|
raw_terminal = terminal;
|
|
raw_terminal.c_lflag = 0;
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_terminal);
|
|
|
|
printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
|
|
|
|
fflush(stdout);
|
|
initialized = true;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static struct {
|
|
char *content;
|
|
size_t allocation_size;
|
|
size_t length;
|
|
size_t position;
|
|
size_t scroll;
|
|
bool reverse_search;
|
|
listent_t *search_line;
|
|
} line;
|
|
|
|
#define CTL(x) ((x) - 'A' + 1)
|
|
|
|
static const char *prompt = "";
|
|
static size_t prompt_length = 0;
|
|
static bool repeat_empty = false;
|
|
|
|
static bool redraw_prompt(bool force)
|
|
{
|
|
if (line.reverse_search) {
|
|
if (!force) return false;
|
|
if (line.length == 0) {
|
|
printf("\r" CSI("K") "%s" SGR("2") "Reverse Search..." SGR("0") CSI("%zuG"), prompt, prompt_length + 1);
|
|
return true;
|
|
}
|
|
if (!line.search_line) {
|
|
printf("\r" CSI("K") "%s" SGR("1") "%s" SGR("0"), prompt, line.content);
|
|
return true;
|
|
}
|
|
const char *loc = strstr(line.search_line->content, line.content);
|
|
printf("\r" CSI("K") "%s" "%.*s" SGR("1") "%s" SGR("0") "%s" CSI("%uG"),
|
|
prompt,
|
|
(int)(loc - line.search_line->content),
|
|
line.search_line->content,
|
|
line.content,
|
|
loc + line.length,
|
|
(unsigned)(loc - line.search_line->content + line.length + prompt_length + 1));
|
|
return true;
|
|
}
|
|
|
|
size_t max = width - 1 - prompt_length;
|
|
|
|
if (line.scroll && line.length <= max) {
|
|
line.scroll = 0;
|
|
force = true;
|
|
}
|
|
|
|
if (line.scroll > line.length - max) {
|
|
line.scroll = line.length - max;
|
|
force = true;
|
|
}
|
|
|
|
if (line.position < line.scroll + 1 && line.position) {
|
|
line.scroll = line.position - 1;
|
|
force = true;
|
|
}
|
|
|
|
if (line.position == 0 && line.scroll) {
|
|
line.scroll = 0;
|
|
force = true;
|
|
}
|
|
|
|
if (line.position > line.scroll + max) {
|
|
line.scroll = line.position - max;
|
|
force = true;
|
|
}
|
|
|
|
if (!force && line.length <= max) {
|
|
return false;
|
|
}
|
|
|
|
if (line.length <= max) {
|
|
printf("\r" CSI("K") "%s%s" CSI("%uG"), prompt, line.content, (unsigned)(line.position + prompt_length + 1));
|
|
return true;
|
|
}
|
|
|
|
size_t left = max;
|
|
const char *string = line.content + line.scroll;
|
|
printf("\r" CSI("K") "%s", prompt);
|
|
if (line.scroll) {
|
|
printf(SGR("2") "%c" SGR("0"), *string);
|
|
string++;
|
|
left--;
|
|
}
|
|
if (line.scroll + max == line.length) {
|
|
printf("%s", string);
|
|
}
|
|
else {
|
|
printf("%.*s", (int)(left - 1), string);
|
|
string += left;
|
|
left = 1;
|
|
printf(SGR("2") "%c" SGR("0"), *string);
|
|
}
|
|
printf(CSI("%uG"), (unsigned)(line.position - line.scroll + prompt_length + 1));
|
|
|
|
return true;
|
|
}
|
|
|
|
static void set_position(size_t position)
|
|
{
|
|
if (position > line.length) {
|
|
printf("\a");
|
|
return;
|
|
}
|
|
line.position = position;
|
|
if (!redraw_prompt(false)) {
|
|
printf(CSI("%uG"), (unsigned)(position + prompt_length + 1));
|
|
}
|
|
}
|
|
|
|
static void set_line(const char *content)
|
|
{
|
|
line.length = strlen(content);
|
|
if (line.length + 1 > line.allocation_size) {
|
|
line.content = realloc(line.content, line.length + 1);
|
|
line.allocation_size = line.length + 1;
|
|
}
|
|
else if (line.allocation_size > 256 && line.length < 128) {
|
|
line.content = realloc(line.content, line.length + 1);
|
|
line.allocation_size = line.length + 1;
|
|
}
|
|
line.position = line.length;
|
|
strcpy(line.content, content);
|
|
redraw_prompt(true);
|
|
}
|
|
|
|
static void insert(const char *string)
|
|
{
|
|
size_t insertion_length = strlen(string);
|
|
size_t new_length = insertion_length + line.length;
|
|
bool need_realloc = false;
|
|
while (line.allocation_size < new_length + 1) {
|
|
line.allocation_size *= 2;
|
|
need_realloc = true;
|
|
}
|
|
if (need_realloc) {
|
|
line.content = realloc(line.content, line.allocation_size);
|
|
}
|
|
memmove(line.content + line.position + insertion_length,
|
|
line.content + line.position,
|
|
line.length - line.position);
|
|
memcpy(line.content + line.position, string, insertion_length);
|
|
line.position += insertion_length;
|
|
line.content[new_length] = 0;
|
|
line.length = new_length;
|
|
if (!redraw_prompt(line.position != line.length)) {
|
|
printf("%s", string);
|
|
}
|
|
}
|
|
|
|
static void delete(size_t size, bool forward)
|
|
{
|
|
if (line.length < size) {
|
|
printf("\a");
|
|
return;
|
|
}
|
|
if (forward) {
|
|
if (line.position > line.length - size) {
|
|
printf("\a");
|
|
return;
|
|
}
|
|
else {
|
|
line.position += size;
|
|
}
|
|
}
|
|
else if (line.position < size) {
|
|
printf("\a");
|
|
return;
|
|
}
|
|
memmove(line.content + line.position - size,
|
|
line.content + line.position,
|
|
line.length - line.position);
|
|
line.length -= size;
|
|
line.content[line.length] = 0;
|
|
line.position -= size;
|
|
|
|
if (!redraw_prompt(line.position != line.length)) {
|
|
printf(CSI("%uG") CSI("K"),
|
|
(unsigned)(line.position + prompt_length + 1));
|
|
}
|
|
}
|
|
|
|
static void move_word(bool forward)
|
|
{
|
|
signed offset = forward? 1 : -1;
|
|
size_t end = forward? line.length : 0;
|
|
signed check_offset = forward? 0 : -1;
|
|
if (line.position == end) {
|
|
printf("\a");
|
|
return;
|
|
}
|
|
line.position += offset;
|
|
while (line.position != end && isalnum(line.content[line.position + check_offset])) {
|
|
line.position += offset;
|
|
}
|
|
if (!redraw_prompt(false)) {
|
|
printf(CSI("%uG"), (unsigned)(line.position + prompt_length + 1));
|
|
}
|
|
}
|
|
|
|
static void delete_word(bool forward)
|
|
{
|
|
size_t original_pos = line.position;
|
|
signed offset = forward? 1 : -1;
|
|
size_t end = forward? line.length : 0;
|
|
signed check_offset = forward? 0 : -1;
|
|
if (line.position == end) {
|
|
printf("\a");
|
|
return;
|
|
}
|
|
line.position += offset;
|
|
while (line.position != end && isalnum(line.content[line.position + check_offset])) {
|
|
line.position += offset;
|
|
}
|
|
if (forward) {
|
|
delete(line.position - original_pos, false);
|
|
}
|
|
else {
|
|
delete(original_pos - line.position, true);
|
|
}
|
|
}
|
|
|
|
#define MOD_ALT(x) (0x100 | x)
|
|
#define MOD_SHIFT(x) (0x200 | x)
|
|
#define MOD_CTRL(x) (0x400 | x)
|
|
#define MOD_SPECIAL(x) (0x800 | x)
|
|
|
|
static unsigned get_extended_key(void)
|
|
{
|
|
unsigned modifiers = 0;
|
|
char c = 0;
|
|
restart:
|
|
c = raw_getc();
|
|
if (c == 0x1B) {
|
|
modifiers = MOD_SHIFT(MOD_ALT(0));
|
|
goto restart;
|
|
}
|
|
else if (c != '[' && c != 'O') {
|
|
return MOD_ALT(c);
|
|
}
|
|
unsigned ret = 0;
|
|
while (true) {
|
|
c = raw_getc();
|
|
if (c >= '0' && c <= '9') {
|
|
ret = ret * 10 + c - '0';
|
|
}
|
|
else if (c == ';') {
|
|
if (ret == 1) {
|
|
modifiers |= MOD_ALT(0);
|
|
}
|
|
else if (ret == 2) {
|
|
modifiers |= MOD_SHIFT(0);
|
|
}
|
|
else if (ret == 5) {
|
|
modifiers |= MOD_CTRL(0);
|
|
}
|
|
ret = 0;
|
|
}
|
|
else if (c == '~') {
|
|
return MOD_SPECIAL(ret) | modifiers;
|
|
}
|
|
else {
|
|
if (ret == 1) {
|
|
modifiers |= MOD_ALT(0);
|
|
}
|
|
else if (ret == 2) {
|
|
modifiers |= MOD_SHIFT(0);
|
|
}
|
|
else if (ret == 5) {
|
|
modifiers |= MOD_CTRL(0);
|
|
}
|
|
return c | modifiers;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SWAP(x, y) do {typeof(*(x)) _tmp = *(x); *(x) = *(y);*(y) = _tmp;} while (0)
|
|
static pthread_mutex_t terminal_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_mutex_t lines_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t lines_cond = PTHREAD_COND_INITIALIZER;
|
|
|
|
static char reverse_search_mainloop(void)
|
|
{
|
|
while (true) {
|
|
char c = raw_getc();
|
|
pthread_mutex_lock(&terminal_lock);
|
|
|
|
switch (c) {
|
|
case CTL('C'):
|
|
line.search_line = NULL;
|
|
set_line("");
|
|
pthread_mutex_unlock(&terminal_lock);
|
|
return CTL('A');
|
|
case CTL('R'):
|
|
line.search_line = reverse_find(line.search_line? line.search_line->prev : history.last, line.content, false);
|
|
if (!line.search_line) {
|
|
printf("\a");
|
|
}
|
|
redraw_prompt(true);
|
|
break;
|
|
case CTL('W'):
|
|
delete_word(false);
|
|
redraw_prompt(true);
|
|
break;
|
|
#ifndef _WIN32
|
|
case CTL('Z'):
|
|
set_line("");
|
|
raise(SIGSTOP);
|
|
initialize(); // Reinitialize
|
|
redraw_prompt(true);
|
|
break;
|
|
#endif
|
|
case CTL('H'):
|
|
case 0x7F: // Backspace
|
|
delete(1, false);
|
|
redraw_prompt(true);
|
|
break;
|
|
default:
|
|
if (c >= ' ') {
|
|
char string[2] = {c, 0};
|
|
insert(string);
|
|
line.search_line = reverse_find(line.search_line?: history.last, line.content, false);
|
|
if (!line.search_line) {
|
|
printf("\a");
|
|
}
|
|
redraw_prompt(true);
|
|
}
|
|
else {
|
|
pthread_mutex_unlock(&terminal_lock);
|
|
return c;
|
|
}
|
|
break;
|
|
}
|
|
pthread_mutex_unlock(&terminal_lock);
|
|
fflush(stdout);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
static
|
|
#ifdef _WIN32
|
|
int __stdcall
|
|
#else
|
|
void *
|
|
#endif
|
|
mainloop(char *(*completer)(const char *substring, uintptr_t *context))
|
|
{
|
|
listent_t *history_line = NULL;
|
|
uintptr_t complete_context = 0;
|
|
size_t completion_length = 0;
|
|
while (true) {
|
|
char c;
|
|
if (line.reverse_search) {
|
|
c = reverse_search_mainloop();
|
|
line.reverse_search = false;
|
|
if (line.search_line) {
|
|
size_t pos = strstr(line.search_line->content, line.content) - line.search_line->content + line.length;
|
|
set_line(line.search_line->content);
|
|
line.search_line = NULL;
|
|
set_position(pos);
|
|
}
|
|
else {
|
|
redraw_prompt(true);
|
|
}
|
|
}
|
|
else {
|
|
c = raw_getc();
|
|
}
|
|
pthread_mutex_lock(&terminal_lock);
|
|
|
|
switch (c) {
|
|
case CTL('A'):
|
|
set_position(0);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case CTL('B'):
|
|
set_position(line.position - 1);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case CTL('C'):
|
|
if (line.length) {
|
|
set_line("");
|
|
history_line = NULL;
|
|
complete_context = completion_length = 0;
|
|
}
|
|
else {
|
|
#ifdef _WIN32
|
|
raise(SIGINT);
|
|
#else
|
|
kill(getpid(), SIGINT);
|
|
#endif
|
|
}
|
|
break;
|
|
case CTL('D'):
|
|
if (line.length) {
|
|
delete(1, true);
|
|
complete_context = completion_length = 0;
|
|
}
|
|
else {
|
|
pthread_mutex_lock(&lines_lock);
|
|
add_entry(&lines, CON_EOF);
|
|
pthread_cond_signal(&lines_cond);
|
|
pthread_mutex_unlock(&lines_lock);
|
|
}
|
|
break;
|
|
case CTL('E'):
|
|
set_position(line.length);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case CTL('F'):
|
|
set_position(line.position + 1);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case CTL('K'):
|
|
printf(CSI("K"));
|
|
if (!redraw_prompt(false)) {
|
|
line.length = line.position;
|
|
line.content[line.length] = 0;
|
|
}
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case CTL('R'):
|
|
complete_context = completion_length = 0;
|
|
line.reverse_search = true;
|
|
set_line("");
|
|
|
|
break;
|
|
case CTL('T'):
|
|
if (line.length < 2) {
|
|
printf("\a");
|
|
break;
|
|
}
|
|
if (line.position && line.position == line.length) {
|
|
line.position--;
|
|
}
|
|
if (line.position == 0) {
|
|
printf("\a");
|
|
break;
|
|
}
|
|
SWAP(line.content + line.position,
|
|
line.content + line.position - 1);
|
|
line.position++;
|
|
redraw_prompt(true);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case CTL('W'):
|
|
delete_word(false);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
#ifndef _WIN32
|
|
case CTL('Z'):
|
|
set_line("");
|
|
complete_context = completion_length = 0;
|
|
raise(SIGSTOP);
|
|
initialize(); // Reinitialize
|
|
break;
|
|
#endif
|
|
case '\r':
|
|
case '\n':
|
|
pthread_mutex_lock(&lines_lock);
|
|
if (line.length == 0 && repeat_empty && history.last) {
|
|
add_entry(&lines, history.last->content);
|
|
}
|
|
else {
|
|
add_entry(&lines, line.content);
|
|
}
|
|
pthread_cond_signal(&lines_cond);
|
|
pthread_mutex_unlock(&lines_lock);
|
|
if (line.length) {
|
|
listent_t *dup = reverse_find(history.last, line.content, true);
|
|
if (dup) {
|
|
remove_entry(&history, dup);
|
|
}
|
|
add_entry(&history, line.content);
|
|
set_line("");
|
|
history_line = NULL;
|
|
}
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case CTL('H'):
|
|
case 0x7F: // Backspace
|
|
delete(1, false);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case 0x1B:
|
|
switch (get_extended_key()) {
|
|
case MOD_SPECIAL(1): // Home
|
|
case MOD_SPECIAL(7):
|
|
case 'H':
|
|
set_position(0);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case MOD_SPECIAL(8): // End
|
|
case 'F':
|
|
set_position(line.length);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case MOD_SPECIAL(3): // Delete
|
|
delete(1, true);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case 'A': // Up
|
|
if (!history_line) {
|
|
history_line = history.last;
|
|
}
|
|
else {
|
|
history_line = history_line->prev;
|
|
}
|
|
if (history_line) {
|
|
set_line(history_line->content);
|
|
complete_context = completion_length = 0;
|
|
}
|
|
else {
|
|
history_line = history.first;
|
|
printf("\a");
|
|
}
|
|
|
|
break;
|
|
case 'B': // Down
|
|
if (!history_line) {
|
|
printf("\a");
|
|
break;
|
|
}
|
|
history_line = history_line->next;
|
|
if (history_line) {
|
|
set_line(history_line->content);
|
|
complete_context = completion_length = 0;
|
|
}
|
|
else {
|
|
set_line("");
|
|
complete_context = completion_length = 0;
|
|
}
|
|
break;
|
|
case 'C': // Right
|
|
set_position(line.position + 1);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case 'D': // Left
|
|
set_position(line.position - 1);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case MOD_ALT('b'):
|
|
case MOD_ALT('D'):
|
|
move_word(false);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case MOD_ALT('f'):
|
|
case MOD_ALT('C'):
|
|
move_word(true);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case MOD_ALT(0x7f): // ALT+Backspace
|
|
delete_word(false);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
case MOD_ALT('('): // ALT+Delete
|
|
delete_word(true);
|
|
complete_context = completion_length = 0;
|
|
break;
|
|
default:
|
|
printf("\a");
|
|
break;
|
|
}
|
|
break;
|
|
case '\t': {
|
|
char temp = line.content[line.position - completion_length];
|
|
line.content[line.position - completion_length] = 0;
|
|
char *completion = completer? completer(line.content, &complete_context) : NULL;
|
|
line.content[line.position - completion_length] = temp;
|
|
if (completion) {
|
|
if (completion_length) {
|
|
delete(completion_length, false);
|
|
}
|
|
insert(completion);
|
|
completion_length = strlen(completion);
|
|
free(completion);
|
|
}
|
|
else {
|
|
printf("\a");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
if (c >= ' ') {
|
|
char string[2] = {c, 0};
|
|
insert(string);
|
|
complete_context = completion_length = 0;
|
|
}
|
|
else {
|
|
printf("\a");
|
|
}
|
|
break;
|
|
}
|
|
fflush(stdout);
|
|
pthread_mutex_unlock(&terminal_lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
char *CON_readline(const char *new_prompt)
|
|
{
|
|
pthread_mutex_lock(&terminal_lock);
|
|
const char *old_prompt = prompt;
|
|
prompt = new_prompt;
|
|
prompt_length = strlen(prompt);
|
|
redraw_prompt(true);
|
|
fflush(stdout);
|
|
pthread_mutex_unlock(&terminal_lock);
|
|
|
|
pthread_mutex_lock(&lines_lock);
|
|
while (!lines.first) {
|
|
pthread_cond_wait(&lines_cond, &lines_lock);
|
|
}
|
|
char *ret = strdup(lines.first->content);
|
|
remove_entry(&lines, lines.first);
|
|
pthread_mutex_unlock(&lines_lock);
|
|
|
|
pthread_mutex_lock(&terminal_lock);
|
|
prompt = old_prompt;
|
|
prompt_length = strlen(prompt);
|
|
redraw_prompt(true);
|
|
fflush(stdout);
|
|
pthread_mutex_unlock(&terminal_lock);
|
|
return ret;
|
|
}
|
|
|
|
char *CON_readline_async(void)
|
|
{
|
|
char *ret = NULL;
|
|
pthread_mutex_lock(&lines_lock);
|
|
if (lines.first) {
|
|
ret = strdup(lines.first->content);
|
|
remove_entry(&lines, lines.first);
|
|
}
|
|
pthread_mutex_unlock(&lines_lock);
|
|
return ret;
|
|
}
|
|
|
|
bool CON_start(char *(*completer)(const char *substring, uintptr_t *context))
|
|
{
|
|
if (!initialize()) {
|
|
return false;
|
|
}
|
|
set_line("");
|
|
pthread_t thread;
|
|
return pthread_create(&thread, NULL, (void *)mainloop, completer) == 0;
|
|
}
|
|
|
|
void CON_attributed_print(const char *string, CON_attributes_t *attributes)
|
|
{
|
|
if (!initialized) {
|
|
printf("%s", string);
|
|
return;
|
|
}
|
|
static bool pending_newline = false;
|
|
pthread_mutex_lock(&terminal_lock);
|
|
printf(ESC("8"));
|
|
bool needs_reset = false;
|
|
if (attributes) {
|
|
if (attributes->color) {
|
|
if (attributes->color >= 0x10) {
|
|
printf(SGR("%d"), attributes->color - 0x11 + 90);
|
|
}
|
|
else {
|
|
printf(SGR("%d"), attributes->color - 1 + 30);
|
|
}
|
|
needs_reset = true;
|
|
}
|
|
if (attributes->background) {
|
|
if (attributes->background >= 0x10) {
|
|
printf(SGR("%d"), attributes->background - 0x11 + 100);
|
|
}
|
|
else {
|
|
printf(SGR("%d"), attributes->background - 1 + 40);
|
|
}
|
|
needs_reset = true;
|
|
}
|
|
if (attributes->bold) {
|
|
printf(SGR("1"));
|
|
needs_reset = true;
|
|
}
|
|
if (attributes->italic) {
|
|
printf(SGR("3"));
|
|
needs_reset = true;
|
|
}
|
|
if (attributes->underline) {
|
|
printf(SGR("4"));
|
|
needs_reset = true;
|
|
}
|
|
}
|
|
const char *it = string;
|
|
bool need_redraw_prompt = false;
|
|
while (*it) {
|
|
if (pending_newline) {
|
|
need_redraw_prompt = true;
|
|
printf("\n" CSI("K") "\n" CSI("A"));
|
|
pending_newline = false;
|
|
continue;
|
|
}
|
|
if (*it == '\n') {
|
|
printf("%.*s", (int)(it - string), string);
|
|
string = it + 1;
|
|
pending_newline = true;
|
|
}
|
|
it++;
|
|
}
|
|
if (*string) {
|
|
printf("%s", string);
|
|
}
|
|
if (needs_reset) {
|
|
printf(SGR("0"));
|
|
}
|
|
printf(ESC("7") CSI("B"));
|
|
if (need_redraw_prompt) {
|
|
redraw_prompt(true);
|
|
}
|
|
else {
|
|
set_position(line.position);
|
|
}
|
|
fflush(stdout);
|
|
pthread_mutex_unlock(&terminal_lock);
|
|
}
|
|
|
|
void CON_print(const char *string)
|
|
{
|
|
CON_attributed_print(string, NULL);
|
|
}
|
|
|
|
void CON_vprintf(const char *fmt, va_list args)
|
|
{
|
|
char *string = NULL;
|
|
vasprintf(&string, fmt, args);
|
|
CON_attributed_print(string, NULL);
|
|
free(string);
|
|
}
|
|
|
|
void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args)
|
|
{
|
|
char *string = NULL;
|
|
vasprintf(&string, fmt, args);
|
|
CON_attributed_print(string, attributes);
|
|
free(string);
|
|
}
|
|
|
|
void CON_printf(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
CON_vprintf(fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...)
|
|
{
|
|
va_list args;
|
|
va_start(args, attributes);
|
|
CON_attributed_vprintf(fmt, attributes, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void CON_set_async_prompt(const char *string)
|
|
{
|
|
pthread_mutex_lock(&terminal_lock);
|
|
prompt = string;
|
|
prompt_length = strlen(string);
|
|
redraw_prompt(true);
|
|
fflush(stdout);
|
|
pthread_mutex_unlock(&terminal_lock);
|
|
}
|
|
|
|
void CON_set_repeat_empty(bool repeat)
|
|
{
|
|
repeat_empty = repeat;
|
|
}
|