From c1450415eb6946c091c8a592e8e4b353ac1c19bc Mon Sep 17 00:00:00 2001 From: retoor Date: Thu, 29 Jan 2026 02:27:20 +0100 Subject: [PATCH] Update. --- src/line.h | 123 +++++++++++++++++ src/main.c | 6 +- src/markdown.h | 351 +++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 146 ++++++++++++++++++++ 4 files changed, 623 insertions(+), 3 deletions(-) create mode 100755 src/line.h create mode 100755 src/markdown.h create mode 100755 src/utils.h diff --git a/src/line.h b/src/line.h new file mode 100755 index 0000000..da22ee0 --- /dev/null +++ b/src/line.h @@ -0,0 +1,123 @@ +// Written by retoor@molodetz.nl + +// This source code provides command-line input functionalities with +// autocomplete and history features using the readline library. It allows users +// to complete commands and manage input history. + +// External includes: +// - +// - +// - + +// MIT License: Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction. + +#include "utils.h" +#include +#include +#include +#include +#include +#define HISTORY_FILE "~/.r_history" + +bool line_initialized = false; + +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", 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); +} + +void line_init() { + if (!line_initialized) { + rl_attempted_completion_function = line_command_completion; + line_initialized = true; + read_history(get_history_file()); + } +} + +char *line_read(char *prefix) { + char *data = readline(prefix); + if (!(data && *data)) { + free(data); + return NULL; + } + return data; +} + +void line_add_history(char *data) { + add_history(data); + write_history(get_history_file()); +} diff --git a/src/main.c b/src/main.c index 4ee2f04..f7fe984 100755 --- a/src/main.c +++ b/src/main.c @@ -7,9 +7,9 @@ #include "r_error.h" #include "tool.h" -#include "../line.h" -#include "../markdown.h" -#include "../utils.h" +#include "line.h" +#include "markdown.h" +#include "utils.h" #include #include diff --git a/src/markdown.h b/src/markdown.h new file mode 100755 index 0000000..46ec120 --- /dev/null +++ b/src/markdown.h @@ -0,0 +1,351 @@ +#include +#include +#include +#include + +// --- ANSI Escape Codes --- +#define RESET "\033[0m" +#define BOLD "\033[1m" +#define ITALIC "\033[3m" +#define STRIKETHROUGH "\033[9m" + +#define FG_YELLOW "\033[33m" +#define FG_BLUE "\033[34m" +#define FG_CYAN "\033[36m" +#define FG_MAGENTA "\033[35m" + +#define BG_YELLOW_FG_BLACK "\033[43;30m" + +/** + * @brief Checks if a given word is a programming language keyword. + * * @param word The word to check. + * @return int 1 if it's a keyword, 0 otherwise. + */ +int is_keyword(const char *word) { + // A comprehensive list of keywords from various popular languages. + const char *keywords[] = { + // C keywords + "int", "float", "double", "char", "void", "if", "else", "while", "for", + "return", "struct", "printf", + // Rust keywords + "let", "fn", "impl", "match", "enum", "trait", "use", "mod", "pub", + "const", "static", + // Python keywords + "def", "class", "import", "from", "as", "with", "try", "except", + "finally", "lambda", "async", "await", + // Java keywords + "public", "private", "protected", "class", "interface", "extends", + "implements", "new", "static", "final", "synchronized", + // JavaScript keywords + "var", "let", "const", "function", "async", "await", "if", "else", + "switch", "case", "break", "continue", "return", + // C++ keywords + "namespace", "template", "typename", "class", "public", "private", + "protected", "virtual", "override", "friend", "new", + // Go keywords + "package", "import", "func", "var", "const", "type", "interface", + "struct", "go", "defer", "select", + // Bash keywords + "if", "then", "else", "elif", "fi", "case", "esac", "for", "while", + "until", "do", "done", "function", + // C# keywords + "namespace", "using", "class", "interface", "public", "private", + "protected", "static", "void", "new", "override"}; + + for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) { + if (strcmp(word, keywords[i]) == 0) { + return 1; + } + } + return 0; +} + +/** + * @brief Applies basic syntax highlighting to a string of code. + * * @param code The code string to highlight. + */ +void highlight_code(const char *code) { + const char *ptr = code; + char buffer[4096]; + size_t index = 0; + + while (*ptr) { + // Highlight keywords + if (isalpha((unsigned char)*ptr) || *ptr == '_') { + while (isalnum((unsigned char)*ptr) || *ptr == '_') { + buffer[index++] = *ptr++; + } + buffer[index] = '\0'; + + if (is_keyword(buffer)) { + printf(FG_BLUE "%s" RESET FG_YELLOW, buffer); + } else { + printf("%s", buffer); + } + index = 0; + // Highlight numbers + } else if (isdigit((unsigned char)*ptr)) { + while (isdigit((unsigned char)*ptr)) { + buffer[index++] = *ptr++; + } + buffer[index] = '\0'; + printf(FG_CYAN "%s" RESET FG_YELLOW, buffer); + index = 0; + // Print other characters as-is + } else { + putchar(*ptr); + ptr++; + } + } +} + +/** + * @brief Parses a Markdown string and prints it to the console with ANSI color + * codes. + * + * This version supports a wide range of Markdown features, including: + * - Headers (H1-H6) + * - Bold (**, __) and Italic (*, _) text + * - Strikethrough (~~) and Highlight (==) + * - Blockquotes (>), Nested Ordered (1.) and Unordered lists (*, -, +) + * - Inline code (`) and full code blocks (```) with syntax highlighting + * - Links ([text](url)) and Horizontal rules (---, ***) + * * @param markdown The raw Markdown string to parse. + */ +void parse_markdown_to_ansi(const char *markdown) { + const char *ptr = markdown; + bool is_start_of_line = true; + + while (*ptr) { + // --- Code Blocks (```) --- + if (is_start_of_line && strncmp(ptr, "```", 3) == 0) { + ptr += 3; + while (*ptr && *ptr != '\n') + ptr++; + if (*ptr) + ptr++; + + const char *code_start = ptr; + const char *code_end = strstr(code_start, "```"); + + if (code_end) { + char block_buffer[code_end - code_start + 1]; + strncpy(block_buffer, code_start, code_end - code_start); + block_buffer[code_end - code_start] = '\0'; + + printf(FG_YELLOW); + highlight_code(block_buffer); + printf(RESET); + + ptr = code_end + 3; + if (*ptr == '\n') + ptr++; + is_start_of_line = true; + continue; + } else { + printf(FG_YELLOW); + highlight_code(code_start); + printf(RESET); + break; + } + } + + // --- Block-level Elements (checked at the start of a line) --- + if (is_start_of_line) { + const char *line_start_ptr = ptr; + int indent_level = 0; + while (*ptr == ' ') { + indent_level++; + ptr++; + } + + bool block_processed = true; + if (strncmp(ptr, "###### ", 7) == 0) { + printf(BOLD FG_YELLOW); + ptr += 7; + } else if (strncmp(ptr, "##### ", 6) == 0) { + printf(BOLD FG_YELLOW); + ptr += 6; + } else if (strncmp(ptr, "#### ", 5) == 0) { + printf(BOLD FG_YELLOW); + ptr += 5; + } else if (strncmp(ptr, "### ", 4) == 0) { + printf(BOLD FG_YELLOW); + ptr += 4; + } else if (strncmp(ptr, "## ", 3) == 0) { + printf(BOLD FG_YELLOW); + ptr += 3; + } else if (strncmp(ptr, "# ", 2) == 0) { + printf(BOLD FG_YELLOW); + ptr += 2; + } else if ((strncmp(ptr, "---", 3) == 0 || strncmp(ptr, "***", 3) == 0) && + (*(ptr + 3) == '\n' || *(ptr + 3) == '\0')) { + printf(FG_CYAN "───────────────────────────────────────────────────────" + "──────────" RESET "\n"); + ptr += 3; + if (*ptr == '\n') + ptr++; + is_start_of_line = true; + continue; + } else if (strncmp(ptr, "> ", 2) == 0) { + for (int i = 0; i < indent_level; i++) + putchar(' '); + printf(ITALIC FG_CYAN "▎ " RESET); + ptr += 2; + is_start_of_line = false; + continue; + } else if ((*ptr == '*' || *ptr == '-' || *ptr == '+') && + *(ptr + 1) == ' ') { + for (int i = 0; i < indent_level; i++) + putchar(' '); + printf(FG_MAGENTA "• " RESET); + ptr += 2; + is_start_of_line = false; + continue; + } else { + const char *temp_ptr = ptr; + while (isdigit((unsigned char)*temp_ptr)) + temp_ptr++; + if (temp_ptr > ptr && *temp_ptr == '.' && *(temp_ptr + 1) == ' ') { + for (int i = 0; i < indent_level; i++) + putchar(' '); + printf(FG_MAGENTA); + fwrite(ptr, 1, (temp_ptr - ptr) + 1, stdout); + printf(" " RESET); + ptr = temp_ptr + 2; + is_start_of_line = false; + continue; + } else { + block_processed = false; + ptr = line_start_ptr; + } + } + if (block_processed) { + while (*ptr && *ptr != '\n') + putchar(*ptr++); + printf(RESET "\n"); + if (*ptr == '\n') + ptr++; + is_start_of_line = true; + continue; + } + } + + // --- Inline Elements (order is important) --- + if (strncmp(ptr, "***", 3) == 0 || strncmp(ptr, "___", 3) == 0) { + const char *marker = strncmp(ptr, "***", 3) == 0 ? "***" : "___"; + printf(BOLD ITALIC); + ptr += 3; + const char *end = strstr(ptr, marker); + if (end) { + fwrite(ptr, 1, end - ptr, stdout); + ptr = end + 3; + } else { + fputs(ptr, stdout); + ptr += strlen(ptr); + } + printf(RESET); + continue; + } + if (strncmp(ptr, "**", 2) == 0 || strncmp(ptr, "__", 2) == 0) { + const char *marker = strncmp(ptr, "**", 2) == 0 ? "**" : "__"; + printf(BOLD); + ptr += 2; + const char *end = strstr(ptr, marker); + if (end) { + fwrite(ptr, 1, end - ptr, stdout); + ptr = end + 2; + } else { + fputs(ptr, stdout); + ptr += strlen(ptr); + } + printf(RESET); + continue; + } + if ((*ptr == '*' || *ptr == '_') && !isspace(*(ptr - 1)) && + !isspace(*(ptr + 1))) { + char marker = *ptr; + printf(ITALIC); + ptr++; + const char *start = ptr; + while (*ptr && *ptr != marker) + ptr++; + if (*ptr == marker) { + fwrite(start, 1, ptr - start, stdout); + ptr++; + } else { + putchar(marker); + ptr = start; + } + printf(RESET); + continue; + } + if (strncmp(ptr, "~~", 2) == 0) { + printf(STRIKETHROUGH); + ptr += 2; + const char *end = strstr(ptr, "~~"); + if (end) { + fwrite(ptr, 1, end - ptr, stdout); + ptr = end + 2; + } else { + fputs(ptr, stdout); + ptr += strlen(ptr); + } + printf(RESET); + continue; + } + if (strncmp(ptr, "==", 2) == 0) { + printf(BG_YELLOW_FG_BLACK); + ptr += 2; + const char *end = strstr(ptr, "=="); + if (end) { + fwrite(ptr, 1, end - ptr, stdout); + ptr = end + 2; + } else { + fputs(ptr, stdout); + ptr += strlen(ptr); + } + printf(RESET); + continue; + } + if (*ptr == '`' && *(ptr + 1) != '`') { + printf(FG_YELLOW); + ptr++; + const char *start = ptr; + while (*ptr && *ptr != '`') + ptr++; + fwrite(start, 1, ptr - start, stdout); + if (*ptr == '`') + ptr++; + printf(RESET); + continue; + } + if (*ptr == '[') { + const char *text_start = ptr + 1; + const char *text_end = strchr(text_start, ']'); + if (text_end && *(text_end + 1) == '(') { + const char *url_start = text_end + 2; + const char *url_end = strchr(url_start, ')'); + if (url_end) { + printf(FG_BLUE); + fwrite(text_start, 1, text_end - text_start, stdout); + printf(RESET " ("); + printf(ITALIC FG_CYAN); + fwrite(url_start, 1, url_end - url_start, stdout); + printf(RESET ")"); + ptr = url_end + 1; + continue; + } + } + } + + // --- Default Character --- + if (*ptr == '\n') { + is_start_of_line = true; + } else if (!isspace((unsigned char)*ptr)) { + is_start_of_line = false; + } + putchar(*ptr); + ptr++; + } +} diff --git a/src/utils.h b/src/utils.h new file mode 100755 index 0000000..d4fa2e4 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,146 @@ +// Written by retoor@molodetz.nl + +// This header file contains utility functions for manipulating file system +// paths, focusing primarily on expanding paths starting with '~' to the home +// directory. + +// This code uses standard libraries: stdio.h, stdlib.h, and string.h, and +// conditionally includes the posix libraries pwd.h and unistd.h when expanding +// the home directory manually. + +// MIT License +// +// Permission is granted to use, copy, modify, merge, distribute, sublicense, +// and/or sell copies of the Software. The license includes conditions about +// providing a copy of the license and the limitation of liability and warranty. + +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +void get_current_directory() { + char buffer[PATH_MAX]; + +#ifdef _WIN32 + DWORD length = GetCurrentDirectory(PATH_MAX, buffer); + if (length > 0 && length < PATH_MAX) { + printf("Current Directory: %s\n", buffer); + } else { + printf("Error getting current directory.\n"); + } +#else + if (getcwd(buffer, sizeof(buffer)) != NULL) { + printf("Current Directory: %s\n", buffer); + } else { + perror("Error getting current directory"); + } +#endif +} + +char *expand_home_directory(const char *path) { + if (path[0] != '~') { + return strdup(path); // Return the original path if it doesn't start with ~ + } + + char *home_dir; + +#ifdef _WIN32 + home_dir = getenv("USERPROFILE"); // Get home directory on Windows +#else + struct passwd *pw = getpwuid(getuid()); + home_dir = pw->pw_dir; // Get home directory on Linux +#endif + + if (home_dir == NULL) { + return NULL; // Error getting home directory + } + + size_t home_len = strlen(home_dir); + size_t path_len = strlen(path + 1); + size_t expanded_size = home_len + path_len + 1; + char *expanded_path = (char *)malloc(expanded_size); + if (expanded_path == NULL) { + return NULL; + } + + memcpy(expanded_path, home_dir, home_len); + memcpy(expanded_path + home_len, path + 1, path_len); + expanded_path[home_len + path_len] = '\0'; + return expanded_path; +} + +unsigned long hash(const char *str) { + unsigned long hash = 5381; // Starting value + int c; + + while ((c = *str++)) { + hash = ((hash << 5) + hash) + c; // hash * 33 + c + } + + return hash; +} + +char *joinpath(const char *base_url, const char *path) { + static char result[4096]; + size_t base_len = strlen(base_url); + size_t pos = 0; + + if (base_len >= sizeof(result) - 2) { + base_len = sizeof(result) - 2; + } + memcpy(result, base_url, base_len); + pos = base_len; + + if (pos > 0 && result[pos - 1] != '/') { + result[pos++] = '/'; + } + if (path[0] == '/') { + path++; + } + + size_t path_len = strlen(path); + if (pos + path_len >= sizeof(result)) { + path_len = sizeof(result) - pos - 1; + } + memcpy(result + pos, path, path_len); + result[pos + path_len] = '\0'; + return result; +} + +char *read_file(const char *path) { + char *expanded_path = expand_home_directory(path); + if (expanded_path == NULL) { + return NULL; + } + FILE *file = fopen(expanded_path, "r"); + free(expanded_path); + if (file == NULL) { + return NULL; + } + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + + char *buffer = (char *)malloc(size + 1); + size_t read = fread(buffer, 1, size, file); + if (read == 0) { + free(buffer); + return NULL; + } + + fclose(file); + buffer[read] = '\0'; + return buffer; +} +#endif