#include "arg.h" #include "common.h" #include "log.h" #include "cli-context.h" #include "cli-view.h" #include #include #include #include #include #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX # define NOMINMAX #endif #include #endif #if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) || defined (_WIN32) static void signal_handler(int) { if (g_cli_interrupted.load()) { // second Ctrl+C - exit immediately // make sure to clear colors before exiting (not using LOG or console.cpp here to avoid deadlock) fprintf(stdout, "\033[0m\n"); fflush(stdout); std::exit(130); } g_cli_interrupted.store(true); } #endif // TODO?: Make this reusable, enums, docs static const std::array cmds = { "/audio ", "/clear", "/exit", "/glob ", "/image ", "/read ", "/regen", "/video ", }; static std::vector> auto_completion_callback(std::string_view line, size_t cursor_byte_pos) { std::vector> matches; std::string cmd; if (line.length() > 1 && line.front() == '/' && !std::any_of(cmds.begin(), cmds.end(), [line](std::string_view prefix) { return string_starts_with(line, prefix); })) { auto it = cmds.begin(); while ((it = std::find_if(it, cmds.end(), [line](std::string_view cmd_line) { return string_starts_with(cmd_line, line); })) != cmds.end()) { matches.emplace_back(*it, it->length()); ++it; } } else { auto it = std::find_if(cmds.begin(), cmds.end(), [line](std::string_view prefix) { return prefix.back() == ' ' && string_starts_with(line, prefix); }); if (it != cmds.end()) { cmd = *it; } } if (!cmd.empty() && cmd != "/glob " && line.length() >= cmd.length() && cursor_byte_pos >= cmd.length()) { const std::string path_prefix = std::string(line.substr(cmd.length(), cursor_byte_pos - cmd.length())); const std::string path_postfix = std::string(line.substr(cursor_byte_pos)); auto cur_dir = std::filesystem::current_path(); std::string cur_dir_str = cur_dir.string(); std::string expanded_prefix = path_prefix; #if !defined(_WIN32) if (string_starts_with(path_prefix, '~')) { const char * home = std::getenv("HOME"); if (home && home[0]) { expanded_prefix = home + path_prefix.substr(1); } } if (string_starts_with(expanded_prefix, '/')) { #else if (std::isalpha(expanded_prefix[0]) && expanded_prefix.find(':') == 1) { #endif cur_dir = std::filesystem::path(expanded_prefix).parent_path(); cur_dir_str.clear(); } else if (!path_prefix.empty()) { cur_dir /= std::filesystem::path(path_prefix).parent_path(); } std::error_code ec; for (const auto & entry : std::filesystem::directory_iterator(cur_dir, ec)) { if (ec) { break; } if (!entry.exists(ec)) { ec.clear(); continue; } const std::string path_full = entry.path().string(); std::string path_entry = !cur_dir_str.empty() && string_starts_with(path_full, cur_dir_str) ? path_full.substr(cur_dir_str.length() + 1) : path_full; if (entry.is_directory(ec)) { path_entry.push_back(std::filesystem::path::preferred_separator); } if (expanded_prefix.empty() || string_starts_with(path_entry, expanded_prefix)) { const std::string updated_line = cmd + path_entry; matches.emplace_back(updated_line + path_postfix, updated_line.length()); } if (ec) { ec.clear(); } } if (matches.empty()) { const std::string updated_line = cmd + path_prefix; matches.emplace_back(updated_line + path_postfix, updated_line.length()); } // Add the longest common prefix if (!expanded_prefix.empty() && matches.size() > 1) { const std::string_view match0(matches[0].first); const std::string_view match1(matches[1].first); auto it = std::mismatch(match0.begin(), match0.end(), match1.begin(), match1.end()); size_t len = it.first - match0.begin(); for (size_t i = 2; i < matches.size(); ++i) { const std::string_view matchi(matches[i].first); auto cmp = std::mismatch(match0.begin(), match0.end(), matchi.begin(), matchi.end()); len = std::min(len, static_cast(cmp.first - match0.begin())); } const std::string updated_line = std::string(match0.substr(0, len)); matches.emplace_back(updated_line + path_postfix, updated_line.length()); } std::sort(matches.begin(), matches.end(), [](const auto & a, const auto & b) { return a.first.compare(0, a.second, b.first, 0, b.second) < 0; }); } return matches; } // satisfies -Wmissing-declarations int llama_cli(int argc, char ** argv); int llama_cli(int argc, char ** argv) { common_params params; params.verbosity = LOG_LEVEL_ERROR; // by default, less verbose logs common_init(); if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_CLI)) { return 1; } view::set_completion_callback(auto_completion_callback); #if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) struct sigaction sigint_action; sigint_action.sa_handler = signal_handler; sigemptyset (&sigint_action.sa_mask); sigint_action.sa_flags = 0; sigaction(SIGINT, &sigint_action, NULL); sigaction(SIGTERM, &sigint_action, NULL); #elif defined (_WIN32) auto console_ctrl_handler = +[](DWORD ctrl_type) -> BOOL { return (ctrl_type == CTRL_C_EVENT) ? (signal_handler(SIGINT), true) : false; }; SetConsoleCtrlHandler(reinterpret_cast(console_ctrl_handler), true); #endif cli_context ctx_cli(params); if (!ctx_cli.init(argc, argv)) { ctx_cli.shutdown(); return 1; } int ret = ctx_cli.run(); ctx_cli.shutdown(); return ret; }