#include #include #include #include "Editor.h" #include "syntax/HighlighterRegistry.h" #include "syntax/CppHighlighter.h" #include "syntax/NullHighlighter.h" Editor::Editor() { swap_ = std::make_unique(); } void Editor::SetDimensions(std::size_t rows, std::size_t cols) { rows_ = rows; cols_ = cols; } void Editor::SetStatus(const std::string &message) { msg_ = message; msgtm_ = std::time(nullptr); } Buffer * Editor::CurrentBuffer() { if (buffers_.empty() || curbuf_ >= buffers_.size()) { return nullptr; } return &buffers_[curbuf_]; } const Buffer * Editor::CurrentBuffer() const { if (buffers_.empty() || curbuf_ >= buffers_.size()) { return nullptr; } return &buffers_[curbuf_]; } static std::vector split_reverse(const std::filesystem::path &p) { std::vector parts; for (auto it = p; !it.empty(); it = it.parent_path()) { if (it == it.parent_path()) { // root or single element if (!it.empty()) parts.push_back(it); break; } parts.push_back(it.filename()); } return parts; // from leaf toward root } std::string Editor::DisplayNameFor(const Buffer &buf) const { std::string full = buf.Filename(); if (full.empty()) return std::string("[no name]"); std::filesystem::path target(full); auto target_parts = split_reverse(target); if (target_parts.empty()) return target.filename().string(); // Prepare list of other buffer paths std::vector > others; others.reserve(buffers_.size()); for (const auto &b: buffers_) { if (&b == &buf) continue; if (b.Filename().empty()) continue; others.push_back(split_reverse(std::filesystem::path(b.Filename()))); } // Increase suffix length until unique among others std::size_t need = 1; // at least basename for (;;) { // Build candidate suffix for target std::filesystem::path cand; for (std::size_t i = 0; i < need && i < target_parts.size(); ++i) { cand = std::filesystem::path(target_parts[i]) / cand; } // Compare against others bool clash = false; for (const auto &o_parts: others) { std::filesystem::path ocand; for (std::size_t i = 0; i < need && i < o_parts.size(); ++i) { ocand = std::filesystem::path(o_parts[i]) / ocand; } if (ocand == cand) { clash = true; break; } } if (!clash || need >= target_parts.size()) { std::string s = cand.string(); // Remove any trailing slash that may appear from root joining if (!s.empty() && (s.back() == '/' || s.back() == '\\')) s.pop_back(); return s; } ++need; } } std::size_t Editor::AddBuffer(const Buffer &buf) { buffers_.push_back(buf); // Attach swap recorder if (swap_) { buffers_.back().SetSwapRecorder(swap_.get()); swap_->Attach(&buffers_.back()); } if (buffers_.size() == 1) { curbuf_ = 0; } return buffers_.size() - 1; } std::size_t Editor::AddBuffer(Buffer &&buf) { buffers_.push_back(std::move(buf)); if (swap_) { buffers_.back().SetSwapRecorder(swap_.get()); swap_->Attach(&buffers_.back()); } if (buffers_.size() == 1) { curbuf_ = 0; } return buffers_.size() - 1; } bool Editor::OpenFile(const std::string &path, std::string &err) { // If there is exactly one unnamed, empty, clean buffer, reuse it instead // of creating a new one. if (buffers_.size() == 1) { Buffer &cur = buffers_[curbuf_]; const bool unnamed = cur.Filename().empty() && !cur.IsFileBacked(); const bool clean = !cur.Dirty(); const auto &rows = cur.Rows(); const bool rows_empty = rows.empty(); const bool single_empty_line = (!rows.empty() && rows.size() == 1 && rows[0].size() == 0); if (unnamed && clean && (rows_empty || single_empty_line)) { bool ok = cur.OpenFromFile(path, err); if (!ok) return false; // Ensure swap recorder is attached for this buffer if (swap_) { cur.SetSwapRecorder(swap_.get()); swap_->Attach(&cur); swap_->NotifyFilenameChanged(cur); } // Setup highlighting using registry (extension + shebang) cur.EnsureHighlighter(); std::string first = ""; const auto &rows = cur.Rows(); if (!rows.empty()) first = static_cast(rows[0]); std::string ft = kte::HighlighterRegistry::DetectForPath(path, first); if (!ft.empty()) { cur.SetFiletype(ft); cur.SetSyntaxEnabled(true); if (auto *eng = cur.Highlighter()) { eng->SetHighlighter(kte::HighlighterRegistry::CreateFor(ft)); eng->InvalidateFrom(0); } } else { cur.SetFiletype(""); cur.SetSyntaxEnabled(true); if (auto *eng = cur.Highlighter()) { eng->SetHighlighter(std::make_unique()); eng->InvalidateFrom(0); } } // Defensive: ensure any active prompt is closed after a successful open CancelPrompt(); return true; } } Buffer b; if (!b.OpenFromFile(path, err)) { return false; } if (swap_) { b.SetSwapRecorder(swap_.get()); // path is known, notify swap_->Attach(&b); swap_->NotifyFilenameChanged(b); } // Initialize syntax highlighting by extension + shebang via registry (v2) b.EnsureHighlighter(); std::string first = ""; { const auto &rows = b.Rows(); if (!rows.empty()) first = static_cast(rows[0]); } std::string ft = kte::HighlighterRegistry::DetectForPath(path, first); if (!ft.empty()) { b.SetFiletype(ft); b.SetSyntaxEnabled(true); if (auto *eng = b.Highlighter()) { eng->SetHighlighter(kte::HighlighterRegistry::CreateFor(ft)); eng->InvalidateFrom(0); } } else { b.SetFiletype(""); b.SetSyntaxEnabled(true); if (auto *eng = b.Highlighter()) { eng->SetHighlighter(std::make_unique()); eng->InvalidateFrom(0); } } // Add as a new buffer and switch to it std::size_t idx = AddBuffer(std::move(b)); SwitchTo(idx); // Defensive: ensure any active prompt is closed after a successful open CancelPrompt(); return true; } bool Editor::SwitchTo(std::size_t index) { if (index >= buffers_.size()) { return false; } curbuf_ = index; // Robustness: ensure a valid highlighter is installed when switching buffers Buffer &b = buffers_[curbuf_]; if (b.SyntaxEnabled()) { b.EnsureHighlighter(); if (auto *eng = b.Highlighter()) { if (!eng->HasHighlighter()) { // Try to set based on existing filetype; fall back to NullHighlighter if (!b.Filetype().empty()) { auto hl = kte::HighlighterRegistry::CreateFor(b.Filetype()); if (hl) { eng->SetHighlighter(std::move(hl)); } else { eng->SetHighlighter(std::make_unique()); } } else { eng->SetHighlighter(std::make_unique()); } eng->InvalidateFrom(0); } } } return true; } bool Editor::CloseBuffer(std::size_t index) { if (index >= buffers_.size()) { return false; } buffers_.erase(buffers_.begin() + static_cast(index)); if (buffers_.empty()) { curbuf_ = 0; } else if (curbuf_ >= buffers_.size()) { curbuf_ = buffers_.size() - 1; } return true; } void Editor::Reset() { rows_ = cols_ = 0; mode_ = 0; kill_ = 0; no_kill_ = 0; dirtyex_ = 0; msg_.clear(); msgtm_ = 0; uarg_ = 0; ucount_ = 0; repeatable_ = false; quit_requested_ = false; quit_confirm_pending_ = false; // Reset close-confirm/save state close_confirm_pending_ = false; close_after_save_ = false; buffers_.clear(); curbuf_ = 0; } // --- Universal argument helpers --- void Editor::UArgStart() { // If not active, start fresh; else multiply by 4 per ke semantics if (uarg_ == 0) { ucount_ = 0; } else { if (ucount_ == 0) { ucount_ = 1; } ucount_ *= 4; } uarg_ = 1; char buf[64]; std::snprintf(buf, sizeof(buf), "C-u %d", ucount_); SetStatus(buf); } void Editor::UArgDigit(int d) { if (d < 0) d = 0; if (d > 9) d = 9; if (uarg_ == 0) { uarg_ = 1; ucount_ = 0; } ucount_ = ucount_ * 10 + d; char buf[64]; std::snprintf(buf, sizeof(buf), "C-u %d", ucount_); SetStatus(buf); } void Editor::UArgClear() { uarg_ = 0; ucount_ = 0; } int Editor::UArgGet() { int n = (ucount_ > 0) ? ucount_ : 1; UArgClear(); return n; }