Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b91406860c | |||
| 8d1e9b2799 | |||
| c91fe214d6 | |||
| 99042f5ef1 | |||
| 96242154f7 | |||
| f34e88c490 | |||
| b8942b9804 | |||
| 65869bd143 |
82
.idea/workspace.xml
generated
82
.idea/workspace.xml
generated
@@ -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">{
|
||||
"useNewFormat": 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. - Delete `packaging.cmake` to streamline build system. - Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`. - Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing. - Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation. - 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. - 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.">
|
||||
<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">{
|
||||
"associatedIndex": 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. - 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.">
|
||||
<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. - 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.">
|
||||
<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. - Document `TestFrontend` for programmatic testing, including examples and usage details. - Add `UpdateBufferReference` to `UndoSystem` to support updating buffer associations." />
|
||||
<MESSAGE value="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure. - Delete `packaging.cmake` to streamline build system. - Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`. - Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing. - Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation. - 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. - Delete `packaging.cmake` to streamline build system. - Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`. - Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing. - Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation. - Enhance kill ring operations and new prompt workflows in `Editor`." />
|
||||
<MESSAGE value="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." />
|
||||
<MESSAGE value="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." />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="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." />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
45
Command.cc
45
Command.cc
@@ -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});
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
2
Editor.h
2
Editor.h
@@ -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)
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
51
default-gui.nix
Normal 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
42
default-nogui.nix
Normal 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
|
||||
'';
|
||||
}
|
||||
44
default.nix
44
default.nix
@@ -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
210
docs/kge.1
Normal 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
205
docs/kte.1
Normal 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
55
flake-gui.nix
Normal 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
38
flake.lock
generated
@@ -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",
|
||||
|
||||
66
flake.nix
66
flake.nix
@@ -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
126
main.cc
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user