#include "filecomplete.h" #include #include #include #include #include #include #include #ifdef USE_BOOST #include namespace fs = boost::filesystem; #else #include namespace fs = std::filesystem; #endif #ifdef _MSC_VER #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #include #include #include #define MAX_OF(x, y) (((x) > (y)) ? (x) : (y)) #if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN64) #ifndef OS_WINDOWS #define OS_WINDOWS #include #include #include #endif #elif defined(__APPLE__) || defined(__unix__) || defined(__unix) || defined(unix) || defined(__linux__) #ifndef OS_UNIX #define OS_UNIX #include #include #include #include #endif #else #error Unknown environment! #endif #if defined(OS_WINDOWS) #define ENTER 13 #define BACKSPACE 8 #define LEFT 75 #define RIGHT 77 #define UP 72 #define DOWN 80 #define DEL 83 #define CTRL_C 3 #define SPECIAL_SEQ_1 0 #define SPECIAL_SEQ_2 224 #define COLOR_TYPE uint16_t #define DEFAULT_TITLE_COLOR 10 #define DEFAULT_PREDICT_COLOR 8 #define DEFAULT_MAIN_COLOR 7 #elif defined(OS_UNIX) #ifdef IOS_ISH #define ENTER 13 #else #define ENTER 10 #endif #define CTRL_C 3 #define BACKSPACE 127 #define LEFT 68 #define RIGHT 67 #define UP 65 #define DOWN 66 #define DEL 51 #define DEL_AFTER 126 #define SPECIAL_SEQ_1 27 #define SPECIAL_SEQ_2 91 #define COLOR_TYPE char* #define DEFAULT_TITLE_COLOR "33" #define DEFAULT_PREDICT_COLOR "90" #define DEFAULT_MAIN_COLOR "0" #endif #define SPACE 32 #define TAB 9 constexpr int buf_size = 1024 * 100; static std::vector> buf{}; static std::vector word{}; static size_t wo{}; static size_t len{}; static char* main_buf{}; static std::mutex mut; static std::string header{}; static size_t header_len{}; static size_t unix_signal{}; static bool need_turn{true}; void trans2buf(char* buffer); void clear_line(); void set_cursor_x(short x); short get_cursor_y(); char* fc_readline(); void color_print(const char* text, const COLOR_TYPE color); void add_newer(const std::vector& wch); void fc_enable_cur(); void recovery_terminal_color(); static std::vector cur_work_content; static std::vector deadline_vch; static std::string str_predict; static std::map> path_cache; static std::vector history; static size_t his_pos{}; static std::string home_path{}; // 获取终端屏幕的行数和列数 // bool get_terminal_size(int& rows, int& cols) // { // #ifdef _WIN32 // #else // FILE* pipe = popen("stty size", "r"); // 执行 stty size 命令 // if (!pipe) { // return false; // } // char buffer[128]; // if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { // // 解析输出,格式为 "行数 列数" // if (sscanf(buffer, "%d %d", &rows, &cols) == 2) { // pclose(pipe); // return true; // } // } // pclose(pipe); // return false; // #endif // } void fc_turn_on() { need_turn = true; } void fc_turn_off() { need_turn = false; } void fc_recovery_color() { recovery_terminal_color(); } std::string get_home() { #if defined(_WIN32) char* value = nullptr; std::size_t len = 0; // _dupenv_s() 在 Visual Studio 2008 的 CRT (msvcr90) 中引入的,似乎没有进入系统 CRT (msvcrt)。 // mingw-w64 GCC 通常默认只链接到系统 CRT,所以找不到这个符号。 #if defined(__MINGW32__) || defined(__MINGW64__) char* homedir = getenv("USERPROFILE"); if (homedir) { return std::string(homedir); } return ""; #else auto err = _dupenv_s(&value, &len, "USERPROFILE"); if (err == 0 && value != nullptr) { std::string ret(value); free(value); return ret; } else { return ""; } #endif #else char* homedir = getenv("HOME"); if (homedir) { return std::string(homedir); } return ""; #endif } void recovery_terminal_color() { #if defined(OS_WINDOWS) HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE); if (h_console == NULL) { fprintf(stderr, "[ERROR] Couldn't handle terminal\n"); exit(1); } if (SetConsoleTextAttribute(h_console, DEFAULT_MAIN_COLOR) == 0) { fprintf(stderr, "[ERROR] Couldn't set terminal color\n"); exit(1); } #else fc_lock_print(); printf("\033[0m"); fc_enable_cur(); fc_unlock_print(); #endif } void append_his(const std::string& his) { history.push_back(his); his_pos = history.size(); } void fc_add_his(const char* his_input) { history.push_back(his_input); his_pos = history.size(); } std::string up_his() { if (history.size() < 1) { return ""; } if (his_pos >= 1) { --his_pos; return history[his_pos]; } else { return history[0]; } } std::string next_his() { if (history.size() < 1) { return ""; } if (his_pos < (history.size() - 1)) { ++his_pos; return history[his_pos]; } else { his_pos = history.size(); return ""; } } void flush_content(const std::string& search_dir, std::vector& out) { out.clear(); bool have_rep = false; auto ts = search_dir; if (search_dir.find("~") == 0) { ts = home_path + search_dir.substr(1, search_dir.size() - 1); have_rep = true; } fs::path work_path(ts); try { if (!fs::exists(work_path)) { return; } } catch (const std::exception& e) { return; } if (path_cache.count(ts)) { out = path_cache[ts]; } else { for (const auto& entry : fs::directory_iterator(work_path)) { auto fstrin = entry.path().lexically_normal().string(); if (have_rep) { fstrin = "~" + fstrin.substr(home_path.size(), fstrin.size() - home_path.size()); } out.push_back(fstrin); } path_cache[ts] = out; } } std::string file_predict(const char* data) { std::string result; std::string cur(data); std::string not_key(" "); std::string search_key; for (const auto& item : deadline_vch) { not_key.push_back(item); } auto fk = cur.find_last_of(not_key); if (fk != std::string::npos) { search_key = cur.substr(fk + 1, cur.size() - fk); } else { search_key = cur; } if (search_key.find("/") == std::string::npos && search_key.find("\\") == std::string::npos) { for (const auto& item : cur_work_content) { if (item != search_key && item.find(search_key) == 0) { return item.substr(search_key.size(), item.size() - search_key.size()); } } } std::string bk_search_path = search_key.substr(0, search_key.find_last_of("\\/") + 1); std::vector sr; flush_content(bk_search_path, sr); for (const auto& item : sr) { if (item != search_key && item.find(search_key) == 0) { return item.substr(search_key.size(), item.size() - search_key.size()); } } return result; } #if defined(OS_UNIX) int _getch() { int character; struct termios old_attr, new_attr; // Backup terminal attributes if (tcgetattr(STDIN_FILENO, &old_attr) == -1) { fprintf(stderr, "[ERROR] Couldn't get terminal attributes\n"); exit(1); } // Disable echo new_attr = old_attr; new_attr.c_lflag &= ~(ICANON | ECHO | ISIG); if (tcsetattr(STDIN_FILENO, TCSANOW, &new_attr) == -1) { fprintf(stderr, "[ERROR] Couldn't set terminal attributes\n"); exit(1); } // Get input character character = getchar(); // Restore terminal attributes if (tcsetattr(STDIN_FILENO, TCSANOW, &old_attr) == -1) { fprintf(stderr, "[ERROR] Couldn't reset terminal attributes\n"); exit(1); } return character; } #endif std::pair get_wh() { #if defined(OS_WINDOWS) HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE); if (h_console == NULL) { fprintf(stderr, "[ERROR] Couldn't handle terminal\n"); exit(1); } CONSOLE_SCREEN_BUFFER_INFO console_info; if (GetConsoleScreenBufferInfo(h_console, &console_info) == 0) { fprintf(stderr, "[ERROR] Couldn't get terminal info\n"); exit(1); } int cw = console_info.srWindow.Right - console_info.srWindow.Left + 1; int ch = console_info.srWindow.Bottom - console_info.srWindow.Top + 1; auto w = static_cast(cw); auto h = static_cast(ch); return std::make_pair(w, h); #elif defined(OS_UNIX) return std::make_pair(12, 12); #endif } size_t get_u8_len(unsigned char ch) { if (ch <= 0x7F) { return 1; } else if ((ch & 0xE0) == 0xC0) { return 2; } else if ((ch & 0xF0) == 0xE0) { return 3; } else if ((ch & 0xF8) == 0xF0) { return 4; } else if ((ch & 0xFC) == 0xF8) { return 5; } else if ((ch & 0xFE) == 0xFC) { return 6; } else { printf("invalid u8 first ch."); exit(1); } return 0; } std::vector> str_to_vec(const std::string& source) { std::vector> result; for (size_t i = 0; i < source.size();) { std::vector b; char curch = source[i]; b.push_back(curch); if (curch >= 0) { result.push_back(b); ++i; } else { #if defined(BINARY_GBK) size_t length = 2; #else size_t length = get_u8_len(curch); #endif for (size_t z = 1; z < length; ++z) { if ((i + z) < source.size()) { b.push_back(source[i + z]); } } result.push_back(b); i += length; } } return result; } void supply(std::vector& wch, char ch) { #if defined(BINARY_GBK) wch.push_back(ch); if (ch >= 0 && ch < 128) { return; } auto tch = _getch(); wch.push_back(tch); #else wch.push_back(ch); int length = get_u8_len(static_cast(ch)); if (length == 0) { printf("Invalid Charactor!\n"); exit(1); } for (int i = 1; i < length; ++i) { auto tch = _getch(); wch.push_back(tch); } #endif } void clear_line() { #if defined(OS_WINDOWS) // Get current terminal width short width = get_wh().first; if (width < 1) { fprintf(stderr, "[ERROR] Size of terminal is too small\n"); exit(1); } // Create long empty string char* empty = (char*)malloc(sizeof(char) * width); if (empty) { memset(empty, ' ', width); empty[width - 1] = '\0'; } HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO cursorInfo; GetConsoleCursorInfo(hConsole, &cursorInfo); cursorInfo.bVisible = FALSE; SetConsoleCursorInfo(hConsole, &cursorInfo); // Clear line printf("\r%s\r", empty); // Free line free(empty); #elif defined(OS_UNIX) printf("\033[2K\r"); #endif } void set_cursor_x(short x) { fc_lock_print(); #if defined(OS_WINDOWS) HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE); if (h_console == NULL) { fprintf(stderr, "[ERROR] Couldn't handle terminal\n"); exit(1); } auto wh = get_wh(); // Create position COORD xy; xy.Y = get_cursor_y(); short px = x % wh.first - 1; xy.X = px < 0 ? 0 : px; // Set cursor position if (SetConsoleCursorPosition(h_console, xy) == 0) { auto code = GetLastError(); fprintf(stderr, "[ERROR] Couldn't set terminal cursor position, err=%lu\n", code); exit(1); } #elif defined(OS_UNIX) printf("\033[%d;%dH", get_cursor_y(), x); fc_enable_cur(); fflush(stdout); #endif fc_unlock_print(); } short get_cursor_y() { #if defined(OS_WINDOWS) HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE); if (h_console == NULL) { fprintf(stderr, "[ERROR] Couldn't handle terminal\n"); exit(1); } // Get terminal info CONSOLE_SCREEN_BUFFER_INFO console_info; if (GetConsoleScreenBufferInfo(h_console, &console_info) == 0) { fprintf(stderr, "[ERROR] Couldn't get terminal Y position\n"); exit(1); } // Return Y position return console_info.dwCursorPosition.Y; #elif defined(OS_UNIX) struct termios old_attr, new_attr; char ch, buf[30] = {0}; int i = 0, pow = 1, y = 0; // Backup terminal attributes if (tcgetattr(STDIN_FILENO, &new_attr) == -1) { fprintf(stderr, "[ERROR] Couldn't get terminal attributes\n"); exit(1); } // Disable echo old_attr = new_attr; old_attr.c_lflag &= ~(ICANON | ECHO); if (tcsetattr(STDIN_FILENO, TCSANOW, &old_attr) == -1) { fprintf(stderr, "[ERROR] Couldn't set terminal attributes\n"); exit(1); } // Get info about cursor if (write(STDOUT_FILENO, "\033[6n", 4) != 4) { fprintf(stderr, "[ERROR] Couldn't get cursor information\n"); exit(1); } // Get ^[[{this};1R value for (ch = 0; ch != 'R'; i++) { if (read(STDIN_FILENO, &ch, 1) != 1) { fprintf(stderr, "[ERROR] Couldn't read cursor information"); exit(1); } buf[i] = ch; } // Reset attributes if (tcsetattr(0, TCSANOW, &new_attr) == -1) { fprintf(stderr, "[ERROR] Couldn't reset terminal attributes\n"); exit(1); } std::regex rg("\033\\[(\\d*);(\\d+)R"); std::smatch match; std::string input(buf); // 使用正则表达式匹配输入 int mx{}, my{}; if (std::regex_search(input, match, rg)) { mx = match[1].str().empty() ? 1 : std::stoi(match[1].str()); // 行号,默认为 1 my = std::stoi(match[2].str()); // 列号 } return mx; #endif } void fc_append(char deadline_ch) { deadline_vch.push_back(deadline_ch); } void add_newer(const std::vector& wch) { if (wo < buf.size()) { if (len >= buf.size()) { buf.resize(buf.size() * 2); } if (len > 0) { for (int i = len - 1; i >= wo; --i) { if (i < 0) { break; } buf[i + 1] = buf[i]; } } buf[wo] = wch; } else { buf.push_back(wch); } ++wo; ++len; }; void fc_set_header(const char* h) { header.clear(); header.append(h); header_len = 0; auto v = str_to_vec(header); for (const auto& item : v) { if (item.size() > 1) { header_len += 2; } else { header_len += 1; } } int a = 0; } char* fc_readline() { main_buf = (char*)malloc(sizeof(char) * buf_size); std::memset(main_buf, 0x0, buf_size); if (main_buf == NULL) { fprintf(stderr, "[ERROR] Couldn't allocate memory for buffer\n"); exit(1); } char* tmp_buf = (char*)malloc(sizeof(char) * buf_size); std::memset(tmp_buf, 0x0, buf_size); std::shared_ptr deleter(new int(), [tmp_buf](int* p) { delete p; free(tmp_buf); }); home_path = get_home(); flush_content(".", cur_work_content); bool need_predic = true; std::chrono::time_point p1, p2; p1 = std::chrono::high_resolution_clock::now(); fc_enable_cur(); auto refresh_show = [&]() { word.clear(); clear_line(); // Print current buffer color_print(header.c_str(), DEFAULT_TITLE_COLOR); color_print(nullptr, DEFAULT_MAIN_COLOR); if (!str_predict.empty()) { color_print(str_predict.data(), DEFAULT_PREDICT_COLOR); } // Move cursor size_t cur_pos{}; #if defined(BINARY_GBK) for (size_t i = 0; i < buf.size() && i < wo; ++i) { cur_pos += buf[i].size(); } #else for (size_t i = 0; i < buf.size() && i < wo; ++i) { if (buf[i].size() > 1) { cur_pos += 2; } else { cur_pos += 1; } } #endif auto ccc = cur_pos + 1 + header_len; set_cursor_x(ccc); }; while (1) { refresh_show(); need_predic = true; // Read character from console int ch = _getch(); p2 = p1; p1 = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(p1 - p2).count(); if (duration <= 10) { need_predic = false; } switch (ch) { case ENTER: fc_disable_cur(); // 这里还需要清除预测显示 str_predict.clear(); refresh_show(); append_his(main_buf); if (need_turn) { std::string rep(main_buf); auto spos = rep.find("~"); if (spos != std::string::npos) { rep = rep.substr(0, spos) + get_home() + rep.substr(spos + 1, rep.size() - spos); std::memset(main_buf, 0x0, buf_size); std::snprintf(main_buf, buf_size, "%s", rep.c_str()); } } return main_buf; case CTRL_C: { free(main_buf); #ifdef OS_UNIX fc_lock_print(); printf("\033[0m"); fc_enable_cur(); fc_unlock_print(); #else recovery_terminal_color(); #endif return nullptr; } case BACKSPACE: { if (wo > 0) { for (size_t i = wo - 1; i < buf.size() - 1; ++i) { buf[i] = buf[i + 1]; } --wo; --len; } break; } case TAB: { // 在这里补全 if (!str_predict.empty()) { auto temp = str_to_vec(str_predict); str_predict.clear(); for (const auto& item : temp) { add_newer(item); } continue; } break; } #if defined(OS_WINDOWS) case SPECIAL_SEQ_1: case SPECIAL_SEQ_2: #elif defined(OS_UNIX) case SPECIAL_SEQ_1: #endif { #if defined(OS_UNIX) if (_getch() != SPECIAL_SEQ_2) { continue; } #endif switch (_getch()) { case LEFT: if (wo > 0) { --wo; } break; case RIGHT: if (wo < len) { ++wo; } break; case UP: { auto up_str = up_his(); buf.clear(); auto t = str_to_vec(up_str); if (t.size() > 0) { buf.insert(buf.end(), t.begin(), t.end()); } len = buf.size(); wo = len; break; } case DOWN: { auto next_str = next_his(); buf.clear(); auto t = str_to_vec(next_str); if (t.size() > 0) { buf.insert(buf.end(), t.begin(), t.end()); } len = buf.size(); wo = len; break; } case DEL: // Edit buffer like DELETE key #if defined(OS_UNIX) if (_getch() == DEL_AFTER) #endif { } break; default: break; } break; } default: { supply(word, ch); add_newer(word); break; } } // 补正 trans2buf(tmp_buf); if (need_predic) { str_predict = file_predict(tmp_buf); } else if (!str_predict.empty()) { str_predict.clear(); } p1 = std::chrono::high_resolution_clock::now(); } return main_buf; } void fc_enable_cur() { #if defined(OS_WINDOWS) auto hConsole = GetStdHandle(STD_OUTPUT_HANDLE); if (hConsole == NULL) { fprintf(stderr, "[ERROR] Couldn't handle terminal\n"); exit(1); } CONSOLE_CURSOR_INFO cursorInfo; GetConsoleCursorInfo(hConsole, &cursorInfo); cursorInfo.bVisible = TRUE; SetConsoleCursorInfo(hConsole, &cursorInfo); #else printf("\033[?25h"); #endif } void fc_disable_cur() { #if defined(OS_WINDOWS) auto hConsole = GetStdHandle(STD_OUTPUT_HANDLE); if (hConsole == NULL) { fprintf(stderr, "[ERROR] Couldn't handle terminal\n"); exit(1); } CONSOLE_CURSOR_INFO cursorInfo; GetConsoleCursorInfo(hConsole, &cursorInfo); cursorInfo.bVisible = FALSE; SetConsoleCursorInfo(hConsole, &cursorInfo); #else printf("\033[?25l"); #endif } void fc_lock_print() { mut.lock(); } void fc_unlock_print() { mut.unlock(); } void fc_free(char* str) { buf.clear(); wo = 0; len = 0; free(str); } void trans2buf(char* buffer) { int j = 0; for (size_t i = 0; i < buf.size() && i < len; ++i) { for (const auto& c : buf[i]) { buffer[j++] = c; } } buffer[j++] = '\0'; } void color_print(const char* text, const COLOR_TYPE color) { fc_lock_print(); #if defined(OS_WINDOWS) HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE); if (h_console == NULL) { fprintf(stderr, "[ERROR] Couldn't handle terminal\n"); exit(1); } CONSOLE_SCREEN_BUFFER_INFO console_info; COLOR_TYPE backup; // Save current attributes if (GetConsoleScreenBufferInfo(h_console, &console_info) == 0) { fprintf(stderr, "[ERROR] Couldn't get terminal info\n"); exit(1); } auto wh = get_wh(); backup = console_info.wAttributes; // Print colored text if (SetConsoleTextAttribute(h_console, color) == 0) { fprintf(stderr, "[ERROR] Couldn't set terminal color\n"); exit(1); } if (!text) { trans2buf(main_buf); printf("%s", main_buf); } else { printf("%s", text); } HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO cursorInfo; GetConsoleCursorInfo(hConsole, &cursorInfo); cursorInfo.bVisible = TRUE; SetConsoleCursorInfo(hConsole, &cursorInfo); // Restore original color if (SetConsoleTextAttribute(h_console, backup) == 0) { fprintf(stderr, "[ERROR] Couldn't reset terminal color\n"); exit(1); } #elif defined(OS_UNIX) // Set new terminal color printf("\033["); printf("%s", color); printf("m"); if (!text) { trans2buf(main_buf); printf("%s", main_buf); } else { printf("%s", text); } fc_disable_cur(); #endif fc_unlock_print(); }