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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
@@ -172,7 +143,7 @@
1764457173148
-
+
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();
}