|
@@ -0,0 +1,491 @@
|
|
|
|
|
+#include "lumacs/command_system.hpp"
|
|
|
|
|
+#include "lumacs/editor_core.hpp"
|
|
|
|
|
+#include "lumacs/lua_api.hpp"
|
|
|
|
|
+#include <algorithm>
|
|
|
|
|
+#include <sstream>
|
|
|
|
|
+#include <regex>
|
|
|
|
|
+#include <iostream>
|
|
|
|
|
+#include <filesystem>
|
|
|
|
|
+#include <unordered_set>
|
|
|
|
|
+#include <iomanip>
|
|
|
|
|
+
|
|
|
|
|
+namespace lumacs {
|
|
|
|
|
+
|
|
|
|
|
+// FileSystemCompletionProvider Implementation
|
|
|
|
|
+
|
|
|
|
|
+std::vector<CompletionCandidate> FileSystemCompletionProvider::complete_path(const std::string& input) const {
|
|
|
|
|
+ std::vector<CompletionCandidate> candidates;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ std::string path_part = input;
|
|
|
|
|
+ std::string dirname = ".";
|
|
|
|
|
+ std::string basename = input;
|
|
|
|
|
+
|
|
|
|
|
+ // Extract directory and filename parts
|
|
|
|
|
+ auto last_slash = input.find_last_of("/\\");
|
|
|
|
|
+ if (last_slash != std::string::npos) {
|
|
|
|
|
+ dirname = input.substr(0, last_slash);
|
|
|
|
|
+ basename = input.substr(last_slash + 1);
|
|
|
|
|
+ if (dirname.empty()) dirname = "/";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Expand ~ to home directory
|
|
|
|
|
+ if (dirname.starts_with("~")) {
|
|
|
|
|
+ const char* home = getenv("HOME");
|
|
|
|
|
+ if (home) {
|
|
|
|
|
+ dirname = std::string(home) + dirname.substr(1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!std::filesystem::exists(dirname)) {
|
|
|
|
|
+ return candidates;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Iterate through directory entries
|
|
|
|
|
+ for (const auto& entry : std::filesystem::directory_iterator(dirname)) {
|
|
|
|
|
+ std::string filename = entry.path().filename().string();
|
|
|
|
|
+
|
|
|
|
|
+ // Skip hidden files unless input starts with .
|
|
|
|
|
+ if (filename.starts_with(".") && !basename.starts_with(".")) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check if filename matches input
|
|
|
|
|
+ if (basename.empty() || filename.starts_with(basename)) {
|
|
|
|
|
+ std::string full_path;
|
|
|
|
|
+ if (dirname == ".") {
|
|
|
|
|
+ full_path = filename;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ full_path = (dirname == "/" ? "/" : dirname + "/") + filename;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Add trailing slash for directories
|
|
|
|
|
+ if (entry.is_directory()) {
|
|
|
|
|
+ full_path += "/";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int score = calculate_path_score(filename, basename);
|
|
|
|
|
+ std::string desc = entry.is_directory() ? "directory" : "file";
|
|
|
|
|
+ candidates.emplace_back(full_path, score, desc);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (const std::filesystem::filesystem_error& e) {
|
|
|
|
|
+ // Ignore filesystem errors
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::sort(candidates.begin(), candidates.end());
|
|
|
|
|
+ return candidates;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<CompletionCandidate> FileSystemCompletionProvider::complete_directory(const std::string& input) const {
|
|
|
|
|
+ auto candidates = complete_path(input);
|
|
|
|
|
+ // Filter to only directories
|
|
|
|
|
+ candidates.erase(
|
|
|
|
|
+ std::remove_if(candidates.begin(), candidates.end(),
|
|
|
|
|
+ [](const CompletionCandidate& c) { return c.description != "directory"; }),
|
|
|
|
|
+ candidates.end());
|
|
|
|
|
+ return candidates;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<CompletionCandidate> FileSystemCompletionProvider::complete_file(const std::string& input, const std::vector<std::string>& extensions) const {
|
|
|
|
|
+ auto candidates = complete_path(input);
|
|
|
|
|
+
|
|
|
|
|
+ if (!extensions.empty()) {
|
|
|
|
|
+ candidates.erase(
|
|
|
|
|
+ std::remove_if(candidates.begin(), candidates.end(),
|
|
|
|
|
+ [&extensions](const CompletionCandidate& c) {
|
|
|
|
|
+ if (c.description == "directory") return false; // Keep directories
|
|
|
|
|
+ for (const auto& ext : extensions) {
|
|
|
|
|
+ if (c.text.ends_with(ext)) return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }),
|
|
|
|
|
+ candidates.end());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return candidates;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool FileSystemCompletionProvider::is_absolute_path(const std::string& path) const {
|
|
|
|
|
+ return !path.empty() && (path[0] == '/' || path[0] == '~');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::string FileSystemCompletionProvider::normalize_path(const std::string& path) const {
|
|
|
|
|
+ std::string normalized = path;
|
|
|
|
|
+ if (normalized.starts_with("~")) {
|
|
|
|
|
+ const char* home = getenv("HOME");
|
|
|
|
|
+ if (home) {
|
|
|
|
|
+ normalized = std::string(home) + normalized.substr(1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return normalized;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int FileSystemCompletionProvider::calculate_path_score(const std::string& candidate, const std::string& input) const {
|
|
|
|
|
+ if (input.empty()) return 50;
|
|
|
|
|
+ if (candidate == input) return 100;
|
|
|
|
|
+ if (candidate.starts_with(input)) return 90;
|
|
|
|
|
+
|
|
|
|
|
+ // Fuzzy matching score
|
|
|
|
|
+ return CommandSystem::fuzzy_match_score(candidate, input);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// CommandSystem Implementation
|
|
|
|
|
+
|
|
|
|
|
+CommandSystem::CommandSystem(EditorCore& core) : core_(core) {
|
|
|
|
|
+ register_builtin_commands();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void CommandSystem::register_command(std::unique_ptr<Command> command) {
|
|
|
|
|
+ if (!command) return;
|
|
|
|
|
+
|
|
|
|
|
+ std::string name = command->name;
|
|
|
|
|
+ auto shared_cmd = std::shared_ptr<Command>(command.release());
|
|
|
|
|
+ commands_[name] = shared_cmd;
|
|
|
|
|
+
|
|
|
|
|
+ // Register aliases
|
|
|
|
|
+ for (const auto& alias : shared_cmd->aliases) {
|
|
|
|
|
+ commands_[alias] = shared_cmd;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void CommandSystem::register_command(const std::string& name, const std::string& description,
|
|
|
|
|
+ CommandFunction function, const std::vector<std::string>& aliases,
|
|
|
|
|
+ bool interactive) {
|
|
|
|
|
+ auto command = std::make_unique<Command>(name, description, std::move(function), aliases, interactive);
|
|
|
|
|
+ register_command(std::move(command));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+CommandResult CommandSystem::execute(const std::string& command_name, const std::vector<std::string>& args) {
|
|
|
|
|
+ auto it = commands_.find(command_name);
|
|
|
|
|
+ if (it == commands_.end()) {
|
|
|
|
|
+ return CommandResult(false, "Command not found: " + command_name);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ return it->second->function(args);
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ return CommandResult(false, "Command error: " + std::string(e.what()));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+CommandResult CommandSystem::execute_string(const std::string& command_string) {
|
|
|
|
|
+ auto parts = parse_command_string(command_string);
|
|
|
|
|
+ if (parts.empty()) {
|
|
|
|
|
+ return CommandResult(false, "Empty command");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string command_name = parts[0];
|
|
|
|
|
+ std::vector<std::string> args(parts.begin() + 1, parts.end());
|
|
|
|
|
+
|
|
|
|
|
+ return execute(command_name, args);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool CommandSystem::has_command(const std::string& name) const {
|
|
|
|
|
+ return commands_.find(name) != commands_.end();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::shared_ptr<Command> CommandSystem::get_command(const std::string& name) const {
|
|
|
|
|
+ auto it = commands_.find(name);
|
|
|
|
|
+ return (it != commands_.end()) ? it->second : nullptr;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<std::string> CommandSystem::get_all_command_names() const {
|
|
|
|
|
+ std::vector<std::string> names;
|
|
|
|
|
+ std::unordered_set<std::string> unique_names;
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& [name, command] : commands_) {
|
|
|
|
|
+ if (unique_names.insert(command->name).second) {
|
|
|
|
|
+ names.push_back(command->name);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::sort(names.begin(), names.end());
|
|
|
|
|
+ return names;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<std::string> CommandSystem::get_interactive_command_names() const {
|
|
|
|
|
+ std::vector<std::string> names;
|
|
|
|
|
+ std::unordered_set<std::string> unique_names;
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& [name, command] : commands_) {
|
|
|
|
|
+ if (command->interactive && unique_names.insert(command->name).second) {
|
|
|
|
|
+ names.push_back(command->name);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::sort(names.begin(), names.end());
|
|
|
|
|
+ return names;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<CompletionCandidate> CommandSystem::complete_command(const std::string& input) const {
|
|
|
|
|
+ std::vector<CompletionCandidate> candidates;
|
|
|
|
|
+ std::unordered_set<std::string> unique_names;
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& [name, command] : commands_) {
|
|
|
|
|
+ if (!command->interactive || !unique_names.insert(command->name).second) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int score = fuzzy_match_score(command->name, input);
|
|
|
|
|
+ if (score > 0) {
|
|
|
|
|
+ candidates.emplace_back(command->name, score, command->description);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::sort(candidates.begin(), candidates.end());
|
|
|
|
|
+ return candidates;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<CompletionCandidate> CommandSystem::complete_buffer_name(const std::string& input) const {
|
|
|
|
|
+ std::vector<CompletionCandidate> candidates;
|
|
|
|
|
+ auto buffer_names = core_.get_buffer_names();
|
|
|
|
|
+
|
|
|
|
|
+ for (const auto& name : buffer_names) {
|
|
|
|
|
+ int score = fuzzy_match_score(name, input);
|
|
|
|
|
+ if (score > 0) {
|
|
|
|
|
+ auto buffer = core_.get_buffer_by_name(name);
|
|
|
|
|
+ std::string desc = "buffer";
|
|
|
|
|
+ if (buffer && buffer->is_modified()) {
|
|
|
|
|
+ desc += " (modified)";
|
|
|
|
|
+ }
|
|
|
|
|
+ candidates.emplace_back(name, score, desc);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::sort(candidates.begin(), candidates.end());
|
|
|
|
|
+ return candidates;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<CompletionCandidate> CommandSystem::complete_file_path(const std::string& input) const {
|
|
|
|
|
+ return fs_provider_.complete_path(input);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void CommandSystem::register_completion_provider(const std::string& command_name, CompletionProvider provider) {
|
|
|
|
|
+ completion_providers_[command_name] = std::move(provider);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<CompletionCandidate> CommandSystem::get_completions(const std::string& command_name, const std::string& input) const {
|
|
|
|
|
+ auto it = completion_providers_.find(command_name);
|
|
|
|
|
+ if (it != completion_providers_.end()) {
|
|
|
|
|
+ return it->second(input);
|
|
|
|
|
+ }
|
|
|
|
|
+ return {};
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+int CommandSystem::fuzzy_match_score(const std::string& candidate, const std::string& input) {
|
|
|
|
|
+ if (input.empty()) return 50;
|
|
|
|
|
+ if (candidate == input) return 100;
|
|
|
|
|
+ if (candidate.starts_with(input)) return 90;
|
|
|
|
|
+
|
|
|
|
|
+ // Convert to lowercase for case-insensitive matching
|
|
|
|
|
+ std::string lower_candidate = candidate;
|
|
|
|
|
+ std::string lower_input = input;
|
|
|
|
|
+ std::transform(lower_candidate.begin(), lower_candidate.end(), lower_candidate.begin(), ::tolower);
|
|
|
|
|
+ std::transform(lower_input.begin(), lower_input.end(), lower_input.begin(), ::tolower);
|
|
|
|
|
+
|
|
|
|
|
+ if (lower_candidate == lower_input) return 95;
|
|
|
|
|
+ if (lower_candidate.starts_with(lower_input)) return 85;
|
|
|
|
|
+
|
|
|
|
|
+ // Fuzzy matching - check if all input characters appear in order
|
|
|
|
|
+ size_t candidate_idx = 0;
|
|
|
|
|
+ size_t matched_chars = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (char c : lower_input) {
|
|
|
|
|
+ while (candidate_idx < lower_candidate.size() && lower_candidate[candidate_idx] != c) {
|
|
|
|
|
+ candidate_idx++;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (candidate_idx < lower_candidate.size()) {
|
|
|
|
|
+ matched_chars++;
|
|
|
|
|
+ candidate_idx++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (matched_chars == input.size()) {
|
|
|
|
|
+ // All characters matched, score based on ratio and position
|
|
|
|
|
+ int base_score = 40;
|
|
|
|
|
+ base_score += (matched_chars * 20) / input.size();
|
|
|
|
|
+ base_score += std::max(0, 20 - (int)candidate.size() + (int)input.size());
|
|
|
|
|
+ return std::min(80, base_score);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return 0; // No match
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool CommandSystem::fuzzy_match(const std::string& candidate, const std::string& input) {
|
|
|
|
|
+ return fuzzy_match_score(candidate, input) > 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void CommandSystem::register_builtin_commands() {
|
|
|
|
|
+ // File operations
|
|
|
|
|
+ register_command("save-buffer", "Save current buffer to file",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ if (core_.buffer().save()) {
|
|
|
|
|
+ return CommandResult(true, "Buffer saved: " + core_.buffer().name());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return CommandResult(false, "Failed to save buffer");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ register_command("find-file", "Open a file",
|
|
|
|
|
+ [this](const std::vector<std::string>& args) -> CommandResult {
|
|
|
|
|
+ if (args.empty()) {
|
|
|
|
|
+ core_.enter_find_file_mode();
|
|
|
|
|
+ return CommandResult(true, "Find file mode activated");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (core_.load_file(args[0])) {
|
|
|
|
|
+ return CommandResult(true, "Loaded: " + args[0]);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return CommandResult(false, "Failed to load: " + args[0]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Buffer operations
|
|
|
|
|
+ register_command("switch-to-buffer", "Switch to buffer",
|
|
|
|
|
+ [this](const std::vector<std::string>& args) -> CommandResult {
|
|
|
|
|
+ if (args.empty()) {
|
|
|
|
|
+ core_.enter_buffer_switch_mode();
|
|
|
|
|
+ return CommandResult(true, "Buffer switch mode activated");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (core_.switch_buffer_in_window(args[0])) {
|
|
|
|
|
+ return CommandResult(true, "Switched to: " + args[0]);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return CommandResult(false, "Buffer not found: " + args[0]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, {"switch-buffer"});
|
|
|
|
|
+
|
|
|
|
|
+ register_command("kill-buffer", "Close buffer",
|
|
|
|
|
+ [this](const std::vector<std::string>& args) -> CommandResult {
|
|
|
|
|
+ if (args.empty()) {
|
|
|
|
|
+ core_.enter_kill_buffer_mode();
|
|
|
|
|
+ return CommandResult(true, "Kill buffer mode activated");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (core_.close_buffer(args[0])) {
|
|
|
|
|
+ return CommandResult(true, "Killed buffer: " + args[0]);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return CommandResult(false, "Cannot kill buffer: " + args[0]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ register_command("list-buffers", "List all open buffers",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ auto buffer_info = core_.get_all_buffer_info();
|
|
|
|
|
+ if (buffer_info.empty()) {
|
|
|
|
|
+ return CommandResult(true, "No buffers open");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::ostringstream ss;
|
|
|
|
|
+ ss << "Open buffers (" << buffer_info.size() << "):\n";
|
|
|
|
|
+ for (const auto& info : buffer_info) {
|
|
|
|
|
+ ss << " " << (info.modified ? "*" : " ") << info.name
|
|
|
|
|
+ << " (" << info.size << " chars)";
|
|
|
|
|
+ if (info.filepath) {
|
|
|
|
|
+ ss << " - " << info.filepath->string();
|
|
|
|
|
+ }
|
|
|
|
|
+ ss << "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return CommandResult(true, ss.str());
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Navigation
|
|
|
|
|
+ register_command("goto-line", "Go to line number",
|
|
|
|
|
+ [this](const std::vector<std::string>& args) -> CommandResult {
|
|
|
|
|
+ if (args.empty()) {
|
|
|
|
|
+ return CommandResult(false, "Line number required");
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ size_t line = std::stoull(args[0]);
|
|
|
|
|
+ core_.goto_line(line - 1); // Convert to 0-based
|
|
|
|
|
+ return CommandResult(true, "Moved to line " + args[0]);
|
|
|
|
|
+ } catch (...) {
|
|
|
|
|
+ return CommandResult(false, "Invalid line number: " + args[0]);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Window management
|
|
|
|
|
+ register_command("split-window-below", "Split window horizontally",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ core_.split_horizontally();
|
|
|
|
|
+ return CommandResult(true, "Window split horizontally");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ register_command("split-window-right", "Split window vertically",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ core_.split_vertically();
|
|
|
|
|
+ return CommandResult(true, "Window split vertically");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ register_command("delete-window", "Close current window",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ core_.close_active_window();
|
|
|
|
|
+ return CommandResult(true, "Window closed");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ register_command("other-window", "Switch to next window",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ core_.next_window_safe();
|
|
|
|
|
+ return CommandResult(true, "Switched to other window");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Editing
|
|
|
|
|
+ register_command("undo", "Undo last change",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ if (core_.undo()) {
|
|
|
|
|
+ return CommandResult(true, "Undid last change");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return CommandResult(false, "Nothing to undo");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ register_command("redo", "Redo last undo",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ if (core_.redo()) {
|
|
|
|
|
+ return CommandResult(true, "Redid last change");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return CommandResult(false, "Nothing to redo");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // System
|
|
|
|
|
+ register_command("quit", "Quit editor",
|
|
|
|
|
+ [this](const std::vector<std::string>&) -> CommandResult {
|
|
|
|
|
+ core_.request_quit();
|
|
|
|
|
+ return CommandResult(true, "Quitting...");
|
|
|
|
|
+ }, {"q", "exit"});
|
|
|
|
|
+
|
|
|
|
|
+ register_command("eval", "Evaluate Lua code",
|
|
|
|
|
+ [this](const std::vector<std::string>& args) -> CommandResult {
|
|
|
|
|
+ if (args.empty()) {
|
|
|
|
|
+ return CommandResult(false, "Lua code required");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string code = args[0];
|
|
|
|
|
+ for (size_t i = 1; i < args.size(); ++i) {
|
|
|
|
|
+ code += " " + args[i];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (core_.lua_api() && core_.lua_api()->execute(code)) {
|
|
|
|
|
+ return CommandResult(true, "Lua code executed");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return CommandResult(false, "Lua code execution failed");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<std::string> CommandSystem::parse_command_string(const std::string& command_string) const {
|
|
|
|
|
+ std::vector<std::string> parts;
|
|
|
|
|
+ std::istringstream iss(command_string);
|
|
|
|
|
+ std::string part;
|
|
|
|
|
+
|
|
|
|
|
+ while (iss >> std::quoted(part) || iss >> part) {
|
|
|
|
|
+ parts.push_back(part);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return parts;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+} // namespace lumacs
|