8 Commits

Author SHA1 Message Date
b91406860c update nix build
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-30 16:07:30 -08:00
8d1e9b2799 default to using the piece table 2025-11-30 10:35:28 -08:00
c91fe214d6 building nix 2025-11-30 04:46:10 -08:00
99042f5ef1 update nix build 2025-11-30 04:42:21 -08:00
96242154f7 build non-gui by default 2025-11-30 04:34:15 -08:00
f34e88c490 revert downwards
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
2025-11-30 04:29:09 -08:00
b8942b9804 Add GUI initialization updates and improve navigation commands.
- Implement terminal detachment for GUI mode to enable terminal closure post-launch.
- Add `+N` support for opening files at specific line numbers and refine cursor positioning.
- Introduce `JumpToLine` command for direct navigation by line number.
- Enhance mouse wheel handling for line-wise scrolling.
2025-11-30 04:28:40 -08:00
65869bd143 Add man pages for kge and kte with installation targets in CMake.
- Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples.
- Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`.
- Ensure `kge` man page installation is conditional on GUI being built.
2025-11-30 03:34:37 -08:00
18 changed files with 883 additions and 224 deletions

82
.idea/workspace.xml generated
View File

@@ -11,9 +11,9 @@
<option name="/Default/Housekeeping/RefactoringsMru/RenameRefactoring/DoSearchForTextInStrings/@EntryValue" value="true" type="bool" />
<option name="/Default/RiderDebugger/RiderRestoreDecompile/RestoreDecompileSetting/@EntryValue" value="false" type="bool" />
</component>
<component name="CMakePresetLoader"><![CDATA[{
"useNewFormat": true
}]]></component>
<component name="CMakePresetLoader">{
&quot;useNewFormat&quot;: true
}</component>
<component name="CMakeProjectFlavorService">
<option name="flavorId" value="CMakePlainProjectFlavor" />
</component>
@@ -21,11 +21,7 @@
<option name="reloaded" value="true" />
</component>
<component name="CMakeRunConfigurationManager">
<generated>
<config projectName="kte" targetName="kte" />
<config projectName="kte" targetName="imgui" />
<config projectName="kte" targetName="kge" />
</generated>
<generated />
</component>
<component name="CMakeSettings" AUTO_RELOAD="true">
<configurations>
@@ -33,8 +29,14 @@
</configurations>
</component>
<component name="ChangeListManager">
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure.&#10;&#10;- Delete `packaging.cmake` to streamline build system.&#10;- Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`.&#10;- Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing.&#10;- Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation.&#10;- Enhance kill ring operations and new prompt workflows in `Editor`.">
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling.">
<change afterPath="$PROJECT_DIR$/default-nogui.nix" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/default-gui.nix" beforeDir="false" afterPath="$PROJECT_DIR$/default-gui.nix" afterDir="false" />
<change beforePath="$PROJECT_DIR$/default.nix" beforeDir="false" afterPath="$PROJECT_DIR$/default.nix" afterDir="false" />
<change beforePath="$PROJECT_DIR$/flake.nix" beforeDir="false" afterPath="$PROJECT_DIR$/flake.nix" afterDir="false" />
<change beforePath="$PROJECT_DIR$/main.cc" beforeDir="false" afterPath="$PROJECT_DIR$/main.cc" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -74,9 +76,9 @@
<option name="minorVersion" value="2.5" />
<option name="productBranch" value="Classic" />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 3
}]]></component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
}</component>
<component name="ProjectId" id="36AlI8oyQOzOwSuZg6WxXf5LbHb" />
<component name="ProjectLevelVcsManager">
<OptionsSetting value="false" id="Update" />
@@ -103,6 +105,7 @@
"RunOnceActivity.RadMigrateCodeStyle": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.cidr.known.project.marker": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.readMode.enableVisualFormatting": "true",
"RunOnceActivity.west.config.association.type.startup.service": "true",
"cf.first.check.clang-format": "false",
@@ -118,7 +121,7 @@
"nodejs_package_manager_path": "npm",
"onboarding.tips.debug.path": "/Users/kyle/src/kte/main.cpp",
"rearrange.code.on.save": "true",
"settings.editor.selected.configurable": "junie.application.models",
"settings.editor.selected.configurable": "editor.preferences.fonts.default",
"to.speed.mode.migration.done": "true",
"vue.rearranger.settings.migration": "true"
}
@@ -128,32 +131,12 @@
<recent name="$PROJECT_DIR$/docs" />
</key>
</component>
<component name="RunManager" selected="CMake Application.imgui">
<component name="RunManager">
<configuration default="true" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="imgui" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="imgui" CONFIG_NAME="Debug">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="kge" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="kge" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="kte" RUN_TARGET_NAME="kge">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="kte" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="kte" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="kte" RUN_TARGET_NAME="kte">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<list>
<item itemvalue="CMake Application.imgui" />
<item itemvalue="CMake Application.kge" />
<item itemvalue="CMake Application.kte" />
</list>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
@@ -162,7 +145,14 @@
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1764457173148</updated>
<workItem from="1764457174208" duration="42867000" />
<workItem from="1764457174208" duration="46950000" />
<workItem from="1764538560497" duration="215000" />
<workItem from="1764539255906" duration="196000" />
<workItem from="1764539459951" duration="64000" />
<workItem from="1764539535105" duration="10000" />
<workItem from="1764539556448" duration="156000" />
<workItem from="1764539725338" duration="1075000" />
<workItem from="1764542392763" duration="3512000" />
</task>
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
<option name="closed" value="true" />
@@ -220,7 +210,23 @@
<option name="project" value="LOCAL" />
<updated>1764501532446</updated>
</task>
<option name="localTasksCounter" value="8" />
<task id="LOCAL-00008" summary="Add man pages for `kge` and `kte` with installation targets in CMake.&#10;&#10;- Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples.&#10;- Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`.&#10;- Ensure `kge` man page installation is conditional on GUI being built.">
<option name="closed" value="true" />
<created>1764502480274</created>
<option name="number" value="00008" />
<option name="presentableId" value="LOCAL-00008" />
<option name="project" value="LOCAL" />
<updated>1764502480274</updated>
</task>
<task id="LOCAL-00009" summary="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling.">
<option name="closed" value="true" />
<created>1764505723411</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1764505723411</updated>
</task>
<option name="localTasksCounter" value="10" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -241,7 +247,9 @@
<MESSAGE value="Add non-linear undo/redo design documentation and improve `UndoSystem` with backspace batching and GUI integration fixes." />
<MESSAGE value="Add `TestFrontend` documentation and `UndoSystem` buffer reference update.&#10;&#10;- Document `TestFrontend` for programmatic testing, including examples and usage details.&#10;- Add `UpdateBufferReference` to `UndoSystem` to support updating buffer associations." />
<MESSAGE value="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure.&#10;&#10;- Delete `packaging.cmake` to streamline build system.&#10;- Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`.&#10;- Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing.&#10;- Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation.&#10;- Enhance kill ring operations and new prompt workflows in `Editor`." />
<option name="LAST_COMMIT_MESSAGE" value="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure.&#10;&#10;- Delete `packaging.cmake` to streamline build system.&#10;- Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`.&#10;- Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing.&#10;- Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation.&#10;- Enhance kill ring operations and new prompt workflows in `Editor`." />
<MESSAGE value="Add man pages for `kge` and `kte` with installation targets in CMake.&#10;&#10;- Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples.&#10;- Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`.&#10;- Ensure `kge` man page installation is conditional on GUI being built." />
<MESSAGE value="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling." />
<option name="LAST_COMMIT_MESSAGE" value="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling." />
</component>
<component name="XSLT-Support.FileAssociations.UIState">
<expand />

View File

@@ -4,13 +4,13 @@ project(kte)
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 17)
set(KTE_VERSION "0.1.0")
set(KTE_VERSION "0.9.1")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
set(BUILD_GUI OFF CACHE BOOL "Enable building the graphical version.")
set(BUILD_TESTS OFF CACHE BOOL "Enable building test programs.")
option(KTE_USE_PIECE_TABLE "Use PieceTable instead of GapBuffer implementation" OFF)
option(KTE_USE_PIECE_TABLE "Use PieceTable instead of GapBuffer implementation" ON)
set(KTE_FONT_SIZE "18.0" CACHE STRING "Default font size for GUI")
if (CMAKE_HOST_UNIX)
@@ -44,6 +44,8 @@ if (${BUILD_GUI})
endif ()
# NCurses for terminal mode
set(CURSES_NEED_NCURSES)
set(CURSES_NEED_WIDE)
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR})
@@ -104,6 +106,9 @@ install(TARGETS kte
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# Man pages
install(FILES docs/kte.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
if (BUILD_TESTS)
# test_undo executable for testing undo/redo system
add_executable(test_undo
@@ -164,9 +169,11 @@ if (${BUILD_GUI})
install(TARGETS kge
BUNDLE DESTINATION .
)
else()
else ()
install(TARGETS kge
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()
endif ()
# Install kge man page only when GUI is built
install(FILES docs/kge.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endif ()

View File

@@ -646,6 +646,16 @@ cmd_open_file_start(CommandContext &ctx)
}
static bool
cmd_jump_to_line_start(CommandContext &ctx)
{
// Start a prompt to read a 1-based line number and jump there (clamped)
ctx.editor.StartPrompt(Editor::PromptKind::GotoLine, "Goto", "");
ctx.editor.SetStatus("Goto line: ");
return true;
}
// --- Buffers: switch/next/prev/close ---
static bool
cmd_buffer_switch_start(CommandContext &ctx)
@@ -990,6 +1000,37 @@ cmd_newline(CommandContext &ctx)
} else {
ctx.editor.SetStatus("Nothing to confirm");
}
} else if (kind == Editor::PromptKind::GotoLine) {
Buffer *buf = ctx.editor.CurrentBuffer();
if (!buf) {
ctx.editor.SetStatus("No buffer");
return true;
}
std::size_t nrows = buf->Nrows();
if (nrows == 0) {
buf->SetCursor(0, 0);
ensure_cursor_visible(ctx.editor, *buf);
ctx.editor.SetStatus("Empty buffer");
return true;
}
// Parse 1-based line number; on failure, keep cursor and show status
std::size_t line1 = 0;
try {
if (!value.empty())
line1 = static_cast<std::size_t>(std::stoull(value));
} catch (...) {
line1 = 0;
}
if (line1 == 0) {
ctx.editor.SetStatus("Goto canceled (invalid line)");
return true;
}
std::size_t y = line1 - 1; // convert to 0-based
if (y >= nrows)
y = nrows - 1; // clamp to last line
buf->SetCursor(0, y);
ensure_cursor_visible(ctx.editor, *buf);
ctx.editor.SetStatus("Goto line " + std::to_string(line1));
}
return true;
}
@@ -2372,6 +2413,10 @@ InstallDefaultCommands()
CommandRegistry::Register({
CommandId::MoveCursorTo, "move-cursor-to", "Move cursor to y:x", cmd_move_cursor_to
});
// Direct navigation by line number
CommandRegistry::Register({
CommandId::JumpToLine, "goto-line", "Prompt for line and jump", cmd_jump_to_line_start
});
// Undo/Redo
CommandRegistry::Register({CommandId::Undo, "undo", "Undo last edit", cmd_undo});
CommandRegistry::Register({CommandId::Redo, "redo", "Redo edit", cmd_redo});

View File

@@ -72,6 +72,8 @@ enum class CommandId {
// Buffer operations
ReloadBuffer, // reload buffer from disk (C-k l)
MarkAllAndJumpEnd, // set mark at beginning, jump to end (C-k a)
// Direct navigation by line number
JumpToLine, // prompt for line and jump (C-k g)
// Meta
UnknownKCommand, // arg: single character that was not recognized after C-k
};

View File

@@ -302,7 +302,7 @@ public:
// --- Generic Prompt subsystem (for search, open-file, save-as, etc.) ---
enum class PromptKind { None = 0, Search, OpenFile, SaveAs, Confirm, BufferSwitch };
enum class PromptKind { None = 0, Search, OpenFile, SaveAs, Confirm, BufferSwitch, GotoLine };
void StartPrompt(PromptKind kind, const std::string &label, const std::string &initial)

View File

@@ -279,6 +279,24 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
MappedInput mi;
bool produced = false;
switch (e.type) {
case SDL_MOUSEWHEEL: {
// Map vertical wheel to line-wise cursor movement (MoveUp/MoveDown)
int dy = e.wheel.y;
#ifdef SDL_MOUSEWHEEL_FLIPPED
if (e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
dy = -dy;
#endif
if (dy != 0) {
int repeat = dy > 0 ? dy : -dy;
CommandId id = dy > 0 ? CommandId::MoveUp : CommandId::MoveDown;
std::lock_guard<std::mutex> lk(mu_);
for (int i = 0; i < repeat; ++i) {
q_.push(MappedInput{true, id, std::string(), 0});
}
return true; // consumed
}
return false;
}
case SDL_KEYDOWN: {
// Remember state before mapping; used for TEXTINPUT suppression heuristics
const bool was_k_prefix = k_prefix_;

View File

@@ -45,7 +45,7 @@ GUIRenderer::Draw(Editor &ed)
const auto &lines = buf->Rows();
// Reserve space for status bar at bottom
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
ImGuiWindowFlags_HorizontalScrollbar);
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
// Detect click-to-move inside this scroll region
ImVec2 list_origin = ImGui::GetCursorScreenPos();
float scroll_y = ImGui::GetScrollY();
@@ -56,24 +56,42 @@ GUIRenderer::Draw(Editor &ed)
const float line_h = ImGui::GetTextLineHeight();
const float row_h = ImGui::GetTextLineHeightWithSpacing();
const float space_w = ImGui::CalcTextSize(" ").x;
// If the command layer requested a specific top-of-screen (via Buffer::Rowoffs),
// force the ImGui scroll to match so paging aligns the first visible row.
// Two-way sync between Buffer::Rowoffs and ImGui scroll position:
// - If command layer changed Buffer::Rowoffs since last frame, drive ImGui scroll from it.
// - Otherwise, propagate ImGui scroll to Buffer::Rowoffs so command layer has an up-to-date view.
// This prevents clicks/wheel from being immediately overridden by stale offsets.
bool forced_scroll = false;
{
std::size_t desired_top = buf->Rowoffs();
long current_top = static_cast<long>(scroll_y / row_h);
if (static_cast<long>(desired_top) != current_top) {
ImGui::SetScrollY(static_cast<float>(desired_top) * row_h);
static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
const long buf_rowoffs = static_cast<long>(buf->Rowoffs());
const long scroll_top = static_cast<long>(scroll_y / row_h);
// Detect programmatic change (e.g., keyboard navigation ensured visibility)
if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
ImGui::SetScrollY(static_cast<float>(buf_rowoffs) * row_h);
scroll_y = ImGui::GetScrollY();
forced_scroll = true;
} else {
// If user scrolled (scroll_y changed), update buffer row offset accordingly
if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y) {
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
// Keep horizontal offset owned by GUI; only update vertical offset here
mbuf->SetOffsets(static_cast<std::size_t>(std::max(0L, scroll_top)),
mbuf->Coloffs());
}
}
}
// Update trackers for next frame
prev_buf_rowoffs = static_cast<long>(buf->Rowoffs());
prev_scroll_y = ImGui::GetScrollY();
}
// Synchronize cursor and scrolling.
// A) When the user scrolls and the cursor goes off-screen, move the cursor to the nearest visible row.
// B) When the cursor moves (via keyboard commands), scroll it back into view.
// Ensure the cursor is visible even on the first frame or when it didn't move,
// unless we already forced scrolling from Buffer::Rowoffs this frame.
{
static float prev_scroll_y = -1.0f;
static long prev_cursor_y = -1;
// Compute visible row range using the child window height
float child_h = ImGui::GetWindowHeight();
long first_row = static_cast<long>(scroll_y / row_h);
@@ -82,37 +100,7 @@ GUIRenderer::Draw(Editor &ed)
vis_rows = 1;
long last_row = first_row + vis_rows - 1;
// A) If user scrolled (scroll_y changed), and cursor outside, move cursor to nearest visible row
// Skip this when we just forced a scroll alignment this frame (programmatic change).
if (!forced_scroll && prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y) {
long cyr = static_cast<long>(cy);
if (cyr < first_row || cyr > last_row) {
long new_row = (cyr < first_row) ? first_row : last_row;
if (new_row < 0)
new_row = 0;
if (new_row >= static_cast<long>(lines.size()))
new_row = static_cast<long>(lines.empty() ? 0 : (lines.size() - 1));
// Clamp column to line length
std::size_t new_col = 0;
if (!lines.empty()) {
const std::string &l = lines[static_cast<std::size_t>(new_row)];
new_col = std::min<std::size_t>(cx, l.size());
}
char tmp2[64];
std::snprintf(tmp2, sizeof(tmp2), "%ld:%zu", new_row, new_col);
Execute(ed, CommandId::MoveCursorTo, std::string(tmp2));
cy = buf->Cury();
cx = buf->Curx();
cyr = static_cast<long>(cy);
// Update visible range again in case content changed
first_row = static_cast<long>(ImGui::GetScrollY() / row_h);
last_row = first_row + vis_rows - 1;
}
}
// B) If cursor moved since last frame and is outside the visible region, scroll to reveal it
// Skip this when we just forced a top-of-screen alignment this frame.
if (!forced_scroll && prev_cursor_y >= 0 && static_cast<long>(cy) != prev_cursor_y) {
if (!forced_scroll) {
long cyr = static_cast<long>(cy);
if (cyr < first_row || cyr > last_row) {
float target = (static_cast<float>(cyr) - std::max(0L, vis_rows / 2)) * row_h;
@@ -128,9 +116,6 @@ GUIRenderer::Draw(Editor &ed)
last_row = first_row + vis_rows - 1;
}
}
prev_scroll_y = ImGui::GetScrollY();
prev_cursor_y = static_cast<long>(cy);
}
// Handle mouse click before rendering to avoid dependent on drawn items
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
@@ -245,8 +230,16 @@ GUIRenderer::Draw(Editor &ed)
std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: not set", row1, col1);
std::string right = rbuf;
// Middle message
const std::string &msg = ed.Status();
// Middle message: if a prompt is active, show "Label: text"; otherwise show status
std::string msg;
if (ed.PromptActive()) {
msg = ed.PromptLabel();
if (!msg.empty())
msg += ": ";
msg += ed.PromptText();
} else {
msg = ed.Status();
}
// Measurements
ImVec2 left_sz = ImGui::CalcTextSize(left.c_str());

View File

@@ -86,6 +86,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
case 'a':
out = CommandId::MarkAllAndJumpEnd;
return true; // C-k a (mark all and jump to end)
case 'g':
out = CommandId::JumpToLine;
return true; // C-k g (goto line)
default:
break;
}
@@ -134,9 +137,6 @@ KLookupCtrlCommand(const int ascii_key, CommandId &out) -> bool
case 'g':
out = CommandId::Refresh;
return true;
case 'x':
out = CommandId::SaveAndQuit; // direct C-x mapping (GUI had this)
return true;
default:
break;
}

View File

@@ -35,6 +35,19 @@ map_key_to_command(const int ch,
case KEY_MOUSE: {
MEVENT ev{};
if (getmouse(&ev) == OK) {
// Mouse wheel → map to MoveUp/MoveDown one line per wheel notch
#ifdef BUTTON4_PRESSED
if (ev.bstate & (BUTTON4_PRESSED | BUTTON4_RELEASED | BUTTON4_CLICKED)) {
out = {true, CommandId::MoveUp, "", 0};
return true;
}
#endif
#ifdef BUTTON5_PRESSED
if (ev.bstate & (BUTTON5_PRESSED | BUTTON5_RELEASED | BUTTON5_CLICKED)) {
out = {true, CommandId::MoveDown, "", 0};
return true;
}
#endif
// React to left button click/press
if (ev.bstate & (BUTTON1_CLICKED | BUTTON1_PRESSED | BUTTON1_RELEASED)) {
char buf[64];

51
default-gui.nix Normal file
View File

@@ -0,0 +1,51 @@
{
lib,
stdenv,
cmake,
ncurses,
SDL2,
libGL,
xorg,
installShellFiles,
...
}:
let
cmakeContent = builtins.readFile ./CMakeLists.txt;
cmakeLines = lib.splitString "\n" cmakeContent;
versionLine = lib.findFirst (l: builtins.match ".*set\\(KTE_VERSION \".+\"\\).*" l != null) (throw "KTE_VERSION not found in CMakeLists.txt") cmakeLines;
version = builtins.head (builtins.match ".*set\\(KTE_VERSION \"(.+)\"\\).*" versionLine);
in
stdenv.mkDerivation {
pname = "kte";
inherit version;
src = lib.cleanSource ./.;
nativeBuildInputs = [
cmake
ncurses
SDL2
libGL
xorg.libX11
installShellFiles
];
cmakeFlags = [
"-DBUILD_GUI=ON"
"-DCMAKE_BUILD_TYPE=Debug"
];
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp kte $out/bin/
cp kge $out/bin/
installManPage ../docs/kte.1
installManPage ../docs/kge.1
runHook postInstall
'';
}

42
default-nogui.nix Normal file
View File

@@ -0,0 +1,42 @@
{
lib,
stdenv,
cmake,
ncurses,
installShellFiles,
...
}:
let
cmakeContent = builtins.readFile ./CMakeLists.txt;
cmakeLines = lib.splitString "\n" cmakeContent;
versionLine = lib.findFirst (l: builtins.match ".*set\\(KTE_VERSION \".+\"\\).*" l != null) (throw "KTE_VERSION not found in CMakeLists.txt") cmakeLines;
version = builtins.head (builtins.match ".*set\\(KTE_VERSION \"(.+)\"\\).*" versionLine);
in
stdenv.mkDerivation {
pname = "kte";
inherit version;
src = lib.cleanSource ./.;
nativeBuildInputs = [
cmake
ncurses
installShellFiles
];
cmakeFlags = [
"-DBUILD_GUI=OFF"
"-DCMAKE_BUILD_TYPE=Debug"
];
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp kte $out/bin/
installManPage ../docs/kte.1
runHook postInstall
'';
}

View File

@@ -1,24 +1,50 @@
# default.nix
{
lib,
stdenv,
cmake,
ncurses,
SDL2,
libGL,
xorg,
installShellFiles,
...
}:
let
pkgs = import <nixpkgs> {};
cmakeContent = builtins.readFile ./CMakeLists.txt;
cmakeLines = lib.splitString "\n" cmakeContent;
versionLine = lib.findFirst (l: builtins.match ".*set\\(KTE_VERSION \".+\"\\).*" l != null) (throw "KTE_VERSION not found in CMakeLists.txt") cmakeLines;
version = builtins.head (builtins.match ".*set\\(KTE_VERSION \"(.+)\"\\).*" versionLine);
in
pkgs.stdenv.mkDerivation {
stdenv.mkDerivation {
pname = "kte";
version = "0.1.0";
inherit version;
src = ./.;
src = lib.cleanSource ./.;
nativeBuildInputs = [ pkgs.cmake pkgs.pkg-config ];
buildInputs = with pkgs; [
nativeBuildInputs = [
cmake
ncurses
SDL2
libGL
xorg.libX11
installShellFiles
];
cmakeFlags = [
"-DBUILD_GUI=ON"
"-DCURSES_NEED_NCURSES=TRUE"
"-DCURSES_NEED_WIDE=TRUE"
"-DCMAKE_BUILD_TYPE=Debug"
];
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp kte $out/bin/
cp kge $out/bin/
installManPage ../docs/kte.1
installManPage ../docs/kge.1
runHook postInstall
'';
}

210
docs/kge.1 Normal file
View File

@@ -0,0 +1,210 @@
.\" kge(1) — Kyle's Graphical Editor (GUI-first)
.\"
.\" Project homepage: https://github.com/wntrmute/kte
.TH KGE 1 "2025-11-30" "kte 0.1.0" "User Commands"
.SH NAME
kge \- Kyle's Graphical Editor (GUI-first)
.SH SYNOPSIS
.B kge
[
.I options
]
[
.I files ...
]
.SH DESCRIPTION
.B kge
is the GUI-first build target of Kyle's Text Editor. It shares the same
editor core and command model as
.BR kte (1),
but defaults to the graphical ImGui frontend when available. A terminal
(ncurses) frontend is also available and can be requested explicitly.
If one or more
.I files
are provided, they are opened on startup; otherwise, an empty buffer is
created.
.SH OPTIONS
.TP
.B -g, --gui
Use the GUI frontend (default for
.B kge
when built).
.TP
.B -t, --term
Use the terminal (ncurses) frontend instead of the GUI.
.TP
.B -h, --help
Display a brief usage summary and exit.
.TP
.B -V, --version
Print version information and exit.
.SH KEYBINDINGS
The GUI shares the same commands and keybindings as the terminal editor.
They are summarized here for convenience. See the ke manual in the source
tree for the canonical reference and notes:
.I docs/ke.md
.
.SS K-commands (prefix Ctrl-K)
.PP
Enter K-command mode with Ctrl-K. Exit K-command mode with ESC or Ctrl-G.
.TP
.B C-k BACKSPACE
Delete from the cursor to the beginning of the line.
.TP
.B C-k SPACE
Toggle the mark.
.TP
.B C-k -
If the mark is set, unindent the region.
.TP
.B C-k =
If the mark is set, indent the region.
.TP
.B C-k a
Set the mark at the beginning of the file, then jump to the end of the file.
.TP
.B C-k b
Switch to a buffer.
.TP
.B C-k c
Close the current buffer. If no other buffers are open, an empty buffer will be opened. To exit, use C-k q.
.TP
.B C-k d
Delete from the cursor to the end of the line.
.TP
.B C-k C-d
Delete the entire line.
.TP
.B C-k e
Edit a new file.
.TP
.B C-k f
Flush the kill ring.
.TP
.B C-k g
Go to a specific line.
.TP
.B C-k j
Jump to the mark.
.TP
.B C-k l
Reload the current buffer from disk.
.TP
.B C-k m
Run make(1), reporting success or failure.
.TP
.B C-k p
Switch to the next buffer.
.TP
.B C-k q
Exit the editor. If the file has unsaved changes, a warning will be printed; a second C-k q will exit.
.TP
.B C-k C-q
Immediately exit the editor.
.TP
.B C-k s
Save the file, prompting for a filename if needed.
.TP
.B C-k u
Undo.
.TP
.B C-k r
Redo changes.
.TP
.B C-k x
Save the file and exit. Also C-k C-x.
.TP
.B C-k y
Yank the kill ring.
.TP
.B C-k \e
Dump core.
.SS Other keybindings
.TP
.B C-g
Cancel the current operation.
.TP
.B C-l
Refresh the display.
.TP
.B C-r
Regex search.
.TP
.B C-s
Incremental find.
.TP
.B C-u
Universal argument. C-u followed by numbers will repeat an operation n times.
.TP
.B C-w
Kill the region if the mark is set.
.TP
.B C-y
Yank the kill ring.
.TP
.B ESC BACKSPACE
Delete the previous word.
.TP
.B ESC b
Move to the previous word.
.TP
.B ESC d
Delete the next word.
.TP
.B ESC f
Move to the next word.
.TP
.B ESC q
Reflow the paragraph to 72 columns or the value of the universal argument.
.TP
.B ESC w
Save the region (if the mark is set) to the kill ring.
.SH ENVIRONMENT
.TP
.B TERM
Used if the terminal frontend is selected.
.TP
.B LANG, LC_ALL, LC_CTYPE
Determine locale and character encoding.
.SH FILES
.TP
.I ~/.kte/
Future configuration directory (not yet stabilized).
.TP
.I kge.app
On macOS, the GUI is built and installed as an app bundle. The command-line
wrapper
.B kge
may still be available for launching with files.
.SH EXIT STATUS
Returns 0 on success, non-zero on failure.
.SH EXAMPLES
.TP
Launch GUI (default) with multiple files:
.RS
.nf
kge main.cc Buffer.cc
.fi
.RE
.TP
Open using the terminal frontend from kge:
.RS
.nf
kge --term README.md
.fi
.RE
.SH SEE ALSO
.BR kte (1),
.I docs/ke.md
(project keybinding manual)
.SH BUGS
Report issues on the project tracker. Some behaviors are inherited from
ke and may evolve over time; see the manual for notes.
.SH AUTHORS
Kyle (wntrmute) and contributors.
.SH COPYRIGHT
Copyright \(co 2025 Kyle. License as per project repository.
.SH NOTES
This page documents kte/kge version 0.1.0.

205
docs/kte.1 Normal file
View File

@@ -0,0 +1,205 @@
.\" kte(1) — Kyle's Text Editor (terminal-first)
.\"
.\" Project homepage: https://github.com/wntrmute/kte
.TH KTE 1 "2025-11-30" "kte 0.1.0" "User Commands"
.SH NAME
kte \- Kyle's Text Editor (terminal-first)
.SH SYNOPSIS
.B kte
[
.I options
]
[
.I files ...
]
.SH DESCRIPTION
.B kte
is a small, fast, and understandable text editor with a terminal-first
experience. It preserves ke's WordStar/VDE-style command model with
Emacs-influenced ergonomics. The core uses ncurses in the terminal and can
optionally run with a GUI frontend if built.
If one or more
.I files
are provided, they are opened on startup; otherwise, an empty buffer is
created.
.SH OPTIONS
.TP
.B -g, --gui
Use the GUI frontend (if the binary was built with GUI support). If GUI was
not built, the editor exits with an error.
.TP
.B -t, --term
Use the terminal (ncurses) frontend. This is the default for
.B kte
.
.TP
.B -h, --help
Display a brief usage summary and exit.
.TP
.B -V, --version
Print version information and exit.
.SH KEYBINDINGS
The command model and keybindings are inherited from
.I ke
and are summarized here for convenience. See
.I docs/ke.md
in the source tree for the canonical reference and notes.
.SS K-commands (prefix Ctrl-K)
.PP
Enter K-command mode with Ctrl-K. Exit K-command mode with ESC or Ctrl-G.
.TP
.B C-k BACKSPACE
Delete from the cursor to the beginning of the line.
.TP
.B C-k SPACE
Toggle the mark.
.TP
.B C-k -
If the mark is set, unindent the region.
.TP
.B C-k =
If the mark is set, indent the region.
.TP
.B C-k a
Set the mark at the beginning of the file, then jump to the end of the file.
.TP
.B C-k b
Switch to a buffer.
.TP
.B C-k c
Close the current buffer. If no other buffers are open, an empty buffer will be opened. To exit, use C-k q.
.TP
.B C-k d
Delete from the cursor to the end of the line.
.TP
.B C-k C-d
Delete the entire line.
.TP
.B C-k e
Edit a new file.
.TP
.B C-k f
Flush the kill ring.
.TP
.B C-k g
Go to a specific line.
.TP
.B C-k j
Jump to the mark.
.TP
.B C-k l
Reload the current buffer from disk.
.TP
.B C-k m
Run make(1), reporting success or failure.
.TP
.B C-k p
Switch to the next buffer.
.TP
.B C-k q
Exit the editor. If the file has unsaved changes, a warning will be printed; a second C-k q will exit.
.TP
.B C-k C-q
Immediately exit the editor.
.TP
.B C-k s
Save the file, prompting for a filename if needed.
.TP
.B C-k u
Undo.
.TP
.B C-k r
Redo changes.
.TP
.B C-k x
Save the file and exit. Also C-k C-x.
.TP
.B C-k y
Yank the kill ring.
.TP
.B C-k \e
Dump core.
.SS Other keybindings
.TP
.B C-g
Cancel the current operation.
.TP
.B C-l
Refresh the display.
.TP
.B C-r
Regex search.
.TP
.B C-s
Incremental find.
.TP
.B C-u
Universal argument. C-u followed by numbers will repeat an operation n times.
.TP
.B C-w
Kill the region if the mark is set.
.TP
.B C-y
Yank the kill ring.
.TP
.B ESC BACKSPACE
Delete the previous word.
.TP
.B ESC b
Move to the previous word.
.TP
.B ESC d
Delete the next word.
.TP
.B ESC f
Move to the next word.
.TP
.B ESC q
Reflow the paragraph to 72 columns or the value of the universal argument.
.TP
.B ESC w
Save the region (if the mark is set) to the kill ring.
.SH ENVIRONMENT
.TP
.B TERM
Specifies terminal type and capabilities for ncurses.
.TP
.B LANG, LC_ALL, LC_CTYPE
Determine locale and character encoding.
.SH FILES
.TP
.I ~/.kte/
Future configuration directory (not yet stabilized).
.SH EXIT STATUS
Returns 0 on success, non-zero on failure.
.SH EXAMPLES
.TP
Edit a file in the terminal (default):
.RS
.nf
kte README.md
.fi
.RE
.TP
Force GUI frontend (if available):
.RS
.nf
kte --gui main.cc
.fi
.RE
.SH SEE ALSO
.BR kge (1),
.I docs/ke.md
(project keybinding manual)
.SH BUGS
Incremental search currently restarts from the top on each invocation; see
\(lqKnown behavior\(rq in the ke manual. Report issues on the project tracker.
.SH AUTHORS
Kyle (wntrmute) and contributors.
.SH COPYRIGHT
Copyright \(co 2025 Kyle. License as per project repository.
.SH NOTES
This page documents kte version 0.1.0.

55
flake-gui.nix Normal file
View File

@@ -0,0 +1,55 @@
# flake.nix
{
description = "kte ImGui/SDL2 text editor";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.default = pkgs.stdenv.mkDerivation {
pname = "kte";
version = "0.1.0";
src = ./.;
nativeBuildInputs = [ pkgs.cmake pkgs.pkg-config ];
buildInputs = with pkgs; [
ncurses
SDL2
libGL
xorg.libX11
];
cmakeFlags = [
"-DBUILD_GUI=ON"
"-DCURSES_NEED_NCURSES=TRUE"
"-DCURSES_NEED_WIDE=TRUE"
];
# Alternative (even stronger): completely hide the broken module
preConfigure = ''
# If the project ships its own FindSDL2.cmake in cmake/, hide it
if [ -f cmake/FindSDL2.cmake ]; then
mv cmake/FindSDL2.cmake cmake/FindSDL2.cmake.disabled
echo "Disabled bundled FindSDL2.cmake"
fi
'';
meta = with pkgs.lib; {
description = "kte ImGui/SDL2 GUI editor";
mainProgram = "kte";
platforms = platforms.linux;
};
};
devShells.default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.default ];
packages = with pkgs; [ gdb clang-tools ];
};
});
}

38
flake.lock generated
View File

@@ -1,34 +1,16 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1764242076,
"narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
"owner": "NixOS",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
"type": "github"
},
"original": {
"owner": "NixOS",
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
@@ -36,24 +18,8 @@
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",

View File

@@ -1,55 +1,21 @@
# flake.nix
{
description = "kte ImGui/SDL2 text editor";
description = "Kyle's Text Editor";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.default = pkgs.stdenv.mkDerivation {
pname = "kte";
version = "0.1.0";
src = ./.;
nativeBuildInputs = [ pkgs.cmake pkgs.pkg-config ];
buildInputs = with pkgs; [
ncurses
SDL2
libGL
xorg.libX11
];
cmakeFlags = [
"-DBUILD_GUI=ON"
"-DCURSES_NEED_NCURSES=TRUE"
"-DCURSES_NEED_WIDE=TRUE"
];
# Alternative (even stronger): completely hide the broken module
preConfigure = ''
# If the project ships its own FindSDL2.cmake in cmake/, hide it
if [ -f cmake/FindSDL2.cmake ]; then
mv cmake/FindSDL2.cmake cmake/FindSDL2.cmake.disabled
echo "Disabled bundled FindSDL2.cmake"
fi
'';
meta = with pkgs.lib; {
description = "kte ImGui/SDL2 GUI editor";
mainProgram = "kte";
platforms = platforms.linux;
};
};
devShells.default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.default ];
packages = with pkgs; [ gdb clang-tools ];
};
});
}
outputs =
{ self, nixpkgs }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
in
{
packages.x86_64-linux = {
default = pkgs.callPackage ./default-nogui.nix { };
kge = pkgs.callPackage ./default-gui.nix { };
kte = pkgs.callPackage ./default-nogui.nix { };
full = pkgs.callPackage ./default.nix { };
};
};
}

126
main.cc
View File

@@ -1,12 +1,18 @@
#include <iostream>
#include <memory>
#include <string>
#include <cctype>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <cstdio>
#include <sys/stat.h>
#include "Editor.h"
#include "Command.h"
#include "Frontend.h"
#include "TerminalFrontend.h"
#if defined(KTE_BUILD_GUI)
#include "GUIFrontend.h"
#endif
@@ -20,53 +26,52 @@ static void
PrintUsage(const char *prog)
{
std::cerr << "Usage: " << prog << " [OPTIONS] [files]\n"
<< "Options:\n"
<< " -g, --gui Use GUI frontend (if built)\n"
<< " -t, --term Use terminal (ncurses) frontend [default]\n"
<< " -h, --help Show this help and exit\n"
<< " -V, --version Show version and exit\n";
<< "Options:\n"
<< " -g, --gui Use GUI frontend (if built)\n"
<< " -t, --term Use terminal (ncurses) frontend [default]\n"
<< " -h, --help Show this help and exit\n"
<< " -V, --version Show version and exit\n";
}
int
main(int argc, const char *argv[])
{
Editor editor;
// CLI parsing using getopt_long
bool req_gui = false;
bool req_term = false;
bool show_help = false;
bool req_gui = false;
bool req_term = false;
bool show_help = false;
bool show_version = false;
static struct option long_opts[] = {
{"gui", no_argument, nullptr, 'g'},
{"term", no_argument, nullptr, 't'},
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, nullptr, 'V'},
{nullptr, 0, nullptr, 0}
{"gui", no_argument, nullptr, 'g'},
{"term", no_argument, nullptr, 't'},
{"help", no_argument, nullptr, 'h'},
{"version", no_argument, nullptr, 'V'},
{nullptr, 0, nullptr, 0}
};
int opt;
int long_index = 0;
while ((opt = getopt_long(argc, const_cast<char * const*>(argv), "gthV", long_opts, &long_index)) != -1) {
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "gthV", long_opts, &long_index)) != -1) {
switch (opt) {
case 'g':
req_gui = true;
break;
case 't':
req_term = true;
break;
case 'h':
show_help = true;
break;
case 'V':
show_version = true;
break;
case '?':
default:
PrintUsage(argv[0]);
return 2;
case 'g':
req_gui = true;
break;
case 't':
req_term = true;
break;
case 'h':
show_help = true;
break;
case 'V':
show_version = true;
break;
case '?':
default:
PrintUsage(argv[0]);
return 2;
}
}
@@ -87,7 +92,7 @@ main(int argc, const char *argv[])
#if !defined(KTE_BUILD_GUI)
if (req_gui) {
std::cerr << "kte: GUI not built. Reconfigure with -DBUILD_GUI=ON and required deps installed." <<
std::endl;
std::endl;
return 2;
}
#else
@@ -106,16 +111,63 @@ main(int argc, const char *argv[])
}
#endif
// Open files passed on the CLI; if none, create an empty buffer
// Open files passed on the CLI; support +N to jump to line N in the next file.
// If no files are provided, create an empty buffer.
if (optind < argc) {
std::size_t pending_line = 0; // 0 = no pending line
for (int i = optind; i < argc; ++i) {
const char *arg = argv[i];
if (arg && arg[0] == '+') {
// Parse +<digits>
const char *p = arg + 1;
if (*p != '\0') {
bool all_digits = true;
for (const char *q = p; *q; ++q) {
if (!std::isdigit(static_cast<unsigned char>(*q))) {
all_digits = false;
break;
}
}
if (all_digits) {
// Clamp to >=1 later; 0 disables.
try {
unsigned long v = std::stoul(p);
pending_line = static_cast<std::size_t>(v);
} catch (...) {
// Ignore malformed huge numbers
pending_line = 0;
}
continue; // look for the next file arg
}
}
// Fall through: not a +number, treat as filename starting with '+'
}
std::string err;
const std::string path = argv[i];
const std::string path = arg;
if (!editor.OpenFile(path, err)) {
editor.SetStatus("open: " + err);
std::cerr << "kte: " << err << "\n";
} else if (pending_line > 0) {
// Apply pending +N to the just-opened (current) buffer
if (Buffer *b = editor.CurrentBuffer()) {
std::size_t nrows = b->Nrows();
std::size_t line = pending_line > 0 ? pending_line - 1 : 0;
// 1-based to 0-based
if (nrows > 0) {
if (line >= nrows)
line = nrows - 1;
} else {
line = 0;
}
b->SetCursor(0, line);
// Do not force viewport offsets here; the frontend/renderer
// will establish dimensions and normalize visibility on first draw.
}
pending_line = 0; // consumed
}
}
// If we ended with a pending +N but no subsequent file, ignore it.
} else {
// Create a single empty buffer
editor.AddBuffer(Buffer());
@@ -126,14 +178,14 @@ main(int argc, const char *argv[])
InstallDefaultCommands();
// Select frontend
std::unique_ptr<Frontend> fe;
std::unique_ptr <Frontend> fe;
#if defined(KTE_BUILD_GUI)
if (use_gui) {
fe.reset(new GUIFrontend());
fe = std::make_unique<GUIFrontend>();
} else
#endif
{
fe.reset(new TerminalFrontend());
fe = std::make_unique<TerminalFrontend>();
}
if (!fe->Init(editor)) {