257 lines
7.1 KiB
C
Raw Normal View History

// retoor <retoor@molodetz.nl>
#include "utils.h"
#include <glob.h>
#include <readline/history.h>
#include <readline/readline.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define HISTORY_FILE "~/.r_history"
#define LINE_BUFFER_INITIAL 4096
static bool line_initialized = false;
static int line_continuation_requested = 0;
char *get_history_file() {
static char result[4096];
result[0] = '\0';
char *expanded = expand_home_directory(HISTORY_FILE);
if (expanded == NULL) {
return result;
}
strncpy(result, expanded, sizeof(result) - 1);
result[sizeof(result) - 1] = '\0';
free(expanded);
return result;
}
char *line_command_generator(const char *text, int state) {
static int list_index, len = 0;
const char *commands[] = {"help", "exit", "list", "review",
"refactor", "obfuscate", "!verbose", "!dump",
"!model", "!debug", "!vi", "!emacs",
"!new", "!clear", "!session", NULL};
if (!state) {
list_index = 0;
len = strlen(text);
}
while (commands[list_index]) {
const char *command = commands[list_index++];
if (strncmp(command, text, len) == 0) {
return strdup(command);
}
}
return NULL;
}
char *line_file_generator(const char *text, int state) {
static int list_index;
static glob_t glob_result;
static int glob_valid = 0;
char pattern[1024];
if (!state) {
if (glob_valid) {
globfree(&glob_result);
glob_valid = 0;
}
list_index = 0;
snprintf(pattern, sizeof(pattern), "%s*", text);
if (glob(pattern, GLOB_NOSORT, NULL, &glob_result) == 0) {
glob_valid = 1;
} else {
return NULL;
}
}
if (glob_valid && (size_t)list_index < glob_result.gl_pathc) {
return strdup(glob_result.gl_pathv[list_index++]);
}
if (glob_valid) {
globfree(&glob_result);
glob_valid = 0;
}
return NULL;
}
char **line_command_completion(const char *text, int start, int end) {
rl_attempted_completion_over = 1;
// Check if the input is a file path
if (start > 0 && text[0] != ' ') {
return rl_completion_matches(text, line_file_generator);
}
return rl_completion_matches(text, line_command_generator);
}
static int line_toggle_editing_mode(int count, int key) {
(void)count;
(void)key;
if (rl_editing_mode == 1) {
rl_variable_bind("editing-mode", "vi");
} else {
rl_variable_bind("editing-mode", "emacs");
}
rl_set_keymap_from_edit_mode();
return 0;
}
static int line_request_continuation(int count, int key) {
(void)count;
(void)key;
line_continuation_requested = 1;
rl_crlf();
rl_done = 1;
return 0;
}
const char *line_build_prompt(void) {
if (rl_editing_mode == 0) {
return "\001\033[38;5;208m\002>\001\033[0m\002 ";
}
return "> ";
}
static const char *line_build_numbered_prompt(int line_number) {
static char prompt[64];
if (rl_editing_mode == 0) {
snprintf(prompt, sizeof(prompt),
"\001\033[38;5;208m\002%d>\001\033[0m\002 ", line_number);
} else {
snprintf(prompt, sizeof(prompt), "%d> ", line_number);
}
return prompt;
}
void line_init() {
if (!line_initialized) {
rl_attempted_completion_function = line_command_completion;
rl_variable_bind("editing-mode", "vi");
rl_variable_bind("enable-bracketed-paste", "on");
rl_add_defun("toggle-editing-mode", line_toggle_editing_mode, -1);
rl_bind_key_in_map(CTRL('T'), line_toggle_editing_mode,
vi_insertion_keymap);
rl_bind_key_in_map(CTRL('T'), line_toggle_editing_mode,
vi_movement_keymap);
rl_bind_key_in_map(CTRL('T'), line_toggle_editing_mode,
emacs_standard_keymap);
rl_add_defun("request-continuation", line_request_continuation, -1);
rl_bind_key_in_map(CTRL('J'), line_request_continuation,
vi_insertion_keymap);
rl_bind_key_in_map(CTRL('J'), line_request_continuation,
vi_movement_keymap);
rl_bind_key_in_map(CTRL('J'), line_request_continuation,
emacs_standard_keymap);
rl_bind_keyseq_in_map("\e\r", line_request_continuation,
vi_insertion_keymap);
rl_bind_keyseq_in_map("\e\r", line_request_continuation,
vi_movement_keymap);
rl_bind_keyseq_in_map("\e\r", line_request_continuation,
emacs_standard_keymap);
rl_bind_keyseq_in_map("\e\n", line_request_continuation,
vi_insertion_keymap);
rl_bind_keyseq_in_map("\e\n", line_request_continuation,
vi_movement_keymap);
rl_bind_keyseq_in_map("\e\n", line_request_continuation,
emacs_standard_keymap);
rl_bind_keyseq_in_map("\033[13;2u", line_request_continuation,
vi_insertion_keymap);
rl_bind_keyseq_in_map("\033[13;2u", line_request_continuation,
vi_movement_keymap);
rl_bind_keyseq_in_map("\033[13;2u", line_request_continuation,
emacs_standard_keymap);
rl_bind_keyseq_in_map("\033[13;5u", line_request_continuation,
vi_insertion_keymap);
rl_bind_keyseq_in_map("\033[13;5u", line_request_continuation,
vi_movement_keymap);
rl_bind_keyseq_in_map("\033[13;5u", line_request_continuation,
emacs_standard_keymap);
rl_bind_keyseq_in_map("\033[27;5;13~", line_request_continuation,
vi_insertion_keymap);
rl_bind_keyseq_in_map("\033[27;5;13~", line_request_continuation,
vi_movement_keymap);
rl_bind_keyseq_in_map("\033[27;5;13~", line_request_continuation,
emacs_standard_keymap);
line_initialized = true;
read_history(get_history_file());
}
}
char *line_read(const char *prompt) {
size_t capacity = LINE_BUFFER_INITIAL;
size_t length = 0;
char *buffer = (char *)malloc(capacity);
if (!buffer) {
return NULL;
}
buffer[0] = '\0';
const char *active_prompt = prompt;
int line_number = 1;
while (1) {
line_continuation_requested = 0;
char *segment = readline(active_prompt);
if (!segment) {
if (length > 0) {
break;
}
free(buffer);
return NULL;
}
size_t seg_len = strlen(segment);
size_t needed = length + seg_len + 2;
if (needed > capacity) {
capacity = needed * 2;
char *grown = (char *)realloc(buffer, capacity);
if (!grown) {
free(segment);
free(buffer);
return NULL;
}
buffer = grown;
}
if (length > 0) {
buffer[length++] = '\n';
}
memcpy(buffer + length, segment, seg_len);
length += seg_len;
buffer[length] = '\0';
free(segment);
segment = NULL;
if (!line_continuation_requested) {
break;
}
line_number++;
active_prompt = line_build_numbered_prompt(line_number);
}
if (length == 0) {
free(buffer);
return NULL;
}
return buffer;
}
void line_add_history(char *data) {
add_history(data);
write_history(get_history_file());
}