|
@@ -214,6 +214,25 @@ private:
|
|
|
std::string message_line_;
|
|
std::string message_line_;
|
|
|
std::vector<std::string> minibuffer_history_;
|
|
std::vector<std::string> minibuffer_history_;
|
|
|
size_t history_index_ = 0;
|
|
size_t history_index_ = 0;
|
|
|
|
|
+ // Completion state
|
|
|
|
|
+ size_t completion_index_ = 0;
|
|
|
|
|
+ std::string last_completion_input_;
|
|
|
|
|
+
|
|
|
|
|
+ // Helper to run Lua completion
|
|
|
|
|
+ std::vector<std::string> run_lua_completion(const std::string& mode, const std::string& input) {
|
|
|
|
|
+ if (!core_ || !core_->lua_api()) return {};
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ sol::function func = core_->lua_api()->state()["get_completion_candidates"];
|
|
|
|
|
+ if (func.valid()) {
|
|
|
|
|
+ std::vector<std::string> candidates = func(mode, input);
|
|
|
|
|
+ return candidates;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ std::cerr << "Lua completion error: " << e.what() << std::endl;
|
|
|
|
|
+ }
|
|
|
|
|
+ return {};
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Member variables
|
|
// Member variables
|
|
|
Gtk::DrawingArea* drawing_area_ = nullptr; // For single-window compatibility
|
|
Gtk::DrawingArea* drawing_area_ = nullptr; // For single-window compatibility
|
|
@@ -1196,88 +1215,38 @@ protected:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (key_name == "Tab") {
|
|
if (key_name == "Tab") {
|
|
|
- if (mode_ == Mode::BufferSwitch || mode_ == Mode::KillBuffer) {
|
|
|
|
|
- auto names = core_->get_buffer_names();
|
|
|
|
|
- std::vector<std::string> matches;
|
|
|
|
|
- for (const auto& name : names) {
|
|
|
|
|
- if (name.find(command_buffer_) == 0) { // Prefix match
|
|
|
|
|
- matches.push_back(name);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (matches.size() == 1) {
|
|
|
|
|
- command_buffer_ = matches[0];
|
|
|
|
|
- } else if (matches.size() > 1) {
|
|
|
|
|
- // Find common prefix
|
|
|
|
|
- std::string common = matches[0];
|
|
|
|
|
- for (size_t i = 1; i < matches.size(); ++i) {
|
|
|
|
|
- const std::string& s = matches[i];
|
|
|
|
|
- size_t j = 0;
|
|
|
|
|
- while (j < common.size() && j < s.size() && common[j] == s[j]) {
|
|
|
|
|
- j++;
|
|
|
|
|
- }
|
|
|
|
|
- common = common.substr(0, j);
|
|
|
|
|
- }
|
|
|
|
|
- command_buffer_ = common;
|
|
|
|
|
- message_line_ = "Multiple matches";
|
|
|
|
|
- } else {
|
|
|
|
|
- message_line_ = "No match";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (content_widget_) queue_redraw_all_windows(content_widget_);
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ std::string mode_str;
|
|
|
|
|
+ switch (mode_) {
|
|
|
|
|
+ case Mode::Command: mode_str = "Command"; break;
|
|
|
|
|
+ case Mode::BufferSwitch: mode_str = "BufferSwitch"; break;
|
|
|
|
|
+ case Mode::KillBuffer: mode_str = "KillBuffer"; break;
|
|
|
|
|
+ case Mode::FindFile: mode_str = "FindFile"; break;
|
|
|
|
|
+ default: break;
|
|
|
}
|
|
}
|
|
|
- // TODO: File completion for FindFile
|
|
|
|
|
- if (mode_ == Mode::FindFile) {
|
|
|
|
|
- std::string input = command_buffer_;
|
|
|
|
|
- namespace fs = std::filesystem;
|
|
|
|
|
-
|
|
|
|
|
- fs::path search_path;
|
|
|
|
|
- std::string prefix;
|
|
|
|
|
-
|
|
|
|
|
- // Determine directory and prefix
|
|
|
|
|
- if (input.empty()) {
|
|
|
|
|
- search_path = fs::current_path();
|
|
|
|
|
- } else {
|
|
|
|
|
- fs::path input_path(input);
|
|
|
|
|
- if (fs::is_directory(input_path) && input.back() == '/') {
|
|
|
|
|
- search_path = input_path;
|
|
|
|
|
- prefix = "";
|
|
|
|
|
- } else {
|
|
|
|
|
- if (input_path.has_parent_path()) {
|
|
|
|
|
- search_path = input_path.parent_path();
|
|
|
|
|
- if (search_path.string().empty()) search_path = fs::current_path();
|
|
|
|
|
- } else {
|
|
|
|
|
- search_path = fs::current_path();
|
|
|
|
|
- }
|
|
|
|
|
- prefix = input_path.filename().string();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (!mode_str.empty()) {
|
|
|
|
|
+ // Reset cycling if input changed (naive check, ideally tracked elsewhere)
|
|
|
|
|
+ if (command_buffer_ != last_completion_input_) {
|
|
|
|
|
+ completion_index_ = 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- std::vector<std::string> matches;
|
|
|
|
|
- try {
|
|
|
|
|
- if (fs::exists(search_path) && fs::is_directory(search_path)) {
|
|
|
|
|
- for (const auto& entry : fs::directory_iterator(search_path)) {
|
|
|
|
|
- std::string filename = entry.path().filename().string();
|
|
|
|
|
- if (filename.find(prefix) == 0) {
|
|
|
|
|
- if (fs::is_directory(entry.path())) {
|
|
|
|
|
- matches.push_back(filename + "/");
|
|
|
|
|
- } else {
|
|
|
|
|
- matches.push_back(filename);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (...) {
|
|
|
|
|
- // Ignore permission errors etc
|
|
|
|
|
|
|
+ std::vector<std::string> matches = run_lua_completion(mode_str, command_buffer_);
|
|
|
|
|
+
|
|
|
|
|
+ // Fallback for FindFile if Lua returns nothing (temporary until Lua impl is complete)
|
|
|
|
|
+ if (matches.empty() && mode_ == Mode::FindFile) {
|
|
|
|
|
+ // Simple C++ fallback logic for FindFile could go here, or we just rely on Lua.
|
|
|
|
|
+ // For now, if Lua returns empty, we do nothing.
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (matches.size() == 1) {
|
|
|
|
|
- // Append the difference
|
|
|
|
|
- std::string completion = matches[0].substr(prefix.length());
|
|
|
|
|
- command_buffer_ += completion;
|
|
|
|
|
- } else if (matches.size() > 1) {
|
|
|
|
|
- // Find common prefix of MATCHES (not including full path)
|
|
|
|
|
|
|
+ if (matches.empty()) {
|
|
|
|
|
+ message_line_ = "No match";
|
|
|
|
|
+ } else if (matches.size() == 1) {
|
|
|
|
|
+ command_buffer_ = matches[0];
|
|
|
|
|
+ last_completion_input_ = command_buffer_; // Update last input to match current
|
|
|
|
|
+ message_line_ = "Sole match";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Multiple matches
|
|
|
|
|
+ // 1. Find common prefix
|
|
|
std::string common = matches[0];
|
|
std::string common = matches[0];
|
|
|
for (size_t i = 1; i < matches.size(); ++i) {
|
|
for (size_t i = 1; i < matches.size(); ++i) {
|
|
|
const std::string& s = matches[i];
|
|
const std::string& s = matches[i];
|
|
@@ -1287,20 +1256,29 @@ protected:
|
|
|
}
|
|
}
|
|
|
common = common.substr(0, j);
|
|
common = common.substr(0, j);
|
|
|
}
|
|
}
|
|
|
- // Append difference between common prefix and current input prefix
|
|
|
|
|
- if (common.length() > prefix.length()) {
|
|
|
|
|
- command_buffer_ += common.substr(prefix.length());
|
|
|
|
|
- } else {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 2. Logic:
|
|
|
|
|
+ // If current input is shorter than prefix, complete to prefix.
|
|
|
|
|
+ // If current input IS the prefix (or longer/different), cycle.
|
|
|
|
|
+
|
|
|
|
|
+ if (command_buffer_.length() < common.length()) {
|
|
|
|
|
+ command_buffer_ = common;
|
|
|
|
|
+ completion_index_ = 0; // Reset cycling
|
|
|
message_line_ = "Multiple matches";
|
|
message_line_ = "Multiple matches";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Cycle
|
|
|
|
|
+ command_buffer_ = matches[completion_index_ % matches.size()];
|
|
|
|
|
+ completion_index_++;
|
|
|
|
|
+ message_line_ = "Match " + std::to_string((completion_index_ - 1) % matches.size() + 1) + "/" + std::to_string(matches.size());
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- message_line_ = "No match";
|
|
|
|
|
|
|
+ last_completion_input_ = command_buffer_;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (content_widget_) queue_redraw_all_windows(content_widget_);
|
|
if (content_widget_) queue_redraw_all_windows(content_widget_);
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
|
|
|
if (key_name == "Return") {
|
|
if (key_name == "Return") {
|
|
|
// Add to history
|
|
// Add to history
|