cli : add command and file auto-completion (#19985)
This commit is contained in:
parent
cf232515c9
commit
b5ed0e058c
|
|
@ -80,6 +80,8 @@ namespace console {
|
||||||
static termios initial_state;
|
static termios initial_state;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static completion_callback completion_cb = nullptr;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Init and cleanup
|
// Init and cleanup
|
||||||
//
|
//
|
||||||
|
|
@ -493,7 +495,7 @@ namespace console {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_line_contents(std::string new_line, std::string & line, std::vector<int> & widths, size_t & char_pos,
|
static void set_line_contents(std::string new_line, std::string & line, std::vector<int> & widths, size_t & char_pos,
|
||||||
size_t & byte_pos) {
|
size_t & byte_pos, int cursor_byte_pos = -1) {
|
||||||
move_to_line_start(char_pos, byte_pos, widths);
|
move_to_line_start(char_pos, byte_pos, widths);
|
||||||
clear_current_line(widths);
|
clear_current_line(widths);
|
||||||
|
|
||||||
|
|
@ -503,6 +505,7 @@ namespace console {
|
||||||
char_pos = 0;
|
char_pos = 0;
|
||||||
|
|
||||||
size_t idx = 0;
|
size_t idx = 0;
|
||||||
|
int back_width = 0;
|
||||||
while (idx < line.size()) {
|
while (idx < line.size()) {
|
||||||
size_t advance = 0;
|
size_t advance = 0;
|
||||||
char32_t cp = decode_utf8(line, idx, advance);
|
char32_t cp = decode_utf8(line, idx, advance);
|
||||||
|
|
@ -511,8 +514,15 @@ namespace console {
|
||||||
if (real_width < 0) real_width = 0;
|
if (real_width < 0) real_width = 0;
|
||||||
widths.push_back(real_width);
|
widths.push_back(real_width);
|
||||||
idx += advance;
|
idx += advance;
|
||||||
++char_pos;
|
if (cursor_byte_pos >= 0 && static_cast<size_t>(cursor_byte_pos) < idx) {
|
||||||
byte_pos = idx;
|
back_width += real_width;
|
||||||
|
} else {
|
||||||
|
++char_pos;
|
||||||
|
byte_pos = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cursor_byte_pos >= 0) {
|
||||||
|
move_cursor(-back_width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -784,6 +794,20 @@ namespace console {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (completion_cb && input_char == '\t') {
|
||||||
|
auto candidates = completion_cb(line, byte_pos);
|
||||||
|
|
||||||
|
if (!candidates.empty()) {
|
||||||
|
if (candidates.size() > 1 || candidates[0].first != line) {
|
||||||
|
// TODO?: Display all candidates
|
||||||
|
set_line_contents(candidates[0].first, line, widths, char_pos, byte_pos, candidates[0].second);
|
||||||
|
} else {
|
||||||
|
// TODO: Move cursor to new byte_pos
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (input_char == (char32_t) WEOF || input_char == 0x04 /* Ctrl+D */) {
|
if (input_char == (char32_t) WEOF || input_char == 0x04 /* Ctrl+D */) {
|
||||||
end_of_stream = true;
|
end_of_stream = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -1062,6 +1086,10 @@ namespace console {
|
||||||
return readline_advanced(line, multiline_input);
|
return readline_advanced(line, multiline_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_completion_callback(completion_callback cb) {
|
||||||
|
completion_cb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
namespace spinner {
|
namespace spinner {
|
||||||
static const char LOADING_CHARS[] = {'|', '/', '-', '\\'};
|
static const char LOADING_CHARS[] = {'|', '/', '-', '\\'};
|
||||||
static std::condition_variable cv_stop;
|
static std::condition_variable cv_stop;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
enum display_type {
|
enum display_type {
|
||||||
DISPLAY_TYPE_RESET = 0,
|
DISPLAY_TYPE_RESET = 0,
|
||||||
|
|
@ -21,6 +23,9 @@ namespace console {
|
||||||
void set_display(display_type display);
|
void set_display(display_type display);
|
||||||
bool readline(std::string & line, bool multiline_input);
|
bool readline(std::string & line, bool multiline_input);
|
||||||
|
|
||||||
|
using completion_callback = std::function<std::vector<std::pair<std::string, size_t>>(std::string_view, size_t)>;
|
||||||
|
void set_completion_callback(completion_callback cb);
|
||||||
|
|
||||||
namespace spinner {
|
namespace spinner {
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@
|
||||||
#include "server-context.h"
|
#include "server-context.h"
|
||||||
#include "server-task.h"
|
#include "server-task.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
@ -195,6 +198,122 @@ struct cli_context {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO?: Make this reusable, enums, docs
|
||||||
|
static const std::array<const std::string, 6> cmds = {
|
||||||
|
"/audio ",
|
||||||
|
"/clear",
|
||||||
|
"/exit",
|
||||||
|
"/image ",
|
||||||
|
"/read ",
|
||||||
|
"/regen",
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<std::pair<std::string, size_t>> auto_completion_callback(std::string_view line, size_t cursor_byte_pos) {
|
||||||
|
std::vector<std::pair<std::string, size_t>> matches;
|
||||||
|
std::string cmd;
|
||||||
|
|
||||||
|
if (line.length() > 1 && line[0] == '/' && !std::any_of(cmds.begin(), cmds.end(), [line](const std::string & prefix) {
|
||||||
|
return string_starts_with(line, prefix);
|
||||||
|
})) {
|
||||||
|
auto it = cmds.begin();
|
||||||
|
|
||||||
|
while ((it = std::find_if(it, cmds.end(), [line](const std::string & 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](const std::string & prefix) {
|
||||||
|
return prefix.back() == ' ' && string_starts_with(line, prefix);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it != cmds.end()) {
|
||||||
|
cmd = *it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cmd.empty() && 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 = std::string(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 = "";
|
||||||
|
} 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)) {
|
||||||
|
std::string updated_line = cmd + path_entry;
|
||||||
|
matches.emplace_back(updated_line + path_postfix, updated_line.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
ec.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.empty()) {
|
||||||
|
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<size_t>(cmp.first - match0.begin()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char ** argv) {
|
int main(int argc, char ** argv) {
|
||||||
common_params params;
|
common_params params;
|
||||||
|
|
||||||
|
|
@ -223,6 +342,7 @@ int main(int argc, char ** argv) {
|
||||||
atexit([]() { console::cleanup(); });
|
atexit([]() { console::cleanup(); });
|
||||||
|
|
||||||
console::set_display(DISPLAY_TYPE_RESET);
|
console::set_display(DISPLAY_TYPE_RESET);
|
||||||
|
console::set_completion_callback(auto_completion_callback);
|
||||||
|
|
||||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||||
struct sigaction sigint_action;
|
struct sigaction sigint_action;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue