3 Commits

Author SHA1 Message Date
37472c71ec Fix UI cursor positioning issues.
Accurately recompute cursor position to prevent drift in terminal and GUI renderers.
2025-12-04 15:18:02 -08:00
5ff4b2ed3e Reflow-paragraph is fixed.
- Forgot to check whether the universal argument value (1 by default), so it was trying to reflow to column 1.
- Minor formatting fixups.
2025-12-04 15:14:30 -08:00
ab2f9918f3 fix build on nixos 2025-12-04 13:11:43 -08:00
5 changed files with 71 additions and 14 deletions

View File

@@ -4,7 +4,7 @@ project(kte)
include(GNUInstallDirs) include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(KTE_VERSION "1.3.8") set(KTE_VERSION "1.3.9")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default. # Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available. # Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.

View File

@@ -3772,7 +3772,9 @@ cmd_reflow_paragraph(CommandContext &ctx)
ensure_at_least_one_line(*buf); ensure_at_least_one_line(*buf);
auto &rows = buf->Rows(); auto &rows = buf->Rows();
std::size_t y = buf->Cury(); std::size_t y = buf->Cury();
int width = ctx.count > 0 ? ctx.count : 72; // Treat a universal-argument count of 1 as "no width specified".
// Editor::UArgGet() returns 1 when no explicit count was provided.
int width = ctx.count > 1 ? ctx.count : 72;
std::size_t para_start = y; std::size_t para_start = y;
while (para_start > 0 && !rows[para_start - 1].empty()) while (para_start > 0 && !rows[para_start - 1].empty())
--para_start; --para_start;
@@ -4199,8 +4201,9 @@ InstallDefaultCommands()
CommandRegistry::Register( CommandRegistry::Register(
{CommandId::UnindentRegion, "unindent-region", "Unindent region", cmd_unindent_region}); {CommandId::UnindentRegion, "unindent-region", "Unindent region", cmd_unindent_region});
CommandRegistry::Register({ CommandRegistry::Register({
CommandId::ReflowParagraph, "reflow-paragraph", "Reflow paragraph to column width", cmd_reflow_paragraph CommandId::ReflowParagraph, "reflow-paragraph",
}); "Reflow paragraph to column width", cmd_reflow_paragraph
});
// Read-only // Read-only
CommandRegistry::Register({ CommandRegistry::Register({
CommandId::ToggleReadOnly, "toggle-read-only", "Toggle buffer read-only", cmd_toggle_read_only CommandId::ToggleReadOnly, "toggle-read-only", "Toggle buffer read-only", cmd_toggle_read_only

View File

@@ -455,7 +455,19 @@ GUIRenderer::Draw(Editor &ed)
} }
// Convert to viewport x by subtracting horizontal col offset // Convert to viewport x by subtracting horizontal col offset
std::size_t rx_viewport = (rx_abs > coloffs_now) ? (rx_abs - coloffs_now) : 0; std::size_t rx_viewport = (rx_abs > coloffs_now) ? (rx_abs - coloffs_now) : 0;
ImVec2 p0 = ImVec2(line_pos.x + static_cast<float>(rx_viewport) * space_w, line_pos.y); // For proportional fonts (Linux GUI), avoid accumulating drift by computing
// the exact pixel width of the expanded substring up to the cursor.
// expanded contains the line with tabs expanded to spaces and is what we draw.
float cursor_px = 0.0f;
if (rx_viewport > 0 && coloffs_now < expanded.size()) {
std::size_t start = coloffs_now;
std::size_t end = std::min(expanded.size(), start + rx_viewport);
// Measure substring width in pixels
ImVec2 sz = ImGui::CalcTextSize(expanded.c_str() + start,
expanded.c_str() + end);
cursor_px = sz.x;
}
ImVec2 p0 = ImVec2(line_pos.x + cursor_px, line_pos.y);
ImVec2 p1 = ImVec2(p0.x + space_w, p0.y + line_h); ImVec2 p1 = ImVec2(p0.x + space_w, p0.y + line_h);
ImU32 col = IM_COL32(200, 200, 255, 128); // soft highlight ImU32 col = IM_COL32(200, 200, 255, 128); // soft highlight
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col); ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);

32
Swap.cc
View File

@@ -9,6 +9,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <cerrno>
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -16,6 +17,25 @@ namespace kte {
namespace { namespace {
constexpr std::uint8_t MAGIC[8] = {'K', 'T', 'E', '_', 'S', 'W', 'P', '\0'}; constexpr std::uint8_t MAGIC[8] = {'K', 'T', 'E', '_', 'S', 'W', 'P', '\0'};
constexpr std::uint32_t VERSION = 1; constexpr std::uint32_t VERSION = 1;
// Write all bytes in buf to fd, handling EINTR and partial writes.
static bool write_full(int fd, const void *buf, size_t len)
{
const std::uint8_t *p = static_cast<const std::uint8_t *>(buf);
while (len > 0) {
ssize_t n = ::write(fd, p, len);
if (n < 0) {
if (errno == EINTR)
continue;
return false;
}
if (n == 0)
return false; // shouldn't happen for regular files; treat as error
p += static_cast<size_t>(n);
len -= static_cast<size_t>(n);
}
return true;
}
} }
@@ -403,10 +423,12 @@ SwapManager::process_one(const Pending &p)
if (!p.payload.empty()) if (!p.payload.empty())
c = crc32(p.payload.data(), p.payload.size(), c); c = crc32(p.payload.data(), p.payload.size(), c);
// Write // Write (handle partial writes and check results)
(void) ::write(ctx.fd, head, sizeof(head)); bool ok = write_full(ctx.fd, head, sizeof(head));
if (!p.payload.empty()) if (ok && !p.payload.empty())
(void) ::write(ctx.fd, p.payload.data(), p.payload.size()); ok = write_full(ctx.fd, p.payload.data(), p.payload.size());
(void) ::write(ctx.fd, &c, sizeof(c)); if (ok)
ok = write_full(ctx.fd, &c, sizeof(c));
(void) ok; // stage 1: best-effort; future work could mark ctx error state
} }
} // namespace kte } // namespace kte

View File

@@ -294,11 +294,31 @@ TerminalRenderer::Draw(Editor &ed)
clrtoeol(); clrtoeol();
} }
// Place terminal cursor at logical position accounting for tabs and coloffs // Place terminal cursor at logical position accounting for tabs and coloffs.
// Recompute the rendered X using the same logic as the drawing loop to avoid
// any drift between the command-layer computation and the terminal renderer.
std::size_t cy = buf->Cury(); std::size_t cy = buf->Cury();
std::size_t rx = buf->Rx(); // render x computed by command layer std::size_t cx = buf->Curx();
int cur_y = static_cast<int>(cy) - static_cast<int>(buf->Rowoffs()); int cur_y = static_cast<int>(cy) - static_cast<int>(buf->Rowoffs());
int cur_x = static_cast<int>(rx) - static_cast<int>(buf->Coloffs()); std::size_t rx_recomputed = 0;
if (cy < lines.size()) {
const std::string line_for_cursor = static_cast<std::string>(lines[cy]);
std::size_t src_i_cur = 0;
std::size_t render_col_cur = 0;
while (src_i_cur < line_for_cursor.size() && src_i_cur < cx) {
unsigned char ccur = static_cast<unsigned char>(line_for_cursor[src_i_cur]);
if (ccur == '\t') {
std::size_t next_tab = tabw - (render_col_cur % tabw);
render_col_cur += next_tab;
++src_i_cur;
} else {
++render_col_cur;
++src_i_cur;
}
}
rx_recomputed = render_col_cur;
}
int cur_x = static_cast<int>(rx_recomputed) - static_cast<int>(buf->Coloffs());
if (cur_y >= 0 && cur_y < content_rows && cur_x >= 0 && cur_x < cols) { if (cur_y >= 0 && cur_y < content_rows && cur_x >= 0 && cur_x < cols) {
// remember where to leave the terminal cursor after status is drawn // remember where to leave the terminal cursor after status is drawn
saved_cur_y = cur_y; saved_cur_y = cur_y;