Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cb7d36f2a | |||
| 09a6df0c33 | |||
| 69457c424c | |||
| 24c8040d8a | |||
| e869249a7c | |||
| 35e957b326 | |||
| e7eb35626c | |||
| f9128a336d | |||
| f8d0e9213f | |||
| 68286ecb7c | |||
| 44807d0f40 | |||
| 41f37478c1 | |||
| d582979eb3 | |||
| 2b194c7910 | |||
| 6498213378 | |||
| 1a37a92534 | |||
| fb5976f123 | |||
| e4cd4877cc |
114
.github/workflows/release.yml
vendored
@@ -9,6 +9,9 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
homebrew:
|
||||
name: Bump Homebrew formula
|
||||
@@ -35,3 +38,114 @@ jobs:
|
||||
Created by https://github.com/mislav/bump-homebrew-formula-action
|
||||
env:
|
||||
COMMITTER_TOKEN: ${{ secrets.GH_CPAT }}
|
||||
linux-build:
|
||||
name: Build Linux ${{ matrix.arch }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
runner: ubuntu-latest
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential cmake pkg-config \
|
||||
libncurses5-dev libncursesw5-dev \
|
||||
libsdl2-dev libfreetype6-dev mesa-common-dev
|
||||
|
||||
- name: Configure (CMake, GUI ON)
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_GUI=ON
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake --build build --config ${BUILD_TYPE} -j
|
||||
|
||||
- name: Prepare dist
|
||||
run: |
|
||||
mkdir -p dist/linux-${{ matrix.arch }}
|
||||
cp build/kte dist/linux-${{ matrix.arch }}/
|
||||
cp build/kge dist/linux-${{ matrix.arch }}/
|
||||
strip dist/linux-${{ matrix.arch }}/kte || true
|
||||
strip dist/linux-${{ matrix.arch }}/kge || true
|
||||
|
||||
- name: Upload artifact (linux-${{ matrix.arch }})
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-${{ matrix.arch }}
|
||||
path: dist/linux-${{ matrix.arch }}/*
|
||||
|
||||
macos-build:
|
||||
name: Build macOS arm64 (.app)
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install deps (brew)
|
||||
run: |
|
||||
brew update
|
||||
brew install cmake ncurses sdl2 freetype
|
||||
|
||||
- name: Configure (CMake, GUI ON, arm64)
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_GUI=ON -DCMAKE_OSX_ARCHITECTURES=arm64
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake --build build --config ${BUILD_TYPE} -j
|
||||
|
||||
- name: Zip kge.app
|
||||
run: |
|
||||
mkdir -p dist/macos-arm64
|
||||
cd build
|
||||
ditto -c -k --sequesterRsrc --keepParent kge.app ../dist/macos-arm64/kge.app.zip
|
||||
|
||||
- name: Upload artifact (macos-arm64)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-arm64
|
||||
path: dist/macos-arm64/kge.app.zip
|
||||
|
||||
release:
|
||||
name: Create GitHub Release
|
||||
needs: [ linux-build, macos-build ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Reshape artifact layout
|
||||
run: |
|
||||
ls -R dist
|
||||
# Actions download-artifact places each named artifact in a subfolder
|
||||
# Move into the expected dist structure for GoReleaser
|
||||
mkdir -p dist/linux-amd64 dist/linux-arm64 dist/macos-arm64
|
||||
if [ -d dist/linux-amd64/linux-amd64 ]; then mv dist/linux-amd64/linux-amd64/* dist/linux-amd64/; fi
|
||||
if [ -d dist/linux-arm64/linux-arm64 ]; then mv dist/linux-arm64/linux-arm64/* dist/linux-arm64/; fi
|
||||
if [ -d dist/macos-arm64/macos-arm64 ]; then mv dist/macos-arm64/macos-arm64/* dist/macos-arm64/; fi
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22.x'
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --config .goreleaser.yaml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_CPAT }}
|
||||
|
||||
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
!.idea
|
||||
cmake-build*
|
||||
build
|
||||
/imgui.ini
|
||||
result
|
||||
|
||||
69
.goreleaser.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
# GoReleaser configuration for kte/kge (C++ project)
|
||||
# We use GoReleaser only for releasing: changelog, checksums, and uploading
|
||||
# prebuilt artifacts that are produced by the CI workflow.
|
||||
|
||||
version: 2
|
||||
|
||||
project_name: kte
|
||||
|
||||
before:
|
||||
hooks:
|
||||
# No build here; artifacts are produced by the CI jobs and placed into dist/
|
||||
- echo "GoReleaser: using prebuilt artifacts from dist/"
|
||||
|
||||
builds:
|
||||
# No Go builds; this is a C++ project.
|
||||
- id: noop
|
||||
skip: true
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
algorithm: sha256
|
||||
|
||||
release:
|
||||
# Rely on GITHUB_TOKEN from the workflow.
|
||||
draft: false
|
||||
prerelease: auto
|
||||
mode: replace
|
||||
footer: |
|
||||
Built with CMake. See README for platform dependencies.
|
||||
extra_files:
|
||||
# Linux binaries (amd64, arm64)
|
||||
- glob: dist/linux-amd64/kte
|
||||
- glob: dist/linux-amd64/kge
|
||||
- glob: dist/linux-arm64/kte
|
||||
- glob: dist/linux-arm64/kge
|
||||
# macOS Apple Silicon app bundle (zipped)
|
||||
- glob: dist/macos-arm64/kge.app.zip
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
use: github
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs: '
|
||||
- '^chore: '
|
||||
- '^ci: '
|
||||
|
||||
announce:
|
||||
skip: true
|
||||
|
||||
signs:
|
||||
# No signing by default.
|
||||
- artifacts: none
|
||||
|
||||
archives:
|
||||
# We are uploading raw binaries / zip created by CI, so no archives here.
|
||||
- id: none
|
||||
formats: [binary]
|
||||
builds: [noop]
|
||||
|
||||
blobs: []
|
||||
|
||||
brews: []
|
||||
|
||||
snapcrafts: []
|
||||
|
||||
nfpm: []
|
||||
|
||||
publishers: []
|
||||
96
.idea/workspace.xml
generated
@@ -33,12 +33,20 @@
|
||||
</configurations>
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="Refactor code for consistency and enhanced functionality. - Normalize path handling for buffer operations, supporting tilde expansion and absolute paths. - Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes. - Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`. - Refine keybindings and enhance existing commands for improved command flow. - Adjust GUI and terminal renderers to display total line counts alongside filenames. - Update coding style to align with project guidelines.">
|
||||
<list default="true" id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="add regex and search/replace functionality to editor">
|
||||
<change afterPath="$PROJECT_DIR$/HelpText.cc" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/HelpText.h" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Buffer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Buffer.h" beforeDir="false" afterPath="$PROJECT_DIR$/Buffer.h" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/CMakeLists.txt" beforeDir="false" afterPath="$PROJECT_DIR$/CMakeLists.txt" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Command.cc" beforeDir="false" afterPath="$PROJECT_DIR$/Command.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Command.h" beforeDir="false" afterPath="$PROJECT_DIR$/Command.h" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Editor.h" beforeDir="false" afterPath="$PROJECT_DIR$/Editor.h" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/GUIRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/GUIRenderer.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/KKeymap.cc" beforeDir="false" afterPath="$PROJECT_DIR$/KKeymap.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/main.cc" beforeDir="false" afterPath="$PROJECT_DIR$/main.cc" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/ROADMAP.md" beforeDir="false" afterPath="$PROJECT_DIR$/ROADMAP.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/TerminalRenderer.cc" beforeDir="false" afterPath="$PROJECT_DIR$/TerminalRenderer.cc" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -53,6 +61,12 @@
|
||||
<option name="myRunOnSave" value="true" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="PUSH_TAGS">
|
||||
<GitPushTagMode>
|
||||
<option name="argument" value="--tags" />
|
||||
<option name="title" value="All" />
|
||||
</GitPushTagMode>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
<option name="UPDATE_TYPE" value="REBASE" />
|
||||
</component>
|
||||
@@ -60,18 +74,13 @@
|
||||
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="mock:///dummy.cpp" root0="SKIP_HIGHLIGHTING" />
|
||||
</component>
|
||||
<component name="OptimizeOnSaveOptions">
|
||||
<option name="myRunOnSave" value="true" />
|
||||
</component>
|
||||
<component name="ProblemsViewState">
|
||||
<option name="selectedTabId" value="AISelfReview" />
|
||||
</component>
|
||||
<component name="ProjectApplicationVersion">
|
||||
<option name="ide" value="CLion" />
|
||||
<option name="majorVersion" value="2025" />
|
||||
@@ -134,12 +143,17 @@
|
||||
</key>
|
||||
</component>
|
||||
<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">
|
||||
<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" WORKING_DIR="file://$PROJECT_DIR$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="kge" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="kte" RUN_TARGET_NAME="kge">
|
||||
<configuration name="kge" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="$PROJECT_DIR$/cmake-build-debug/test.txt" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$PROJECT_DIR$" 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>
|
||||
@@ -170,7 +184,7 @@
|
||||
<workItem from="1764539556448" duration="156000" />
|
||||
<workItem from="1764539725338" duration="1075000" />
|
||||
<workItem from="1764542392763" duration="3512000" />
|
||||
<workItem from="1764548345516" duration="3453000" />
|
||||
<workItem from="1764548345516" duration="28341000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
|
||||
<option name="closed" value="true" />
|
||||
@@ -252,7 +266,55 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1764550164829</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="11" />
|
||||
<task id="LOCAL-00011" summary="Add horizontal scrolling support and refactor mouse click handling in GUI. - Introduce horizontal scrolling with column offset synchronization in GUI. - Refactor mouse click handling for improved accuracy and viewport alignment. - Enhance tab expansion and cursor rendering logic for better user experience. - Replace redundant variable declarations in `Buffer` for cleaner code.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1764551986561</created>
|
||||
<option name="number" value="00011" />
|
||||
<option name="presentableId" value="LOCAL-00011" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1764551986561</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00012" summary="Introduce file picker and GUI configuration with enhancements. - Add visual file picker for GUI with toggle support. - Introduce `GUIConfig` class for loading GUI settings from configuration file. - Refactor window initialization to support dynamic sizing based on configuration. - Add macOS-specific handling for fullscreen behavior. - Improve header inclusion order and minor code cleanup.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1764556512864</created>
|
||||
<option name="number" value="00012" />
|
||||
<option name="presentableId" value="LOCAL-00012" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1764556512864</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00013" summary="Add buffer position display and documentation improvements. - Display buffer position prefix "[x/N]" in GUI and terminal renderers. - Improve `kte` and `kge` man pages with frontend usage details and project homepage. - Update README with GUI invocation instructions. - Bump version to 1.0.0.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1764556854788</created>
|
||||
<option name="number" value="00013" />
|
||||
<option name="presentableId" value="LOCAL-00013" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1764556854788</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00014" summary="Actually add the screenshot.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1764557759844</created>
|
||||
<option name="number" value="00014" />
|
||||
<option name="presentableId" value="LOCAL-00014" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1764557759844</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00015" summary="Fix void crash in kge.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1764568264996</created>
|
||||
<option name="number" value="00015" />
|
||||
<option name="presentableId" value="LOCAL-00015" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1764568264996</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00016" summary="add regex and search/replace functionality to editor">
|
||||
<option name="closed" value="true" />
|
||||
<created>1764574397967</created>
|
||||
<option name="number" value="00016" />
|
||||
<option name="presentableId" value="LOCAL-00016" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1764574397967</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="17" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -276,7 +338,13 @@
|
||||
<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." />
|
||||
<MESSAGE value="Refactor code for consistency and enhanced functionality. - Normalize path handling for buffer operations, supporting tilde expansion and absolute paths. - Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes. - Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`. - Refine keybindings and enhance existing commands for improved command flow. - Adjust GUI and terminal renderers to display total line counts alongside filenames. - Update coding style to align with project guidelines." />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Refactor code for consistency and enhanced functionality. - Normalize path handling for buffer operations, supporting tilde expansion and absolute paths. - Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes. - Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`. - Refine keybindings and enhance existing commands for improved command flow. - Adjust GUI and terminal renderers to display total line counts alongside filenames. - Update coding style to align with project guidelines." />
|
||||
<MESSAGE value="Add horizontal scrolling support and refactor mouse click handling in GUI. - Introduce horizontal scrolling with column offset synchronization in GUI. - Refactor mouse click handling for improved accuracy and viewport alignment. - Enhance tab expansion and cursor rendering logic for better user experience. - Replace redundant variable declarations in `Buffer` for cleaner code." />
|
||||
<MESSAGE value="Introduce file picker and GUI configuration with enhancements. - Add visual file picker for GUI with toggle support. - Introduce `GUIConfig` class for loading GUI settings from configuration file. - Refactor window initialization to support dynamic sizing based on configuration. - Add macOS-specific handling for fullscreen behavior. - Improve header inclusion order and minor code cleanup." />
|
||||
<MESSAGE value="Add buffer position display and documentation improvements. - Display buffer position prefix "[x/N]" in GUI and terminal renderers. - Improve `kte` and `kge` man pages with frontend usage details and project homepage. - Update README with GUI invocation instructions. - Bump version to 1.0.0." />
|
||||
<MESSAGE value="Actually add the screenshot." />
|
||||
<MESSAGE value="Fix void crash in kge." />
|
||||
<MESSAGE value="add regex and search/replace functionality to editor" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="add regex and search/replace functionality to editor" />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
||||
24
Buffer.cc
@@ -1,12 +1,12 @@
|
||||
#include "Buffer.h"
|
||||
#include "UndoSystem.h"
|
||||
#include "UndoTree.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "Buffer.h"
|
||||
#include "UndoSystem.h"
|
||||
#include "UndoTree.h"
|
||||
|
||||
|
||||
Buffer::Buffer()
|
||||
{
|
||||
@@ -36,6 +36,7 @@ Buffer::Buffer(const Buffer &other)
|
||||
filename_ = other.filename_;
|
||||
is_file_backed_ = other.is_file_backed_;
|
||||
dirty_ = other.dirty_;
|
||||
read_only_ = other.read_only_;
|
||||
mark_set_ = other.mark_set_;
|
||||
mark_curx_ = other.mark_curx_;
|
||||
mark_cury_ = other.mark_cury_;
|
||||
@@ -60,6 +61,7 @@ Buffer::operator=(const Buffer &other)
|
||||
filename_ = other.filename_;
|
||||
is_file_backed_ = other.is_file_backed_;
|
||||
dirty_ = other.dirty_;
|
||||
read_only_ = other.read_only_;
|
||||
mark_set_ = other.mark_set_;
|
||||
mark_curx_ = other.mark_curx_;
|
||||
mark_cury_ = other.mark_cury_;
|
||||
@@ -82,6 +84,7 @@ Buffer::Buffer(Buffer &&other) noexcept
|
||||
filename_(std::move(other.filename_)),
|
||||
is_file_backed_(other.is_file_backed_),
|
||||
dirty_(other.dirty_),
|
||||
read_only_(other.read_only_),
|
||||
mark_set_(other.mark_set_),
|
||||
mark_curx_(other.mark_curx_),
|
||||
mark_cury_(other.mark_cury_),
|
||||
@@ -112,6 +115,7 @@ Buffer::operator=(Buffer &&other) noexcept
|
||||
filename_ = std::move(other.filename_);
|
||||
is_file_backed_ = other.is_file_backed_;
|
||||
dirty_ = other.dirty_;
|
||||
read_only_ = other.read_only_;
|
||||
mark_set_ = other.mark_set_;
|
||||
mark_curx_ = other.mark_curx_;
|
||||
mark_cury_ = other.mark_cury_;
|
||||
@@ -364,9 +368,9 @@ Buffer::insert_text(int row, int col, std::string_view text)
|
||||
rows_[y].insert(x, seg);
|
||||
x += seg.size();
|
||||
// Split line at x
|
||||
std::string tail = rows_[y].substr(x);
|
||||
rows_[y].erase(x);
|
||||
rows_.insert(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1), tail);
|
||||
std::string tail = rows_[y].substr(x);
|
||||
rows_[y].erase(x);
|
||||
rows_.insert(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1), Line(tail));
|
||||
y += 1;
|
||||
x = 0;
|
||||
remain.erase(0, pos + 1);
|
||||
@@ -426,8 +430,8 @@ Buffer::split_line(int row, const int col)
|
||||
const auto y = static_cast<std::size_t>(row);
|
||||
const auto x = std::min<std::size_t>(static_cast<std::size_t>(col), rows_[y].size());
|
||||
const auto tail = rows_[y].substr(x);
|
||||
rows_[y].erase(x);
|
||||
rows_.insert(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1), tail);
|
||||
rows_[y].erase(x);
|
||||
rows_.insert(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1), Line(tail));
|
||||
}
|
||||
|
||||
|
||||
@@ -455,7 +459,7 @@ Buffer::insert_row(int row, const std::string_view text)
|
||||
row = 0;
|
||||
if (static_cast<std::size_t>(row) > rows_.size())
|
||||
row = static_cast<int>(rows_.size());
|
||||
rows_.insert(rows_.begin() + row, std::string(text));
|
||||
rows_.insert(rows_.begin() + row, Line(std::string(text)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
92
Buffer.h
@@ -5,6 +5,7 @@
|
||||
#define KTE_BUFFER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
@@ -12,9 +13,10 @@
|
||||
#include "AppendBuffer.h"
|
||||
#include "UndoSystem.h"
|
||||
|
||||
|
||||
class Buffer {
|
||||
public:
|
||||
Buffer();
|
||||
Buffer();
|
||||
|
||||
Buffer(const Buffer &other);
|
||||
|
||||
@@ -75,13 +77,13 @@ public:
|
||||
Line() = default;
|
||||
|
||||
|
||||
Line(const char *s)
|
||||
explicit Line(const char *s)
|
||||
{
|
||||
assign_from(s ? std::string(s) : std::string());
|
||||
}
|
||||
|
||||
|
||||
Line(const std::string &s)
|
||||
explicit Line(const std::string &s)
|
||||
{
|
||||
assign_from(s);
|
||||
}
|
||||
@@ -137,29 +139,38 @@ public:
|
||||
|
||||
|
||||
// conversions
|
||||
operator std::string() const
|
||||
explicit operator std::string() const
|
||||
{
|
||||
return std::string(buf_.Data() ? buf_.Data() : "", buf_.Size());
|
||||
return {buf_.Data() ? buf_.Data() : "", buf_.Size()};
|
||||
}
|
||||
|
||||
|
||||
// string-like API used by command/renderer layers (implemented via materialization for now)
|
||||
std::string substr(std::size_t pos) const
|
||||
[[nodiscard]] std::string substr(std::size_t pos) const
|
||||
{
|
||||
const std::size_t n = buf_.Size();
|
||||
if (pos >= n)
|
||||
return std::string();
|
||||
return std::string(buf_.Data() + pos, n - pos);
|
||||
return {};
|
||||
return {buf_.Data() + pos, n - pos};
|
||||
}
|
||||
|
||||
|
||||
std::string substr(std::size_t pos, std::size_t len) const
|
||||
[[nodiscard]] std::string substr(std::size_t pos, std::size_t len) const
|
||||
{
|
||||
const std::size_t n = buf_.Size();
|
||||
if (pos >= n)
|
||||
return std::string();
|
||||
return {};
|
||||
const std::size_t take = (pos + len > n) ? (n - pos) : len;
|
||||
return std::string(buf_.Data() + pos, take);
|
||||
return {buf_.Data() + pos, take};
|
||||
}
|
||||
|
||||
|
||||
// minimal find() to support search within a line
|
||||
[[nodiscard]] std::size_t find(const std::string &needle, const std::size_t pos = 0) const
|
||||
{
|
||||
// Materialize to std::string for now; Line is backed by AppendBuffer
|
||||
const auto s = static_cast<std::string>(*this);
|
||||
return s.find(needle, pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -251,6 +262,14 @@ public:
|
||||
return filename_;
|
||||
}
|
||||
|
||||
// Set a virtual (non file-backed) display name for this buffer, e.g. "+HELP+"
|
||||
// This does not mark the buffer as file-backed.
|
||||
void SetVirtualName(const std::string &name)
|
||||
{
|
||||
filename_ = name;
|
||||
is_file_backed_ = false;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] bool IsFileBacked() const
|
||||
{
|
||||
@@ -258,26 +277,42 @@ public:
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] bool Dirty() const
|
||||
{
|
||||
return dirty_;
|
||||
}
|
||||
[[nodiscard]] bool Dirty() const
|
||||
{
|
||||
return dirty_;
|
||||
}
|
||||
|
||||
// Read-only flag
|
||||
[[nodiscard]] bool IsReadOnly() const
|
||||
{
|
||||
return read_only_;
|
||||
}
|
||||
|
||||
void SetReadOnly(bool ro)
|
||||
{
|
||||
read_only_ = ro;
|
||||
}
|
||||
|
||||
void ToggleReadOnly()
|
||||
{
|
||||
read_only_ = !read_only_;
|
||||
}
|
||||
|
||||
|
||||
void SetCursor(std::size_t x, std::size_t y)
|
||||
void SetCursor(const std::size_t x, const std::size_t y)
|
||||
{
|
||||
curx_ = x;
|
||||
cury_ = y;
|
||||
}
|
||||
|
||||
|
||||
void SetRenderX(std::size_t rx)
|
||||
void SetRenderX(const std::size_t rx)
|
||||
{
|
||||
rx_ = rx;
|
||||
}
|
||||
|
||||
|
||||
void SetOffsets(std::size_t row, std::size_t col)
|
||||
void SetOffsets(const std::size_t row, const std::size_t col)
|
||||
{
|
||||
rowoffs_ = row;
|
||||
coloffs_ = col;
|
||||
@@ -297,7 +332,7 @@ public:
|
||||
}
|
||||
|
||||
|
||||
void SetMark(std::size_t x, std::size_t y)
|
||||
void SetMark(const std::size_t x, const std::size_t y)
|
||||
{
|
||||
mark_set_ = true;
|
||||
mark_curx_ = x;
|
||||
@@ -342,20 +377,21 @@ public:
|
||||
// Undo system accessors (created per-buffer)
|
||||
UndoSystem *Undo();
|
||||
|
||||
const UndoSystem *Undo() const;
|
||||
[[nodiscard]] const UndoSystem *Undo() const;
|
||||
|
||||
private:
|
||||
// State mirroring original C struct (without undo_tree)
|
||||
std::size_t curx_ = 0, cury_ = 0; // cursor position in characters
|
||||
std::size_t rx_ = 0; // render x (tabs expanded)
|
||||
std::size_t nrows_ = 0; // number of rows
|
||||
// State mirroring original C struct (without undo_tree)
|
||||
std::size_t curx_ = 0, cury_ = 0; // cursor position in characters
|
||||
std::size_t rx_ = 0; // render x (tabs expanded)
|
||||
std::size_t nrows_ = 0; // number of rows
|
||||
std::size_t rowoffs_ = 0, coloffs_ = 0; // viewport offsets
|
||||
std::vector<Line> rows_; // buffer rows (without trailing newlines)
|
||||
std::string filename_;
|
||||
bool is_file_backed_ = false;
|
||||
bool dirty_ = false;
|
||||
bool mark_set_ = false;
|
||||
std::size_t mark_curx_ = 0, mark_cury_ = 0;
|
||||
bool is_file_backed_ = false;
|
||||
bool dirty_ = false;
|
||||
bool read_only_ = false;
|
||||
bool mark_set_ = false;
|
||||
std::size_t mark_curx_ = 0, mark_cury_ = 0;
|
||||
|
||||
// Per-buffer undo state
|
||||
std::unique_ptr<struct UndoTree> undo_tree_;
|
||||
|
||||
@@ -4,14 +4,15 @@ project(kte)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(KTE_VERSION "0.9.2")
|
||||
set(KTE_VERSION "1.0.5")
|
||||
|
||||
# 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_GUI ON 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" ON)
|
||||
set(KTE_FONT_SIZE "18.0" CACHE STRING "Default font size for GUI")
|
||||
option(KTE_UNDO_DEBUG "Enable undo instrumentation logs" OFF)
|
||||
|
||||
if (CMAKE_HOST_UNIX)
|
||||
message(STATUS "Build system is POSIX.")
|
||||
@@ -55,6 +56,7 @@ set(COMMON_SOURCES
|
||||
Buffer.cc
|
||||
Editor.cc
|
||||
Command.cc
|
||||
HelpText.cc
|
||||
KKeymap.cc
|
||||
TerminalInputHandler.cc
|
||||
TerminalRenderer.cc
|
||||
@@ -74,6 +76,7 @@ set(COMMON_HEADERS
|
||||
Editor.h
|
||||
AppendBuffer.h
|
||||
Command.h
|
||||
HelpText.h
|
||||
KKeymap.h
|
||||
InputHandler.h
|
||||
TerminalInputHandler.h
|
||||
@@ -99,6 +102,9 @@ add_executable(kte
|
||||
if (KTE_USE_PIECE_TABLE)
|
||||
target_compile_definitions(kte PRIVATE KTE_USE_PIECE_TABLE=1)
|
||||
endif ()
|
||||
if (KTE_UNDO_DEBUG)
|
||||
target_compile_definitions(kte PRIVATE KTE_UNDO_DEBUG=1)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(kte ${CURSES_LIBRARIES})
|
||||
|
||||
@@ -121,6 +127,10 @@ if (BUILD_TESTS)
|
||||
target_compile_definitions(test_undo PRIVATE KTE_USE_PIECE_TABLE=1)
|
||||
endif ()
|
||||
|
||||
if (KTE_UNDO_DEBUG)
|
||||
target_compile_definitions(test_undo PRIVATE KTE_UNDO_DEBUG=1)
|
||||
endif ()
|
||||
|
||||
|
||||
target_link_libraries(test_undo ${CURSES_LIBRARIES})
|
||||
endif ()
|
||||
@@ -128,6 +138,8 @@ endif ()
|
||||
if (${BUILD_GUI})
|
||||
target_sources(kte PRIVATE
|
||||
Font.h
|
||||
GUIConfig.cc
|
||||
GUIConfig.h
|
||||
GUIRenderer.cc
|
||||
GUIRenderer.h
|
||||
GUIInputHandler.cc
|
||||
@@ -142,6 +154,8 @@ if (${BUILD_GUI})
|
||||
main.cc
|
||||
${COMMON_SOURCES}
|
||||
${COMMON_HEADERS}
|
||||
GUIConfig.cc
|
||||
GUIConfig.h
|
||||
GUIRenderer.cc
|
||||
GUIRenderer.h
|
||||
GUIInputHandler.cc
|
||||
@@ -149,10 +163,22 @@ if (${BUILD_GUI})
|
||||
GUIFrontend.cc
|
||||
GUIFrontend.h)
|
||||
target_compile_definitions(kge PRIVATE KTE_BUILD_GUI=1 KTE_DEFAULT_GUI=1 KTE_FONT_SIZE=${KTE_FONT_SIZE})
|
||||
if (KTE_UNDO_DEBUG)
|
||||
target_compile_definitions(kge PRIVATE KTE_UNDO_DEBUG=1)
|
||||
endif ()
|
||||
target_link_libraries(kge ${CURSES_LIBRARIES} imgui)
|
||||
|
||||
# On macOS, build kge as a proper .app bundle
|
||||
if (APPLE)
|
||||
# Define the icon file
|
||||
set(MACOSX_BUNDLE_ICON_FILE kge.icns)
|
||||
set(kge_ICON "${CMAKE_CURRENT_SOURCE_DIR}/${MACOSX_BUNDLE_ICON_FILE}")
|
||||
|
||||
# Add icon to the target sources and mark it as a resource
|
||||
target_sources(kge PRIVATE ${kge_ICON})
|
||||
set_source_files_properties(${kge_ICON} PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources)
|
||||
|
||||
# Configure Info.plist with version and identifiers
|
||||
set(KGE_BUNDLE_ID "dev.wntrmute.kge")
|
||||
configure_file(
|
||||
@@ -164,11 +190,23 @@ if (${BUILD_GUI})
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER ${KGE_BUNDLE_ID}
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "kge"
|
||||
MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE}
|
||||
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/kge-Info.plist")
|
||||
|
||||
add_dependencies(kge kte)
|
||||
add_custom_command(TARGET kge POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:kte>
|
||||
$<TARGET_FILE_DIR:kge>/kte
|
||||
COMMENT "Copying kte binary into kge.app bundle")
|
||||
|
||||
install(TARGETS kge
|
||||
BUNDLE DESTINATION .
|
||||
)
|
||||
|
||||
install(TARGETS kte
|
||||
RUNTIME DESTINATION kge.app/Contents/MacOS
|
||||
)
|
||||
else ()
|
||||
install(TARGETS kge
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
@@ -176,4 +214,5 @@ if (${BUILD_GUI})
|
||||
endif ()
|
||||
# Install kge man page only when GUI is built
|
||||
install(FILES docs/kge.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
||||
install(FILES kge.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons)
|
||||
endif ()
|
||||
|
||||
881
Command.cc
@@ -23,7 +23,11 @@ enum class CommandId {
|
||||
Refresh, // force redraw
|
||||
KPrefix, // show "C-k _" prompt in status when entering k-command
|
||||
FindStart, // begin incremental search (placeholder)
|
||||
RegexFindStart, // begin regex search (C-r)
|
||||
RegexpReplace, // begin regex search & replace (C-t)
|
||||
SearchReplace, // begin search & replace (two-step prompt)
|
||||
OpenFileStart, // begin open-file prompt
|
||||
VisualFilePickerToggle,
|
||||
// Buffers
|
||||
BufferSwitchStart, // begin buffer switch prompt
|
||||
BufferClose,
|
||||
@@ -69,6 +73,8 @@ enum class CommandId {
|
||||
IndentRegion, // indent region (C-k =)
|
||||
UnindentRegion, // unindent region (C-k -)
|
||||
ReflowParagraph, // reflow paragraph to column width (ESC q)
|
||||
// Read-only buffers
|
||||
ToggleReadOnly, // toggle current buffer read-only (C-k ')
|
||||
// Buffer operations
|
||||
ReloadBuffer, // reload buffer from disk (C-k l)
|
||||
MarkAllAndJumpEnd, // set mark at beginning, jump to end (C-k a)
|
||||
@@ -76,6 +82,8 @@ enum class CommandId {
|
||||
JumpToLine, // prompt for line and jump (C-k g)
|
||||
ShowWorkingDirectory, // Display the current working directory in the editor message.
|
||||
ChangeWorkingDirectory, // Change the editor's current directory.
|
||||
// Help
|
||||
ShowHelp, // open +HELP+ buffer with manual text (C-k h)
|
||||
// Meta
|
||||
UnknownKCommand, // arg: single character that was not recognized after C-k
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "Editor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <filesystem>
|
||||
|
||||
#include "Editor.h"
|
||||
|
||||
|
||||
Editor::Editor() = default;
|
||||
|
||||
|
||||
58
Editor.h
@@ -301,8 +301,22 @@ public:
|
||||
}
|
||||
|
||||
|
||||
// --- Generic Prompt subsystem (for search, open-file, save-as, etc.) ---
|
||||
enum class PromptKind { None = 0, Search, OpenFile, SaveAs, Confirm, BufferSwitch, GotoLine, Chdir };
|
||||
// --- Generic Prompt subsystem (for search, open-file, save-as, etc.) ---
|
||||
enum class PromptKind {
|
||||
None = 0,
|
||||
Search,
|
||||
RegexSearch,
|
||||
RegexReplaceFind, // step 1 of Regex Search & Replace: find pattern
|
||||
RegexReplaceWith, // step 2 of Regex Search & Replace: replacement text
|
||||
OpenFile,
|
||||
SaveAs,
|
||||
Confirm,
|
||||
BufferSwitch,
|
||||
GotoLine,
|
||||
Chdir,
|
||||
ReplaceFind, // step 1 of Search & Replace: find what
|
||||
ReplaceWith // step 2 of Search & Replace: replace with
|
||||
};
|
||||
|
||||
|
||||
void StartPrompt(PromptKind kind, const std::string &label, const std::string &initial)
|
||||
@@ -441,6 +455,31 @@ public:
|
||||
return buffers_;
|
||||
}
|
||||
|
||||
|
||||
// --- GUI: Visual File Picker state ---
|
||||
void SetFilePickerVisible(bool on)
|
||||
{
|
||||
file_picker_visible_ = on;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] bool FilePickerVisible() const
|
||||
{
|
||||
return file_picker_visible_;
|
||||
}
|
||||
|
||||
|
||||
void SetFilePickerDir(const std::string &path)
|
||||
{
|
||||
file_picker_dir_ = path;
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] const std::string &FilePickerDir() const
|
||||
{
|
||||
return file_picker_dir_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t rows_ = 0, cols_ = 0;
|
||||
int mode_ = 0;
|
||||
@@ -478,6 +517,21 @@ private:
|
||||
std::string prompt_label_;
|
||||
std::string prompt_text_;
|
||||
std::string pending_overwrite_path_;
|
||||
|
||||
// GUI-only state (safe no-op in terminal builds)
|
||||
bool file_picker_visible_ = false;
|
||||
std::string file_picker_dir_;
|
||||
|
||||
// Temporary state for Search & Replace flow
|
||||
public:
|
||||
void SetReplaceFindTmp(const std::string &s) { replace_find_tmp_ = s; }
|
||||
void SetReplaceWithTmp(const std::string &s) { replace_with_tmp_ = s; }
|
||||
[[nodiscard]] const std::string &ReplaceFindTmp() const { return replace_find_tmp_; }
|
||||
[[nodiscard]] const std::string &ReplaceWithTmp() const { return replace_with_tmp_; }
|
||||
|
||||
private:
|
||||
std::string replace_find_tmp_;
|
||||
std::string replace_with_tmp_;
|
||||
};
|
||||
|
||||
#endif // KTE_EDITOR_H
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#ifndef KTE_FRONTEND_H
|
||||
#define KTE_FRONTEND_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
class Editor;
|
||||
class InputHandler;
|
||||
|
||||
107
GUIConfig.cc
Normal file
@@ -0,0 +1,107 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "GUIConfig.h"
|
||||
|
||||
|
||||
static void
|
||||
trim(std::string &s)
|
||||
{
|
||||
auto not_space = [](int ch) {
|
||||
return !std::isspace(ch);
|
||||
};
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());
|
||||
}
|
||||
|
||||
|
||||
static std::string
|
||||
default_config_path()
|
||||
{
|
||||
const char *home = std::getenv("HOME");
|
||||
if (!home || !*home)
|
||||
return std::string();
|
||||
std::string path(home);
|
||||
path += "/.config/kte/kge.ini";
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
GUIConfig
|
||||
GUIConfig::Load()
|
||||
{
|
||||
GUIConfig cfg; // defaults already set
|
||||
std::string path = default_config_path();
|
||||
if (!path.empty()) {
|
||||
cfg.LoadFromFile(path);
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
GUIConfig::LoadFromFile(const std::string &path)
|
||||
{
|
||||
std::ifstream in(path);
|
||||
if (!in.good())
|
||||
return false;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(in, line)) {
|
||||
// Remove comments starting with '#' or ';'
|
||||
auto hash = line.find('#');
|
||||
if (hash != std::string::npos)
|
||||
line.erase(hash);
|
||||
auto sc = line.find("//");
|
||||
if (sc != std::string::npos)
|
||||
line.erase(sc);
|
||||
// Basic key=value
|
||||
auto eq = line.find('=');
|
||||
if (eq == std::string::npos)
|
||||
continue;
|
||||
std::string key = line.substr(0, eq);
|
||||
std::string val = line.substr(eq + 1);
|
||||
trim(key);
|
||||
trim(val);
|
||||
// Strip trailing semicolon
|
||||
if (!val.empty() && val.back() == ';') {
|
||||
val.pop_back();
|
||||
trim(val);
|
||||
}
|
||||
|
||||
// Lowercase key
|
||||
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
|
||||
return (char) std::tolower(c);
|
||||
});
|
||||
|
||||
if (key == "fullscreen") {
|
||||
fullscreen = (val == "1" || val == "true" || val == "on" || val == "yes");
|
||||
} else if (key == "columns" || key == "cols") {
|
||||
int v = columns;
|
||||
try {
|
||||
v = std::stoi(val);
|
||||
} catch (...) {}
|
||||
if (v > 0)
|
||||
columns = v;
|
||||
} else if (key == "rows") {
|
||||
int v = rows;
|
||||
try {
|
||||
v = std::stoi(val);
|
||||
} catch (...) {}
|
||||
if (v > 0)
|
||||
rows = v;
|
||||
} else if (key == "font_size" || key == "fontsize") {
|
||||
float v = font_size;
|
||||
try {
|
||||
v = std::stof(val);
|
||||
} catch (...) {}
|
||||
if (v > 0.0f)
|
||||
font_size = v;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
27
GUIConfig.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* GUIConfig - loads simple GUI configuration from $HOME/.config/kte/kge.ini
|
||||
*/
|
||||
#ifndef KTE_GUI_CONFIG_H
|
||||
#define KTE_GUI_CONFIG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifndef KTE_FONT_SIZE
|
||||
#define KTE_FONT_SIZE 16.0f
|
||||
#endif
|
||||
|
||||
class GUIConfig {
|
||||
public:
|
||||
bool fullscreen = false;
|
||||
int columns = 80;
|
||||
int rows = 42;
|
||||
float font_size = (float) KTE_FONT_SIZE;
|
||||
|
||||
// Load from default path: $HOME/.config/kte/kge.ini
|
||||
static GUIConfig Load();
|
||||
|
||||
// Load from explicit path. Returns true if file existed and was parsed.
|
||||
bool LoadFromFile(const std::string &path);
|
||||
};
|
||||
|
||||
#endif // KTE_GUI_CONFIG_H
|
||||
@@ -1,18 +1,25 @@
|
||||
#include <SDL.h>
|
||||
#include <SDL_opengl.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <backends/imgui_impl_sdl2.h>
|
||||
#include <backends/imgui_impl_opengl3.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_opengl.h>
|
||||
#include <imgui.h>
|
||||
#include <backends/imgui_impl_sdl2.h>
|
||||
#include <backends/imgui_impl_opengl3.h>
|
||||
|
||||
#include "Editor.h"
|
||||
#include "Command.h"
|
||||
#include "GUIFrontend.h"
|
||||
#include "Font.h" // embedded default font (DefaultFontRegular)
|
||||
#include "GUIConfig.h"
|
||||
|
||||
|
||||
#ifndef KTE_FONT_SIZE
|
||||
#define KTE_FONT_SIZE 16.0f
|
||||
#endif
|
||||
|
||||
static const char *kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
|
||||
|
||||
@@ -24,6 +31,9 @@ GUIFrontend::Init(Editor &ed)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load GUI configuration (fullscreen, columns/rows, font size)
|
||||
const auto [fullscreen, columns, rows, font_size] = GUIConfig::Load();
|
||||
|
||||
// GL attributes for core profile
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
@@ -33,14 +43,56 @@ GUIFrontend::Init(Editor &ed)
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
|
||||
// Compute desired window size from config
|
||||
Uint32 win_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
|
||||
if (fullscreen) {
|
||||
// "Fullscreen": fill the usable bounds of the primary display.
|
||||
// On macOS, do NOT use true fullscreen so the menu/status bar remains visible.
|
||||
SDL_Rect usable{};
|
||||
if (SDL_GetDisplayUsableBounds(0, &usable) == 0) {
|
||||
width_ = usable.w;
|
||||
height_ = usable.h;
|
||||
}
|
||||
#if !defined(__APPLE__)
|
||||
// Non-macOS: desktop fullscreen uses the current display resolution.
|
||||
win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
#endif
|
||||
} else {
|
||||
// Windowed: width = columns * font_size, height = (rows * 2) * font_size
|
||||
int w = static_cast<int>(columns * font_size);
|
||||
int h = static_cast<int>((rows * 2) * font_size);
|
||||
|
||||
// As a safety, clamp to display usable bounds if retrievable
|
||||
SDL_Rect usable{};
|
||||
if (SDL_GetDisplayUsableBounds(0, &usable) == 0) {
|
||||
w = std::min(w, usable.w);
|
||||
h = std::min(h, usable.h);
|
||||
}
|
||||
width_ = std::max(320, w);
|
||||
height_ = std::max(200, h);
|
||||
}
|
||||
|
||||
window_ = SDL_CreateWindow(
|
||||
"kte",
|
||||
"kge - kyle's text editor " KTE_VERSION_STR,
|
||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
width_, height_,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
win_flags);
|
||||
if (!window_)
|
||||
return false;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// macOS: when "fullscreen" is requested, position the window at the
|
||||
// top-left of the usable display area to mimic fullscreen while keeping
|
||||
// the system menu bar visible.
|
||||
if (fullscreen) {
|
||||
SDL_Rect usable{};
|
||||
if (SDL_GetDisplayUsableBounds(0, &usable) == 0) {
|
||||
SDL_SetWindowPosition(window_, usable.x, usable.y);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
gl_ctx_ = SDL_GL_CreateContext(window_);
|
||||
if (!gl_ctx_)
|
||||
return false;
|
||||
@@ -64,11 +116,23 @@ GUIFrontend::Init(Editor &ed)
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
// Initialize GUI font from embedded default
|
||||
#ifndef KTE_FONT_SIZE
|
||||
#define KTE_FONT_SIZE 16.0f
|
||||
#if defined(__APPLE__)
|
||||
// Workaround: On macOS Retina when starting maximized, we sometimes get a
|
||||
// subtle input vs draw alignment mismatch until the first manual resize.
|
||||
// Nudge the window size by 1px and back to trigger a proper internal
|
||||
// recomputation, without visible impact.
|
||||
if (w > 1 && h > 1) {
|
||||
SDL_SetWindowSize(window_, w - 1, h - 1);
|
||||
SDL_SetWindowSize(window_, w, h);
|
||||
// Update cached size in case backend reports immediately
|
||||
SDL_GetWindowSize(window_, &w, &h);
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
}
|
||||
#endif
|
||||
LoadGuiFont_(nullptr, (float) KTE_FONT_SIZE);
|
||||
|
||||
// Initialize GUI font from embedded default (use configured size or compiled default)
|
||||
LoadGuiFont_(nullptr, (float) font_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "GUIInputHandler.h"
|
||||
#include "GUIRenderer.h"
|
||||
|
||||
|
||||
struct SDL_Window;
|
||||
typedef void *SDL_GLContext;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include <SDL.h>
|
||||
#include <cstdio>
|
||||
#include <ncurses.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "GUIInputHandler.h"
|
||||
#include "KKeymap.h"
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
#ifndef KTE_GUI_INPUT_HANDLER_H
|
||||
#define KTE_GUI_INPUT_HANDLER_H
|
||||
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include "InputHandler.h"
|
||||
|
||||
|
||||
union SDL_Event; // fwd decl to avoid including SDL here (SDL defines SDL_Event as a union)
|
||||
|
||||
class GUIInputHandler final : public InputHandler {
|
||||
|
||||
574
GUIRenderer.cc
@@ -1,29 +1,47 @@
|
||||
#include "GUIRenderer.h"
|
||||
|
||||
#include "Editor.h"
|
||||
#include "Buffer.h"
|
||||
#include "Command.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <regex>
|
||||
|
||||
#include "GUIRenderer.h"
|
||||
#include "Buffer.h"
|
||||
#include "Command.h"
|
||||
#include "Editor.h"
|
||||
|
||||
|
||||
// Version string expected to be provided by build system as KTE_VERSION_STR
|
||||
#ifndef KTE_VERSION_STR
|
||||
# define KTE_VERSION_STR "dev"
|
||||
#endif
|
||||
|
||||
// ImGui compatibility: some bundled ImGui versions (or builds without docking)
|
||||
// don't define ImGuiWindowFlags_NoDocking. Treat it as 0 in that case.
|
||||
#ifndef ImGuiWindowFlags_NoDocking
|
||||
# define ImGuiWindowFlags_NoDocking 0
|
||||
#endif
|
||||
|
||||
|
||||
void
|
||||
GUIRenderer::Draw(Editor &ed)
|
||||
{
|
||||
// Make the editor window occupy the entire GUI container/viewport
|
||||
ImGuiViewport *vp = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(vp->Pos);
|
||||
ImGui::SetNextWindowSize(vp->Size);
|
||||
// On HiDPI/Retina, snap to integer pixels to prevent any draw vs hit-test
|
||||
// mismatches that can appear on the very first maximized frame.
|
||||
ImVec2 main_pos = vp->Pos;
|
||||
ImVec2 main_sz = vp->Size;
|
||||
main_pos.x = std::floor(main_pos.x + 0.5f);
|
||||
main_pos.y = std::floor(main_pos.y + 0.5f);
|
||||
main_sz.x = std::floor(main_sz.x + 0.5f);
|
||||
main_sz.y = std::floor(main_sz.y + 0.5f);
|
||||
ImGui::SetNextWindowPos(main_pos);
|
||||
ImGui::SetNextWindowSize(main_sz);
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoResize
|
||||
@@ -134,14 +152,14 @@ GUIRenderer::Draw(Editor &ed)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle mouse click before rendering to avoid dependent on drawn items
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
ImVec2 mp = ImGui::GetIO().MousePos;
|
||||
// Compute viewport-relative row so (0) is top row of the visible area
|
||||
float vy_f = (mp.y - list_origin.y - scroll_y) / row_h;
|
||||
long vy = static_cast<long>(vy_f);
|
||||
if (vy < 0)
|
||||
vy = 0;
|
||||
// Handle mouse click before rendering to avoid dependent on drawn items
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
ImVec2 mp = ImGui::GetIO().MousePos;
|
||||
// Compute viewport-relative row so (0) is top row of the visible area
|
||||
float vy_f = (mp.y - list_origin.y - scroll_y) / row_h;
|
||||
long vy = static_cast<long>(vy_f);
|
||||
if (vy < 0)
|
||||
vy = 0;
|
||||
|
||||
// Clamp vy within visible content height to avoid huge jumps
|
||||
ImVec2 cr_min = ImGui::GetWindowContentRegionMin();
|
||||
@@ -153,97 +171,163 @@ GUIRenderer::Draw(Editor &ed)
|
||||
if (vy >= vis_rows)
|
||||
vy = vis_rows - 1;
|
||||
|
||||
// Translate viewport row to buffer row using Buffer::Rowoffs
|
||||
std::size_t by = buf->Rowoffs() + static_cast<std::size_t>(vy);
|
||||
if (by >= lines.size()) {
|
||||
if (!lines.empty())
|
||||
by = lines.size() - 1;
|
||||
else
|
||||
by = 0;
|
||||
}
|
||||
// Translate viewport row to buffer row using Buffer::Rowoffs
|
||||
std::size_t by = buf->Rowoffs() + static_cast<std::size_t>(vy);
|
||||
if (by >= lines.size()) {
|
||||
if (!lines.empty())
|
||||
by = lines.size() - 1;
|
||||
else
|
||||
by = 0;
|
||||
}
|
||||
|
||||
// Compute desired pixel X inside the viewport content (subtract horizontal scroll)
|
||||
float px = (mp.x - list_origin.x - scroll_x);
|
||||
if (px < 0.0f)
|
||||
px = 0.0f;
|
||||
// Compute desired pixel X inside the viewport content (subtract horizontal scroll)
|
||||
float px = (mp.x - list_origin.x - scroll_x);
|
||||
if (px < 0.0f)
|
||||
px = 0.0f;
|
||||
|
||||
// Convert pixel X to a render-column target including horizontal col offset
|
||||
// Use our own tab expansion of width 8 to match command layer logic.
|
||||
const std::string &line_clicked = lines[by];
|
||||
const std::size_t tabw = 8;
|
||||
// We iterate source columns computing absolute rendered column (rx_abs) from 0,
|
||||
// then translate to viewport-space by subtracting Coloffs.
|
||||
std::size_t coloffs = buf->Coloffs();
|
||||
std::size_t rx_abs = 0; // absolute rendered column
|
||||
std::size_t i = 0; // source column iterator
|
||||
// Empty buffer guard: if there are no lines yet, just move to 0:0
|
||||
if (lines.empty()) {
|
||||
Execute(ed, CommandId::MoveCursorTo, std::string("0:0"));
|
||||
} else {
|
||||
// Convert pixel X to a render-column target including horizontal col offset
|
||||
// Use our own tab expansion of width 8 to match command layer logic.
|
||||
std::string line_clicked = static_cast<std::string>(lines[by]);
|
||||
const std::size_t tabw = 8;
|
||||
// We iterate source columns computing absolute rendered column (rx_abs) from 0,
|
||||
// then translate to viewport-space by subtracting Coloffs.
|
||||
std::size_t coloffs = buf->Coloffs();
|
||||
std::size_t rx_abs = 0; // absolute rendered column
|
||||
std::size_t i = 0; // source column iterator
|
||||
|
||||
// Fast-forward i until rx_abs >= coloffs to align with leftmost visible column
|
||||
if (!line_clicked.empty() && coloffs > 0) {
|
||||
while (i < line_clicked.size() && rx_abs < coloffs) {
|
||||
if (line_clicked[i] == '\t') {
|
||||
rx_abs += (tabw - (rx_abs % tabw));
|
||||
} else {
|
||||
rx_abs += 1;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
// Fast-forward i until rx_abs >= coloffs to align with leftmost visible column
|
||||
if (!line_clicked.empty() && coloffs > 0) {
|
||||
while (i < line_clicked.size() && rx_abs < coloffs) {
|
||||
if (line_clicked[i] == '\t') {
|
||||
rx_abs += (tabw - (rx_abs % tabw));
|
||||
} else {
|
||||
rx_abs += 1;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Now search for closest source column to clicked px within/after viewport
|
||||
std::size_t best_col = i; // default to first visible column
|
||||
float best_dist = std::numeric_limits<float>::infinity();
|
||||
while (true) {
|
||||
// For i in [current..size], evaluate candidate including the implicit end position
|
||||
std::size_t rx_view = (rx_abs >= coloffs) ? (rx_abs - coloffs) : 0;
|
||||
float rx_px = static_cast<float>(rx_view) * space_w;
|
||||
float dist = std::fabs(px - rx_px);
|
||||
if (dist <= best_dist) {
|
||||
best_dist = dist;
|
||||
best_col = i;
|
||||
}
|
||||
if (i == line_clicked.size())
|
||||
break;
|
||||
// advance to next source column
|
||||
if (line_clicked[i] == '\t') {
|
||||
rx_abs += (tabw - (rx_abs % tabw));
|
||||
} else {
|
||||
rx_abs += 1;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// Now search for closest source column to clicked px within/after viewport
|
||||
std::size_t best_col = i; // default to first visible column
|
||||
float best_dist = std::numeric_limits<float>::infinity();
|
||||
while (true) {
|
||||
// For i in [current..size], evaluate candidate including the implicit end position
|
||||
std::size_t rx_view = (rx_abs >= coloffs) ? (rx_abs - coloffs) : 0;
|
||||
float rx_px = static_cast<float>(rx_view) * space_w;
|
||||
float dist = std::fabs(px - rx_px);
|
||||
if (dist <= best_dist) {
|
||||
best_dist = dist;
|
||||
best_col = i;
|
||||
}
|
||||
if (i == line_clicked.size())
|
||||
break;
|
||||
// advance to next source column
|
||||
if (line_clicked[i] == '\t') {
|
||||
rx_abs += (tabw - (rx_abs % tabw));
|
||||
} else {
|
||||
rx_abs += 1;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
// Dispatch absolute buffer coordinates (row:col)
|
||||
char tmp[64];
|
||||
std::snprintf(tmp, sizeof(tmp), "%zu:%zu", by, best_col);
|
||||
Execute(ed, CommandId::MoveCursorTo, std::string(tmp));
|
||||
}
|
||||
// Dispatch absolute buffer coordinates (row:col)
|
||||
char tmp[64];
|
||||
std::snprintf(tmp, sizeof(tmp), "%zu:%zu", by, best_col);
|
||||
Execute(ed, CommandId::MoveCursorTo, std::string(tmp));
|
||||
}
|
||||
}
|
||||
// Cache current horizontal offset in rendered columns
|
||||
const std::size_t coloffs_now = buf->Coloffs();
|
||||
for (std::size_t i = rowoffs; i < lines.size(); ++i) {
|
||||
// Capture the screen position before drawing the line
|
||||
ImVec2 line_pos = ImGui::GetCursorScreenPos();
|
||||
const std::string &line = lines[i];
|
||||
for (std::size_t i = rowoffs; i < lines.size(); ++i) {
|
||||
// Capture the screen position before drawing the line
|
||||
ImVec2 line_pos = ImGui::GetCursorScreenPos();
|
||||
std::string line = static_cast<std::string>(lines[i]);
|
||||
|
||||
// Expand tabs to spaces with width=8 and apply horizontal scroll offset
|
||||
const std::size_t tabw = 8;
|
||||
std::string expanded;
|
||||
expanded.reserve(line.size() + 16);
|
||||
std::size_t rx_abs_draw = 0; // rendered column for drawing
|
||||
// Emit entire line (ImGui child scrolling will handle clipping)
|
||||
for (std::size_t src = 0; src < line.size(); ++src) {
|
||||
char c = line[src];
|
||||
if (c == '\t') {
|
||||
std::size_t adv = (tabw - (rx_abs_draw % tabw));
|
||||
// Emit spaces for the tab
|
||||
expanded.append(adv, ' ');
|
||||
rx_abs_draw += adv;
|
||||
} else {
|
||||
expanded.push_back(c);
|
||||
rx_abs_draw += 1;
|
||||
}
|
||||
}
|
||||
// Expand tabs to spaces with width=8 and apply horizontal scroll offset
|
||||
const std::size_t tabw = 8;
|
||||
std::string expanded;
|
||||
expanded.reserve(line.size() + 16);
|
||||
std::size_t rx_abs_draw = 0; // rendered column for drawing
|
||||
// Compute search highlight ranges for this line in source indices
|
||||
bool search_mode = ed.SearchActive() && !ed.SearchQuery().empty();
|
||||
std::vector<std::pair<std::size_t, std::size_t>> hl_src_ranges;
|
||||
if (search_mode) {
|
||||
// If we're in RegexSearch or RegexReplaceFind mode, compute ranges using regex; otherwise plain substring
|
||||
if (ed.PromptActive() && (ed.CurrentPromptKind() == Editor::PromptKind::RegexSearch || ed.CurrentPromptKind() == Editor::PromptKind::RegexReplaceFind)) {
|
||||
try {
|
||||
std::regex rx(ed.SearchQuery());
|
||||
for (auto it = std::sregex_iterator(line.begin(), line.end(), rx);
|
||||
it != std::sregex_iterator(); ++it) {
|
||||
const auto &m = *it;
|
||||
std::size_t sx = static_cast<std::size_t>(m.position());
|
||||
std::size_t ex = sx + static_cast<std::size_t>(m.length());
|
||||
hl_src_ranges.emplace_back(sx, ex);
|
||||
}
|
||||
} catch (const std::regex_error &) {
|
||||
// ignore invalid patterns here; status line already shows the error
|
||||
}
|
||||
} else {
|
||||
const std::string &q = ed.SearchQuery();
|
||||
std::size_t pos = 0;
|
||||
while (!q.empty() && (pos = line.find(q, pos)) != std::string::npos) {
|
||||
hl_src_ranges.emplace_back(pos, pos + q.size());
|
||||
pos += q.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
auto src_to_rx = [&](std::size_t upto_src_exclusive) -> std::size_t {
|
||||
std::size_t rx = 0;
|
||||
std::size_t s = 0;
|
||||
while (s < upto_src_exclusive && s < line.size()) {
|
||||
if (line[s] == '\t')
|
||||
rx += (tabw - (rx % tabw));
|
||||
else
|
||||
rx += 1;
|
||||
++s;
|
||||
}
|
||||
return rx;
|
||||
};
|
||||
// Draw background highlights (under text)
|
||||
if (search_mode && !hl_src_ranges.empty()) {
|
||||
// Current match emphasis
|
||||
bool has_current = ed.SearchMatchLen() > 0 && ed.SearchMatchY() == i;
|
||||
std::size_t cur_x = has_current ? ed.SearchMatchX() : 0;
|
||||
std::size_t cur_end = has_current ? (ed.SearchMatchX() + ed.SearchMatchLen()) : 0;
|
||||
for (const auto &rg : hl_src_ranges) {
|
||||
std::size_t sx = rg.first, ex = rg.second;
|
||||
std::size_t rx_start = src_to_rx(sx);
|
||||
std::size_t rx_end = src_to_rx(ex);
|
||||
// Apply horizontal scroll offset
|
||||
if (rx_end <= coloffs_now) continue; // fully left of view
|
||||
std::size_t vx0 = (rx_start > coloffs_now) ? (rx_start - coloffs_now) : 0;
|
||||
std::size_t vx1 = rx_end - coloffs_now;
|
||||
ImVec2 p0 = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
|
||||
ImVec2 p1 = ImVec2(line_pos.x + static_cast<float>(vx1) * space_w, line_pos.y + line_h);
|
||||
// Choose color: current match stronger
|
||||
bool is_current = has_current && sx == cur_x && ex == cur_end;
|
||||
ImU32 col = is_current ? IM_COL32(255, 220, 120, 140) : IM_COL32(200, 200, 0, 90);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, col);
|
||||
}
|
||||
}
|
||||
// Emit entire line (ImGui child scrolling will handle clipping)
|
||||
for (std::size_t src = 0; src < line.size(); ++src) {
|
||||
char c = line[src];
|
||||
if (c == '\t') {
|
||||
std::size_t adv = (tabw - (rx_abs_draw % tabw));
|
||||
// Emit spaces for the tab
|
||||
expanded.append(adv, ' ');
|
||||
rx_abs_draw += adv;
|
||||
} else {
|
||||
expanded.push_back(c);
|
||||
rx_abs_draw += 1;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted(expanded.c_str());
|
||||
ImGui::TextUnformatted(expanded.c_str());
|
||||
|
||||
// Draw a visible cursor indicator on the current line
|
||||
if (i == cy) {
|
||||
@@ -265,28 +349,106 @@ GUIRenderer::Draw(Editor &ed)
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
// Status bar spanning full width
|
||||
ImGui::Separator();
|
||||
// Status bar spanning full width
|
||||
ImGui::Separator();
|
||||
|
||||
// Build three segments: left (app/version/buffer/dirty), middle (message), right (cursor/mark)
|
||||
// Compute full content width and draw a filled background rectangle
|
||||
ImVec2 win_pos = ImGui::GetWindowPos();
|
||||
ImVec2 cr_min = ImGui::GetWindowContentRegionMin();
|
||||
ImVec2 cr_max = ImGui::GetWindowContentRegionMax();
|
||||
float x0 = win_pos.x + cr_min.x;
|
||||
float x1 = win_pos.x + cr_max.x;
|
||||
ImVec2 cursor = ImGui::GetCursorScreenPos();
|
||||
float bar_h = ImGui::GetFrameHeight();
|
||||
ImVec2 p0(x0, cursor.y);
|
||||
ImVec2 p1(x1, cursor.y + bar_h);
|
||||
ImU32 bg_col = ImGui::GetColorU32(ImGuiCol_HeaderActive);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col);
|
||||
// Build left text
|
||||
std::string left;
|
||||
left.reserve(256);
|
||||
left += "kge"; // GUI app name
|
||||
left += " ";
|
||||
left += KTE_VERSION_STR;
|
||||
// Compute full content width and draw a filled background rectangle
|
||||
ImVec2 win_pos = ImGui::GetWindowPos();
|
||||
ImVec2 cr_min = ImGui::GetWindowContentRegionMin();
|
||||
ImVec2 cr_max = ImGui::GetWindowContentRegionMax();
|
||||
float x0 = win_pos.x + cr_min.x;
|
||||
float x1 = win_pos.x + cr_max.x;
|
||||
ImVec2 cursor = ImGui::GetCursorScreenPos();
|
||||
float bar_h = ImGui::GetFrameHeight();
|
||||
ImVec2 p0(x0, cursor.y);
|
||||
ImVec2 p1(x1, cursor.y + bar_h);
|
||||
ImU32 bg_col = ImGui::GetColorU32(ImGuiCol_HeaderActive);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col);
|
||||
// If a prompt is active, replace the entire status bar with the prompt text
|
||||
if (ed.PromptActive()) {
|
||||
std::string label = ed.PromptLabel();
|
||||
std::string ptext = ed.PromptText();
|
||||
auto kind = ed.CurrentPromptKind();
|
||||
if (kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs ||
|
||||
kind == Editor::PromptKind::Chdir) {
|
||||
const char *home_c = std::getenv("HOME");
|
||||
if (home_c && *home_c) {
|
||||
std::string home(home_c);
|
||||
if (ptext.rfind(home, 0) == 0) {
|
||||
std::string rest = ptext.substr(home.size());
|
||||
if (rest.empty())
|
||||
ptext = "~";
|
||||
else if (!rest.empty() && (rest[0] == '/' || rest[0] == '\\'))
|
||||
ptext = std::string("~") + rest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float pad = 6.f;
|
||||
float left_x = p0.x + pad;
|
||||
float right_x = p1.x - pad;
|
||||
float max_px = std::max(0.0f, right_x - left_x);
|
||||
|
||||
std::string prefix;
|
||||
if (!label.empty()) prefix = label + ": ";
|
||||
|
||||
// Compose showing right-end of filename portion when too long for space
|
||||
std::string final_msg;
|
||||
ImVec2 prefix_sz = ImGui::CalcTextSize(prefix.c_str());
|
||||
float avail_px = std::max(0.0f, max_px - prefix_sz.x);
|
||||
if ((kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs || kind == Editor::PromptKind::Chdir) && avail_px > 0.0f) {
|
||||
// Trim from left until it fits by pixel width
|
||||
std::string tail = ptext;
|
||||
ImVec2 tail_sz = ImGui::CalcTextSize(tail.c_str());
|
||||
if (tail_sz.x > avail_px) {
|
||||
// Remove leading chars until it fits
|
||||
// Use a simple loop; text lengths are small here
|
||||
size_t start = 0;
|
||||
// To avoid O(n^2) worst-case, remove chunks
|
||||
while (start < tail.size()) {
|
||||
// Estimate how many chars to skip based on ratio
|
||||
float ratio = tail_sz.x / avail_px;
|
||||
size_t skip = ratio > 1.5f ? std::min(tail.size() - start, (size_t)std::max<size_t>(1, (size_t)(tail.size() / 4))) : 1;
|
||||
start += skip;
|
||||
std::string candidate = tail.substr(start);
|
||||
ImVec2 cand_sz = ImGui::CalcTextSize(candidate.c_str());
|
||||
if (cand_sz.x <= avail_px) {
|
||||
tail = candidate;
|
||||
tail_sz = cand_sz;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ImGui::CalcTextSize(tail.c_str()).x > avail_px && !tail.empty()) {
|
||||
// As a last resort, ensure fit by chopping exactly
|
||||
// binary reduce
|
||||
size_t lo = 0, hi = tail.size();
|
||||
while (lo < hi) {
|
||||
size_t mid = (lo + hi) / 2;
|
||||
std::string cand = tail.substr(mid);
|
||||
if (ImGui::CalcTextSize(cand.c_str()).x <= avail_px) hi = mid; else lo = mid + 1;
|
||||
}
|
||||
tail = tail.substr(lo);
|
||||
}
|
||||
}
|
||||
final_msg = prefix + tail;
|
||||
} else {
|
||||
final_msg = prefix + ptext;
|
||||
}
|
||||
|
||||
ImVec2 msg_sz = ImGui::CalcTextSize(final_msg.c_str());
|
||||
ImGui::PushClipRect(ImVec2(p0.x, p0.y), ImVec2(p1.x, p1.y), true);
|
||||
ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - msg_sz.y) * 0.5f));
|
||||
ImGui::TextUnformatted(final_msg.c_str());
|
||||
ImGui::PopClipRect();
|
||||
// Advance cursor to after the bar to keep layout consistent
|
||||
ImGui::Dummy(ImVec2(x1 - x0, bar_h));
|
||||
} else {
|
||||
// Build left text
|
||||
std::string left;
|
||||
left.reserve(256);
|
||||
left += "kge"; // GUI app name
|
||||
left += " ";
|
||||
left += KTE_VERSION_STR;
|
||||
std::string fname;
|
||||
try {
|
||||
fname = ed.DisplayNameFor(*buf);
|
||||
@@ -297,6 +459,18 @@ GUIRenderer::Draw(Editor &ed)
|
||||
} catch (...) {}
|
||||
}
|
||||
left += " ";
|
||||
// Insert buffer position prefix "[x/N] " before filename
|
||||
{
|
||||
std::size_t total = ed.BufferCount();
|
||||
if (total > 0) {
|
||||
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // 1-based for display
|
||||
left += "[";
|
||||
left += std::to_string(static_cast<unsigned long long>(idx1));
|
||||
left += "/";
|
||||
left += std::to_string(static_cast<unsigned long long>(total));
|
||||
left += "] ";
|
||||
}
|
||||
}
|
||||
left += fname;
|
||||
if (buf->Dirty())
|
||||
left += " *";
|
||||
@@ -373,10 +547,160 @@ GUIRenderer::Draw(Editor &ed)
|
||||
ImGui::PopClipRect();
|
||||
}
|
||||
}
|
||||
// Advance cursor to after the bar to keep layout consistent
|
||||
ImGui::Dummy(ImVec2(x1 - x0, bar_h));
|
||||
// Advance cursor to after the bar to keep layout consistent
|
||||
ImGui::Dummy(ImVec2(x1 - x0, bar_h));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(3);
|
||||
|
||||
// --- Visual File Picker overlay (GUI only) ---
|
||||
if (ed.FilePickerVisible()) {
|
||||
// Centered popup-style window that always fits within the current viewport
|
||||
ImGuiViewport *vp2 = ImGui::GetMainViewport();
|
||||
|
||||
// Desired size, min size, and margins
|
||||
const ImVec2 want(800.0f, 500.0f);
|
||||
const ImVec2 min_sz(240.0f, 160.0f);
|
||||
const float margin = 20.0f; // space from viewport edges
|
||||
|
||||
// Compute the maximum allowed size (viewport minus margins) and make sure it's not negative
|
||||
ImVec2 max_sz(std::max(32.0f, vp2->Size.x - 2.0f * margin),
|
||||
std::max(32.0f, vp2->Size.y - 2.0f * margin));
|
||||
|
||||
// Clamp desired size to [min_sz, max_sz]
|
||||
ImVec2 size(std::min(want.x, max_sz.x), std::min(want.y, max_sz.y));
|
||||
size.x = std::max(size.x, std::min(min_sz.x, max_sz.x));
|
||||
size.y = std::max(size.y, std::min(min_sz.y, max_sz.y));
|
||||
|
||||
// Center within the viewport using the final size
|
||||
ImVec2 pos(vp2->Pos.x + std::max(margin, (vp2->Size.x - size.x) * 0.5f),
|
||||
vp2->Pos.y + std::max(margin, (vp2->Size.y - size.y) * 0.5f));
|
||||
|
||||
// On HiDPI displays (macOS Retina), ensure integer pixel alignment to avoid
|
||||
// potential hit-test vs draw mismatches from sub-pixel positions.
|
||||
pos.x = std::floor(pos.x + 0.5f);
|
||||
pos.y = std::floor(pos.y + 0.5f);
|
||||
size.x = std::floor(size.x + 0.5f);
|
||||
size.y = std::floor(size.y + 0.5f);
|
||||
|
||||
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(size, ImGuiCond_Always);
|
||||
ImGuiWindowFlags wflags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoDocking;
|
||||
bool open = true;
|
||||
if (ImGui::Begin("File Picker", &open, wflags)) {
|
||||
// Current directory
|
||||
std::string curdir = ed.FilePickerDir();
|
||||
if (curdir.empty()) {
|
||||
try {
|
||||
curdir = std::filesystem::current_path().string();
|
||||
} catch (...) {
|
||||
curdir = ".";
|
||||
}
|
||||
ed.SetFilePickerDir(curdir);
|
||||
}
|
||||
ImGui::TextUnformatted(curdir.c_str());
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Up")) {
|
||||
try {
|
||||
std::filesystem::path p(curdir);
|
||||
if (p.has_parent_path()) {
|
||||
ed.SetFilePickerDir(p.parent_path().string());
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Close")) {
|
||||
ed.SetFilePickerVisible(false);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Header
|
||||
ImGui::TextUnformatted("Name");
|
||||
ImGui::Separator();
|
||||
|
||||
// Scrollable list
|
||||
ImGui::BeginChild("picker-list", ImVec2(0, 0), true);
|
||||
|
||||
// Build entries: directories first then files, alphabetical
|
||||
struct Entry {
|
||||
std::string name;
|
||||
std::filesystem::path path;
|
||||
bool is_dir;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
entries.reserve(256);
|
||||
// Optional parent entry
|
||||
try {
|
||||
std::filesystem::path base(curdir);
|
||||
std::error_code ec;
|
||||
for (auto it = std::filesystem::directory_iterator(base, ec);
|
||||
!ec && it != std::filesystem::directory_iterator(); it.increment(ec)) {
|
||||
const auto &p = it->path();
|
||||
std::string nm;
|
||||
try {
|
||||
nm = p.filename().string();
|
||||
} catch (...) {
|
||||
continue;
|
||||
}
|
||||
if (nm == "." || nm == "..")
|
||||
continue;
|
||||
bool is_dir = false;
|
||||
std::error_code ec2;
|
||||
is_dir = it->is_directory(ec2);
|
||||
entries.push_back({nm, p, is_dir});
|
||||
}
|
||||
} catch (...) {
|
||||
// ignore listing errors; show empty
|
||||
}
|
||||
std::sort(entries.begin(), entries.end(), [](const Entry &a, const Entry &b) {
|
||||
if (a.is_dir != b.is_dir)
|
||||
return a.is_dir && !b.is_dir;
|
||||
return a.name < b.name;
|
||||
});
|
||||
|
||||
// Draw rows
|
||||
int idx = 0;
|
||||
for (const auto &e: entries) {
|
||||
ImGui::PushID(idx++); // ensure unique/stable IDs even if names repeat
|
||||
std::string label;
|
||||
label.reserve(e.name.size() + 4);
|
||||
if (e.is_dir)
|
||||
label += "[";
|
||||
label += e.name;
|
||||
if (e.is_dir)
|
||||
label += "]";
|
||||
|
||||
// Render selectable row
|
||||
ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick);
|
||||
|
||||
// Activate based strictly on hover + mouse, to avoid any off-by-one due to click routing
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (e.is_dir && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
// Enter directory on double-click
|
||||
ed.SetFilePickerDir(e.path.string());
|
||||
} else if (!e.is_dir && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
// Open file on single click
|
||||
std::string err;
|
||||
if (!ed.OpenFile(e.path.string(), err)) {
|
||||
ed.SetStatus(std::string("open: ") + err);
|
||||
} else {
|
||||
ed.SetStatus(std::string("Opened: ") + e.name);
|
||||
}
|
||||
ed.SetFilePickerVisible(false);
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::End();
|
||||
if (!open) {
|
||||
ed.SetFilePickerVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "Renderer.h"
|
||||
|
||||
class GUIRenderer : public Renderer {
|
||||
class GUIRenderer final : public Renderer {
|
||||
public:
|
||||
GUIRenderer() = default;
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include "GapBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include "GapBuffer.h"
|
||||
|
||||
|
||||
GapBuffer::GapBuffer() = default;
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
|
||||
class GapBuffer {
|
||||
public:
|
||||
GapBuffer();
|
||||
|
||||
55
HelpText.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* HelpText.cc - embedded/customizable help content
|
||||
*/
|
||||
|
||||
#include "HelpText.h"
|
||||
|
||||
|
||||
std::string
|
||||
HelpText::Text()
|
||||
{
|
||||
// Customize the help text here. This string will be used by C-k h first.
|
||||
// You can keep it empty to fall back to the manpage or built-in defaults.
|
||||
// Note: keep newline characters as-is; the renderer splits lines on '\n'.
|
||||
|
||||
return std::string(
|
||||
"KTE - Kyle's Text Editor\n\n"
|
||||
"About:\n"
|
||||
" kte is Kyle's Text Editor and is probably ill-suited to everyone else. It was\n"
|
||||
" inspired by Antirez' kilo text editor by way of someone's writeup of the\n"
|
||||
" process of writing a text editor from scratch. It has keybindings inspired by\n"
|
||||
" VDE (and the Wordstar family) and emacs; its spiritual parent is mg(1).\n"
|
||||
"\n"
|
||||
"Core keybindings:\n"
|
||||
" C-k ' Toggle read-only\n"
|
||||
" C-k - Unindent region\n"
|
||||
" C-k = Indent region\n"
|
||||
" C-k C-d Kill entire line\n"
|
||||
" C-k C-q Quit now (no confirm)\n"
|
||||
" C-k a Mark all and jump to end\n"
|
||||
" C-k b Switch buffer\n"
|
||||
" C-k c Close current buffer\n"
|
||||
" C-k d Kill to end of line\n"
|
||||
" C-k e Open file (prompt)\n"
|
||||
" C-k g Jump to line\n"
|
||||
" C-k h Show this help\n"
|
||||
" C-k l Reload buffer from disk\n"
|
||||
" C-k n Previous buffer\n"
|
||||
" C-k o Change working directory (prompt)\n"
|
||||
" C-k p Next buffer\n"
|
||||
" C-k q Quit (confirm if dirty)\n"
|
||||
" C-k r Redo\n"
|
||||
" C-k s Save buffer\n"
|
||||
" C-k u Undo\n"
|
||||
" C-k v Toggle visual file picker (GUI)\n"
|
||||
" C-k w Show working directory\n"
|
||||
" C-k x Save and quit\n"
|
||||
"\n"
|
||||
"ESC/Alt commands:\n"
|
||||
" ESC q Reflow paragraph\n"
|
||||
" ESC BACKSPACE Delete previous word\n"
|
||||
" ESC d Delete next word\n"
|
||||
" Alt-w Copy region to kill ring\n\n"
|
||||
"Buffers:\n +HELP+ is read-only. Press C-k ' to toggle if you need to edit; C-k h restores it.\n"
|
||||
);
|
||||
}
|
||||
17
HelpText.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* HelpText.h - embedded/customizable help content
|
||||
*/
|
||||
#ifndef KTE_HELPTEXT_H
|
||||
#define KTE_HELPTEXT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
class HelpText {
|
||||
public:
|
||||
// Returns the embedded help text as a single string with newlines.
|
||||
// Project maintainers can customize the returned string below
|
||||
// (in HelpText.cc) without touching the help command logic.
|
||||
static std::string Text();
|
||||
};
|
||||
|
||||
#endif // KTE_HELPTEXT_H
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "Command.h"
|
||||
|
||||
|
||||
// Result of translating raw input into an editor command.
|
||||
struct MappedInput {
|
||||
bool hasCommand = false;
|
||||
|
||||
25
KKeymap.cc
@@ -1,9 +1,9 @@
|
||||
#include "KKeymap.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <ncurses.h>
|
||||
#include <ostream>
|
||||
|
||||
#include "KKeymap.h"
|
||||
|
||||
|
||||
auto
|
||||
KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
|
||||
@@ -33,6 +33,10 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
|
||||
out = CommandId::Redo; // C-k r (redo)
|
||||
return true;
|
||||
}
|
||||
if (ascii_key == '\'') {
|
||||
out = CommandId::ToggleReadOnly; // C-k ' (toggle read-only)
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (k_lower) {
|
||||
case 'a':
|
||||
@@ -59,6 +63,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
|
||||
case 'g':
|
||||
out = CommandId::JumpToLine;
|
||||
return true;
|
||||
case 'h':
|
||||
out = CommandId::ShowHelp;
|
||||
return true;
|
||||
case 'j':
|
||||
out = CommandId::JumpToMark;
|
||||
return true;
|
||||
@@ -83,6 +90,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
|
||||
case 'u':
|
||||
out = CommandId::Undo;
|
||||
return true;
|
||||
case 'v':
|
||||
out = CommandId::VisualFilePickerToggle;
|
||||
return true;
|
||||
case 'w':
|
||||
out = CommandId::ShowWorkingDirectory;
|
||||
return true;
|
||||
@@ -111,7 +121,7 @@ auto
|
||||
KLookupCtrlCommand(const int ascii_key, CommandId &out) -> bool
|
||||
{
|
||||
const int k = KLowerAscii(ascii_key);
|
||||
switch (k) {
|
||||
switch (k) {
|
||||
case 'w':
|
||||
out = CommandId::KillRegion; // C-w
|
||||
return true;
|
||||
@@ -142,6 +152,15 @@ KLookupCtrlCommand(const int ascii_key, CommandId &out) -> bool
|
||||
case 's':
|
||||
out = CommandId::FindStart;
|
||||
return true;
|
||||
case 'r':
|
||||
out = CommandId::RegexFindStart; // C-r regex search
|
||||
return true;
|
||||
case 't':
|
||||
out = CommandId::RegexpReplace; // C-t regex search & replace
|
||||
return true;
|
||||
case 'h':
|
||||
out = CommandId::SearchReplace; // C-h: search & replace
|
||||
return true;
|
||||
case 'l':
|
||||
out = CommandId::Refresh;
|
||||
return true;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "Command.h"
|
||||
|
||||
|
||||
// Lookup the command to execute after a C-k prefix.
|
||||
// Parameters:
|
||||
// - ascii_key: ASCII code of the key, preferably lowercased if it's a letter.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
class PieceTable {
|
||||
public:
|
||||
PieceTable();
|
||||
|
||||
165
README.md
@@ -1,9 +1,11 @@
|
||||
kte — Kyle's Text Editor
|
||||
kte - Kyle's Text Editor
|
||||
|
||||

|
||||
|
||||
Vision
|
||||
-------
|
||||
kte will be a small, fast, and understandable text editor with a
|
||||
terminal<EFBFBD>first UX and an optional ImGui GUI. It modernizes the
|
||||
kte is a small, fast, and understandable text editor with a
|
||||
terminal-first UX and an optional ImGui GUI. It modernizes the
|
||||
original ke editor while preserving its familiar WordStar/VDE‑style
|
||||
command model and Emacs‑influenced ergonomics. The focus is on
|
||||
simplicity of design, excellent latency, and pragmatic features you
|
||||
@@ -11,7 +13,9 @@ can learn and keep in your head.
|
||||
|
||||
I am experimenting with using Jetbrains Junie to assist in
|
||||
development, largely as a way to learn the effective use of agentic
|
||||
coding.
|
||||
coding. I worked with the agent by feeding it notes that I've been
|
||||
taking about text editors for the last few years, as well as the
|
||||
sources from the original ke editor that is all handwritten C.
|
||||
|
||||
Project Goals
|
||||
-------------
|
||||
@@ -26,89 +30,6 @@ Project Goals
|
||||
so a GUI can grow independently of the TUI.
|
||||
- Minimize dependencies; the GUI layer remains optional and isolated.
|
||||
|
||||
User Experience (intended)
|
||||
--------------------------
|
||||
|
||||
- Terminal first: instant startup, responsive editing, no surprises
|
||||
over SSH.
|
||||
- Optional GUI: an ImGui‑based window with tabs, menus, and
|
||||
palette—sharing the same editor core and command model.
|
||||
- Discoverable command model: WordStar/VDE style with a `C-k` prefix,
|
||||
Emacs‑like incremental search, and context help.
|
||||
- Sensible defaults with a simple config file for remaps and theme
|
||||
selection.
|
||||
- Respect the file system: no magic project files; autosave and
|
||||
crash‑recovery journals are opt‑in and visible.
|
||||
|
||||
Core Features (roadmapped)
|
||||
--------------------------
|
||||
|
||||
- Buffers and windows
|
||||
- Multiple file buffers; fast switching, closing, and reopening.
|
||||
- Split views (horizontal/vertical) in TUI and tiled panels in
|
||||
GUI.
|
||||
- Editing primitives
|
||||
- Gap buffer (primary) with an alternative piece table for
|
||||
large‑edit scenarios.
|
||||
- Kill/yank ring, word/sentence/paragraph motions, and rectangle
|
||||
ops.
|
||||
- Undo/redo with grouped edits and time‑travel scrubbing.
|
||||
- Search and replace
|
||||
- Incremental search (C-s) and regex search (C-r) with live
|
||||
highlighting.
|
||||
- Multi‑file grep with a quickfix list; replace with confirm.
|
||||
- Files and projects
|
||||
- Robust encoding/line‑ending detection; safe writes (atomic where
|
||||
possible).
|
||||
- File tree sidebar (GUI) and quick‑open palette.
|
||||
- Lightweight session restore.
|
||||
- Language niceties (opt‑in, no runtime servers required)
|
||||
- Syntax highlighting via fast, table‑driven lexers.
|
||||
- Basic indentation rules per language; trailing whitespace/EOF
|
||||
newline helpers.
|
||||
- Extensibility (later)
|
||||
- Command palette actions backed by the core command model.
|
||||
- Small C++ plugin ABI and a scripting shim for config‑time
|
||||
customization.
|
||||
|
||||
Interfaces
|
||||
----------
|
||||
|
||||
- CLI: the primary interface. `kte [files]` starts in the terminal,
|
||||
adopting your `$TERM` capabilities. Terminal mode is implemented
|
||||
using ncurses.
|
||||
- GUI: an optional ImGui‑based frontend that embeds the same editor
|
||||
core.
|
||||
|
||||
Architecture (intended)
|
||||
-----------------------
|
||||
|
||||
- Core model
|
||||
- Buffer: file I/O, cursor/mark, viewport state, and edit
|
||||
operations.
|
||||
- GapBuffer: fast in‑memory text structure for typical edits.
|
||||
- PieceTable: alternative representation for heavy insert/delete
|
||||
workflows.
|
||||
- Controller layer
|
||||
- InputHandler interface with `TerminalInputHandler` and
|
||||
`GUIInputHandler` implementations.
|
||||
- Command: normalized operations (save, kill, yank, move, search,
|
||||
etc.).
|
||||
- View layer
|
||||
- Renderer interface with `TerminalRenderer` and `GUIRenderer`
|
||||
implementations.
|
||||
- Editor: top‑level state managing buffers, messaging, and global
|
||||
flags.
|
||||
|
||||
Performance and Reliability Targets
|
||||
-----------------------------------
|
||||
|
||||
- Sub‑millisecond keystroke to screen update on typical files in TUI.
|
||||
- Sustain fluid editing on multi‑megabyte files; graceful degradation
|
||||
on very large files.
|
||||
- Atomic/safe writes; autosave and crash‑recovery journals are
|
||||
explicit and transparent.
|
||||
|
||||
Keybindings
|
||||
-----------
|
||||
kte maintains ke’s command model while internals evolve. Highlights (subject to refinement):
|
||||
@@ -137,26 +58,26 @@ Dependencies by platform
|
||||
------------------------
|
||||
|
||||
- macOS (Homebrew)
|
||||
- Terminal (default):
|
||||
- `brew install ncurses`
|
||||
- Optional GUI (enable with `-DBUILD_GUI=ON`):
|
||||
- `brew install sdl2 freetype`
|
||||
- OpenGL is provided by the system framework on macOS; no package needed.
|
||||
- Terminal (default):
|
||||
- `brew install ncurses`
|
||||
- Optional GUI (enable with `-DBUILD_GUI=ON`):
|
||||
- `brew install sdl2 freetype`
|
||||
- OpenGL is provided by the system framework on macOS; no package needed.
|
||||
|
||||
- Debian/Ubuntu
|
||||
- Terminal (default):
|
||||
- `sudo apt-get install -y libncurses5-dev libncursesw5-dev`
|
||||
- Optional GUI (enable with `-DBUILD_GUI=ON`):
|
||||
- `sudo apt-get install -y libsdl2-dev libfreetype6-dev mesa-common-dev`
|
||||
- The `mesa-common-dev` package provides OpenGL headers/libs (`libGL`).
|
||||
- Terminal (default):
|
||||
- `sudo apt-get install -y libncurses5-dev libncursesw5-dev`
|
||||
- Optional GUI (enable with `-DBUILD_GUI=ON`):
|
||||
- `sudo apt-get install -y libsdl2-dev libfreetype6-dev mesa-common-dev`
|
||||
- The `mesa-common-dev` package provides OpenGL headers/libs (`libGL`).
|
||||
|
||||
- NixOS/Nix
|
||||
- Terminal (default):
|
||||
- Ad-hoc shell: `nix-shell -p cmake gcc ncurses`
|
||||
- Optional GUI (enable with `-DBUILD_GUI=ON`):
|
||||
- Ad-hoc shell: `nix-shell -p cmake gcc ncurses SDL2 freetype libGL`
|
||||
- With flakes/devshell (example `flake.nix` inputs not provided): include
|
||||
`ncurses` for TUI, and `SDL2`, `freetype`, `libGL` for GUI in your devShell.
|
||||
- Terminal (default):
|
||||
- Ad-hoc shell: `nix-shell -p cmake gcc ncurses`
|
||||
- Optional GUI (enable with `-DBUILD_GUI=ON`):
|
||||
- Ad-hoc shell: `nix-shell -p cmake gcc ncurses SDL2 freetype libGL`
|
||||
- With flakes/devshell (example `flake.nix` inputs not provided): include
|
||||
`ncurses` for TUI, and `SDL2`, `freetype`, `libGL` for GUI in your devShell.
|
||||
|
||||
Notes
|
||||
-----
|
||||
@@ -180,6 +101,15 @@ Run:
|
||||
./cmake-build-debug/kte [files]
|
||||
```
|
||||
|
||||
If you configured the GUI, you can also run the GUI-first target (when
|
||||
built as `kge`) or request the GUI from `kte`:
|
||||
|
||||
```
|
||||
./cmake-build-debug/kte --gui [files]
|
||||
# or if built/installed as a separate GUI target
|
||||
./cmake-build-debug/kge [files]
|
||||
```
|
||||
|
||||
GUI build example
|
||||
-----------------
|
||||
|
||||
@@ -194,30 +124,5 @@ cmake --build cmake-build-debug
|
||||
Status
|
||||
------
|
||||
|
||||
- The project is under active evolution toward the above architecture
|
||||
and UX. The terminal interface now uses ncurses for input and
|
||||
rendering. GUI work will follow as a thin, optional layer. ke
|
||||
compatibility remains a primary constraint while internals modernize.
|
||||
|
||||
Roadmap (high level)
|
||||
--------------------
|
||||
|
||||
1. Solidify core buffer model (gap buffer), file I/O, and
|
||||
ke‑compatible commands.
|
||||
2. Introduce structured undo/redo and search/replace with
|
||||
highlighting.
|
||||
3. Stabilize terminal renderer and input handling across common
|
||||
terminals. (initial ncurses implementation landed)
|
||||
4. Add piece table as an alternative backend with runtime selection
|
||||
per buffer.
|
||||
5. Optional GUI frontend using ImGui; shared command palette.
|
||||
6. Language niceties (syntax highlighting, indentation rules) behind a
|
||||
zero‑deps, fast path.
|
||||
7. Session restore, autosave/journaling, and safe write guarantees.
|
||||
8. Extensibility hooks with a small, stable API.
|
||||
References
|
||||
----------
|
||||
|
||||
- [ke](https://git.wntrmute.dev/kyle/ke) manual and keybinding
|
||||
reference: `ke.md`
|
||||
- Inspirations: Antirez’ kilo, WordStar/VDE, Emacs, and `mg(1)`
|
||||
- This project is a hobby text editor meant to be my personal editor. I
|
||||
do not warrant its suitability for anyone else.
|
||||
|
||||
124
ROADMAP.md
@@ -1,116 +1,10 @@
|
||||
kte ROADMAP — from skeleton to a working editor
|
||||
ROADMAP / TODO:
|
||||
|
||||
Scope for “working editor” v0.1
|
||||
|
||||
- Runs in a terminal; opens files passed on the CLI or an empty buffer.
|
||||
- Basic navigation, insert/delete, newline handling.
|
||||
- Status line and message area; shows filename, dirty flag, cursor position.
|
||||
- Save file(s) to disk safely; quit/confirm on dirty buffers.
|
||||
- Core ke key chords: C-g (cancel), C-k s/x/q/C-q, C-l, basic arrows, Enter/Backspace, C-s (simple find).
|
||||
|
||||
Guiding principles
|
||||
|
||||
- Keep the core small and understandable; evolve incrementally.
|
||||
- Separate model (Buffer/Editor), control (Input/Command), and view (Renderer).
|
||||
- Favor terminal first; GUI hooks arrive later behind interfaces.
|
||||
|
||||
✓ Milestone 0 — Wire up a minimal app shell
|
||||
|
||||
1. main.cpp
|
||||
- Replace demo printing with real startup using `Editor`.
|
||||
- Parse CLI args; open each path into a buffer (create empty if none). ✓ when `kte file1 file2` loads buffers and
|
||||
exits cleanly.
|
||||
2. Editor integration
|
||||
- Ensure `Editor` can open/switch/close buffers and hold status messages.
|
||||
- Add a temporary “headless loop” to prove open/save calls work.
|
||||
|
||||
✓ Milestone 1 — Command model
|
||||
|
||||
1. Command vocabulary
|
||||
- Flesh out `Command.h/.cpp`: enums/struct for operations and data (e.g., InsertChar, MoveCursor, Save, Quit,
|
||||
FindNext, etc.).
|
||||
- Provide a dispatcher entry point callable from the input layer to mutate `Editor`/`Buffer`.
|
||||
- Definition of done: commands exist for minimal edit/navigation/save/quit; no rendering yet.
|
||||
|
||||
✓ Milestone 2 — Terminal input
|
||||
|
||||
1. Input interfaces
|
||||
- Add `InputHandler.h` interface plus `TerminalInputHandler` implementation.
|
||||
- Terminal input via ncurses (`getch`, `keypad`, non‑blocking with `nodelay`), basic key decoding (arrows, Ctrl, ESC
|
||||
sequences).
|
||||
2. Keymap
|
||||
- Map ke chords to `Command` (C-k prefix handling, C-g cancel, C-l refresh, C-k s/x/q/C-q, C-s find start, text
|
||||
input → InsertChar).
|
||||
3. Event loop
|
||||
- Introduce the core loop in main: read key → translate to `Command` → dispatch → trigger render.
|
||||
|
||||
✓ Milestone 3 — Terminal renderer
|
||||
|
||||
1. View interfaces
|
||||
- Add `Renderer.h` with `TerminalRenderer` implementation (ncurses‑based).
|
||||
2. Minimal draw
|
||||
- Render viewport lines from current buffer; draw status bar (filename, dirty, row:col, message).
|
||||
- Handle scrolling when cursor moves past edges; support window resize (SIGWINCH).
|
||||
3. Cursor
|
||||
- Place terminal cursor at logical buffer location (account for tabs later; start with plain text).
|
||||
|
||||
Milestone 4 — Buffer fundamentals to support editing
|
||||
|
||||
1. GapBuffer
|
||||
- Ensure `GapBuffer` supports insert char, backspace, delete, newline, and efficient cursor moves.
|
||||
2. Buffer API
|
||||
- File I/O (open/save), dirty tracking, encoding/line ending kept simple (UTF‑8, LF) for v0.1.
|
||||
- Cursor state, mark (optional later), and viewport bookkeeping.
|
||||
3. Basic motions
|
||||
- Left/Right/Up/Down, Home/End, PageUp/PageDown; word f/b (optional in v0.1).
|
||||
|
||||
Milestone 5 — Core editing loop complete
|
||||
|
||||
1. Tighten loop timing
|
||||
- Ensure keystroke→update→render latency is reliably low; avoid unnecessary redraws.
|
||||
2. Status/messages
|
||||
- `Editor::SetStatus()` shows transient messages; C-l forces full refresh.
|
||||
3. Prompts
|
||||
- Minimal prompt line for save‑as/confirm quit; blocking read in prompt mode is acceptable for v0.1.
|
||||
|
||||
Milestone 6 — Search (minimal)
|
||||
|
||||
1. Incremental search (C-s)
|
||||
- Simple forward substring search with live highlight of current match; arrow keys navigate matches while in search
|
||||
mode (ke‑style quirk acceptable).
|
||||
- ESC/C-g exits search; Enter confirms and leaves cursor on match.
|
||||
|
||||
Milestone 7 — Safety and polish for v0.1
|
||||
|
||||
1. Safe writes
|
||||
- Write to temp file then rename; preserve permissions where possible.
|
||||
2. Dirty/quit logic
|
||||
- Confirm on quit when any buffer is dirty; `C-k C-q` bypasses confirmation.
|
||||
3. Resize/terminal quirks
|
||||
- Handle small terminals gracefully; no crashes on narrow widths.
|
||||
4. Basic tests
|
||||
- Unit tests for `GapBuffer`, Buffer open/save round‑trip, and command mapping.
|
||||
|
||||
Out of scope for v0.1 (tracked, not blocking)
|
||||
|
||||
- Undo/redo, regex search, kill ring, word motions, tabs/render width, syntax highlighting, piece table selection, GUI.
|
||||
|
||||
Implementation notes (files to add)
|
||||
|
||||
- Input: `InputHandler.h`, `TerminalInputHandler.cc/h` (ncurses).
|
||||
- Rendering: `Renderer.h`, `TerminalRenderer.cc/h` (ncurses).
|
||||
- Prompt helpers: minimal utility for line input in raw mode.
|
||||
- Platform: small termios wrapper; SIGWINCH handler.
|
||||
|
||||
Acceptance checklist for v0.1
|
||||
|
||||
- Start: `./kte [files]` opens files or an empty buffer.
|
||||
- Edit: insert text, backspace, newlines; move cursor; content scrolls.
|
||||
- Save: `C-k s` writes file atomically; dirty flag clears; status shows bytes written.
|
||||
- Quit: `C-k q` confirms if dirty; `C-k C-q` exits without confirm; `C-k x` save+exit.
|
||||
- Refresh: `C-l` redraws.
|
||||
- Search: `C-s` finds next while typing; ESC cancels.
|
||||
|
||||
Next concrete step
|
||||
|
||||
- Stabilize cursor placement and scrolling logic; add resize handling and begin minimal prompt for save‑as.
|
||||
- [x] Search + Replace
|
||||
- [x] Regex search + replace
|
||||
- [ ] The undo system should actually work
|
||||
- [x] Able to mark buffers as read-only
|
||||
- [x] Built-in help text
|
||||
- [x] Shorten paths in the homedir with ~
|
||||
- [x] When the filename is longer than the message window, scoot left to
|
||||
keep it in view
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <ncurses.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "Editor.h"
|
||||
#include "Command.h"
|
||||
#include "TerminalFrontend.h"
|
||||
#include "Command.h"
|
||||
#include "Editor.h"
|
||||
|
||||
|
||||
bool
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
#ifndef KTE_TERMINAL_FRONTEND_H
|
||||
#define KTE_TERMINAL_FRONTEND_H
|
||||
|
||||
#include <termios.h>
|
||||
|
||||
#include "Frontend.h"
|
||||
#include "TerminalInputHandler.h"
|
||||
#include "TerminalRenderer.h"
|
||||
#include <termios.h>
|
||||
|
||||
|
||||
class TerminalFrontend final : public Frontend {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <ncurses.h>
|
||||
#include <cstdio>
|
||||
#include <ncurses.h>
|
||||
|
||||
#include "KKeymap.h"
|
||||
#include "TerminalInputHandler.h"
|
||||
#include "KKeymap.h"
|
||||
|
||||
namespace {
|
||||
constexpr int
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#ifndef KTE_TERMINAL_INPUT_HANDLER_H
|
||||
#define KTE_TERMINAL_INPUT_HANDLER_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "InputHandler.h"
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
#include "TerminalRenderer.h"
|
||||
|
||||
#include <ncurses.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <cstdlib>
|
||||
#include <ncurses.h>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
#include "Editor.h"
|
||||
#include "TerminalRenderer.h"
|
||||
#include "Buffer.h"
|
||||
#include "Editor.h"
|
||||
|
||||
// Version string expected to be provided by build system as KTE_VERSION_STR
|
||||
#ifndef KTE_VERSION_STR
|
||||
# define KTE_VERSION_STR "dev"
|
||||
#endif
|
||||
|
||||
|
||||
TerminalRenderer::TerminalRenderer() = default;
|
||||
|
||||
TerminalRenderer::~TerminalRenderer() = default;
|
||||
@@ -39,100 +41,140 @@ TerminalRenderer::Draw(Editor &ed)
|
||||
std::size_t coloffs = buf->Coloffs();
|
||||
|
||||
const int tabw = 8;
|
||||
for (int r = 0; r < content_rows; ++r) {
|
||||
move(r, 0);
|
||||
std::size_t li = rowoffs + static_cast<std::size_t>(r);
|
||||
std::size_t render_col = 0;
|
||||
std::size_t src_i = 0;
|
||||
bool do_hl = ed.SearchActive() && li == ed.SearchMatchY() && ed.SearchMatchLen() > 0;
|
||||
std::size_t mx = do_hl ? ed.SearchMatchX() : 0;
|
||||
std::size_t mlen = do_hl ? ed.SearchMatchLen() : 0;
|
||||
bool hl_on = false;
|
||||
int written = 0;
|
||||
if (li < lines.size()) {
|
||||
const std::string &line = lines[li];
|
||||
src_i = 0;
|
||||
render_col = 0;
|
||||
while (written < cols) {
|
||||
char ch = ' ';
|
||||
bool from_src = false;
|
||||
if (src_i < line.size()) {
|
||||
unsigned char c = static_cast<unsigned char>(line[src_i]);
|
||||
if (c == '\t') {
|
||||
std::size_t next_tab = tabw - (render_col % tabw);
|
||||
if (render_col + next_tab <= coloffs) {
|
||||
render_col += next_tab;
|
||||
++src_i;
|
||||
continue;
|
||||
}
|
||||
// Emit spaces for tab
|
||||
if (render_col < coloffs) {
|
||||
// skip to coloffs
|
||||
std::size_t to_skip = std::min<std::size_t>(
|
||||
next_tab, coloffs - render_col);
|
||||
render_col += to_skip;
|
||||
next_tab -= to_skip;
|
||||
}
|
||||
// Now render visible spaces
|
||||
while (next_tab > 0 && written < cols) {
|
||||
bool in_hl = do_hl && src_i >= mx && src_i < mx + mlen;
|
||||
// highlight by source index
|
||||
if (in_hl && !hl_on) {
|
||||
attron(A_STANDOUT);
|
||||
hl_on = true;
|
||||
}
|
||||
if (!in_hl && hl_on) {
|
||||
attroff(A_STANDOUT);
|
||||
hl_on = false;
|
||||
}
|
||||
addch(' ');
|
||||
++written;
|
||||
++render_col;
|
||||
--next_tab;
|
||||
}
|
||||
++src_i;
|
||||
continue;
|
||||
} else {
|
||||
// normal char
|
||||
if (render_col < coloffs) {
|
||||
++render_col;
|
||||
++src_i;
|
||||
continue;
|
||||
}
|
||||
ch = static_cast<char>(c);
|
||||
from_src = true;
|
||||
}
|
||||
} else {
|
||||
// beyond EOL, fill spaces
|
||||
ch = ' ';
|
||||
from_src = false;
|
||||
}
|
||||
if (do_hl) {
|
||||
bool in_hl = from_src && src_i >= mx && src_i < mx + mlen;
|
||||
if (in_hl && !hl_on) {
|
||||
attron(A_STANDOUT);
|
||||
hl_on = true;
|
||||
}
|
||||
if (!in_hl && hl_on) {
|
||||
attroff(A_STANDOUT);
|
||||
hl_on = false;
|
||||
}
|
||||
}
|
||||
addch(static_cast<unsigned char>(ch));
|
||||
++written;
|
||||
++render_col;
|
||||
if (from_src)
|
||||
++src_i;
|
||||
if (src_i >= line.size() && written >= cols)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hl_on) {
|
||||
attroff(A_STANDOUT);
|
||||
hl_on = false;
|
||||
}
|
||||
clrtoeol();
|
||||
}
|
||||
for (int r = 0; r < content_rows; ++r) {
|
||||
move(r, 0);
|
||||
std::size_t li = rowoffs + static_cast<std::size_t>(r);
|
||||
std::size_t render_col = 0;
|
||||
std::size_t src_i = 0;
|
||||
// Compute matches for this line if search highlighting is active
|
||||
bool search_mode = ed.SearchActive() && !ed.SearchQuery().empty();
|
||||
std::vector<std::pair<std::size_t, std::size_t>> ranges; // [start, end)
|
||||
if (search_mode && li < lines.size()) {
|
||||
std::string sline = static_cast<std::string>(lines[li]);
|
||||
// If regex search prompt is active (RegexSearch or RegexReplaceFind), use regex to compute highlight ranges
|
||||
if (ed.PromptActive() && (ed.CurrentPromptKind() == Editor::PromptKind::RegexSearch || ed.CurrentPromptKind() == Editor::PromptKind::RegexReplaceFind)) {
|
||||
try {
|
||||
std::regex rx(ed.SearchQuery());
|
||||
for (auto it = std::sregex_iterator(sline.begin(), sline.end(), rx);
|
||||
it != std::sregex_iterator(); ++it) {
|
||||
const auto &m = *it;
|
||||
std::size_t sx = static_cast<std::size_t>(m.position());
|
||||
std::size_t ex = sx + static_cast<std::size_t>(m.length());
|
||||
ranges.emplace_back(sx, ex);
|
||||
}
|
||||
} catch (const std::regex_error &) {
|
||||
// ignore invalid patterns here; status shows error
|
||||
}
|
||||
} else {
|
||||
const std::string &q = ed.SearchQuery();
|
||||
std::size_t pos = 0;
|
||||
while (!q.empty() && (pos = sline.find(q, pos)) != std::string::npos) {
|
||||
ranges.emplace_back(pos, pos + q.size());
|
||||
pos += q.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
auto is_src_in_hl = [&](std::size_t si) -> bool {
|
||||
if (ranges.empty()) return false;
|
||||
// ranges are non-overlapping and ordered by construction
|
||||
// linear scan is fine for now
|
||||
for (const auto &rg : ranges) {
|
||||
if (si < rg.first) break;
|
||||
if (si >= rg.first && si < rg.second) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// Track current-match to optionally emphasize
|
||||
const bool has_current = ed.SearchActive() && ed.SearchMatchLen() > 0;
|
||||
const std::size_t cur_mx = has_current ? ed.SearchMatchX() : 0;
|
||||
const std::size_t cur_my = has_current ? ed.SearchMatchY() : 0;
|
||||
const std::size_t cur_mend = has_current ? (ed.SearchMatchX() + ed.SearchMatchLen()) : 0;
|
||||
bool hl_on = false;
|
||||
bool cur_on = false;
|
||||
int written = 0;
|
||||
if (li < lines.size()) {
|
||||
std::string line = static_cast<std::string>(lines[li]);
|
||||
src_i = 0;
|
||||
render_col = 0;
|
||||
while (written < cols) {
|
||||
char ch = ' ';
|
||||
bool from_src = false;
|
||||
if (src_i < line.size()) {
|
||||
unsigned char c = static_cast<unsigned char>(line[src_i]);
|
||||
if (c == '\t') {
|
||||
std::size_t next_tab = tabw - (render_col % tabw);
|
||||
if (render_col + next_tab <= coloffs) {
|
||||
render_col += next_tab;
|
||||
++src_i;
|
||||
continue;
|
||||
}
|
||||
// Emit spaces for tab
|
||||
if (render_col < coloffs) {
|
||||
// skip to coloffs
|
||||
std::size_t to_skip = std::min<std::size_t>(
|
||||
next_tab, coloffs - render_col);
|
||||
render_col += to_skip;
|
||||
next_tab -= to_skip;
|
||||
}
|
||||
// Now render visible spaces
|
||||
while (next_tab > 0 && written < cols) {
|
||||
bool in_hl = search_mode && is_src_in_hl(src_i);
|
||||
bool in_cur = has_current && li == cur_my && src_i >= cur_mx && src_i < cur_mend;
|
||||
// Toggle highlight attributes
|
||||
int attr = 0;
|
||||
if (in_hl) attr |= A_STANDOUT;
|
||||
if (in_cur) attr |= A_BOLD;
|
||||
if ((attr & A_STANDOUT) && !hl_on) { attron(A_STANDOUT); hl_on = true; }
|
||||
if (!(attr & A_STANDOUT) && hl_on) { attroff(A_STANDOUT); hl_on = false; }
|
||||
if ((attr & A_BOLD) && !cur_on) { attron(A_BOLD); cur_on = true; }
|
||||
if (!(attr & A_BOLD) && cur_on) { attroff(A_BOLD); cur_on = false; }
|
||||
addch(' ');
|
||||
++written;
|
||||
++render_col;
|
||||
--next_tab;
|
||||
}
|
||||
++src_i;
|
||||
continue;
|
||||
} else {
|
||||
// normal char
|
||||
if (render_col < coloffs) {
|
||||
++render_col;
|
||||
++src_i;
|
||||
continue;
|
||||
}
|
||||
ch = static_cast<char>(c);
|
||||
from_src = true;
|
||||
}
|
||||
} else {
|
||||
// beyond EOL, fill spaces
|
||||
ch = ' ';
|
||||
from_src = false;
|
||||
}
|
||||
bool in_hl = search_mode && from_src && is_src_in_hl(src_i);
|
||||
bool in_cur = has_current && li == cur_my && from_src && src_i >= cur_mx && src_i < cur_mend;
|
||||
if (in_hl && !hl_on) { attron(A_STANDOUT); hl_on = true; }
|
||||
if (!in_hl && hl_on) { attroff(A_STANDOUT); hl_on = false; }
|
||||
if (in_cur && !cur_on) { attron(A_BOLD); cur_on = true; }
|
||||
if (!in_cur && cur_on) { attroff(A_BOLD); cur_on = false; }
|
||||
addch(static_cast<unsigned char>(ch));
|
||||
++written;
|
||||
++render_col;
|
||||
if (from_src)
|
||||
++src_i;
|
||||
if (src_i >= line.size() && written >= cols)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hl_on) {
|
||||
attroff(A_STANDOUT);
|
||||
hl_on = false;
|
||||
}
|
||||
if (cur_on) {
|
||||
attroff(A_BOLD);
|
||||
cur_on = false;
|
||||
}
|
||||
clrtoeol();
|
||||
}
|
||||
|
||||
// Place terminal cursor at logical position accounting for tabs and coloffs
|
||||
std::size_t cy = buf->Cury();
|
||||
@@ -149,13 +191,71 @@ TerminalRenderer::Draw(Editor &ed)
|
||||
mvaddstr(0, 0, "[no buffer]");
|
||||
}
|
||||
|
||||
// Status line (inverse) — left: app/version/buffer/dirty, middle: message, right: cursor/mark
|
||||
move(rows - 1, 0);
|
||||
attron(A_REVERSE);
|
||||
// Status line (inverse)
|
||||
move(rows - 1, 0);
|
||||
attron(A_REVERSE);
|
||||
|
||||
// Fill the status line with spaces first
|
||||
for (int i = 0; i < cols; ++i)
|
||||
addch(' ');
|
||||
// Fill the status line with spaces first
|
||||
for (int i = 0; i < cols; ++i)
|
||||
addch(' ');
|
||||
|
||||
// If a prompt is active, replace the status bar with the full prompt text
|
||||
if (ed.PromptActive()) {
|
||||
// Build prompt text: "Label: text" and shorten HOME path for file-related prompts
|
||||
std::string label = ed.PromptLabel();
|
||||
std::string ptext = ed.PromptText();
|
||||
auto kind = ed.CurrentPromptKind();
|
||||
if (kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs ||
|
||||
kind == Editor::PromptKind::Chdir) {
|
||||
const char *home_c = std::getenv("HOME");
|
||||
if (home_c && *home_c) {
|
||||
std::string home(home_c);
|
||||
// Ensure we match only at the start
|
||||
if (ptext.rfind(home, 0) == 0) {
|
||||
std::string rest = ptext.substr(home.size());
|
||||
if (rest.empty())
|
||||
ptext = "~";
|
||||
else if (rest[0] == '/' || rest[0] == '\\')
|
||||
ptext = std::string("~") + rest;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Prefer keeping the tail of the filename visible when it exceeds the window
|
||||
std::string msg;
|
||||
if (!label.empty()) {
|
||||
msg = label + ": ";
|
||||
}
|
||||
// When dealing with file-related prompts, left-trim the filename text so the tail stays visible
|
||||
if ((kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs || kind == Editor::PromptKind::Chdir) && cols > 0) {
|
||||
int avail = cols - static_cast<int>(msg.size());
|
||||
if (avail <= 0) {
|
||||
// No room for label; fall back to showing the rightmost portion of the whole string
|
||||
std::string whole = msg + ptext;
|
||||
if ((int)whole.size() > cols)
|
||||
whole = whole.substr(whole.size() - cols);
|
||||
msg = whole;
|
||||
} else {
|
||||
if ((int)ptext.size() > avail) {
|
||||
ptext = ptext.substr(ptext.size() - avail);
|
||||
}
|
||||
msg += ptext;
|
||||
}
|
||||
} else {
|
||||
// Non-file prompts: simple concatenation and clip by terminal
|
||||
msg += ptext;
|
||||
}
|
||||
|
||||
// Draw left-aligned, clipped to width
|
||||
if (!msg.empty())
|
||||
mvaddnstr(rows - 1, 0, msg.c_str(), std::max(0, cols));
|
||||
|
||||
// End status rendering for prompt mode
|
||||
attroff(A_REVERSE);
|
||||
// Restore logical cursor position in content area
|
||||
if (saved_cur_y >= 0 && saved_cur_x >= 0)
|
||||
move(saved_cur_y, saved_cur_x);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build left segment
|
||||
std::string left;
|
||||
@@ -180,9 +280,24 @@ TerminalRenderer::Draw(Editor &ed)
|
||||
fname = "[no name]";
|
||||
}
|
||||
left += " ";
|
||||
// Insert buffer position prefix "[x/N] " before filename
|
||||
{
|
||||
std::size_t total = ed.BufferCount();
|
||||
if (total > 0) {
|
||||
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // human-friendly 1-based
|
||||
left += "[";
|
||||
left += std::to_string(static_cast<unsigned long long>(idx1));
|
||||
left += "/";
|
||||
left += std::to_string(static_cast<unsigned long long>(total));
|
||||
left += "] ";
|
||||
}
|
||||
}
|
||||
left += fname;
|
||||
if (b && b->Dirty())
|
||||
left += " *";
|
||||
// Append read-only indicator
|
||||
if (b && b->IsReadOnly())
|
||||
left += " [RO]";
|
||||
// Append total line count as "<n>L"
|
||||
if (b) {
|
||||
unsigned long lcount = static_cast<unsigned long>(b->Rows().size());
|
||||
@@ -231,10 +346,10 @@ TerminalRenderer::Draw(Editor &ed)
|
||||
if (llen > 0)
|
||||
mvaddnstr(rows - 1, 0, left.c_str(), llen);
|
||||
|
||||
// Draw right, flush to end
|
||||
int rstart = std::max(0, cols - rlen);
|
||||
if (rlen > 0)
|
||||
mvaddnstr(rows - 1, rstart, right.c_str(), rlen);
|
||||
// Draw right, flush to end
|
||||
int rstart = std::max(0, cols - rlen);
|
||||
if (rlen > 0)
|
||||
mvaddnstr(rows - 1, rstart, right.c_str(), rlen);
|
||||
|
||||
// Middle message
|
||||
const std::string &msg = ed.Status();
|
||||
@@ -250,7 +365,7 @@ TerminalRenderer::Draw(Editor &ed)
|
||||
}
|
||||
}
|
||||
|
||||
attroff(A_REVERSE);
|
||||
attroff(A_REVERSE);
|
||||
|
||||
// Restore terminal cursor to the content position so a visible caret
|
||||
// remains in the editing area (not on the status line).
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "TestFrontend.h"
|
||||
#include "Editor.h"
|
||||
#include "Command.h"
|
||||
#include <iostream>
|
||||
#include "Editor.h"
|
||||
|
||||
|
||||
bool
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
#ifndef KTE_TEST_INPUT_HANDLER_H
|
||||
#define KTE_TEST_INPUT_HANDLER_H
|
||||
|
||||
#include "InputHandler.h"
|
||||
#include <queue>
|
||||
|
||||
#include "InputHandler.h"
|
||||
|
||||
class TestInputHandler : public InputHandler {
|
||||
|
||||
class TestInputHandler final : public InputHandler {
|
||||
public:
|
||||
TestInputHandler() = default;
|
||||
|
||||
@@ -21,7 +22,7 @@ public:
|
||||
void QueueText(const std::string &text);
|
||||
|
||||
|
||||
bool IsEmpty() const
|
||||
[[nodiscard]] bool IsEmpty() const
|
||||
{
|
||||
return queue_.empty();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "TestRenderer.h"
|
||||
#include "Editor.h"
|
||||
|
||||
|
||||
void
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
#ifndef KTE_TEST_RENDERER_H
|
||||
#define KTE_TEST_RENDERER_H
|
||||
|
||||
#include "Renderer.h"
|
||||
#include <cstddef>
|
||||
|
||||
#include "Renderer.h"
|
||||
|
||||
class TestRenderer : public Renderer {
|
||||
|
||||
class TestRenderer final : public Renderer {
|
||||
public:
|
||||
TestRenderer() = default;
|
||||
|
||||
@@ -17,7 +18,7 @@ public:
|
||||
void Draw(Editor &ed) override;
|
||||
|
||||
|
||||
std::size_t GetDrawCount() const
|
||||
[[nodiscard]] std::size_t GetDrawCount() const
|
||||
{
|
||||
return draw_count_;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#ifndef KTE_UNDONODE_H
|
||||
#define KTE_UNDONODE_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
|
||||
enum class UndoType : uint8_t {
|
||||
enum class UndoType : std::uint8_t {
|
||||
Insert,
|
||||
Delete,
|
||||
Paste,
|
||||
|
||||
@@ -16,7 +16,7 @@ UndoSystem::Begin(UndoType type)
|
||||
if (type == UndoType::Delete) {
|
||||
// Support batching both forward deletes (DeleteChar) and backspace (prepend case)
|
||||
// Forward delete: cursor stays at anchor col; expected == col
|
||||
std::size_t anchor = static_cast<std::size_t>(tree_.pending->col);
|
||||
const auto anchor = static_cast<std::size_t>(tree_.pending->col);
|
||||
if (anchor + tree_.pending->text.size() == static_cast<std::size_t>(col)) {
|
||||
pending_prepend_ = false;
|
||||
return; // keep batching forward delete
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#define KTE_UNDOSYSTEM_H
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "UndoTree.h"
|
||||
|
||||
|
||||
class Buffer;
|
||||
|
||||
class UndoSystem {
|
||||
@@ -39,7 +41,6 @@ private:
|
||||
|
||||
void update_dirty_flag();
|
||||
|
||||
private:
|
||||
Buffer *buf_;
|
||||
UndoTree &tree_;
|
||||
// Internal hint for Delete batching: whether next Append() should prepend
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define KTE_UNDOTREE_H
|
||||
|
||||
#include "UndoNode.h"
|
||||
#include <memory>
|
||||
|
||||
|
||||
struct UndoTree {
|
||||
UndoNode *root = nullptr; // first edit ever
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>kge</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>@MACOSX_BUNDLE_ICON_FILE@</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>@KGE_BUNDLE_ID@</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
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
|
||||
'';
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
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
|
||||
'';
|
||||
}
|
||||
22
default.nix
@@ -7,12 +7,16 @@
|
||||
libGL,
|
||||
xorg,
|
||||
installShellFiles,
|
||||
|
||||
graphical ? false,
|
||||
...
|
||||
}:
|
||||
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;
|
||||
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 {
|
||||
@@ -24,14 +28,16 @@ stdenv.mkDerivation {
|
||||
nativeBuildInputs = [
|
||||
cmake
|
||||
ncurses
|
||||
installShellFiles
|
||||
]
|
||||
++ lib.optionals graphical [
|
||||
SDL2
|
||||
libGL
|
||||
xorg.libX11
|
||||
installShellFiles
|
||||
];
|
||||
|
||||
cmakeFlags = [
|
||||
"-DBUILD_GUI=ON"
|
||||
"-DBUILD_GUI=${if graphical then "ON" else "OFF"}"
|
||||
"-DCMAKE_BUILD_TYPE=Debug"
|
||||
];
|
||||
|
||||
@@ -40,11 +46,17 @@ stdenv.mkDerivation {
|
||||
|
||||
mkdir -p $out/bin
|
||||
cp kte $out/bin/
|
||||
cp kge $out/bin/
|
||||
|
||||
installManPage ../docs/kte.1
|
||||
installManPage ../docs/kge.1
|
||||
|
||||
''
|
||||
+ lib.optionalString graphical ''
|
||||
cp kge $out/bin/
|
||||
installManPage ../docs/kge.1
|
||||
mkdir -p $out/share/icons
|
||||
cp ../kge.png $out/share/icons/
|
||||
''
|
||||
+ ''
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -17,8 +17,11 @@ kge \- Kyle's Graphical Editor (GUI-first)
|
||||
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.
|
||||
and defaults to the graphical ImGui frontend when available. A terminal
|
||||
(ncurses) frontend is also available and can be requested explicitly with
|
||||
.B --term
|
||||
or by invoking
|
||||
.BR kte (1).
|
||||
|
||||
If one or more
|
||||
.I files
|
||||
@@ -199,6 +202,8 @@ Open using the terminal frontend from kge:
|
||||
.BR kte (1),
|
||||
.I docs/ke.md
|
||||
(project keybinding manual)
|
||||
.br
|
||||
Project homepage: https://github.com/wntrmute/kte
|
||||
.SH BUGS
|
||||
Report issues on the project tracker. Some behaviors are inherited from
|
||||
ke and may evolve over time; see the manual for notes.
|
||||
|
||||
13
docs/kte.1
@@ -16,8 +16,15 @@ kte \- Kyle's Text Editor (terminal-first)
|
||||
.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.
|
||||
Emacs-influenced ergonomics. The core uses ncurses in the terminal.
|
||||
|
||||
By default, .B kte
|
||||
runs in the terminal (ncurses) frontend. If the binary was built with GUI
|
||||
support, the same editor core can be shown with an ImGui-based GUI by passing
|
||||
.B --gui
|
||||
or by invoking the GUI-first target
|
||||
.BR kge (1)
|
||||
when available.
|
||||
|
||||
If one or more
|
||||
.I files
|
||||
@@ -194,6 +201,8 @@ Force GUI frontend (if available):
|
||||
.BR kge (1),
|
||||
.I docs/ke.md
|
||||
(project keybinding manual)
|
||||
.br
|
||||
Project homepage: https://github.com/wntrmute/kte
|
||||
.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.
|
||||
|
||||
BIN
docs/screenshot.jpg
Normal file
|
After Width: | Height: | Size: 196 KiB |
@@ -1,55 +0,0 @@
|
||||
# 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 ];
|
||||
};
|
||||
});
|
||||
}
|
||||
10
flake.lock
generated
@@ -2,15 +2,15 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1764242076,
|
||||
"narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=",
|
||||
"owner": "nixos",
|
||||
"lastModified": 1764517877,
|
||||
"narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4",
|
||||
"rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
|
||||
23
flake.nix
@@ -1,21 +1,20 @@
|
||||
{
|
||||
description = "Kyle's Text Editor";
|
||||
description = "kyle's text editor";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
};
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs }:
|
||||
inputs@{ self, nixpkgs, ... }:
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
eachSystem = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
|
||||
pkgsFor = system: import nixpkgs { inherit system; };
|
||||
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 { };
|
||||
};
|
||||
packages = eachSystem (system: rec {
|
||||
default = kte;
|
||||
full = kge;
|
||||
kte = (pkgsFor system).callPackage ./default.nix { graphical = false; };
|
||||
kge = (pkgsFor system).callPackage ./default.nix { graphical = true; };
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
BIN
kge.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
kge.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
kge.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 911 B |
BIN
kge.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
kge.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
kge.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
kge.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
kge.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
kge.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
kge.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
13
main.cc
@@ -1,15 +1,15 @@
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <getopt.h>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cctype>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "Editor.h"
|
||||
#include "Command.h"
|
||||
#include "Editor.h"
|
||||
#include "Frontend.h"
|
||||
#include "TerminalFrontend.h"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
# define KTE_VERSION_STR "devel"
|
||||
#endif
|
||||
|
||||
|
||||
static void
|
||||
PrintUsage(const char *prog)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "Buffer.h"
|
||||
#include "Command.h"
|
||||
#include "Editor.h"
|
||||
#include "TestFrontend.h"
|
||||
#include "Command.h"
|
||||
#include "Buffer.h"
|
||||
|
||||
|
||||
int
|
||||
|
||||