// retoor #include "utils.h" #include #include #include #include #include #include #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()); }