fix modeline

This commit is contained in:
2025-11-29 20:34:09 -08:00
parent 57bfab633d
commit f7c6e3db9f
5 changed files with 258 additions and 149 deletions

39
.idea/workspace.xml generated
View File

@@ -34,40 +34,11 @@
</component>
<component name="ChangeListManager">
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/Font.h" afterDir="false" />
<change afterPath="$PROJECT_DIR$/GUIInputHandler.cc" afterDir="false" />
<change afterPath="$PROJECT_DIR$/TerminalRenderer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/codeStyles/Project.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/codeStyles/Project.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/editor.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/editor.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Buffer.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Command.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/Command.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Command.h" beforeDir="false" afterPath="$PROJECT_DIR$/Command.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Editor.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Editor.h" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Frontend.h" beforeDir="false" afterPath="$PROJECT_DIR$/Frontend.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIFrontend.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/GUIFrontend.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIFrontend.h" beforeDir="false" afterPath="$PROJECT_DIR$/GUIFrontend.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIInputHandler.cpp" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/GUIInputHandler.h" beforeDir="false" afterPath="$PROJECT_DIR$/GUIInputHandler.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIRenderer.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/GUIRenderer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIRenderer.h" beforeDir="false" afterPath="$PROJECT_DIR$/GUIRenderer.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GapBuffer.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/GapBuffer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GapBuffer.h" beforeDir="false" afterPath="$PROJECT_DIR$/GapBuffer.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/InputHandler.h" beforeDir="false" afterPath="$PROJECT_DIR$/InputHandler.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/KKeymap.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/KKeymap.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/KKeymap.h" beforeDir="false" afterPath="$PROJECT_DIR$/KKeymap.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/PieceTable.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/PieceTable.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ROADMAP.md" beforeDir="false" afterPath="$PROJECT_DIR$/ROADMAP.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Renderer.h" beforeDir="false" afterPath="$PROJECT_DIR$/Renderer.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalFrontend.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalFrontend.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalFrontend.h" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalFrontend.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalInputHandler.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalInputHandler.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalInputHandler.h" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalInputHandler.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalRenderer.cpp" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalRenderer.h" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalRenderer.h" afterDir="false" />
<change beforePath="$PROJECT_DIR$/main.cpp" beforeDir="false" afterPath="$PROJECT_DIR$/main.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Buffer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIInputHandler.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIInputHandler.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/GUIRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIRenderer.cc" afterDir="false" />
<change beforePath="$PROJECT_DIR$/TerminalRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalRenderer.cc" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -172,7 +143,7 @@
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1764457173148</updated>
<workItem from="1764457174208" duration="17712000" />
<workItem from="1764457174208" duration="18589000" />
</task>
<servers />
</component>

View File

@@ -139,4 +139,4 @@ Buffer::AsString() const
}
ss << ">: " << rows_.size() << " lines";
return ss.str();
}
}

View File

@@ -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;

View File

@@ -6,6 +6,8 @@
#include <imgui.h>
#include <cstdio>
#include <string>
#include <filesystem>
// 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<version> | <filename>*"
const char *fname = (buf->IsFileBacked()) ? buf->Filename().c_str() : "(new)";
bool dirty = buf->Dirty();
int row1 = static_cast<int>(buf->Cury()) + 1;
int col1 = static_cast<int>(buf->Curx()) + 1;
bool have_mark = buf->MarkSet();
int mrow1 = have_mark ? static_cast<int>(buf->MarkCury()) + 1 : 0;
int mcol1 = have_mark ? static_cast<int>(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<int>(buf->Cury()) + 1;
int col1 = static_cast<int>(buf->Curx()) + 1;
bool have_mark = buf->MarkSet();
int mrow1 = have_mark ? static_cast<int>(buf->MarkCury()) + 1 : 0;
int mcol1 = have_mark ? static_cast<int>(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);

View File

@@ -2,6 +2,9 @@
#include <ncurses.h>
#include <cstdio>
#include <string>
#include <algorithm>
#include <filesystem>
#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<version> | <filename>* | row:col [mk row:col]"
int row1 = 0, col1 = 0;
int mrow1 = 0, mcol1 = 0;
int have_mark = 0;
if (buf) {
row1 = static_cast<int>(buf->Cury()) + 1;
col1 = static_cast<int>(buf->Curx()) + 1;
if (buf->MarkSet()) {
have_mark = 1;
mrow1 = static_cast<int>(buf->MarkCury()) + 1;
mcol1 = static_cast<int>(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<int>(buf->Cury()) + 1;
col1 = static_cast<int>(buf->Curx()) + 1;
if (buf->MarkSet()) {
have_mark = true;
mrow1 = static_cast<int>(buf->MarkCury()) + 1;
mcol1 = static_cast<int>(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<int>(right.size());
if (rlen > cols) {
// Hard clip right if too long
right = right.substr(static_cast<std::size_t>(rlen - cols), static_cast<std::size_t>(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<int>(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<int>(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();
}