diff --git a/CMakeLists.txt b/CMakeLists.txt index a385fdd..ca489b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(kte) include(GNUInstallDirs) set(CMAKE_CXX_STANDARD 20) -set(KTE_VERSION "1.4.0") +set(KTE_VERSION "1.4.1") # Default to terminal-only build to avoid SDL/OpenGL dependency by default. # Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available. diff --git a/Command.cc b/Command.cc index 1b8c442..a50d378 100644 --- a/Command.cc +++ b/Command.cc @@ -109,6 +109,33 @@ ensure_cursor_visible(const Editor &ed, Buffer &buf) } +static bool +cmd_center_on_cursor(CommandContext &ctx) +{ + Buffer *buf = ctx.editor.CurrentBuffer(); + if (!buf) + return false; + const auto &rows = buf->Rows(); + std::size_t total = rows.size(); + std::size_t content = ctx.editor.ContentRows(); + if (content == 0) + content = 1; + std::size_t cy = buf->Cury(); + std::size_t half = content / 2; + std::size_t new_rowoffs = (cy > half) ? (cy - half) : 0; + // Clamp to valid range + if (total > content) { + std::size_t max_rowoffs = total - content; + if (new_rowoffs > max_rowoffs) + new_rowoffs = max_rowoffs; + } else { + new_rowoffs = 0; + } + buf->SetOffsets(new_rowoffs, buf->Coloffs()); + return true; +} + + static void ensure_at_least_one_line(Buffer &buf) { @@ -4445,6 +4472,11 @@ InstallDefaultCommands() // Syntax highlighting (public commands) CommandRegistry::Register({CommandId::Syntax, "syntax", "Syntax: on|off|reload", cmd_syntax, true}); CommandRegistry::Register({CommandId::SetOption, "set", "Set option: key=value", cmd_set_option, true}); + // Viewport control + CommandRegistry::Register({ + CommandId::CenterOnCursor, "center-on-cursor", "Center viewport on current line", cmd_center_on_cursor, + false, false + }); } diff --git a/Command.h b/Command.h index 4f6caa0..1a1bdf5 100644 --- a/Command.h +++ b/Command.h @@ -106,6 +106,8 @@ enum class CommandId { // Syntax highlighting Syntax, // ":syntax on|off|reload" SetOption, // generic ":set key=value" (v1: filetype=) + // Viewport control + CenterOnCursor, // center the viewport on the current cursor line (C-k k) }; diff --git a/ImGuiFrontend.cc b/ImGuiFrontend.cc index 958c4b4..d775206 100644 --- a/ImGuiFrontend.cc +++ b/ImGuiFrontend.cc @@ -272,10 +272,11 @@ GUIFrontend::Step(Editor &ed, bool &running) float disp_w = io.DisplaySize.x > 0 ? io.DisplaySize.x : static_cast(width_); float disp_h = io.DisplaySize.y > 0 ? io.DisplaySize.y : static_cast(height_); - // Account for the GUI window padding and the status bar height used in GUIRenderer - const ImGuiStyle &style = ImGui::GetStyle(); - float pad_x = style.WindowPadding.x; - float pad_y = style.WindowPadding.y; + // Account for the GUI window padding and the status bar height used in ImGuiRenderer. + // ImGuiRenderer pushes WindowPadding = (6,6) every frame, so use the same constants here + // to avoid mismatches that would cause premature scrolling. + const float pad_x = 6.0f; + const float pad_y = 6.0f; // Status bar reserves one frame height (with spacing) inside the window float status_h = ImGui::GetFrameHeightWithSpacing(); diff --git a/ImGuiRenderer.cc b/ImGuiRenderer.cc index e6c90f2..2a686fc 100644 --- a/ImGuiRenderer.cc +++ b/ImGuiRenderer.cc @@ -140,7 +140,8 @@ ImGuiRenderer::Draw(Editor &ed) prev_buf_coloffs = buf_coloffs; // Synchronize cursor and scrolling. - // Ensure the cursor is visible even on the first frame or when it didn't move. + // Ensure the cursor is visible, but avoid aggressive centering so that + // the same lines remain visible until the cursor actually goes off-screen. { // Compute visible row range using the child window height float child_h = ImGui::GetWindowHeight(); @@ -151,15 +152,30 @@ ImGuiRenderer::Draw(Editor &ed) long last_row = first_row + vis_rows - 1; long cyr = static_cast(cy); - if (cyr < first_row || cyr > last_row) { - float target = (static_cast(cyr) - std::max(0L, vis_rows / 2)) * row_h; + if (cyr < first_row) { + // Scroll just enough to bring the cursor line to the top + float target = static_cast(cyr) * row_h; + if (target < 0.f) + target = 0.f; + float max_y = ImGui::GetScrollMaxY(); + if (max_y >= 0.f && target > max_y) + target = max_y; + ImGui::SetScrollY(target); + scroll_y = ImGui::GetScrollY(); + first_row = static_cast(scroll_y / row_h); + last_row = first_row + vis_rows - 1; + } else if (cyr > last_row) { + // Scroll just enough to bring the cursor line to the bottom + long new_first = cyr - vis_rows + 1; + if (new_first < 0) + new_first = 0; + float target = static_cast(new_first) * row_h; float max_y = ImGui::GetScrollMaxY(); if (target < 0.f) target = 0.f; if (max_y >= 0.f && target > max_y) target = max_y; ImGui::SetScrollY(target); - // refresh local variables scroll_y = ImGui::GetScrollY(); first_row = static_cast(scroll_y / row_h); last_row = first_row + vis_rows - 1; diff --git a/KKeymap.cc b/KKeymap.cc index 18ee035..a3c800b 100644 --- a/KKeymap.cc +++ b/KKeymap.cc @@ -42,6 +42,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool case 'a': out = CommandId::MarkAllAndJumpEnd; return true; + case 'k': + out = CommandId::CenterOnCursor; // C-k k center current line + return true; case 'b': out = CommandId::BufferSwitchStart; return true; @@ -215,4 +218,4 @@ KLookupEscCommand(const int ascii_key, CommandId &out) -> bool break; } return false; -} +} \ No newline at end of file diff --git a/make-app-release b/make-app-release index e42a66c..3d53539 100755 --- a/make-app-release +++ b/make-app-release @@ -20,9 +20,10 @@ cmake -S . -B cmake-build-release -DBUILD_GUI=ON -DCMAKE_BUILD_TYPE=Release -DEN cd cmake-build-release-qt make clean -rm -fr kge.app* +rm -fr kge.app* kge-qt.app* make -zip -r kge.app.zip kge.app -sha256sum kge.app.zip +mv kge.app kge-qt.app +zip -r kge-qt.app.zip kge-qt.app +sha256sum kge-qt.app.zip open . cd ..