Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f34e88c490 | |||
| b8942b9804 | |||
| 65869bd143 |
29
.idea/workspace.xml
generated
29
.idea/workspace.xml
generated
@@ -33,7 +33,8 @@
|
|||||||
</configurations>
|
</configurations>
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<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 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$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
@@ -128,7 +129,7 @@
|
|||||||
<recent name="$PROJECT_DIR$/docs" />
|
<recent name="$PROJECT_DIR$/docs" />
|
||||||
</key>
|
</key>
|
||||||
</component>
|
</component>
|
||||||
<component name="RunManager" selected="CMake Application.imgui">
|
<component name="RunManager" selected="CMake Application.kge">
|
||||||
<configuration default="true" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true">
|
<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">
|
<method v="2">
|
||||||
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||||
@@ -162,7 +163,7 @@
|
|||||||
<option name="number" value="Default" />
|
<option name="number" value="Default" />
|
||||||
<option name="presentableId" value="Default" />
|
<option name="presentableId" value="Default" />
|
||||||
<updated>1764457173148</updated>
|
<updated>1764457173148</updated>
|
||||||
<workItem from="1764457174208" duration="42867000" />
|
<workItem from="1764457174208" duration="46950000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
|
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -220,7 +221,23 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1764501532446</updated>
|
<updated>1764501532446</updated>
|
||||||
</task>
|
</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 />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -241,7 +258,9 @@
|
|||||||
<MESSAGE value="Add non-linear undo/redo design documentation and improve `UndoSystem` with backspace batching and GUI integration fixes." />
|
<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="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`." />
|
<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>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ project(kte)
|
|||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(KTE_VERSION "0.1.0")
|
set(KTE_VERSION "0.9.0")
|
||||||
|
|
||||||
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
|
||||||
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
|
||||||
@@ -104,6 +104,9 @@ install(TARGETS kte
|
|||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Man pages
|
||||||
|
install(FILES docs/kte.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
||||||
|
|
||||||
if (BUILD_TESTS)
|
if (BUILD_TESTS)
|
||||||
# test_undo executable for testing undo/redo system
|
# test_undo executable for testing undo/redo system
|
||||||
add_executable(test_undo
|
add_executable(test_undo
|
||||||
@@ -164,9 +167,11 @@ if (${BUILD_GUI})
|
|||||||
install(TARGETS kge
|
install(TARGETS kge
|
||||||
BUNDLE DESTINATION .
|
BUNDLE DESTINATION .
|
||||||
)
|
)
|
||||||
else()
|
else ()
|
||||||
install(TARGETS kge
|
install(TARGETS kge
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
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 ()
|
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 ---
|
// --- Buffers: switch/next/prev/close ---
|
||||||
static bool
|
static bool
|
||||||
cmd_buffer_switch_start(CommandContext &ctx)
|
cmd_buffer_switch_start(CommandContext &ctx)
|
||||||
@@ -990,6 +1000,37 @@ cmd_newline(CommandContext &ctx)
|
|||||||
} else {
|
} else {
|
||||||
ctx.editor.SetStatus("Nothing to confirm");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2372,6 +2413,10 @@ InstallDefaultCommands()
|
|||||||
CommandRegistry::Register({
|
CommandRegistry::Register({
|
||||||
CommandId::MoveCursorTo, "move-cursor-to", "Move cursor to y:x", cmd_move_cursor_to
|
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
|
// Undo/Redo
|
||||||
CommandRegistry::Register({CommandId::Undo, "undo", "Undo last edit", cmd_undo});
|
CommandRegistry::Register({CommandId::Undo, "undo", "Undo last edit", cmd_undo});
|
||||||
CommandRegistry::Register({CommandId::Redo, "redo", "Redo edit", cmd_redo});
|
CommandRegistry::Register({CommandId::Redo, "redo", "Redo edit", cmd_redo});
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ enum class CommandId {
|
|||||||
// Buffer operations
|
// Buffer operations
|
||||||
ReloadBuffer, // reload buffer from disk (C-k l)
|
ReloadBuffer, // reload buffer from disk (C-k l)
|
||||||
MarkAllAndJumpEnd, // set mark at beginning, jump to end (C-k a)
|
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
|
// Meta
|
||||||
UnknownKCommand, // arg: single character that was not recognized after C-k
|
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.) ---
|
// --- 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)
|
void StartPrompt(PromptKind kind, const std::string &label, const std::string &initial)
|
||||||
|
|||||||
@@ -279,6 +279,24 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
|
|||||||
MappedInput mi;
|
MappedInput mi;
|
||||||
bool produced = false;
|
bool produced = false;
|
||||||
switch (e.type) {
|
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: {
|
case SDL_KEYDOWN: {
|
||||||
// Remember state before mapping; used for TEXTINPUT suppression heuristics
|
// Remember state before mapping; used for TEXTINPUT suppression heuristics
|
||||||
const bool was_k_prefix = k_prefix_;
|
const bool was_k_prefix = k_prefix_;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ GUIRenderer::Draw(Editor &ed)
|
|||||||
const auto &lines = buf->Rows();
|
const auto &lines = buf->Rows();
|
||||||
// Reserve space for status bar at bottom
|
// Reserve space for status bar at bottom
|
||||||
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
|
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
|
||||||
ImGuiWindowFlags_HorizontalScrollbar);
|
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||||
// Detect click-to-move inside this scroll region
|
// Detect click-to-move inside this scroll region
|
||||||
ImVec2 list_origin = ImGui::GetCursorScreenPos();
|
ImVec2 list_origin = ImGui::GetCursorScreenPos();
|
||||||
float scroll_y = ImGui::GetScrollY();
|
float scroll_y = ImGui::GetScrollY();
|
||||||
@@ -56,24 +56,42 @@ GUIRenderer::Draw(Editor &ed)
|
|||||||
const float line_h = ImGui::GetTextLineHeight();
|
const float line_h = ImGui::GetTextLineHeight();
|
||||||
const float row_h = ImGui::GetTextLineHeightWithSpacing();
|
const float row_h = ImGui::GetTextLineHeightWithSpacing();
|
||||||
const float space_w = ImGui::CalcTextSize(" ").x;
|
const float space_w = ImGui::CalcTextSize(" ").x;
|
||||||
// If the command layer requested a specific top-of-screen (via Buffer::Rowoffs),
|
// Two-way sync between Buffer::Rowoffs and ImGui scroll position:
|
||||||
// force the ImGui scroll to match so paging aligns the first visible row.
|
// - 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;
|
bool forced_scroll = false;
|
||||||
{
|
{
|
||||||
std::size_t desired_top = buf->Rowoffs();
|
static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
|
||||||
long current_top = static_cast<long>(scroll_y / row_h);
|
static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
|
||||||
if (static_cast<long>(desired_top) != current_top) {
|
|
||||||
ImGui::SetScrollY(static_cast<float>(desired_top) * row_h);
|
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();
|
scroll_y = ImGui::GetScrollY();
|
||||||
forced_scroll = true;
|
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.
|
// Synchronize cursor and scrolling.
|
||||||
// A) When the user scrolls and the cursor goes off-screen, move the cursor to the nearest visible row.
|
// Ensure the cursor is visible even on the first frame or when it didn't move,
|
||||||
// B) When the cursor moves (via keyboard commands), scroll it back into view.
|
// 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
|
// Compute visible row range using the child window height
|
||||||
float child_h = ImGui::GetWindowHeight();
|
float child_h = ImGui::GetWindowHeight();
|
||||||
long first_row = static_cast<long>(scroll_y / row_h);
|
long first_row = static_cast<long>(scroll_y / row_h);
|
||||||
@@ -82,37 +100,7 @@ GUIRenderer::Draw(Editor &ed)
|
|||||||
vis_rows = 1;
|
vis_rows = 1;
|
||||||
long last_row = first_row + 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
|
if (!forced_scroll) {
|
||||||
// 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) {
|
|
||||||
long cyr = static_cast<long>(cy);
|
long cyr = static_cast<long>(cy);
|
||||||
if (cyr < first_row || cyr > last_row) {
|
if (cyr < first_row || cyr > last_row) {
|
||||||
float target = (static_cast<float>(cyr) - std::max(0L, vis_rows / 2)) * row_h;
|
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;
|
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
|
// Handle mouse click before rendering to avoid dependent on drawn items
|
||||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
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::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: not set", row1, col1);
|
||||||
std::string right = rbuf;
|
std::string right = rbuf;
|
||||||
|
|
||||||
// Middle message
|
// Middle message: if a prompt is active, show "Label: text"; otherwise show status
|
||||||
const std::string &msg = ed.Status();
|
std::string msg;
|
||||||
|
if (ed.PromptActive()) {
|
||||||
|
msg = ed.PromptLabel();
|
||||||
|
if (!msg.empty())
|
||||||
|
msg += ": ";
|
||||||
|
msg += ed.PromptText();
|
||||||
|
} else {
|
||||||
|
msg = ed.Status();
|
||||||
|
}
|
||||||
|
|
||||||
// Measurements
|
// Measurements
|
||||||
ImVec2 left_sz = ImGui::CalcTextSize(left.c_str());
|
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':
|
case 'a':
|
||||||
out = CommandId::MarkAllAndJumpEnd;
|
out = CommandId::MarkAllAndJumpEnd;
|
||||||
return true; // C-k a (mark all and jump to end)
|
return true; // C-k a (mark all and jump to end)
|
||||||
|
case 'g':
|
||||||
|
out = CommandId::JumpToLine;
|
||||||
|
return true; // C-k g (goto line)
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,19 @@ map_key_to_command(const int ch,
|
|||||||
case KEY_MOUSE: {
|
case KEY_MOUSE: {
|
||||||
MEVENT ev{};
|
MEVENT ev{};
|
||||||
if (getmouse(&ev) == OK) {
|
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
|
// React to left button click/press
|
||||||
if (ev.bstate & (BUTTON1_CLICKED | BUTTON1_PRESSED | BUTTON1_RELEASED)) {
|
if (ev.bstate & (BUTTON1_CLICKED | BUTTON1_PRESSED | BUTTON1_RELEASED)) {
|
||||||
char buf[64];
|
char buf[64];
|
||||||
|
|||||||
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.
|
||||||
165
main.cc
165
main.cc
@@ -1,7 +1,12 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <cctype>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "Editor.h"
|
#include "Editor.h"
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
@@ -28,6 +33,109 @@ PrintUsage(const char *prog)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(KTE_BUILD_GUI)
|
||||||
|
// Detach the process from the controlling terminal when running the GUI so the
|
||||||
|
// launching terminal can be closed. This mirrors typical GUI app behavior on
|
||||||
|
// POSIX systems.
|
||||||
|
static void
|
||||||
|
DetachFromTerminalIfGUI(bool use_gui)
|
||||||
|
{
|
||||||
|
#if defined(__APPLE__) || defined(__linux__) || defined(__unix__)
|
||||||
|
if (!use_gui)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ignore SIGHUP so closing the terminal won't terminate us.
|
||||||
|
signal(SIGHUP, SIG_IGN);
|
||||||
|
|
||||||
|
// Helper: redirect stdio to /dev/null and optionally close extra FDs.
|
||||||
|
auto redirect_stdio_and_close = []() {
|
||||||
|
// Reset file mode creation mask and working directory to a safe default
|
||||||
|
umask(0);
|
||||||
|
chdir("/");
|
||||||
|
|
||||||
|
FILE *fnull_r = fopen("/dev/null", "r");
|
||||||
|
if (fnull_r) {
|
||||||
|
dup2(fileno(fnull_r), STDIN_FILENO);
|
||||||
|
}
|
||||||
|
FILE *fnull_w = fopen("/dev/null", "w");
|
||||||
|
if (fnull_w) {
|
||||||
|
dup2(fileno(fnull_w), STDOUT_FILENO);
|
||||||
|
dup2(fileno(fnull_w), STDERR_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close any other inherited FDs to avoid keeping terminal/pty or pipes open
|
||||||
|
long max_fd = sysconf(_SC_OPEN_MAX);
|
||||||
|
if (max_fd < 0)
|
||||||
|
max_fd = 256; // conservative fallback
|
||||||
|
for (long fd = 3; fd < max_fd; ++fd) {
|
||||||
|
close(static_cast<int>(fd));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
// macOS: daemon(3) is deprecated and treated as an error with -Werror.
|
||||||
|
// Use double-fork + setsid and redirect stdio to /dev/null.
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid > 0) {
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setsid() < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t pid2 = fork();
|
||||||
|
if (pid2 < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid2 > 0) {
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect_stdio_and_close();
|
||||||
|
#else
|
||||||
|
// Prefer daemon(3) on non-Apple POSIX; fall back to manual detach if it fails.
|
||||||
|
if (daemon(0, 0) == 0) {
|
||||||
|
redirect_stdio_and_close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid > 0) {
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setsid() < 0) {
|
||||||
|
// bogus check
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t pid2 = fork();
|
||||||
|
if (pid2 < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid2 > 0) {
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect_stdio_and_close();
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
(void) use_gui;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, const char *argv[])
|
main(int argc, const char *argv[])
|
||||||
{
|
{
|
||||||
@@ -104,18 +212,67 @@ main(int argc, const char *argv[])
|
|||||||
use_gui = false;
|
use_gui = false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
// If using GUI, detach from the controlling terminal so the terminal can be closed.
|
||||||
|
DetachFromTerminalIfGUI(use_gui);
|
||||||
#endif
|
#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) {
|
if (optind < argc) {
|
||||||
|
std::size_t pending_line = 0; // 0 = no pending line
|
||||||
for (int i = optind; i < argc; ++i) {
|
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;
|
std::string err;
|
||||||
const std::string path = argv[i];
|
const std::string path = arg;
|
||||||
if (!editor.OpenFile(path, err)) {
|
if (!editor.OpenFile(path, err)) {
|
||||||
editor.SetStatus("open: " + err);
|
editor.SetStatus("open: " + err);
|
||||||
std::cerr << "kte: " << err << "\n";
|
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 {
|
} else {
|
||||||
// Create a single empty buffer
|
// Create a single empty buffer
|
||||||
editor.AddBuffer(Buffer());
|
editor.AddBuffer(Buffer());
|
||||||
@@ -129,11 +286,11 @@ main(int argc, const char *argv[])
|
|||||||
std::unique_ptr<Frontend> fe;
|
std::unique_ptr<Frontend> fe;
|
||||||
#if defined(KTE_BUILD_GUI)
|
#if defined(KTE_BUILD_GUI)
|
||||||
if (use_gui) {
|
if (use_gui) {
|
||||||
fe.reset(new GUIFrontend());
|
fe = std::make_unique<GUIFrontend>();
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
fe.reset(new TerminalFrontend());
|
fe = std::make_unique<TerminalFrontend>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fe->Init(editor)) {
|
if (!fe->Init(editor)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user