diff --git a/.idea/workspace.xml b/.idea/workspace.xml index fc4192b..e543175 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -34,40 +34,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/Buffer.cc b/Buffer.cc index b2f57e2..b2fec81 100644 --- a/Buffer.cc +++ b/Buffer.cc @@ -139,4 +139,4 @@ Buffer::AsString() const } ss << ">: " << rows_.size() << " lines"; return ss.str(); -} \ No newline at end of file +} diff --git a/GUIInputHandler.cc b/GUIInputHandler.cc index f049df6..30c1e91 100644 --- a/GUIInputHandler.cc +++ b/GUIInputHandler.cc @@ -7,11 +7,12 @@ static bool map_key(const SDL_Keycode key, const SDL_Keymod mod, bool &k_prefix, MappedInput &out) { - // Ctrl handling - const bool is_ctrl = (mod & KMOD_CTRL) != 0; + // Ctrl handling + const bool is_ctrl = (mod & KMOD_CTRL) != 0; + const bool is_alt = (mod & (KMOD_ALT | KMOD_LALT | KMOD_RALT)) != 0; - // Movement and basic keys - switch (key) { + // Movement and basic keys + switch (key) { case SDLK_LEFT: out = {true, CommandId::MoveLeft, "", 0}; return true; @@ -27,12 +28,18 @@ map_key(const SDL_Keycode key, const SDL_Keymod mod, bool &k_prefix, MappedInput case SDLK_HOME: out = {true, CommandId::MoveHome, "", 0}; return true; - case SDLK_END: - out = {true, CommandId::MoveEnd, "", 0}; - return true; - case SDLK_DELETE: - out = {true, CommandId::DeleteChar, "", 0}; - return true; + case SDLK_END: + out = {true, CommandId::MoveEnd, "", 0}; + return true; + case SDLK_PAGEUP: + out = {true, CommandId::PageUp, "", 0}; + return true; + case SDLK_PAGEDOWN: + out = {true, CommandId::PageDown, "", 0}; + return true; + case SDLK_DELETE: + out = {true, CommandId::DeleteChar, "", 0}; + return true; case SDLK_BACKSPACE: out = {true, CommandId::Backspace, "", 0}; return true; @@ -48,33 +55,53 @@ map_key(const SDL_Keycode key, const SDL_Keymod mod, bool &k_prefix, MappedInput break; } - if (is_ctrl) { - switch (key) { - case SDLK_k: - case SDLK_KP_EQUALS: // treat Ctrl-K - k_prefix = true; - out = {true, CommandId::KPrefix, "", 0}; - return true; - case SDLK_g: - k_prefix = false; - out = {true, CommandId::Refresh, "", 0}; - return true; - case SDLK_l: - out = {true, CommandId::Refresh, "", 0}; - return true; - case SDLK_s: - out = {true, CommandId::FindStart, "", 0}; - return true; - case SDLK_q: - out = {true, CommandId::Quit, "", 0}; - return true; - case SDLK_x: - out = {true, CommandId::SaveAndQuit, "", 0}; - return true; - default: - break; - } - } + if (is_ctrl) { + switch (key) { + case SDLK_k: + case SDLK_KP_EQUALS: // treat Ctrl-K + k_prefix = true; + out = {true, CommandId::KPrefix, "", 0}; + return true; + case SDLK_a: + out = {true, CommandId::MoveHome, "", 0}; + return true; + case SDLK_e: + out = {true, CommandId::MoveEnd, "", 0}; + return true; + case SDLK_g: + k_prefix = false; + out = {true, CommandId::Refresh, "", 0}; + return true; + case SDLK_l: + out = {true, CommandId::Refresh, "", 0}; + return true; + case SDLK_s: + out = {true, CommandId::FindStart, "", 0}; + return true; + case SDLK_q: + out = {true, CommandId::Quit, "", 0}; + return true; + case SDLK_x: + out = {true, CommandId::SaveAndQuit, "", 0}; + return true; + default: + break; + } + } + + // Alt/Meta bindings (ESC f/b equivalent) + if (is_alt) { + switch (key) { + case SDLK_b: + out = {true, CommandId::WordPrev, "", 0}; + return true; + case SDLK_f: + out = {true, CommandId::WordNext, "", 0}; + return true; + default: + break; + } + } if (k_prefix) { k_prefix = false; diff --git a/GUIRenderer.cc b/GUIRenderer.cc index 029aa28..42f3e19 100644 --- a/GUIRenderer.cc +++ b/GUIRenderer.cc @@ -6,6 +6,8 @@ #include #include +#include +#include // Version string expected to be provided by build system as KTE_VERSION_STR #ifndef KTE_VERSION_STR @@ -151,47 +153,98 @@ GUIRenderer::Draw(Editor &ed) ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col); } } - ImGui::EndChild(); + ImGui::EndChild(); - // Status bar spanning full width - ImGui::Separator(); - // Build status string: "kge v | *" - const char *fname = (buf->IsFileBacked()) ? buf->Filename().c_str() : "(new)"; - bool dirty = buf->Dirty(); - int row1 = static_cast(buf->Cury()) + 1; - int col1 = static_cast(buf->Curx()) + 1; - bool have_mark = buf->MarkSet(); - int mrow1 = have_mark ? static_cast(buf->MarkCury()) + 1 : 0; - int mcol1 = have_mark ? static_cast(buf->MarkCurx()) + 1 : 0; - char left[512]; - if (have_mark) { - std::snprintf(left, sizeof(left), " kge %s | %s%s | %d:%d | mk %d:%d ", - KTE_VERSION_STR, fname, dirty ? "*" : "", row1, col1, mrow1, mcol1); - } else { - std::snprintf(left, sizeof(left), " kge %s | %s%s | %d:%d ", - KTE_VERSION_STR, fname, dirty ? "*" : "", row1, col1); - } + // Status bar spanning full width + ImGui::Separator(); - // Compute full content width and draw a filled background rectangle - ImVec2 win_pos = ImGui::GetWindowPos(); - ImVec2 cr_min = ImGui::GetWindowContentRegionMin(); - ImVec2 cr_max = ImGui::GetWindowContentRegionMax(); - float x0 = win_pos.x + cr_min.x; - float x1 = win_pos.x + cr_max.x; - ImVec2 cursor = ImGui::GetCursorScreenPos(); - float bar_h = ImGui::GetFrameHeight(); - ImVec2 p0(x0, cursor.y); - ImVec2 p1(x1, cursor.y + bar_h); - ImU32 bg_col = ImGui::GetColorU32(ImGuiCol_HeaderActive); - ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col); - // Place status text within the bar - // Draw status text (left-aligned) - ImVec2 left_sz = ImGui::CalcTextSize(left); - ImGui::SetCursorScreenPos(ImVec2(p0.x + 6.f, p0.y + (bar_h - left_sz.y) * 0.5f)); - ImGui::TextUnformatted(left); - // Advance cursor to after the bar to keep layout consistent - ImGui::Dummy(ImVec2(x1 - x0, bar_h)); - } + // Build three segments: left (app/version/buffer/dirty), middle (message), right (cursor/mark) + // Compute full content width and draw a filled background rectangle + ImVec2 win_pos = ImGui::GetWindowPos(); + ImVec2 cr_min = ImGui::GetWindowContentRegionMin(); + ImVec2 cr_max = ImGui::GetWindowContentRegionMax(); + float x0 = win_pos.x + cr_min.x; + float x1 = win_pos.x + cr_max.x; + ImVec2 cursor = ImGui::GetCursorScreenPos(); + float bar_h = ImGui::GetFrameHeight(); + ImVec2 p0(x0, cursor.y); + ImVec2 p1(x1, cursor.y + bar_h); + ImU32 bg_col = ImGui::GetColorU32(ImGuiCol_HeaderActive); + ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col); + // Build left text + std::string left; + left.reserve(256); + left += "kge"; // GUI app name + left += " "; + left += KTE_VERSION_STR; + std::string fname = buf->Filename(); + if (!fname.empty()) { + try { fname = std::filesystem::path(fname).filename().string(); } catch (...) {} + } else { + fname = "[no name]"; + } + left += " "; + left += fname; + if (buf->Dirty()) left += " *"; + + // Build right text (cursor/mark) + int row1 = static_cast(buf->Cury()) + 1; + int col1 = static_cast(buf->Curx()) + 1; + bool have_mark = buf->MarkSet(); + int mrow1 = have_mark ? static_cast(buf->MarkCury()) + 1 : 0; + int mcol1 = have_mark ? static_cast(buf->MarkCurx()) + 1 : 0; + char rbuf[128]; + if (have_mark) std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: %d,%d", row1, col1, mrow1, mcol1); + else std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: not set", row1, col1); + std::string right = rbuf; + + // Middle message + const std::string &msg = ed.Status(); + + // Measurements + ImVec2 left_sz = ImGui::CalcTextSize(left.c_str()); + ImVec2 right_sz = ImGui::CalcTextSize(right.c_str()); + float pad = 6.f; + float left_x = p0.x + pad; + float right_x = p1.x - pad - right_sz.x; + if (right_x < left_x + left_sz.x + pad) { + // Not enough room; clip left to fit + float max_left = std::max(0.0f, right_x - left_x - pad); + if (max_left < left_sz.x && max_left > 10.0f) { + // Render a clipped left using a child region + ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - left_sz.y) * 0.5f)); + ImGui::PushClipRect(ImVec2(left_x, p0.y), ImVec2(right_x - pad, p1.y), true); + ImGui::TextUnformatted(left.c_str()); + ImGui::PopClipRect(); + } + } else { + // Draw left normally + ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - left_sz.y) * 0.5f)); + ImGui::TextUnformatted(left.c_str()); + } + + // Draw right + ImGui::SetCursorScreenPos(ImVec2(std::max(right_x, left_x), p0.y + (bar_h - right_sz.y) * 0.5f)); + ImGui::TextUnformatted(right.c_str()); + + // Draw middle message centered in remaining space + if (!msg.empty()) { + float mid_left = left_x + left_sz.x + pad; + float mid_right = std::max(right_x - pad, mid_left); + float mid_w = std::max(0.0f, mid_right - mid_left); + if (mid_w > 1.0f) { + ImVec2 msg_sz = ImGui::CalcTextSize(msg.c_str()); + float msg_x = mid_left + std::max(0.0f, (mid_w - msg_sz.x) * 0.5f); + // Clip to middle region + ImGui::PushClipRect(ImVec2(mid_left, p0.y), ImVec2(mid_right, p1.y), true); + ImGui::SetCursorScreenPos(ImVec2(msg_x, p0.y + (bar_h - msg_sz.y) * 0.5f)); + ImGui::TextUnformatted(msg.c_str()); + ImGui::PopClipRect(); + } + } + // Advance cursor to after the bar to keep layout consistent + ImGui::Dummy(ImVec2(x1 - x0, bar_h)); + } ImGui::End(); ImGui::PopStyleVar(3); diff --git a/TerminalRenderer.cc b/TerminalRenderer.cc index 875a6f2..3ec5467 100644 --- a/TerminalRenderer.cc +++ b/TerminalRenderer.cc @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include "Editor.h" #include "Buffer.h" @@ -23,8 +26,8 @@ TerminalRenderer::Draw(Editor &ed) int rows, cols; getmaxyx(stdscr, rows, cols); - // Clear screen - erase(); + // Clear screen + erase(); const Buffer *buf = ed.CurrentBuffer(); int content_rows = rows - 1; // last line is status @@ -140,44 +143,99 @@ TerminalRenderer::Draw(Editor &ed) } } else { mvaddstr(0, 0, "[no buffer]"); - } + } - // Status line (inverse) - move(rows - 1, 0); - attron(A_REVERSE); - char status[1024]; - const char *fname = (buf && buf->IsFileBacked()) ? buf->Filename().c_str() : "(new)"; - int dirty = (buf && buf->Dirty()) ? 1 : 0; - // New compact status: "kte v | * | row:col [mk row:col]" - int row1 = 0, col1 = 0; - int mrow1 = 0, mcol1 = 0; - int have_mark = 0; - if (buf) { - row1 = static_cast(buf->Cury()) + 1; - col1 = static_cast(buf->Curx()) + 1; - if (buf->MarkSet()) { - have_mark = 1; - mrow1 = static_cast(buf->MarkCury()) + 1; - mcol1 = static_cast(buf->MarkCurx()) + 1; + // Status line (inverse) — left: app/version/buffer/dirty, middle: message, right: cursor/mark + move(rows - 1, 0); + attron(A_REVERSE); + + // Fill the status line with spaces first + for (int i = 0; i < cols; ++i) addch(' '); + + // Build left segment + std::string left; + { + const char *app = "kte"; + left.reserve(256); + left += app; + left += " "; + left += KTE_VERSION_STR; // already includes leading 'v' + const Buffer *b = buf; + std::string fname; + if (b) { + fname = b->Filename(); + } + if (!fname.empty()) { + try { + fname = std::filesystem::path(fname).filename().string(); + } catch (...) { + // keep original on any error + } + } else { + fname = "[no name]"; + } + left += " "; + left += fname; + if (b && b->Dirty()) + left += " *"; + } + + // Build right segment (cursor and mark) + std::string right; + { + int row1 = 0, col1 = 0; + int mrow1 = 0, mcol1 = 0; + bool have_mark = false; + if (buf) { + row1 = static_cast(buf->Cury()) + 1; + col1 = static_cast(buf->Curx()) + 1; + if (buf->MarkSet()) { + have_mark = true; + mrow1 = static_cast(buf->MarkCury()) + 1; + mcol1 = static_cast(buf->MarkCurx()) + 1; + } + } + char rbuf[128]; + if (have_mark) + std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: %d,%d", row1, col1, mrow1, mcol1); + else + std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: not set", row1, col1); + right = rbuf; + } + + // Compute placements with truncation rules: prioritize left and right; middle gets remaining + int rlen = static_cast(right.size()); + if (rlen > cols) { + // Hard clip right if too long + right = right.substr(static_cast(rlen - cols), static_cast(cols)); + rlen = cols; + } + int left_max = std::max(0, cols - rlen - 1); // leave at least 1 space between left and right areas + int llen = static_cast(left.size()); + if (llen > left_max) llen = left_max; + + // Draw left + if (llen > 0) mvaddnstr(rows - 1, 0, left.c_str(), llen); + + // Draw right, flush to end + int rstart = std::max(0, cols - rlen); + if (rlen > 0) mvaddnstr(rows - 1, rstart, right.c_str(), rlen); + + // Middle message + const std::string &msg = ed.Status(); + if (!msg.empty()) { + int mid_start = llen + 1; // one space after left + int mid_end = rstart - 1; // one space before right + if (mid_end >= mid_start) { + int avail = mid_end - mid_start + 1; + int mlen = static_cast(msg.size()); + int mdraw = std::min(avail, mlen); + int mstart = mid_start + std::max(0, (avail - mdraw) / 2); // center within middle area + mvaddnstr(rows - 1, mstart, msg.c_str(), mdraw); } } - if (have_mark) { - snprintf(status, sizeof(status), " kte %s | %s%s | %d:%d | mk %d:%d ", - KTE_VERSION_STR, - fname, - dirty ? "*" : "", - row1, col1, - mrow1, mcol1); - } else { - snprintf(status, sizeof(status), " kte %s | %s%s | %d:%d ", - KTE_VERSION_STR, - fname, - dirty ? "*" : "", - row1, col1); - } - addnstr(status, cols); - clrtoeol(); - attroff(A_REVERSE); - refresh(); + attroff(A_REVERSE); + + refresh(); }