|
|
@@ -52,6 +52,9 @@ private:
|
|
|
EditorCore& core_;
|
|
|
};
|
|
|
|
|
|
+// Include cstdlib for std::getenv
|
|
|
+#include <cstdlib>
|
|
|
+
|
|
|
/// @brief Completion source for file paths.
|
|
|
class FilePathCompletionSource : public ICompletionSource {
|
|
|
public:
|
|
|
@@ -59,46 +62,101 @@ public:
|
|
|
|
|
|
std::vector<CompletionCandidate> get_candidates(const std::string& input_text) override {
|
|
|
std::vector<CompletionCandidate> candidates;
|
|
|
- std::filesystem::path current_path = input_text;
|
|
|
+ std::string expanded_input = expand_tilde(input_text);
|
|
|
|
|
|
- // If input ends with /, list contents of that directory
|
|
|
- if (input_text.empty() || input_text.back() == '/') {
|
|
|
- try {
|
|
|
- for (const auto& entry : std::filesystem::directory_iterator(current_path)) {
|
|
|
- std::string path_str = entry.path().string();
|
|
|
- if (entry.is_directory()) {
|
|
|
- path_str += "/";
|
|
|
- }
|
|
|
- candidates.emplace_back(path_str);
|
|
|
- }
|
|
|
- } catch (const std::filesystem::filesystem_error& e) {
|
|
|
- // Ignore errors like "No such file or directory"
|
|
|
- }
|
|
|
+ std::filesystem::path path_obj(expanded_input);
|
|
|
+ std::filesystem::path dir;
|
|
|
+ std::string prefix;
|
|
|
+
|
|
|
+ // Determine directory to search and prefix to match
|
|
|
+ if (input_text.empty()) {
|
|
|
+ dir = std::filesystem::current_path();
|
|
|
+ prefix = "";
|
|
|
+ } else if (input_text.back() == '/') {
|
|
|
+ dir = expanded_input;
|
|
|
+ prefix = "";
|
|
|
} else {
|
|
|
- // Find files/dirs that start with the input_text in the parent directory
|
|
|
- std::filesystem::path parent_path = current_path.parent_path();
|
|
|
- std::string stem = current_path.filename().string();
|
|
|
+ // If expanded_input is just a directory (like "/tmp"), parent_path is "/" and filename is "tmp".
|
|
|
+ // But we want to search INSIDE /tmp if the user typed "/tmp/" (handled above).
|
|
|
+ // If user typed "/tmp", we search in "/" for "tmp*".
|
|
|
+ // The filesystem::path parsing handles this:
|
|
|
+ // path("/tmp").parent_path() -> "/"
|
|
|
+ // path("/tmp").filename() -> "tmp"
|
|
|
+
|
|
|
+ dir = path_obj.parent_path();
|
|
|
+ prefix = path_obj.filename().string();
|
|
|
|
|
|
- try {
|
|
|
- for (const auto& entry : std::filesystem::directory_iterator(parent_path)) {
|
|
|
- std::string entry_name = entry.path().filename().string();
|
|
|
- if (entry_name.rfind(stem, 0) == 0) {
|
|
|
- std::string full_path = entry.path().string();
|
|
|
+ // Handle relative paths (empty parent means current dir, unless it's root)
|
|
|
+ if (dir.empty()) {
|
|
|
+ if (path_obj.is_absolute()) {
|
|
|
+ // Should typically not happen for unix paths unless it is root?
|
|
|
+ // path("/").parent_path() is "/".
|
|
|
+ } else {
|
|
|
+ dir = std::filesystem::current_path();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Check if directory exists
|
|
|
+ if (std::filesystem::exists(dir) && std::filesystem::is_directory(dir)) {
|
|
|
+ for (const auto& entry : std::filesystem::directory_iterator(dir)) {
|
|
|
+ std::string filename = entry.path().filename().string();
|
|
|
+
|
|
|
+ // Skip hidden files unless prefix starts with '.'
|
|
|
+ if (filename.size() > 0 && filename[0] == '.' && (prefix.empty() || prefix[0] != '.')) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check prefix match
|
|
|
+ if (filename.rfind(prefix, 0) == 0) {
|
|
|
+ // Reconstruct the completion text based on ORIGINAL input
|
|
|
+ std::string result_str;
|
|
|
+
|
|
|
+ if (input_text.empty()) {
|
|
|
+ result_str = filename;
|
|
|
+ } else if (input_text.back() == '/') {
|
|
|
+ result_str = input_text + filename;
|
|
|
+ } else {
|
|
|
+ // Replace the partial filename in input with the full match
|
|
|
+ size_t last_sep = input_text.find_last_of('/');
|
|
|
+ if (last_sep != std::string::npos) {
|
|
|
+ result_str = input_text.substr(0, last_sep + 1) + filename;
|
|
|
+ } else {
|
|
|
+ result_str = filename;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (entry.is_directory()) {
|
|
|
- full_path += "/";
|
|
|
+ result_str += "/";
|
|
|
}
|
|
|
- candidates.emplace_back(full_path);
|
|
|
+
|
|
|
+ candidates.emplace_back(result_str);
|
|
|
}
|
|
|
}
|
|
|
- } catch (const std::filesystem::filesystem_error& e) {
|
|
|
- // Ignore errors
|
|
|
}
|
|
|
+ } catch (...) {
|
|
|
+ // Ignore filesystem errors (permissions, etc.)
|
|
|
}
|
|
|
+
|
|
|
return candidates;
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
- [[maybe_unused]] EditorCore& core_; // Reserved for future filesystem operations
|
|
|
+ [[maybe_unused]] EditorCore& core_;
|
|
|
+
|
|
|
+ std::string expand_tilde(const std::string& path) {
|
|
|
+ if (path.empty()) return path;
|
|
|
+ if (path[0] == '~') {
|
|
|
+ const char* home = std::getenv("HOME");
|
|
|
+ if (home) {
|
|
|
+ // Handle "~" -> "/home/user" and "~/foo" -> "/home/user/foo"
|
|
|
+ if (path.length() == 1) return std::string(home);
|
|
|
+ if (path[1] == '/') return std::string(home) + path.substr(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return path;
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
/// @brief Completion source for theme names.
|