2026-02-14 08:07:05 +01:00
|
|
|
// retoor <retoor@molodetz.nl>
|
2026-01-29 02:27:20 +01:00
|
|
|
|
|
|
|
|
#include "utils.h"
|
|
|
|
|
#include <glob.h>
|
|
|
|
|
#include <readline/history.h>
|
|
|
|
|
#include <readline/readline.h>
|
|
|
|
|
#include <stdbool.h>
|
2026-02-14 08:07:05 +01:00
|
|
|
#include <stdlib.h>
|
2026-01-29 02:27:20 +01:00
|
|
|
#include <string.h>
|
|
|
|
|
#define HISTORY_FILE "~/.r_history"
|
2026-02-14 08:07:05 +01:00
|
|
|
#define LINE_BUFFER_INITIAL 4096
|
2026-01-29 02:27:20 +01:00
|
|
|
|
2026-02-14 08:07:05 +01:00
|
|
|
static bool line_initialized = false;
|
|
|
|
|
static int line_continuation_requested = 0;
|
2026-01-29 02:27:20 +01:00
|
|
|
|
|
|
|
|
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",
|
2026-02-14 08:07:05 +01:00
|
|
|
"!model", "!debug", "!vi", "!emacs",
|
|
|
|
|
"!new", "!clear", "!session", NULL};
|
2026-01-29 02:27:20 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 08:07:05 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 02:27:20 +01:00
|
|
|
void line_init() {
|
|
|
|
|
if (!line_initialized) {
|
|
|
|
|
rl_attempted_completion_function = line_command_completion;
|
2026-02-14 08:07:05 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2026-01-29 02:27:20 +01:00
|
|
|
line_initialized = true;
|
|
|
|
|
read_history(get_history_file());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 08:07:05 +01:00
|
|
|
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);
|
2026-01-29 02:27:20 +01:00
|
|
|
return NULL;
|
|
|
|
|
}
|
2026-02-14 08:07:05 +01:00
|
|
|
return buffer;
|
2026-01-29 02:27:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void line_add_history(char *data) {
|
|
|
|
|
add_history(data);
|
|
|
|
|
write_history(get_history_file());
|
|
|
|
|
}
|