29 Commits

Author SHA1 Message Date
c98d9e717a Code cleanups.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
- Implement font management improvements
- Add commands for font selection and size
- Handle pending font loading consistently
- Update related documentation.
- Version bump to 1.3.1.
2025-12-03 08:28:44 -08:00
c864af7daa Add new GUI themes and update documentation.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
- Amber
- LCARS
- Orbital
- Weyland-Yutani
- Kanagawa Paper (dark mode extension)

Update help text to reflect additions.
2025-12-03 00:36:01 -08:00
64022766c5 Add new GUI themes and prep version bump.
- Everforest
- Kanagawa Paper
- Old Book
- Zenburn

Update help/docs accordingly.
2025-12-03 00:05:49 -08:00
d706b6db44 split fonts out to separate repo 2025-12-03 00:03:09 -08:00
bce9b3b33e Ensure k-prefix mode exits after special key handling 2025-12-02 23:59:15 -08:00
56dc904432 Fix horizontal scrolling and cursor placement.
Ensure horizontal scrolling for cursor visibility, add accurate click handling for rendered columns, and improve syntax highlighting bounds adjustment.
2025-12-02 23:46:35 -08:00
a8197939f8 Add thread-safe font loading requests and integrate FiraCode-Regular font 2025-12-02 23:41:52 -08:00
9f722ec2bb fix naming issue 2025-12-02 19:26:23 -08:00
094020dab5 Adding additional fonts. 2025-12-02 19:18:34 -08:00
09e4cd7ec6 Stashing fonts, start font registry. 2025-12-02 19:05:08 -08:00
49fa7ff8a7 refactoring some font support 2025-12-02 19:05:08 -08:00
38915484ac bump version
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
2025-12-02 18:44:17 -08:00
87b1e6f502 Prevent kge from inhibiting sleep. 2025-12-02 18:43:45 -08:00
ae822083c2 Bump version.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
2025-12-02 11:22:59 -08:00
0c93d619c8 Set custom ImGui ini file path
Path is set to `~/.config/kte/imgui.ini`, enable keyboard and gamepad navigation, and ensure configuration directory creation.
2025-12-02 11:22:38 -08:00
483ff18b0d Add ScrollUp and ScrollDown commands for viewport scrolling, refine mouse wheel handling in GUI and terminal, and bump version to 1.2.2.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
2025-12-02 02:53:02 -08:00
cd33e8feb1 Refactor scrolling logic for GUIRenderer and terminal to improve synchronization and cursor visibility. 2025-12-02 02:43:05 -08:00
0bfe75fbf0 Refactor indentation for consistent style across codebase.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
2025-12-02 01:37:44 -08:00
d15b241140 Refactor syntax highlighting infrastructure and related classes.
- Moved all language highlighter implementations (`CppHighlighter`, `GoHighlighter`, `JsonHighlighter`, etc.), the engine, and registry to `syntax/`.
2025-12-02 01:36:26 -08:00
ceef6af3ae Add extensible highlighter registration and Tree-sitter support.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
- Implemented runtime API for registering custom highlighters.
- Added optional Tree-sitter integration for advanced syntax parsing (disabled by default).
- Updated buffer initialization and copying to support dynamic highlighter configuration.
- Introduced `NullHighlighter` as a fallback for unsupported filetypes.
- Enhanced CMake configuration with `KTE_ENABLE_TREESITTER` option.
2025-12-01 19:04:37 -08:00
e62cf3ee28 Add viewport-aware syntax prefetching and background warming.
- Added prefetching in both terminal and GUI renderers to optimize visible row highlights.
- Introduced background worker for offscreen highlight warming to improve scrolling performance.
- Refactored `HighlighterEngine` to manage thread-safety, caching, and stateful re-computation.
- Integrated changes into `HighlighterEngine`, `TerminalRenderer`, and `GUIRenderer`.
- Bumped version to 1.2.0 in preparation for the release.
2025-12-01 18:37:01 -08:00
1a77f28ce4 Add syntax highlighting infrastructure
- Introduced `HighlighterRegistry` with support for multiple language highlighters (e.g., JSON, Markdown, Python).
- Added `JsonHighlighter` implementation for basic JSON syntax highlighting.
2025-12-01 18:20:36 -08:00
4d84b352eb format style 2025-12-01 16:47:16 -08:00
1892075d82 bump version
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
2025-12-01 16:38:10 -08:00
719862c842 Fix double scroll issue. 2025-12-01 16:37:51 -08:00
655cc40162 Refine help text, keybindings, GUI themes, and undo system.
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
- Expanded help text and command documentation with detailed keybinding descriptions.
- Added theme customization support to GUIConfig (Nord default, light/dark variants).
- Adjusted for consistent indentation and debug instrumentation in undo system.
- Enhanced test cases for multi-line, UTF-8, and branching scenarios.
2025-12-01 15:21:52 -08:00
d98785e825 bump
Some checks failed
Release / Bump Homebrew formula (push) Has been cancelled
Release / Build Linux amd64 (push) Has been cancelled
Release / Build Linux arm64 (push) Has been cancelled
Release / Build macOS arm64 (.app) (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
2025-12-01 12:00:52 -08:00
970a31e0d9 Add Nord theme for real 2025-12-01 12:00:10 -08:00
464ad8d1ae Nord theme and undo system refinements
- Improve text input/event batching
- Enhance debugging with optional instrumentation
- Begin implementation of non-linear undo tree structure.
2025-12-01 11:59:51 -08:00
115 changed files with 219045 additions and 12193 deletions

353
.idea/workspace.xml generated
View File

@@ -1,353 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="BackendCodeEditorMiscSettings">
<option name="/Default/Environment/Hierarchy/GeneratedFilesCacheKey/Timestamp/@EntryValue" value="3" type="long" />
<option name="/Default/Housekeeping/FeatureSuggestion/FeatureSuggestionManager/DisabledSuggesters/=SwitchToGoToActionSuggester/@EntryIndexedValue" value="true" type="bool" />
<option name="/Default/Housekeeping/GlobalSettingsUpgraded/IsUpgraded/@EntryValue" value="true" type="bool" />
<option name="/Default/Housekeeping/OptionsDialog/SelectedPageId/@EntryValue" value="CppFormatterOtherPage" type="string" />
<option name="/Default/Housekeeping/RefactoringsMru/RenameRefactoring/DoSearchForTextInStrings/@EntryValue" value="true" type="bool" />
<option name="/Default/RiderDebugger/RiderRestoreDecompile/RestoreDecompileSetting/@EntryValue" value="false" type="bool" />
</component>
<component name="CMakePresetLoader">{
&quot;useNewFormat&quot;: true
}</component>
<component name="CMakeProjectFlavorService">
<option name="flavorId" value="CMakePlainProjectFlavor" />
</component>
<component name="CMakeReloadState">
<option name="reloaded" value="true" />
</component>
<component name="CMakeRunConfigurationManager">
<generated>
<config projectName="kte" targetName="kte" />
<config projectName="kte" targetName="imgui" />
<config projectName="kte" targetName="kge" />
</generated>
</component>
<component name="CMakeSettings" AUTO_RELOAD="true">
<configurations>
<configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" GENERATION_OPTIONS="-G &quot;Unix Makefiles&quot; -DKTE_USE_PIECE_TABLE:BOOL=ON -DBUILD_GUI:BOOL=ON" />
</configurations>
</component>
<component name="ChangeListManager">
<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$/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" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="ExecutionTargetManager" SELECTED_TARGET="CMakeBuildProfile:Debug" />
<component name="FormatOnSaveOptions">
<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>
<component name="HighlightingSettingsPerFile">
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///AIAssistantSnippet.." root0="SKIP_HIGHLIGHTING" />
<setting file="mock:///AIAssistantSnippet.." 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" />
<option name="minorVersion" value="2.5" />
<option name="productBranch" value="Classic" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
}</component>
<component name="ProjectId" id="36AlI8oyQOzOwSuZg6WxXf5LbHb" />
<component name="ProjectLevelVcsManager">
<OptionsSetting value="false" id="Update" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
<option name="sortByType" value="true" />
<option name="sortKey" value="BY_TYPE" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;CMake Application.kge.executor&quot;: &quot;Run&quot;,
&quot;CMake Application.test_example.executor&quot;: &quot;Run&quot;,
&quot;CMake Application.test_undo.executor&quot;: &quot;Run&quot;,
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;NIXITCH_NIXPKGS_CONFIG&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_CONF_DIR&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_OTHER_STORES&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_PATH&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_PROFILES&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_REMOTE&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_USER_PROFILE_DIR&quot;: &quot;&quot;,
&quot;RunOnceActivity.RadMigrateCodeStyle&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.readMode.enableVisualFormatting&quot;: &quot;true&quot;,
&quot;RunOnceActivity.west.config.association.type.startup.service&quot;: &quot;true&quot;,
&quot;cf.first.check.clang-format&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;code.cleanup.on.save&quot;: &quot;true&quot;,
&quot;com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;junie.onboarding.icon.badge.shown&quot;: &quot;true&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;onboarding.tips.debug.path&quot;: &quot;/Users/kyle/src/kte/main.cpp&quot;,
&quot;rearrange.code.on.save&quot;: &quot;true&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;editor.preferences.fonts.default&quot;,
&quot;to.speed.mode.migration.done&quot;: &quot;true&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/docs" />
</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" 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>
</configuration>
<configuration name="kte" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="kte" TARGET_NAME="kte" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="kte" RUN_TARGET_NAME="kte">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<list>
<item itemvalue="CMake Application.imgui" />
<item itemvalue="CMake Application.kge" />
<item itemvalue="CMake Application.kte" />
</list>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="e1fe3ab0-3650-4fca-8664-a247d5dfa457" name="Changes" comment="" />
<created>1764457173148</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1764457173148</updated>
<workItem from="1764457174208" duration="46950000" />
<workItem from="1764538560497" duration="215000" />
<workItem from="1764539255906" duration="196000" />
<workItem from="1764539459951" duration="64000" />
<workItem from="1764539535105" duration="10000" />
<workItem from="1764539556448" duration="156000" />
<workItem from="1764539725338" duration="1075000" />
<workItem from="1764542392763" duration="3512000" />
<workItem from="1764548345516" duration="28341000" />
</task>
<task id="LOCAL-00001" summary="Add undo/redo infrastructure and buffer management additions.">
<option name="closed" value="true" />
<created>1764485311566</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1764485311566</updated>
</task>
<task id="LOCAL-00002" summary="Refactor `Buffer` to use `Line` abstraction and improve handling of row operations.&#10;&#10;This uses either a GapBuffer or PieceTable depending on the compilation.">
<option name="closed" value="true" />
<created>1764486011231</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1764486011231</updated>
</task>
<task id="LOCAL-00003" summary="Handle end-of-file newline semantics and improve scroll alignment logic.">
<option name="closed" value="true" />
<created>1764486876984</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1764486876984</updated>
</task>
<task id="LOCAL-00004" summary="Add `UndoSystem` implementation and refactor `UndoNode` for simplicity.">
<option name="closed" value="true" />
<created>1764489870957</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1764489870957</updated>
</task>
<task id="LOCAL-00005" summary="Add non-linear undo/redo design documentation and improve `UndoSystem` with backspace batching and GUI integration fixes.">
<option name="closed" value="true" />
<created>1764496151303</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1764496151303</updated>
</task>
<task id="LOCAL-00006" summary="Add `TestFrontend` documentation and `UndoSystem` buffer reference update.&#10;&#10;- Document `TestFrontend` for programmatic testing, including examples and usage details.&#10;- Add `UpdateBufferReference` to `UndoSystem` to support updating buffer associations.">
<option name="closed" value="true" />
<created>1764500200942</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1764500200942</updated>
</task>
<task id="LOCAL-00007" summary="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure.&#10;&#10;- Delete `packaging.cmake` to streamline build system.&#10;- Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`.&#10;- Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing.&#10;- Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation.&#10;- Enhance kill ring operations and new prompt workflows in `Editor`.">
<option name="closed" value="true" />
<created>1764501532446</created>
<option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" />
<updated>1764501532446</updated>
</task>
<task id="LOCAL-00008" summary="Add man pages for `kge` and `kte` with installation targets in CMake.&#10;&#10;- Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples.&#10;- Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`.&#10;- Ensure `kge` man page installation is conditional on GUI being built.">
<option name="closed" value="true" />
<created>1764502480274</created>
<option name="number" value="00008" />
<option name="presentableId" value="LOCAL-00008" />
<option name="project" value="LOCAL" />
<updated>1764502480274</updated>
</task>
<task id="LOCAL-00009" summary="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling.">
<option name="closed" value="true" />
<created>1764505723411</created>
<option name="number" value="00009" />
<option name="presentableId" value="LOCAL-00009" />
<option name="project" value="LOCAL" />
<updated>1764505723411</updated>
</task>
<task id="LOCAL-00010" summary="Refactor code for consistency and enhanced functionality.&#10;&#10;- Normalize path handling for buffer operations, supporting tilde expansion and absolute paths.&#10;- Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes.&#10;- Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`.&#10;- Refine keybindings and enhance existing commands for improved command flow.&#10;- Adjust GUI and terminal renderers to display total line counts alongside filenames.&#10;- Update coding style to align with project guidelines.">
<option name="closed" value="true" />
<created>1764550164829</created>
<option name="number" value="00010" />
<option name="presentableId" value="LOCAL-00010" />
<option name="project" value="LOCAL" />
<updated>1764550164829</updated>
</task>
<task id="LOCAL-00011" summary="Add horizontal scrolling support and refactor mouse click handling in GUI.&#10;&#10;- Introduce horizontal scrolling with column offset synchronization in GUI.&#10;- Refactor mouse click handling for improved accuracy and viewport alignment.&#10;- Enhance tab expansion and cursor rendering logic for better user experience.&#10;- 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.&#10;&#10;- Add visual file picker for GUI with toggle support.&#10;- Introduce `GUIConfig` class for loading GUI settings from configuration file.&#10;- Refactor window initialization to support dynamic sizing based on configuration.&#10;- Add macOS-specific handling for fullscreen behavior.&#10;- 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.&#10;&#10;- Display buffer position prefix &quot;[x/N]&quot; in GUI and terminal renderers.&#10;- Improve `kte` and `kge` man pages with frontend usage details and project homepage.&#10;- Update README with GUI invocation instructions.&#10;- 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">
<option name="version" value="3" />
</component>
<component name="VCPKGProject">
<isAutomaticCheckingOnLaunch value="false" />
<isAutomaticFoundErrors value="true" />
<isAutomaticReloadCMake value="true" />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="Refactoring" />
<MESSAGE value="Add undo/redo infrastructure and buffer management additions." />
<MESSAGE value="Refactor `Buffer` to use `Line` abstraction and improve handling of row operations.&#10;&#10;This uses either a GapBuffer or PieceTable depending on the compilation." />
<MESSAGE value="Handle end-of-file newline semantics and improve scroll alignment logic." />
<MESSAGE value="Enable installation targets." />
<MESSAGE value="Add `UndoSystem` implementation and refactor `UndoNode` for simplicity." />
<MESSAGE value="Add non-linear undo/redo design documentation and improve `UndoSystem` with backspace batching and GUI integration fixes." />
<MESSAGE value="Add `TestFrontend` documentation and `UndoSystem` buffer reference update.&#10;&#10;- Document `TestFrontend` for programmatic testing, including examples and usage details.&#10;- Add `UpdateBufferReference` to `UndoSystem` to support updating buffer associations." />
<MESSAGE value="Remove `packaging.cmake`, deprecate `test_undo` setup, and add new testing infrastructure.&#10;&#10;- Delete `packaging.cmake` to streamline build system.&#10;- Deprecate `test_undo` in CMake setup; condition builds on `BUILD_TESTS`.&#10;- Introduce `TestFrontend`, `TestRenderer`, and `TestInputHandler` for structured testing.&#10;- Update `GUIInputHandler` and `Command` for enhanced buffer save handling and overwrite confirmation.&#10;- Enhance kill ring operations and new prompt workflows in `Editor`." />
<MESSAGE value="Add man pages for `kge` and `kte` with installation targets in CMake.&#10;&#10;- Introduce `docs/kge.1` and `docs/kte.1` man pages covering usage, options, keybindings, and examples.&#10;- Update `CMakeLists.txt` to install man pages under `${CMAKE_INSTALL_MANDIR}/man1`.&#10;- Ensure `kge` man page installation is conditional on GUI being built." />
<MESSAGE value="Add GUI initialization updates and improve navigation commands.&#10;&#10;- Implement terminal detachment for GUI mode to enable terminal closure post-launch.&#10;- Add `+N` support for opening files at specific line numbers and refine cursor positioning.&#10;- Introduce `JumpToLine` command for direct navigation by line number.&#10;- Enhance mouse wheel handling for line-wise scrolling." />
<MESSAGE value="Refactor code for consistency and enhanced functionality.&#10;&#10;- Normalize path handling for buffer operations, supporting tilde expansion and absolute paths.&#10;- Introduce `DisplayNameFor` to uniquely resolve buffer display names, minimizing filename clashes.&#10;- Add new commands: `ShowWorkingDirectory` and `ChangeWorkingDirectory`.&#10;- Refine keybindings and enhance existing commands for improved command flow.&#10;- Adjust GUI and terminal renderers to display total line counts alongside filenames.&#10;- Update coding style to align with project guidelines." />
<MESSAGE value="Add horizontal scrolling support and refactor mouse click handling in GUI.&#10;&#10;- Introduce horizontal scrolling with column offset synchronization in GUI.&#10;- Refactor mouse click handling for improved accuracy and viewport alignment.&#10;- Enhance tab expansion and cursor rendering logic for better user experience.&#10;- Replace redundant variable declarations in `Buffer` for cleaner code." />
<MESSAGE value="Introduce file picker and GUI configuration with enhancements.&#10;&#10;- Add visual file picker for GUI with toggle support.&#10;- Introduce `GUIConfig` class for loading GUI settings from configuration file.&#10;- Refactor window initialization to support dynamic sizing based on configuration.&#10;- Add macOS-specific handling for fullscreen behavior.&#10;- Improve header inclusion order and minor code cleanup." />
<MESSAGE value="Add buffer position display and documentation improvements.&#10;&#10;- Display buffer position prefix &quot;[x/N]&quot; in GUI and terminal renderers.&#10;- Improve `kte` and `kge` man pages with frontend usage details and project homepage.&#10;- Update README with GUI invocation instructions.&#10;- 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 />
<select />
</component>
</project>

View File

@@ -6,6 +6,9 @@
#include "Buffer.h"
#include "UndoSystem.h"
#include "UndoTree.h"
// For reconstructing highlighter state on copies
#include "syntax/HighlighterRegistry.h"
#include "syntax/NullHighlighter.h"
Buffer::Buffer()
@@ -40,9 +43,32 @@ Buffer::Buffer(const Buffer &other)
mark_set_ = other.mark_set_;
mark_curx_ = other.mark_curx_;
mark_cury_ = other.mark_cury_;
// Copy syntax/highlighting flags
version_ = other.version_;
syntax_enabled_ = other.syntax_enabled_;
filetype_ = other.filetype_;
// Fresh undo system for the copy
undo_tree_ = std::make_unique<UndoTree>();
undo_sys_ = std::make_unique<UndoSystem>(*this, *undo_tree_);
// Recreate a highlighter engine for this copy based on filetype/syntax state
if (syntax_enabled_) {
// Allocate engine and install an appropriate highlighter
highlighter_ = std::make_unique<kte::HighlighterEngine>();
if (!filetype_.empty()) {
auto hl = kte::HighlighterRegistry::CreateFor(filetype_);
if (hl) {
highlighter_->SetHighlighter(std::move(hl));
} else {
// Unsupported filetype -> NullHighlighter keeps syntax pipeline active
highlighter_->SetHighlighter(std::make_unique<kte::NullHighlighter>());
}
} else {
// No filetype -> keep syntax enabled but use NullHighlighter
highlighter_->SetHighlighter(std::make_unique<kte::NullHighlighter>());
}
// Fresh engine has empty caches; nothing to invalidate
}
}
@@ -65,9 +91,28 @@ Buffer::operator=(const Buffer &other)
mark_set_ = other.mark_set_;
mark_curx_ = other.mark_curx_;
mark_cury_ = other.mark_cury_;
version_ = other.version_;
syntax_enabled_ = other.syntax_enabled_;
filetype_ = other.filetype_;
// Recreate undo system for this instance
undo_tree_ = std::make_unique<UndoTree>();
undo_sys_ = std::make_unique<UndoSystem>(*this, *undo_tree_);
// Recreate highlighter engine consistent with syntax settings
highlighter_.reset();
if (syntax_enabled_) {
highlighter_ = std::make_unique<kte::HighlighterEngine>();
if (!filetype_.empty()) {
auto hl = kte::HighlighterRegistry::CreateFor(filetype_);
if (hl) {
highlighter_->SetHighlighter(std::move(hl));
} else {
highlighter_->SetHighlighter(std::make_unique<kte::NullHighlighter>());
}
} else {
highlighter_->SetHighlighter(std::make_unique<kte::NullHighlighter>());
}
}
return *this;
}
@@ -91,6 +136,11 @@ Buffer::Buffer(Buffer &&other) noexcept
undo_tree_(std::move(other.undo_tree_)),
undo_sys_(std::move(other.undo_sys_))
{
// Move syntax/highlighting state
version_ = other.version_;
syntax_enabled_ = other.syntax_enabled_;
filetype_ = std::move(other.filetype_);
highlighter_ = std::move(other.highlighter_);
// Update UndoSystem's buffer reference to point to this object
if (undo_sys_) {
undo_sys_->UpdateBufferReference(*this);
@@ -122,6 +172,12 @@ Buffer::operator=(Buffer &&other) noexcept
undo_tree_ = std::move(other.undo_tree_);
undo_sys_ = std::move(other.undo_sys_);
// Move syntax/highlighting state
version_ = other.version_;
syntax_enabled_ = other.syntax_enabled_;
filetype_ = std::move(other.filetype_);
highlighter_ = std::move(other.highlighter_);
// Update UndoSystem's buffer reference to point to this object
if (undo_sys_) {
undo_sys_->UpdateBufferReference(*this);
@@ -368,9 +424,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), Line(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);
@@ -430,8 +486,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), Line(tail));
rows_[y].erase(x);
rows_.insert(rows_.begin() + static_cast<std::ptrdiff_t>(y + 1), Line(tail));
}
@@ -459,7 +515,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, Line(std::string(text)));
rows_.insert(rows_.begin() + row, Line(std::string(text)));
}

126
Buffer.h
View File

@@ -12,11 +12,15 @@
#include "AppendBuffer.h"
#include "UndoSystem.h"
#include <cstdint>
#include <memory>
#include "syntax/HighlighterEngine.h"
#include "Highlight.h"
class Buffer {
public:
Buffer();
Buffer();
Buffer(const Buffer &other);
@@ -262,11 +266,12 @@ 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;
filename_ = name;
is_file_backed_ = false;
}
@@ -277,26 +282,29 @@ 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;
}
// Read-only flag
[[nodiscard]] bool IsReadOnly() const
{
return read_only_;
}
void ToggleReadOnly()
{
read_only_ = !read_only_;
}
void SetReadOnly(bool ro)
{
read_only_ = ro;
}
void ToggleReadOnly()
{
read_only_ = !read_only_;
}
void SetCursor(const std::size_t x, const std::size_t y)
@@ -322,6 +330,12 @@ public:
void SetDirty(bool d)
{
dirty_ = d;
if (d) {
++version_;
if (highlighter_) {
highlighter_->InvalidateFrom(0);
}
}
}
@@ -360,6 +374,56 @@ public:
[[nodiscard]] std::string AsString() const;
// Syntax highlighting integration (per-buffer)
[[nodiscard]] std::uint64_t Version() const
{
return version_;
}
void SetSyntaxEnabled(bool on)
{
syntax_enabled_ = on;
}
[[nodiscard]] bool SyntaxEnabled() const
{
return syntax_enabled_;
}
void SetFiletype(const std::string &ft)
{
filetype_ = ft;
}
[[nodiscard]] const std::string &Filetype() const
{
return filetype_;
}
kte::HighlighterEngine *Highlighter()
{
return highlighter_.get();
}
const kte::HighlighterEngine *Highlighter() const
{
return highlighter_.get();
}
void EnsureHighlighter()
{
if (!highlighter_)
highlighter_ = std::make_unique<kte::HighlighterEngine>();
}
// Raw, low-level editing APIs used by UndoSystem apply().
// These must NOT trigger undo recording. They also do not move the cursor.
void insert_text(int row, int col, std::string_view text);
@@ -380,22 +444,28 @@ public:
[[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 read_only_ = 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_;
std::unique_ptr<UndoSystem> undo_sys_;
// Syntax/highlighting state
std::uint64_t version_ = 0; // increment on edits
bool syntax_enabled_ = true;
std::string filetype_;
std::unique_ptr<kte::HighlighterEngine> highlighter_;
};
#endif // KTE_BUFFER_H

View File

@@ -4,7 +4,7 @@ project(kte)
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 17)
set(KTE_VERSION "1.0.5")
set(KTE_VERSION "1.3.1")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
@@ -13,6 +13,7 @@ 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)
option(KTE_ENABLE_TREESITTER "Enable optional Tree-sitter highlighter adapter" OFF)
if (CMAKE_HOST_UNIX)
message(STATUS "Build system is POSIX.")
@@ -20,6 +21,17 @@ else ()
message(STATUS "Build system is NOT POSIX.")
endif ()
add_compile_options(
"-static"
"-Wall"
"-Wextra"
"-Werror"
"-Wno-unused-function"
"-Wno-unused-parameter"
"-g"
"$<$<CONFIG:RELEASE>:-O2>"
)
if (MSVC)
add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/O2>")
else ()
@@ -37,6 +49,9 @@ else ()
endif ()
add_compile_definitions(KGE_PLATFORM=${CMAKE_HOST_SYSTEM_NAME})
add_compile_definitions(KTE_VERSION_STR="v${KTE_VERSION}")
if (KTE_ENABLE_TREESITTER)
add_compile_definitions(KTE_ENABLE_TREESITTER)
endif ()
message(STATUS "Build system: ${CMAKE_HOST_SYSTEM_NAME}")
@@ -50,6 +65,43 @@ set(CURSES_NEED_WIDE)
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR})
set(SYNTAX_SOURCES
syntax/GoHighlighter.cc
syntax/CppHighlighter.cc
syntax/JsonHighlighter.cc
syntax/ErlangHighlighter.cc
syntax/MarkdownHighlighter.cc
syntax/TreeSitterHighlighter.cc
syntax/LispHighlighter.cc
syntax/HighlighterEngine.cc
syntax/RustHighlighter.cc
syntax/HighlighterRegistry.cc
syntax/SqlHighlighter.cc
syntax/NullHighlighter.cc
syntax/ForthHighlighter.cc
syntax/PythonHighlighter.cc
syntax/ShellHighlighter.cc
)
if (KTE_ENABLE_TREESITTER)
list(APPEND SYNTAX_SOURCES
TreeSitterHighlighter.cc)
endif ()
set(FONT_SOURCES
fonts/Font.cc
fonts/FontRegistry.cc
)
set(GUI_SOURCES
${FONT_SOURCES}
GUIConfig.cc
GUIRenderer.cc
GUIInputHandler.cc
GUIFrontend.cc
)
set(COMMON_SOURCES
GapBuffer.cc
PieceTable.cc
@@ -67,6 +119,71 @@ set(COMMON_SOURCES
UndoNode.cc
UndoTree.cc
UndoSystem.cc
${SYNTAX_SOURCES}
)
set(SYNTAX_HEADERS
syntax/GoHighlighter.h
syntax/HighlighterEngine.h
syntax/ShellHighlighter.h
syntax/MarkdownHighlighter.h
syntax/LispHighlighter.h
syntax/SqlHighlighter.h
syntax/ForthHighlighter.h
syntax/JsonHighlighter.h
syntax/TreeSitterHighlighter.h
syntax/NullHighlighter.h
syntax/CppHighlighter.h
syntax/ErlangHighlighter.h
syntax/LanguageHighlighter.h
syntax/RustHighlighter.h
syntax/PythonHighlighter.h
)
if (KTE_ENABLE_TREESITTER)
list(APPEND THEME_HEADERS
TreeSitterHighlighter.h)
endif ()
set(THEME_HEADERS
themes/ThemeHelpers.h
themes/EInk.h
themes/Gruvbox.h
themes/Solarized.h
themes/Plan9.h
themes/Nord.h
themes/Everforest.h
themes/KanagawaPaper.h
themes/LCARS.h
themes/OldBook.h
themes/Amber.h
themes/Orbital.h
themes/WeylandYutani.h
themes/Zenburn.h
)
set(FONT_HEADERS
fonts/Font.h
fonts/FontRegistry.h
fonts/FontRegistry.h
fonts/FontList.h
fonts/B612Mono.h
fonts/BrassMono.h
fonts/BrassMonoCode.h
fonts/FiraCode.h
fonts/Go.h
fonts/IBMPlexMono.h
fonts/Idealist.h
fonts/Inconsolata.h
fonts/InconsolataExpanded.h
fonts/Iosevka.h
fonts/IosevkaExtended.h
fonts/ShareTech.h
fonts/SpaceMono.h
fonts/Syne.h
fonts/Triplicate.h
fonts/Unispace.h
)
set(COMMON_HEADERS
@@ -90,6 +207,18 @@ set(COMMON_HEADERS
UndoNode.h
UndoTree.h
UndoSystem.h
Highlight.h
${SYNTAX_HEADERS}
)
set(GUI_HEADERS
${THEME_HEADERS}
${FONT_HEADERS}
GUIConfig.h
GUIRenderer.h
GUIInputHandler.h
GUIFrontend.h
)
# kte (terminal-first) executable
@@ -108,6 +237,18 @@ endif ()
target_link_libraries(kte ${CURSES_LIBRARIES})
if (KTE_ENABLE_TREESITTER)
# Users can provide their own tree-sitter include/lib via cache variables
set(TREESITTER_INCLUDE_DIR "" CACHE PATH "Path to tree-sitter include directory")
set(TREESITTER_LIBRARY "" CACHE FILEPATH "Path to tree-sitter library (.a/.dylib)")
if (TREESITTER_INCLUDE_DIR)
target_include_directories(kte PRIVATE ${TREESITTER_INCLUDE_DIR})
endif ()
if (TREESITTER_LIBRARY)
target_link_libraries(kte ${TREESITTER_LIBRARY})
endif ()
endif ()
install(TARGETS kte
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
@@ -133,35 +274,40 @@ if (BUILD_TESTS)
target_link_libraries(test_undo ${CURSES_LIBRARIES})
if (KTE_ENABLE_TREESITTER)
if (TREESITTER_INCLUDE_DIR)
target_include_directories(test_undo PRIVATE ${TREESITTER_INCLUDE_DIR})
endif ()
if (TREESITTER_LIBRARY)
target_link_libraries(test_undo ${TREESITTER_LIBRARY})
endif ()
endif ()
endif ()
if (${BUILD_GUI})
target_sources(kte PRIVATE
Font.h
GUIConfig.cc
GUIConfig.h
GUIRenderer.cc
GUIRenderer.h
GUIInputHandler.cc
GUIInputHandler.h
GUIFrontend.cc
GUIFrontend.h)
target_compile_definitions(kte PRIVATE KTE_BUILD_GUI=1)
target_link_libraries(kte imgui)
# ImGui::CreateContext();
# ImGuiIO& io = ImGui::GetIO();
# // Set custom ini filename path to ~/.config/kte/imgui.ini
# if (const char* home = std::getenv("HOME")) {
# static std::string ini_path = std::string(home) + "/.config/kte/imgui.ini";
# io.IniFilename = ini_path.c_str();
# }
# io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
# io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
# Do not enable GUI in the terminal-first 'kte' binary; GUI is built as separate 'kge'.
# This avoids referencing GUI classes from kte and keeps dependencies minimal.
# kge (GUI-first) executable
add_executable(kge
main.cc
${COMMON_SOURCES}
${GUI_SOURCES}
${COMMON_HEADERS}
GUIConfig.cc
GUIConfig.h
GUIRenderer.cc
GUIRenderer.h
GUIInputHandler.cc
GUIInputHandler.h
GUIFrontend.cc
GUIFrontend.h)
${GUI_HEADERS}
)
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)

1508
Command.cc

File diff suppressed because it is too large Load Diff

View File

@@ -58,6 +58,8 @@ enum class CommandId {
MoveEnd,
PageUp,
PageDown,
ScrollUp, // scroll viewport up (towards beginning) without moving cursor
ScrollDown, // scroll viewport down (towards end) without moving cursor
WordPrev,
WordNext,
DeleteWordPrev, // delete previous word (ESC BACKSPACE)
@@ -69,6 +71,9 @@ enum class CommandId {
Redo,
// UI/status helpers
UArgStatus, // update status line during universal-argument collection
// Themes (GUI)
ThemeNext,
ThemePrev,
// Region formatting
IndentRegion, // indent region (C-k =)
UnindentRegion, // unindent region (C-k -)
@@ -86,6 +91,19 @@ enum class CommandId {
ShowHelp, // open +HELP+ buffer with manual text (C-k h)
// Meta
UnknownKCommand, // arg: single character that was not recognized after C-k
// Generic command prompt
CommandPromptStart, // begin generic command prompt (C-k ;)
// Theme by name
ThemeSetByName,
// Font by name (GUI)
FontSetByName,
// Font size (GUI)
FontSetSize,
// Background mode (GUI)
BackgroundSet,
// Syntax highlighting
Syntax, // ":syntax on|off|reload"
SetOption, // generic ":set key=value" (v1: filetype=<lang>)
};
@@ -109,6 +127,8 @@ struct Command {
std::string name; // stable, unique name (e.g., "save", "save-as")
std::string help; // short help/description
CommandHandler handler;
// Public commands are exposed in the ": " prompt (C-k ;)
bool isPublic = false;
};
@@ -137,4 +157,4 @@ bool Execute(Editor &ed, CommandId id, const std::string &arg = std::string(), i
bool Execute(Editor &ed, const std::string &name, const std::string &arg = std::string(), int count = 0);
#endif // KTE_COMMAND_H
#endif // KTE_COMMAND_H

View File

@@ -3,6 +3,9 @@
#include <filesystem>
#include "Editor.h"
#include "syntax/HighlighterRegistry.h"
#include "syntax/CppHighlighter.h"
#include "syntax/NullHighlighter.h"
Editor::Editor() = default;
@@ -151,7 +154,32 @@ Editor::OpenFile(const std::string &path, std::string &err)
const bool rows_empty = rows.empty();
const bool single_empty_line = (!rows.empty() && rows.size() == 1 && rows[0].size() == 0);
if (unnamed && clean && (rows_empty || single_empty_line)) {
return cur.OpenFromFile(path, err);
bool ok = cur.OpenFromFile(path, err);
if (!ok)
return false;
// Setup highlighting using registry (extension + shebang)
cur.EnsureHighlighter();
std::string first = "";
const auto &rows = cur.Rows();
if (!rows.empty())
first = static_cast<std::string>(rows[0]);
std::string ft = kte::HighlighterRegistry::DetectForPath(path, first);
if (!ft.empty()) {
cur.SetFiletype(ft);
cur.SetSyntaxEnabled(true);
if (auto *eng = cur.Highlighter()) {
eng->SetHighlighter(kte::HighlighterRegistry::CreateFor(ft));
eng->InvalidateFrom(0);
}
} else {
cur.SetFiletype("");
cur.SetSyntaxEnabled(true);
if (auto *eng = cur.Highlighter()) {
eng->SetHighlighter(std::make_unique<kte::NullHighlighter>());
eng->InvalidateFrom(0);
}
}
return true;
}
}
@@ -159,6 +187,30 @@ Editor::OpenFile(const std::string &path, std::string &err)
if (!b.OpenFromFile(path, err)) {
return false;
}
// Initialize syntax highlighting by extension + shebang via registry (v2)
b.EnsureHighlighter();
std::string first = "";
{
const auto &rows = b.Rows();
if (!rows.empty())
first = static_cast<std::string>(rows[0]);
}
std::string ft = kte::HighlighterRegistry::DetectForPath(path, first);
if (!ft.empty()) {
b.SetFiletype(ft);
b.SetSyntaxEnabled(true);
if (auto *eng = b.Highlighter()) {
eng->SetHighlighter(kte::HighlighterRegistry::CreateFor(ft));
eng->InvalidateFrom(0);
}
} else {
b.SetFiletype("");
b.SetSyntaxEnabled(true);
if (auto *eng = b.Highlighter()) {
eng->SetHighlighter(std::make_unique<kte::NullHighlighter>());
eng->InvalidateFrom(0);
}
}
// Add as a new buffer and switch to it
std::size_t idx = AddBuffer(std::move(b));
SwitchTo(idx);
@@ -173,6 +225,27 @@ Editor::SwitchTo(std::size_t index)
return false;
}
curbuf_ = index;
// Robustness: ensure a valid highlighter is installed when switching buffers
Buffer &b = buffers_[curbuf_];
if (b.SyntaxEnabled()) {
b.EnsureHighlighter();
if (auto *eng = b.Highlighter()) {
if (!eng->HasHighlighter()) {
// Try to set based on existing filetype; fall back to NullHighlighter
if (!b.Filetype().empty()) {
auto hl = kte::HighlighterRegistry::CreateFor(b.Filetype());
if (hl) {
eng->SetHighlighter(std::move(hl));
} else {
eng->SetHighlighter(std::make_unique<kte::NullHighlighter>());
}
} else {
eng->SetHighlighter(std::make_unique<kte::NullHighlighter>());
}
eng->InvalidateFrom(0);
}
}
}
return true;
}

View File

@@ -32,6 +32,16 @@ public:
}
[[nodiscard]] std::size_t ContentRows() const
{
// Always compute from current rows_ to avoid stale values.
// Reserve 1 row for status line.
if (rows_ == 0)
return 1;
return std::max<std::size_t>(1, rows_ - 1);
}
// Mode and flags (mirroring legacy fields)
void SetMode(int m)
{
@@ -301,22 +311,23 @@ public:
}
// --- 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
};
// --- 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
Command // generic command prompt (": ")
};
void StartPrompt(PromptKind kind, const std::string &label, const std::string &initial)
@@ -518,20 +529,38 @@ private:
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_;
// 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
// 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_; }
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_;
std::string replace_find_tmp_;
std::string replace_with_tmp_;
};
#endif // KTE_EDITOR_H
#endif // KTE_EDITOR_H

4892
Font.h

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ default_config_path()
{
const char *home = std::getenv("HOME");
if (!home || !*home)
return std::string();
return {};
std::string path(home);
path += "/.config/kte/kge.ini";
return path;
@@ -34,7 +34,8 @@ GUIConfig
GUIConfig::Load()
{
GUIConfig cfg; // defaults already set
std::string path = default_config_path();
const std::string path = default_config_path();
if (!path.empty()) {
cfg.LoadFromFile(path);
}
@@ -98,10 +99,32 @@ GUIConfig::LoadFromFile(const std::string &path)
try {
v = std::stof(val);
} catch (...) {}
if (v > 0.0f)
if (v > 0.0f) {
font_size = v;
}
} else if (key == "font") {
font = val;
} else if (key == "theme") {
theme = val;
} else if (key == "background" || key == "bg") {
std::string v = val;
std::transform(v.begin(), v.end(), v.begin(), [](unsigned char c) {
return (char) std::tolower(c);
});
if (v == "light" || v == "dark")
background = v;
} else if (key == "syntax") {
std::string v = val;
std::transform(v.begin(), v.end(), v.begin(), [](unsigned char c) {
return (char) std::tolower(c);
});
if (v == "1" || v == "on" || v == "true" || v == "yes") {
syntax = true;
} else if (v == "0" || v == "off" || v == "false" || v == "no") {
syntax = false;
}
}
}
return true;
}
}

View File

@@ -12,10 +12,19 @@
class GUIConfig {
public:
bool fullscreen = false;
int columns = 80;
int rows = 42;
float font_size = (float) KTE_FONT_SIZE;
bool fullscreen = false;
int columns = 80;
int rows = 42;
float font_size = (float) KTE_FONT_SIZE;
std::string font = "default";
std::string theme = "nord";
// Background mode for themes that support light/dark variants
// Values: "dark" (default), "light"
std::string background = "dark";
// Default syntax highlighting state for GUI (kge): on/off
// Accepts: on/off/true/false/yes/no/1/0 in the ini file.
bool syntax = true; // default: enabled
// Load from default path: $HOME/.config/kte/kge.ini
static GUIConfig Load();
@@ -24,4 +33,4 @@ public:
bool LoadFromFile(const std::string &path);
};
#endif // KTE_GUI_CONFIG_H
#endif // KTE_GUI_CONFIG_H

View File

@@ -1,27 +1,32 @@
#include <cstdio>
#include <string>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <string>
#include <imgui.h>
#include <SDL.h>
#include <SDL_opengl.h>
#include <imgui.h>
#include <backends/imgui_impl_sdl2.h>
#include <backends/imgui_impl_opengl3.h>
#include <backends/imgui_impl_sdl2.h>
#include "Editor.h"
#include "Command.h"
#include "GUIFrontend.h"
#include "Font.h" // embedded default font (DefaultFontRegular)
#include "Command.h"
#include "Editor.h"
#include "GUIConfig.h"
#include "GUITheme.h"
#include "fonts/Font.h" // embedded default font (DefaultFont)
#include "fonts/FontRegistry.h"
#include "syntax/HighlighterRegistry.h"
#include "syntax/NullHighlighter.h"
#ifndef KTE_FONT_SIZE
#define KTE_FONT_SIZE 16.0f
#endif
static const char *kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
static auto kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
bool
GUIFrontend::Init(Editor &ed)
@@ -31,8 +36,8 @@ GUIFrontend::Init(Editor &ed)
return false;
}
// Load GUI configuration (fullscreen, columns/rows, font size)
const auto [fullscreen, columns, rows, font_size] = GUIConfig::Load();
// Load GUI configuration (fullscreen, columns/rows, font size, theme, background)
GUIConfig cfg = GUIConfig::Load();
// GL attributes for core profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
@@ -46,7 +51,7 @@ GUIFrontend::Init(Editor &ed)
// Compute desired window size from config
Uint32 win_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
if (fullscreen) {
if (cfg.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{};
@@ -60,8 +65,8 @@ GUIFrontend::Init(Editor &ed)
#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);
int w = cfg.columns * static_cast<int>(cfg.font_size);
int h = cfg.rows * static_cast<int>(cfg.font_size * 1.2);
// As a safety, clamp to display usable bounds if retrievable
SDL_Rect usable{};
@@ -73,19 +78,23 @@ GUIFrontend::Init(Editor &ed)
height_ = std::max(200, h);
}
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
window_ = SDL_CreateWindow(
"kge - kyle's text editor " KTE_VERSION_STR,
"kge - kyle's graphical editor " KTE_VERSION_STR,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width_, height_,
win_flags);
if (!window_)
if (!window_) {
return false;
}
SDL_EnableScreenSaver();
#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) {
if (cfg.fullscreen) {
SDL_Rect usable{};
if (SDL_GetDisplayUsableBounds(0, &usable) == 0) {
SDL_SetWindowPosition(window_, usable.x, usable.y);
@@ -102,9 +111,66 @@ GUIFrontend::Init(Editor &ed)
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
(void) io;
// Set custom ini filename path to ~/.config/kte/imgui.ini
if (const char *home = std::getenv("HOME")) {
namespace fs = std::filesystem;
fs::path config_dir = fs::path(home) / ".config" / "kte";
std::error_code ec;
if (!fs::exists(config_dir)) {
fs::create_directories(config_dir, ec);
}
if (fs::exists(config_dir)) {
static std::string ini_path = (config_dir / "imgui.ini").string();
io.IniFilename = ini_path.c_str();
}
}
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
ImGui::StyleColorsDark();
// Apply background mode and selected theme (default: Nord). Can be changed at runtime via commands.
if (cfg.background == "light")
kte::SetBackgroundMode(kte::BackgroundMode::Light);
else
kte::SetBackgroundMode(kte::BackgroundMode::Dark);
kte::ApplyThemeByName(cfg.theme);
// Apply default syntax highlighting preference from GUI config to the current buffer
if (Buffer *b = ed.CurrentBuffer()) {
if (cfg.syntax) {
b->SetSyntaxEnabled(true);
// Ensure a highlighter is available if possible
b->EnsureHighlighter();
if (auto *eng = b->Highlighter()) {
if (!eng->HasHighlighter()) {
// Try detect from filename and first line; fall back to cpp or existing filetype
std::string first_line;
const auto &rows = b->Rows();
if (!rows.empty())
first_line = static_cast<std::string>(rows[0]);
std::string ft = kte::HighlighterRegistry::DetectForPath(
b->Filename(), first_line);
if (!ft.empty()) {
eng->SetHighlighter(kte::HighlighterRegistry::CreateFor(ft));
b->SetFiletype(ft);
eng->InvalidateFrom(0);
} else {
// Unknown/unsupported -> install a null highlighter to keep syntax enabled
eng->SetHighlighter(std::make_unique<kte::NullHighlighter>());
b->SetFiletype("");
eng->InvalidateFrom(0);
}
}
}
} else {
b->SetSyntaxEnabled(false);
}
}
if (!ImGui_ImplSDL2_InitForOpenGL(window_, gl_ctx_))
return false;
if (!ImGui_ImplOpenGL3_Init(kGlslVersion))
@@ -131,8 +197,19 @@ GUIFrontend::Init(Editor &ed)
}
#endif
// Initialize GUI font from embedded default (use configured size or compiled default)
LoadGuiFont_(nullptr, (float) font_size);
// Install embedded fonts into registry and load configured font
kte::Fonts::InstallDefaultFonts();
// Initialize font atlas using configured font name and size; fallback to embedded default helper
if (!kte::Fonts::FontRegistry::Instance().LoadFont(cfg.font, (float) cfg.font_size)) {
LoadGuiFont_(nullptr, (float) cfg.font_size);
// Record defaults in registry so subsequent size changes have a base
kte::Fonts::FontRegistry::Instance().RequestLoadFont("default", (float) cfg.font_size);
std::string n;
float s = 0.0f;
if (kte::Fonts::FontRegistry::Instance().ConsumePendingFontRequest(n, s)) {
kte::Fonts::FontRegistry::Instance().LoadFont(n, s);
}
}
return true;
}
@@ -161,28 +238,21 @@ GUIFrontend::Step(Editor &ed, bool &running)
input_.ProcessSDLEvent(e);
}
// Execute pending mapped inputs (drain queue)
for (;;) {
MappedInput mi;
if (!input_.Poll(mi))
break;
if (mi.hasCommand) {
// Track kill ring before and after to sync GUI clipboard when it changes
const std::string before = ed.KillRingHead();
Execute(ed, mi.id, mi.arg, mi.count);
const std::string after = ed.KillRingHead();
if (after != before && !after.empty()) {
// Update the system clipboard to mirror the kill ring head in GUI
SDL_SetClipboardText(after.c_str());
// Apply pending font change before starting a new frame
{
std::string fname;
float fsize = 0.0f;
if (kte::Fonts::FontRegistry::Instance().ConsumePendingFontRequest(fname, fsize)) {
if (!fname.empty() && fsize > 0.0f) {
kte::Fonts::FontRegistry::Instance().LoadFont(fname, fsize);
// Recreate backend font texture
ImGui_ImplOpenGL3_DestroyFontsTexture();
ImGui_ImplOpenGL3_CreateFontsTexture();
}
}
}
if (ed.QuitRequested()) {
running = false;
}
// Start a new ImGui frame
// Start a new ImGui frame BEFORE processing commands so dimensions are correct
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(window_);
ImGui::NewFrame();
@@ -211,7 +281,7 @@ GUIFrontend::Step(Editor &ed, bool &running)
float avail_h = std::max(0.0f, disp_h - 2.0f * pad_y - status_h);
// Visible content rows inside the scroll child
std::size_t content_rows = static_cast<std::size_t>(std::floor(avail_h / line_h));
auto content_rows = static_cast<std::size_t>(std::floor(avail_h / line_h));
// Editor::Rows includes the status line; add 1 back for it.
std::size_t rows = std::max<std::size_t>(1, content_rows + 1);
std::size_t cols = static_cast<std::size_t>(std::max(1.0f, std::floor(avail_w / ch_w)));
@@ -222,6 +292,27 @@ GUIFrontend::Step(Editor &ed, bool &running)
}
}
// Execute pending mapped inputs (drain queue) AFTER dimensions are updated
for (;;) {
MappedInput mi;
if (!input_.Poll(mi))
break;
if (mi.hasCommand) {
// Track kill ring before and after to sync GUI clipboard when it changes
const std::string before = ed.KillRingHead();
Execute(ed, mi.id, mi.arg, mi.count);
const std::string after = ed.KillRingHead();
if (after != before && !after.empty()) {
// Update the system clipboard to mirror the kill ring head in GUI
SDL_SetClipboardText(after.c_str());
}
}
}
if (ed.QuitRequested()) {
running = false;
}
// No runtime font UI; always use embedded font.
// Draw editor UI
@@ -259,13 +350,13 @@ GUIFrontend::Shutdown()
bool
GUIFrontend::LoadGuiFont_(const char * /*path*/, float size_px)
GUIFrontend::LoadGuiFont_(const char * /*path*/, const float size_px)
{
ImGuiIO &io = ImGui::GetIO();
const ImGuiIO &io = ImGui::GetIO();
io.Fonts->Clear();
ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
(void *) DefaultFontRegularCompressedData,
(int) DefaultFontRegularCompressedSize,
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
kte::Fonts::DefaultFontData,
kte::Fonts::DefaultFontSize,
size_px);
if (!font) {
font = io.Fonts->AddFontDefault();
@@ -273,7 +364,4 @@ GUIFrontend::LoadGuiFont_(const char * /*path*/, float size_px)
(void) font;
io.Fonts->Build();
return true;
}
// No runtime font reload or system font resolution in this simplified build.
}

View File

@@ -5,6 +5,7 @@
#define KTE_GUI_FRONTEND_H
#include "Frontend.h"
#include "GUIConfig.h"
#include "GUIInputHandler.h"
#include "GUIRenderer.h"
@@ -25,8 +26,9 @@ public:
void Shutdown() override;
private:
bool LoadGuiFont_(const char *path, float size_px);
static bool LoadGuiFont_(const char *path, float size_px);
GUIConfig config_{};
GUIInputHandler input_{};
GUIRenderer renderer_{};
SDL_Window *window_ = nullptr;
@@ -35,4 +37,4 @@ private:
int height_ = 800;
};
#endif // KTE_GUI_FRONTEND_H
#endif // KTE_GUI_FRONTEND_H

View File

@@ -1,7 +1,9 @@
#include <cstdio>
#include <algorithm>
#include <ncurses.h>
#include <SDL.h>
#include <imgui.h>
#include "GUIInputHandler.h"
#include "KKeymap.h"
@@ -59,44 +61,57 @@ map_key(const SDL_Keycode key,
}
// Movement and basic keys
// These keys exit k-prefix mode if active (user pressed C-k then a special key).
switch (key) {
case SDLK_LEFT:
k_prefix = false;
out = {true, CommandId::MoveLeft, "", 0};
return true;
case SDLK_RIGHT:
k_prefix = false;
out = {true, CommandId::MoveRight, "", 0};
return true;
case SDLK_UP:
k_prefix = false;
out = {true, CommandId::MoveUp, "", 0};
return true;
case SDLK_DOWN:
k_prefix = false;
out = {true, CommandId::MoveDown, "", 0};
return true;
case SDLK_HOME:
k_prefix = false;
out = {true, CommandId::MoveHome, "", 0};
return true;
case SDLK_END:
k_prefix = false;
out = {true, CommandId::MoveEnd, "", 0};
return true;
case SDLK_PAGEUP:
k_prefix = false;
out = {true, CommandId::PageUp, "", 0};
return true;
case SDLK_PAGEDOWN:
k_prefix = false;
out = {true, CommandId::PageDown, "", 0};
return true;
case SDLK_DELETE:
k_prefix = false;
out = {true, CommandId::DeleteChar, "", 0};
return true;
case SDLK_BACKSPACE:
k_prefix = false;
out = {true, CommandId::Backspace, "", 0};
return true;
case SDLK_TAB:
// Insert a literal tab character
out.hasCommand = true;
out.id = CommandId::InsertText;
out.arg = "\t";
out.count = 0;
return true;
// Insert a literal tab character when not interpreting a k-prefix suffix.
// If k-prefix is active, let the k-prefix handler below consume the key
// (so Tab doesn't leave k-prefix stuck).
if (!k_prefix) {
out = {true, CommandId::InsertText, std::string("\t"), 0};
return true;
}
break; // fall through so k-prefix handler can process
case SDLK_RETURN:
case SDLK_KP_ENTER:
out = {true, CommandId::Newline, "", 0};
@@ -281,7 +296,12 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
bool produced = false;
switch (e.type) {
case SDL_MOUSEWHEEL: {
// Map vertical wheel to line-wise cursor movement (MoveUp/MoveDown)
// Let ImGui handle mouse wheel when it wants to capture the mouse
// (e.g., when hovering the editor child window with scrollbars).
// This enables native vertical and horizontal scrolling behavior in GUI.
if (ImGui::GetIO().WantCaptureMouse)
return false;
// Otherwise, fallback to mapping vertical wheel to editor scroll commands.
int dy = e.wheel.y;
#ifdef SDL_MOUSEWHEEL_FLIPPED
if (e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
@@ -289,7 +309,7 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
#endif
if (dy != 0) {
int repeat = dy > 0 ? dy : -dy;
CommandId id = dy > 0 ? CommandId::MoveUp : CommandId::MoveDown;
CommandId id = dy > 0 ? CommandId::ScrollUp : CommandId::ScrollDown;
std::lock_guard<std::mutex> lk(mu_);
for (int i = 0; i < repeat; ++i) {
q_.push(MappedInput{true, id, std::string(), 0});
@@ -348,13 +368,19 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
uarg_text_,
mi);
// If we inserted a TAB on KEYDOWN, suppress any subsequent SDL_TEXTINPUT
// for this keystroke to avoid double insertion on platforms that emit it.
if (produced && mi.hasCommand && mi.id == CommandId::InsertText && mi.arg == "\t") {
suppress_text_input_once_ = true;
}
// If we just consumed a universal-argument digit or '-' on KEYDOWN and emitted UArgStatus,
// suppress the subsequent SDL_TEXTINPUT for this keystroke to avoid duplicating the digit in status.
if (produced && mi.hasCommand && mi.id == CommandId::UArgStatus) {
// Digits without shift, or a plain '-'
const bool is_digit_key = (key >= SDLK_0 && key <= SDLK_9) && !(mods & KMOD_SHIFT);
const bool is_minus_key = (key == SDLK_MINUS);
if (uarg_active_ && uarg_collecting_ && (is_digit_key || is_minus_key)) {
if (uarg_active_ && uarg_collecting_ &&(is_digit_key || is_minus_key)) {
suppress_text_input_once_ = true;
}
}
@@ -520,11 +546,21 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
break;
}
if (!k_prefix_ && e.text.text[0] != '\0') {
mi.hasCommand = true;
mi.id = CommandId::InsertText;
mi.arg = std::string(e.text.text);
mi.count = 0;
produced = true;
// Ensure InsertText never carries a newline; those must originate from KEYDOWN
std::string text(e.text.text);
// Strip any CR/LF that might slip through from certain platforms/IME behaviors
text.erase(std::remove(text.begin(), text.end(), '\n'), text.end());
text.erase(std::remove(text.begin(), text.end(), '\r'), text.end());
if (!text.empty()) {
mi.hasCommand = true;
mi.id = CommandId::InsertText;
mi.arg = std::move(text);
mi.count = 0;
produced = true;
} else {
// Nothing to insert after filtering; consume the event
produced = true;
}
} else {
produced = true; // consumed while k-prefix is active
}
@@ -536,16 +572,23 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
if (produced && mi.hasCommand) {
// Attach universal-argument count if present, then clear the state
if (uarg_active_ && mi.id != CommandId::UArgStatus) {
if (uarg_active_ &&mi
.
id != CommandId::UArgStatus
)
{
int count = 0;
if (!uarg_had_digits_ && !uarg_negative_) {
// No explicit digits: use current value (default 4 or 4^n)
count = (uarg_value_ > 0) ? uarg_value_ : 4;
} else {
count = uarg_value_;
if (uarg_negative_)
count = -count;
}
mi.count = count;
mi.count = count;
// Clear universal-argument state after applying it
uarg_active_ = false;
uarg_collecting_ = false;
uarg_negative_ = false;
@@ -569,4 +612,4 @@ GUIInputHandler::Poll(MappedInput &out)
out = q_.front();
q_.pop();
return true;
}
}

View File

@@ -10,6 +10,8 @@
#include <regex>
#include "GUIRenderer.h"
#include "Highlight.h"
#include "GUITheme.h"
#include "Buffer.h"
#include "Command.h"
#include "Editor.h"
@@ -44,6 +46,8 @@ GUIRenderer::Draw(Editor &ed)
ImGui::SetNextWindowSize(main_sz);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoScrollWithMouse
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoCollapse
@@ -62,55 +66,66 @@ GUIRenderer::Draw(Editor &ed)
if (!buf) {
ImGui::TextUnformatted("[no buffer]");
} else {
const auto &lines = buf->Rows();
// Reserve space for status bar at bottom
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
// Detect click-to-move inside this scroll region
ImVec2 list_origin = ImGui::GetCursorScreenPos();
float scroll_y = ImGui::GetScrollY();
float scroll_x = ImGui::GetScrollX();
std::size_t rowoffs = 0; // we render from the first line; scrolling is handled by ImGui
const auto &lines = buf->Rows();
std::size_t cy = buf->Cury();
std::size_t cx = buf->Curx();
const float line_h = ImGui::GetTextLineHeight();
const float row_h = ImGui::GetTextLineHeightWithSpacing();
const float space_w = ImGui::CalcTextSize(" ").x;
// Two-way sync between Buffer::Rowoffs and ImGui scroll position:
// - If command layer changed Buffer::Rowoffs since last frame, drive ImGui scroll from it.
// - Otherwise, propagate ImGui scroll to Buffer::Rowoffs so command layer has an up-to-date view.
// This prevents clicks/wheel from being immediately overridden by stale offsets.
static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
static long prev_buf_coloffs = -1; // previous frame's Buffer::Coloffs
const long buf_rowoffs = static_cast<long>(buf->Rowoffs());
const long buf_coloffs = static_cast<long>(buf->Coloffs());
// Detect programmatic change (e.g., page_down command changed rowoffs)
// Use SetNextWindowScroll BEFORE BeginChild to set initial scroll position
if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
float target_y = static_cast<float>(buf_rowoffs) * row_h;
ImGui::SetNextWindowScroll(ImVec2(-1.0f, target_y));
}
if (prev_buf_coloffs >= 0 && buf_coloffs != prev_buf_coloffs) {
float target_x = static_cast<float>(buf_coloffs) * space_w;
float target_y = static_cast<float>(buf_rowoffs) * row_h;
ImGui::SetNextWindowScroll(ImVec2(target_x, target_y));
}
// Reserve space for status bar at bottom
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
// Get child window position and scroll for click handling
ImVec2 child_window_pos = ImGui::GetWindowPos();
float scroll_y = ImGui::GetScrollY();
float scroll_x = ImGui::GetScrollX();
std::size_t rowoffs = 0; // we render from the first line; scrolling is handled by ImGui
// Synchronize buffer offsets from ImGui scroll if user scrolled manually
bool forced_scroll = false;
{
static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
static long prev_buf_coloffs = -1; // previous frame's Buffer::Coloffs
static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
static float prev_scroll_x = -1.0f; // previous frame's ImGui scroll X in pixels
static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
static float prev_scroll_x = -1.0f; // previous frame's ImGui scroll X in pixels
const long buf_rowoffs = static_cast<long>(buf->Rowoffs());
const long buf_coloffs = static_cast<long>(buf->Coloffs());
const long scroll_top = static_cast<long>(scroll_y / row_h);
const long scroll_left = static_cast<long>(scroll_x / space_w);
// Detect programmatic change (e.g., keyboard navigation ensured visibility)
// Check if rowoffs was programmatically changed this frame
if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
ImGui::SetScrollY(static_cast<float>(buf_rowoffs) * row_h);
scroll_y = ImGui::GetScrollY();
forced_scroll = true;
}
if (prev_buf_coloffs >= 0 && buf_coloffs != prev_buf_coloffs) {
ImGui::SetScrollX(static_cast<float>(buf_coloffs) * space_w);
scroll_x = ImGui::GetScrollX();
forced_scroll = true;
}
// If user scrolled, update buffer offsets accordingly
if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y) {
// If user scrolled (not programmatic), update buffer offsets accordingly
if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y && !forced_scroll) {
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
mbuf->SetOffsets(static_cast<std::size_t>(std::max(0L, scroll_top)),
mbuf->Coloffs());
}
}
if (prev_scroll_x >= 0.0f && scroll_x != prev_scroll_x) {
if (prev_scroll_x >= 0.0f && scroll_x != prev_scroll_x && !forced_scroll) {
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
mbuf->SetOffsets(mbuf->Rowoffs(),
static_cast<std::size_t>(std::max(0L, scroll_left)));
@@ -118,11 +133,12 @@ GUIRenderer::Draw(Editor &ed)
}
// Update trackers for next frame
prev_buf_rowoffs = static_cast<long>(buf->Rowoffs());
prev_buf_coloffs = static_cast<long>(buf->Coloffs());
prev_scroll_y = ImGui::GetScrollY();
prev_scroll_x = ImGui::GetScrollX();
prev_scroll_y = scroll_y;
prev_scroll_x = scroll_x;
}
prev_buf_rowoffs = buf_rowoffs;
prev_buf_coloffs = buf_coloffs;
// Synchronize cursor and scrolling.
// Ensure the cursor is visible even on the first frame or when it didn't move,
// unless we already forced scrolling from Buffer::Rowoffs this frame.
@@ -150,184 +166,261 @@ GUIRenderer::Draw(Editor &ed)
first_row = static_cast<long>(scroll_y / row_h);
last_row = first_row + vis_rows - 1;
}
// Horizontal scroll: ensure cursor column is visible
float child_w = ImGui::GetWindowWidth();
long vis_cols = static_cast<long>(child_w / space_w);
if (vis_cols < 1)
vis_cols = 1;
long first_col = static_cast<long>(scroll_x / space_w);
long last_col = first_col + vis_cols - 1;
// Compute cursor's rendered X position (accounting for tabs)
std::size_t cursor_rx = 0;
if (cy < lines.size()) {
std::string cur_line = static_cast<std::string>(lines[cy]);
const std::size_t tabw = 8;
for (std::size_t i = 0; i < cx && i < cur_line.size(); ++i) {
if (cur_line[i] == '\t') {
cursor_rx += tabw - (cursor_rx % tabw);
} else {
cursor_rx += 1;
}
}
}
long cxr = static_cast<long>(cursor_rx);
if (cxr < first_col || cxr > last_col) {
float target_x = static_cast<float>(cxr) * space_w;
// Center horizontally if possible
target_x -= (child_w / 2.0f);
if (target_x < 0.f)
target_x = 0.f;
float max_x = ImGui::GetScrollMaxX();
if (max_x >= 0.f && target_x > max_x)
target_x = max_x;
ImGui::SetScrollX(target_x);
scroll_x = ImGui::GetScrollX();
}
}
// Phase 3: prefetch visible viewport highlights and warm around in background
if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
int fr = static_cast<int>(std::max(0L, first_row));
int rc = static_cast<int>(std::max(1L, vis_rows));
buf->Highlighter()->PrefetchViewport(*buf, fr, rc, buf->Version());
}
}
// 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();
ImVec2 cr_max = ImGui::GetWindowContentRegionMax();
float child_h = (cr_max.y - cr_min.y);
long vis_rows = static_cast<long>(child_h / row_h);
if (vis_rows < 1)
vis_rows = 1;
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;
}
// 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;
// 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;
}
}
// 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));
}
}
// Cache current horizontal offset in rendered columns
// Cache current horizontal offset in rendered columns for click handling
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();
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
// 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;
}
}
// Handle mouse click before rendering to avoid dependent on drawn items
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
ImVec2 mp = ImGui::GetIO().MousePos;
// Compute content-relative position accounting for scroll
// mp.y - child_window_pos.y gives us pixels from top of child window
// Adding scroll_y gives us pixels from top of content (buffer row 0)
float content_y = (mp.y - child_window_pos.y) + scroll_y;
long by_l = static_cast<long>(content_y / row_h);
if (by_l < 0)
by_l = 0;
ImGui::TextUnformatted(expanded.c_str());
// Convert to buffer row
std::size_t by = static_cast<std::size_t>(by_l);
if (by >= lines.size()) {
if (!lines.empty())
by = lines.size() - 1;
else
by = 0;
}
// Compute click X position relative to left edge of child window (in pixels)
// This gives us the visual offset from the start of displayed content
float visual_x = mp.x - child_window_pos.x;
if (visual_x < 0.0f)
visual_x = 0.0f;
// Convert visual pixel offset to rendered column, then add coloffs_now
// to get the absolute rendered column in the buffer
std::size_t clicked_rx = static_cast<std::size_t>(visual_x / space_w) + coloffs_now;
// 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 rendered column (clicked_rx) to source column accounting for tabs
std::string line_clicked = static_cast<std::string>(lines[by]);
const std::size_t tabw = 8;
// Iterate through source columns, computing rendered position, to find closest match
std::size_t rx = 0; // rendered column position
std::size_t best_col = 0;
float best_dist = std::numeric_limits<float>::infinity();
float clicked_rx_f = static_cast<float>(clicked_rx);
for (std::size_t i = 0; i <= line_clicked.size(); ++i) {
// Check current position
float dist = std::fabs(clicked_rx_f - static_cast<float>(rx));
if (dist < best_dist) {
best_dist = dist;
best_col = i;
}
// Advance to next position if not at end
if (i < line_clicked.size()) {
if (line_clicked[i] == '\t') {
rx += (tabw - (rx % tabw));
} else {
rx += 1;
}
}
}
// 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));
}
}
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
// 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 to an expanded buffer (tabs -> spaces)
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));
expanded.append(adv, ' ');
rx_abs_draw += adv;
} else {
expanded.push_back(c);
rx_abs_draw += 1;
}
}
// Draw syntax-colored runs (text above background highlights)
if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
const kte::LineHighlight &lh = buf->Highlighter()->GetLine(
*buf, static_cast<int>(i), buf->Version());
// Helper to convert a src column to expanded rx position
auto src_to_rx_full = [&](std::size_t sidx) -> std::size_t {
std::size_t rx = 0;
for (std::size_t k = 0; k < sidx && k < line.size(); ++k) {
rx += (line[k] == '\t') ? (tabw - (rx % tabw)) : 1;
}
return rx;
};
for (const auto &sp: lh.spans) {
std::size_t rx_s = src_to_rx_full(
static_cast<std::size_t>(std::max(0, sp.col_start)));
std::size_t rx_e = src_to_rx_full(
static_cast<std::size_t>(std::max(sp.col_start, sp.col_end)));
if (rx_e <= coloffs_now)
continue;
// Clamp rx_s/rx_e to the visible portion
std::size_t draw_start = (rx_s > coloffs_now) ? rx_s : coloffs_now;
std::size_t draw_end = rx_e;
if (draw_start >= expanded.size())
continue;
draw_end = std::min<std::size_t>(draw_end, expanded.size());
if (draw_end <= draw_start)
continue;
// Screen position is relative to coloffs_now
std::size_t screen_x = draw_start - coloffs_now;
ImU32 col = ImGui::GetColorU32(kte::SyntaxInk(sp.kind));
ImVec2 p = ImVec2(line_pos.x + static_cast<float>(screen_x) * space_w,
line_pos.y);
ImGui::GetWindowDrawList()->AddText(
p, col, expanded.c_str() + draw_start, expanded.c_str() + draw_end);
}
// We drew text via draw list (no layout advance). Manually advance the cursor to the next line.
// Use row_h (with spacing) to match click calculation and ensure consistent line positions.
ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
} else {
// No syntax: draw as one run, accounting for horizontal scroll offset
if (coloffs_now < expanded.size()) {
ImVec2 p = ImVec2(line_pos.x, line_pos.y);
ImGui::GetWindowDrawList()->AddText(
p, ImGui::GetColorU32(ImGuiCol_Text),
expanded.c_str() + coloffs_now);
ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
} else {
// Line is fully scrolled out of view horizontally
ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
}
}
// Draw a visible cursor indicator on the current line
if (i == cy) {
@@ -349,207 +442,220 @@ GUIRenderer::Draw(Editor &ed)
}
ImGui::EndChild();
// Status bar spanning full width
ImGui::Separator();
// Status bar spanning full width
ImGui::Separator();
// 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);
} catch (...) {
fname = buf->Filename();
try {
fname = std::filesystem::path(fname).filename().string();
} 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 += " *";
// Append total line count as "<n>L"
{
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
left += " ";
left += std::to_string(lcount);
left += "L";
}
// Build right text (cursor/mark)
int row1 = static_cast<int>(buf->Cury()) + 1;
int col1 = static_cast<int>(buf->Curx()) + 1;
bool have_mark = buf->MarkSet();
int mrow1 = have_mark ? static_cast<int>(buf->MarkCury()) + 1 : 0;
int mcol1 = have_mark ? static_cast<int>(buf->MarkCurx()) + 1 : 0;
char rbuf[128];
if (have_mark)
std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: %d,%d", row1, col1, mrow1, mcol1);
else
std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: not set", row1, col1);
std::string right = rbuf;
// Middle message: if a prompt is active, show "Label: text"; otherwise show status
std::string msg;
// 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()) {
msg = ed.PromptLabel();
if (!msg.empty())
msg += ": ";
msg += ed.PromptText();
} else {
msg = ed.Status();
}
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;
}
}
}
// Measurements
ImVec2 left_sz = ImGui::CalcTextSize(left.c_str());
ImVec2 right_sz = ImGui::CalcTextSize(right.c_str());
float pad = 6.f;
float left_x = p0.x + pad;
float right_x = p1.x - pad - right_sz.x;
if (right_x < left_x + left_sz.x + pad) {
// Not enough room; clip left to fit
float max_left = std::max(0.0f, right_x - left_x - pad);
if (max_left < left_sz.x && max_left > 10.0f) {
// Render a clipped left using a child region
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 (kind == Editor::PromptKind::Command) {
prefix = ": ";
} else 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);
} catch (...) {
fname = buf->Filename();
try {
fname = std::filesystem::path(fname).filename().string();
} 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 += " *";
// Append total line count as "<n>L"
{
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
left += " ";
left += std::to_string(lcount);
left += "L";
}
// Build right text (cursor/mark)
int row1 = static_cast<int>(buf->Cury()) + 1;
int col1 = static_cast<int>(buf->Curx()) + 1;
bool have_mark = buf->MarkSet();
int mrow1 = have_mark ? static_cast<int>(buf->MarkCury()) + 1 : 0;
int mcol1 = have_mark ? static_cast<int>(buf->MarkCurx()) + 1 : 0;
char rbuf[128];
if (have_mark)
std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: %d,%d", row1, col1, mrow1, mcol1);
else
std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: not set", row1, col1);
std::string right = rbuf;
// Middle message: if a prompt is active, show "Label: text"; otherwise show status
std::string msg;
if (ed.PromptActive()) {
msg = ed.PromptLabel();
if (!msg.empty())
msg += ": ";
msg += ed.PromptText();
} else {
msg = ed.Status();
}
// Measurements
ImVec2 left_sz = ImGui::CalcTextSize(left.c_str());
ImVec2 right_sz = ImGui::CalcTextSize(right.c_str());
float pad = 6.f;
float left_x = p0.x + pad;
float right_x = p1.x - pad - right_sz.x;
if (right_x < left_x + left_sz.x + pad) {
// Not enough room; clip left to fit
float max_left = std::max(0.0f, right_x - left_x - pad);
if (max_left < left_sz.x && max_left > 10.0f) {
// Render a clipped left using a child region
ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - left_sz.y) * 0.5f));
ImGui::PushClipRect(ImVec2(left_x, p0.y), ImVec2(right_x - pad, p1.y), true);
ImGui::TextUnformatted(left.c_str());
ImGui::PopClipRect();
}
} else {
// Draw left normally
ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - left_sz.y) * 0.5f));
ImGui::PushClipRect(ImVec2(left_x, p0.y), ImVec2(right_x - pad, p1.y), true);
ImGui::TextUnformatted(left.c_str());
ImGui::PopClipRect();
}
} else {
// Draw left normally
ImGui::SetCursorScreenPos(ImVec2(left_x, p0.y + (bar_h - left_sz.y) * 0.5f));
ImGui::TextUnformatted(left.c_str());
}
// Draw right
ImGui::SetCursorScreenPos(ImVec2(std::max(right_x, left_x), p0.y + (bar_h - right_sz.y) * 0.5f));
ImGui::TextUnformatted(right.c_str());
// Draw right
ImGui::SetCursorScreenPos(ImVec2(std::max(right_x, left_x),
p0.y + (bar_h - right_sz.y) * 0.5f));
ImGui::TextUnformatted(right.c_str());
// Draw middle message centered in remaining space
if (!msg.empty()) {
float mid_left = left_x + left_sz.x + pad;
float mid_right = std::max(right_x - pad, mid_left);
float mid_w = std::max(0.0f, mid_right - mid_left);
if (mid_w > 1.0f) {
ImVec2 msg_sz = ImGui::CalcTextSize(msg.c_str());
float msg_x = mid_left + std::max(0.0f, (mid_w - msg_sz.x) * 0.5f);
// Clip to middle region
ImGui::PushClipRect(ImVec2(mid_left, p0.y), ImVec2(mid_right, p1.y), true);
ImGui::SetCursorScreenPos(ImVec2(msg_x, p0.y + (bar_h - msg_sz.y) * 0.5f));
ImGui::TextUnformatted(msg.c_str());
ImGui::PopClipRect();
// Draw middle message centered in remaining space
if (!msg.empty()) {
float mid_left = left_x + left_sz.x + pad;
float mid_right = std::max(right_x - pad, mid_left);
float mid_w = std::max(0.0f, mid_right - mid_left);
if (mid_w > 1.0f) {
ImVec2 msg_sz = ImGui::CalcTextSize(msg.c_str());
float msg_x = mid_left + std::max(0.0f, (mid_w - msg_sz.x) * 0.5f);
// Clip to middle region
ImGui::PushClipRect(ImVec2(mid_left, p0.y), ImVec2(mid_right, p1.y), true);
ImGui::SetCursorScreenPos(ImVec2(msg_x, p0.y + (bar_h - msg_sz.y) * 0.5f));
ImGui::TextUnformatted(msg.c_str());
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();
@@ -703,4 +809,4 @@ GUIRenderer::Draw(Editor &ed)
ed.SetFilePickerVisible(false);
}
}
}
}

647
GUITheme.h Normal file
View File

@@ -0,0 +1,647 @@
// GUITheme.h — ImGui theming helpers and background mode
#pragma once
#include <imgui.h>
#include <vector>
#include <memory>
#include <string>
#include <cstddef>
#include <algorithm>
#include <cctype>
#include "themes/ThemeHelpers.h"
namespace kte {
// Background mode selection for light/dark palettes
enum class BackgroundMode { Light, Dark };
// Global background mode; default to Dark to match prior defaults
static inline auto gBackgroundMode = BackgroundMode::Dark;
// Basic theme identifier (kept minimal; some ids are aliases)
enum class ThemeId {
EInk = 0,
GruvboxDarkMedium = 1,
GruvboxLightMedium = 1, // alias to unified gruvbox index
Nord = 2,
Plan9 = 3,
Solarized = 4,
Everforest = 5,
KanagawaPaper = 6,
LCARS = 7,
OldBook = 8,
Zenburn = 9,
Amber = 10,
WeylandYutani = 11,
Orbital = 12,
};
// Current theme tracking
static inline auto gCurrentTheme = ThemeId::Nord;
static inline std::size_t gCurrentThemeIndex = 0;
// Forward declarations for helpers used below
static size_t ThemeIndexFromId(ThemeId id);
static ThemeId ThemeIdFromIndex(size_t idx);
// Helpers to set/query background mode
static void
SetBackgroundMode(const BackgroundMode m)
{
gBackgroundMode = m;
}
static BackgroundMode
GetBackgroundMode()
{
return gBackgroundMode;
}
static inline const char *
BackgroundModeName()
{
return gBackgroundMode == BackgroundMode::Light ? "light" : "dark";
}
// Include individual theme implementations split under ./themes
#include "themes/Nord.h"
#include "themes/Plan9.h"
#include "themes/Solarized.h"
#include "themes/Gruvbox.h"
#include "themes/EInk.h"
#include "themes/Everforest.h"
#include "themes/KanagawaPaper.h"
#include "themes/LCARS.h"
#include "themes/OldBook.h"
#include "themes/Amber.h"
#include "themes/WeylandYutani.h"
#include "themes/Zenburn.h"
#include "themes/Orbital.h"
// Theme abstraction and registry (generalized theme system)
class Theme {
public:
virtual ~Theme() = default;
[[nodiscard]] virtual const char *Name() const = 0; // canonical name (e.g., "nord", "gruvbox-dark")
virtual void Apply() const = 0; // apply to current ImGui style
virtual ThemeId Id() = 0; // theme identifier
};
namespace detail {
struct LCARSTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "lcars";
}
void Apply() const override
{
ApplyLcarsTheme();
}
ThemeId Id() override
{
return ThemeId::LCARS;
}
};
struct EverforestTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "everforest";
}
void Apply() const override
{
ApplyEverforestTheme();
}
ThemeId Id() override
{
return ThemeId::Everforest;
}
};
struct KanagawaPaperTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "kanagawa-paper";
}
void Apply() const override
{
ApplyKanagawaPaperTheme();
}
ThemeId Id() override
{
return ThemeId::KanagawaPaper;
}
};
struct OldBookTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "old-book";
}
void Apply() const override
{
if (gBackgroundMode == BackgroundMode::Dark)
ApplyOldBookDarkTheme();
else
ApplyOldBookLightTheme();
}
ThemeId Id() override
{
return ThemeId::OldBook;
}
};
struct OrbitalTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "orbital";
}
void Apply() const override
{
ApplyOrbitalTheme();
}
ThemeId Id() override
{
return ThemeId::Orbital;
}
};
struct ZenburnTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "zenburn";
}
void Apply() const override
{
ApplyZenburnTheme();
}
ThemeId Id() override
{
return ThemeId::Zenburn;
}
};
struct NordTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "nord";
}
void Apply() const override
{
ApplyNordImGuiTheme();
}
ThemeId Id() override
{
return ThemeId::Nord;
}
};
struct AmberTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "amber";
}
void Apply() const override
{
ApplyAmberTheme();
}
ThemeId Id() override
{
return ThemeId::Amber;
}
};
struct WeylandYutaniTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "weyland-yutani";
}
void Apply() const override
{
ApplyWeylandYutaniTheme();
}
ThemeId Id() override
{
return ThemeId::WeylandYutani;
}
};
struct GruvboxTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "gruvbox";
}
void Apply() const override
{
if (gBackgroundMode == BackgroundMode::Light)
ApplyGruvboxLightMediumTheme();
else
ApplyGruvboxDarkMediumTheme();
}
ThemeId Id() override
{
// Legacy maps to dark; unified under base id GruvboxDarkMedium
return ThemeId::GruvboxDarkMedium;
}
};
struct EInkTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "eink";
}
void Apply() const override
{
if (gBackgroundMode == BackgroundMode::Dark)
ApplyEInkDarkImGuiTheme();
else
ApplyEInkImGuiTheme();
}
ThemeId Id() override
{
return ThemeId::EInk;
}
};
struct SolarizedTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "solarized";
}
void Apply() const override
{
if (gBackgroundMode == BackgroundMode::Light)
ApplySolarizedLightTheme();
else
ApplySolarizedDarkTheme();
}
ThemeId Id() override
{
return ThemeId::Solarized;
}
};
struct Plan9Theme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "plan9";
}
void Apply() const override
{
ApplyPlan9Theme();
}
ThemeId Id() override
{
return ThemeId::Plan9;
}
};
} // namespace detail
static const std::vector<std::unique_ptr<Theme> > &
ThemeRegistry()
{
static std::vector<std::unique_ptr<Theme> > reg;
if (reg.empty()) {
// Alphabetical by canonical name:
// amber, eink, everforest, gruvbox, kanagawa-paper, lcars, nord, old-book, orbital, plan9, solarized, weyland-yutani, zenburn
reg.emplace_back(std::make_unique<detail::AmberTheme>());
reg.emplace_back(std::make_unique<detail::EInkTheme>());
reg.emplace_back(std::make_unique<detail::EverforestTheme>());
reg.emplace_back(std::make_unique<detail::GruvboxTheme>());
reg.emplace_back(std::make_unique<detail::KanagawaPaperTheme>());
reg.emplace_back(std::make_unique<detail::LCARSTheme>());
reg.emplace_back(std::make_unique<detail::NordTheme>());
reg.emplace_back(std::make_unique<detail::OldBookTheme>());
reg.emplace_back(std::make_unique<detail::OrbitalTheme>());
reg.emplace_back(std::make_unique<detail::Plan9Theme>());
reg.emplace_back(std::make_unique<detail::SolarizedTheme>());
reg.emplace_back(std::make_unique<detail::WeylandYutaniTheme>());
reg.emplace_back(std::make_unique<detail::ZenburnTheme>());
}
return reg;
}
// Canonical theme name for a given ThemeId (via registry order)
[[maybe_unused]] static const char *
ThemeName(const ThemeId id)
{
const auto &reg = ThemeRegistry();
const size_t idx = ThemeIndexFromId(id);
if (idx < reg.size())
return reg[idx]->Name();
return "unknown";
}
// Helper to apply a theme by id and update current theme
static void
ApplyTheme(const ThemeId id)
{
const auto &reg = ThemeRegistry();
const size_t idx = ThemeIndexFromId(id);
if (idx < reg.size()) {
reg[idx]->Apply();
gCurrentTheme = id;
gCurrentThemeIndex = idx;
}
}
[[maybe_unused]] static ThemeId
CurrentTheme()
{
return gCurrentTheme;
}
// Cycle helpers
[[maybe_unused]] static ThemeId
NextTheme()
{
const auto &reg = ThemeRegistry();
if (reg.empty()) {
return gCurrentTheme;
}
const size_t nxt = (gCurrentThemeIndex + 1) % reg.size();
ApplyTheme(ThemeIdFromIndex(nxt));
return gCurrentTheme;
}
[[maybe_unused]] static ThemeId
PrevTheme()
{
const auto &reg = ThemeRegistry();
if (reg.empty()) {
return gCurrentTheme;
}
const size_t prv = (gCurrentThemeIndex + reg.size() - 1) % reg.size();
ApplyTheme(ThemeIdFromIndex(prv));
return gCurrentTheme;
}
// Name-based API
[[maybe_unused]] static const Theme *
GetThemeByName(const std::string &name)
{
const auto &reg = ThemeRegistry();
for (const auto &t: reg) {
if (name == t->Name())
return t.get();
}
return nullptr;
}
[[maybe_unused]] static bool
ApplyThemeByName(const std::string &name)
{
// Handle aliases and background-specific names
std::string n = name;
// lowercase copy
std::transform(n.begin(), n.end(), n.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
if (n == "gruvbox-dark") {
SetBackgroundMode(BackgroundMode::Dark);
n = "gruvbox";
} else if (n == "gruvbox-light") {
SetBackgroundMode(BackgroundMode::Light);
n = "gruvbox";
} else if (n == "solarized-dark") {
SetBackgroundMode(BackgroundMode::Dark);
n = "solarized";
} else if (n == "solarized-light") {
SetBackgroundMode(BackgroundMode::Light);
n = "solarized";
} else if (n == "eink-dark") {
SetBackgroundMode(BackgroundMode::Dark);
n = "eink";
} else if (n == "eink-light") {
SetBackgroundMode(BackgroundMode::Light);
n = "eink";
} else if (n == "everforest-hard") {
// Request asks for everforest hard; map to canonical name
n = "everforest";
} else if (n == "oldbook") {
// alias to old-book
n = "old-book";
} else if (n == "old-book-dark" || n == "oldbook-dark") {
SetBackgroundMode(BackgroundMode::Dark);
n = "old-book";
} else if (n == "old-book-light" || n == "oldbook-light") {
SetBackgroundMode(BackgroundMode::Light);
n = "old-book";
} else if (n == "kanagawa" || n == "kanagawa-paper-light" || n == "kanagawa-light"
|| n == "kanagawa-dark" || n == "kanagawa-paper-dark") {
// map to canonical kanagawa-paper; background controls light/dark
n = "kanagawa-paper";
} else if (n == "vim-amber") {
n = "amber";
} else if (n == "weyland") {
n = "weyland-yutani";
}
const auto &reg = ThemeRegistry();
for (size_t i = 0; i < reg.size(); ++i) {
if (n == reg[i]->Name()) {
reg[i]->Apply();
gCurrentThemeIndex = i;
gCurrentTheme = ThemeIdFromIndex(i);
return true;
}
}
return false;
}
[[maybe_unused]] static const char *
CurrentThemeName()
{
const auto &reg = ThemeRegistry();
if (gCurrentThemeIndex < reg.size()) {
return reg[gCurrentThemeIndex]->Name();
}
return "unknown";
}
// Helpers to map between legacy ThemeId and registry index
static size_t
ThemeIndexFromId(const ThemeId id)
{
switch (id) {
case ThemeId::Amber:
return 0;
case ThemeId::EInk:
return 1;
case ThemeId::Everforest:
return 2;
case ThemeId::GruvboxDarkMedium:
return 3;
case ThemeId::KanagawaPaper:
return 4;
case ThemeId::LCARS:
return 5;
case ThemeId::Nord:
return 6;
case ThemeId::OldBook:
return 7;
case ThemeId::Orbital:
return 8;
case ThemeId::Plan9:
return 9;
case ThemeId::Solarized:
return 10;
case ThemeId::WeylandYutani:
return 11;
case ThemeId::Zenburn:
return 12;
}
return 0;
}
static ThemeId
ThemeIdFromIndex(const size_t idx)
{
switch (idx) {
default:
case 0:
return ThemeId::Amber;
case 1:
return ThemeId::EInk;
case 2:
return ThemeId::Everforest;
case 3:
return ThemeId::GruvboxDarkMedium; // unified gruvbox
case 4:
return ThemeId::KanagawaPaper;
case 5:
return ThemeId::LCARS;
case 6:
return ThemeId::Nord;
case 7:
return ThemeId::OldBook;
case 8:
return ThemeId::Orbital;
case 9:
return ThemeId::Plan9;
case 10:
return ThemeId::Solarized;
case 11:
return ThemeId::WeylandYutani;
case 12:
return ThemeId::Zenburn;
}
}
// --- Syntax palette (v1): map TokenKind to ink color per current theme/background ---
[[maybe_unused]] static ImVec4
SyntaxInk(const TokenKind k)
{
// Basic palettes for dark/light backgrounds; tuned for Nord-ish defaults
const bool dark = (GetBackgroundMode() == BackgroundMode::Dark);
// Base text
const ImVec4 def = dark ? RGBA(0xD8DEE9) : RGBA(0x2E3440);
switch (k) {
case TokenKind::Keyword:
return dark ? RGBA(0x81A1C1) : RGBA(0x5E81AC);
case TokenKind::Type:
return dark ? RGBA(0x8FBCBB) : RGBA(0x4C566A);
case TokenKind::String:
return dark ? RGBA(0xA3BE8C) : RGBA(0x6C8E5E);
case TokenKind::Char:
return dark ? RGBA(0xA3BE8C) : RGBA(0x6C8E5E);
case TokenKind::Comment:
return dark ? RGBA(0x616E88) : RGBA(0x7A869A);
case TokenKind::Number:
return dark ? RGBA(0xEBCB8B) : RGBA(0xB58900);
case TokenKind::Preproc:
return dark ? RGBA(0xD08770) : RGBA(0xAF3A03);
case TokenKind::Constant:
return dark ? RGBA(0xB48EAD) : RGBA(0x7B4B7F);
case TokenKind::Function:
return dark ? RGBA(0x88C0D0) : RGBA(0x3465A4);
case TokenKind::Operator:
return dark ? RGBA(0xECEFF4) : RGBA(0x2E3440);
case TokenKind::Punctuation:
return dark ? RGBA(0xECEFF4) : RGBA(0x2E3440);
case TokenKind::Identifier:
return def;
case TokenKind::Whitespace:
return def;
case TokenKind::Error:
return dark ? RGBA(0xBF616A) : RGBA(0xCC0000);
case TokenKind::Default: default:
return def;
}
}
} // namespace kte

View File

@@ -15,24 +15,26 @@ HelpText::Text()
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"
" kte is Kyle's Text Editor. It keeps a small, fast core and uses a\n"
" WordStar/VDE-style command model with some emacs influences.\n"
"\n"
"Core keybindings:\n"
"K-commands (prefix C-k):\n"
" C-k ' Toggle read-only\n"
" C-k - Unindent region\n"
" C-k = Indent region\n"
" C-k - Unindent region (mark required)\n"
" C-k = Indent region (mark required)\n"
" C-k ; Command prompt (:\\ )\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 C-x Save and quit\n"
" C-k a Mark start of file, 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 f Flush kill ring\n"
" C-k g Jump to line\n"
" C-k h Show this help\n"
" C-k j Jump to mark\n"
" C-k l Reload buffer from disk\n"
" C-k n Previous buffer\n"
" C-k o Change working directory (prompt)\n"
@@ -44,12 +46,36 @@ HelpText::Text()
" C-k v Toggle visual file picker (GUI)\n"
" C-k w Show working directory\n"
" C-k x Save and quit\n"
" C-k y Yank\n"
"\n"
"ESC/Alt commands:\n"
" ESC < Go to beginning of file\n"
" ESC > Go to end of file\n"
" ESC m Toggle mark\n"
" ESC w Copy region to kill ring (Alt-w)\n"
" ESC b Previous word\n"
" ESC f Next word\n"
" ESC d Delete next word (Alt-d)\n"
" ESC BACKSPACE Delete previous word (Alt-Backspace)\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"
"\n"
"Control keys:\n"
" C-a C-e Line start / end\n"
" C-b C-f Move left / right\n"
" C-n C-p Move down / up\n"
" C-d Delete char\n"
" C-w / C-y Kill region / Yank\n"
" C-s Incremental find\n"
" C-r Regex search\n"
" C-t Regex search & replace\n"
" C-h Search & replace\n"
" C-l / C-g Refresh / Cancel\n"
" C-u [digits] Universal argument (repeat count)\n"
"\n"
"Buffers:\n +HELP+ is read-only. Press C-k ' to toggle; C-k h restores it.\n"
"\n"
"GUI appearance (command prompt):\n"
" : theme NAME Set GUI theme (amber, eink, everforest, gruvbox, kanagawa-paper, lcars, nord, old-book, plan9, solarized, weyland-yutani, zenburn)\n"
" : background MODE Set background: light | dark (affects eink, gruvbox, old-book, solarized)\n"
);
}
}

View File

@@ -8,10 +8,10 @@
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();
// 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

37
Highlight.h Normal file
View File

@@ -0,0 +1,37 @@
// Highlight.h - core syntax highlighting types for kte
#pragma once
#include <cstdint>
#include <vector>
namespace kte {
// Token kinds shared between renderers and highlighters
enum class TokenKind {
Default,
Keyword,
Type,
String,
Char,
Comment,
Number,
Preproc,
Constant,
Function,
Operator,
Punctuation,
Identifier,
Whitespace,
Error
};
struct HighlightSpan {
int col_start{0}; // inclusive, 0-based columns in buffer indices
int col_end{0}; // exclusive
TokenKind kind{TokenKind::Default};
};
struct LineHighlight {
std::vector<HighlightSpan> spans;
std::uint64_t version{0}; // buffer version used for this line
};
} // namespace kte

View File

@@ -33,10 +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;
}
if (ascii_key == '\'') {
out = CommandId::ToggleReadOnly; // C-k ' (toggle read-only)
return true;
}
switch (k_lower) {
case 'a':
@@ -108,6 +108,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
case '=':
out = CommandId::IndentRegion;
return true;
case ';':
out = CommandId::CommandPromptStart; // C-k ; : generic command prompt
return true;
default:
break;
}
@@ -121,7 +124,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;
@@ -152,12 +155,12 @@ 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 '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;

View File

@@ -2,9 +2,11 @@ ROADMAP / TODO:
- [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
- [x] Syntax highlighting
- [ ] The undo system should actually work
- [ ] LSP integration

View File

@@ -31,20 +31,22 @@ map_key_to_command(const int ch,
MappedInput &out)
{
// Handle special keys from ncurses
// These keys exit k-prefix mode if active (user pressed C-k then a special key).
switch (ch) {
case KEY_MOUSE: {
k_prefix = false;
MEVENT ev{};
if (getmouse(&ev) == OK) {
// Mouse wheel → map to MoveUp/MoveDown one line per wheel notch
// Mouse wheel → scroll viewport without moving cursor
#ifdef BUTTON4_PRESSED
if (ev.bstate & (BUTTON4_PRESSED | BUTTON4_RELEASED | BUTTON4_CLICKED)) {
out = {true, CommandId::MoveUp, "", 0};
out = {true, CommandId::ScrollUp, "", 0};
return true;
}
#endif
#ifdef BUTTON5_PRESSED
if (ev.bstate & (BUTTON5_PRESSED | BUTTON5_RELEASED | BUTTON5_CLICKED)) {
out = {true, CommandId::MoveDown, "", 0};
out = {true, CommandId::ScrollDown, "", 0};
return true;
}
#endif
@@ -62,33 +64,43 @@ map_key_to_command(const int ch,
return true;
}
case KEY_LEFT:
k_prefix = false;
out = {true, CommandId::MoveLeft, "", 0};
return true;
case KEY_RIGHT:
k_prefix = false;
out = {true, CommandId::MoveRight, "", 0};
return true;
case KEY_UP:
k_prefix = false;
out = {true, CommandId::MoveUp, "", 0};
return true;
case KEY_DOWN:
k_prefix = false;
out = {true, CommandId::MoveDown, "", 0};
return true;
case KEY_HOME:
k_prefix = false;
out = {true, CommandId::MoveHome, "", 0};
return true;
case KEY_END:
k_prefix = false;
out = {true, CommandId::MoveEnd, "", 0};
return true;
case KEY_PPAGE:
k_prefix = false;
out = {true, CommandId::PageUp, "", 0};
return true;
case KEY_NPAGE:
k_prefix = false;
out = {true, CommandId::PageDown, "", 0};
return true;
case KEY_DC:
k_prefix = false;
out = {true, CommandId::DeleteChar, "", 0};
return true;
case KEY_RESIZE:
k_prefix = false;
out = {true, CommandId::Refresh, "", 0};
return true;
default:
@@ -320,4 +332,4 @@ TerminalInputHandler::Poll(MappedInput &out)
{
out = {};
return decode_(out) && out.hasCommand;
}
}

View File

@@ -9,6 +9,7 @@
#include "TerminalRenderer.h"
#include "Buffer.h"
#include "Editor.h"
#include "Highlight.h"
// Version string expected to be provided by build system as KTE_VERSION_STR
#ifndef KTE_VERSION_STR
@@ -33,6 +34,8 @@ TerminalRenderer::Draw(Editor &ed)
const Buffer *buf = ed.CurrentBuffer();
int content_rows = rows - 1; // last line is status
if (content_rows < 1)
content_rows = 1;
int saved_cur_y = -1, saved_cur_x = -1; // logical cursor position within content area
if (buf) {
@@ -41,140 +44,230 @@ 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;
// 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();
}
// Phase 3: prefetch visible viewport highlights (current terminal area)
if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
int fr = static_cast<int>(rowoffs);
int rc = std::max(0, content_rows);
buf->Highlighter()->PrefetchViewport(*buf, fr, rc, buf->Version());
}
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;
// Syntax highlighting: fetch per-line spans
const kte::LineHighlight *lh_ptr = nullptr;
if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->
HasHighlighter()) {
lh_ptr = &buf->Highlighter()->GetLine(
*buf, static_cast<int>(li), buf->Version());
}
auto token_at = [&](std::size_t src_index) -> kte::TokenKind {
if (!lh_ptr)
return kte::TokenKind::Default;
for (const auto &sp: lh_ptr->spans) {
if (static_cast<int>(src_index) >= sp.col_start && static_cast<int>(
src_index) < sp.col_end)
return sp.kind;
}
return kte::TokenKind::Default;
};
auto apply_token_attr = [&](kte::TokenKind k) {
// Map to simple attributes; search highlight uses A_STANDOUT which takes precedence below
attrset(A_NORMAL);
switch (k) {
case kte::TokenKind::Keyword:
case kte::TokenKind::Type:
case kte::TokenKind::Constant:
case kte::TokenKind::Function:
attron(A_BOLD);
break;
case kte::TokenKind::Comment:
attron(A_DIM);
break;
case kte::TokenKind::String:
case kte::TokenKind::Char:
case kte::TokenKind::Number:
// standout a bit using A_UNDERLINE if available
attron(A_UNDERLINE);
break;
default:
break;
}
};
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;
}
// Apply syntax attribute only if not in search highlight
if (!in_hl) {
apply_token_attr(token_at(src_i));
}
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;
}
if (!in_hl && from_src) {
apply_token_attr(token_at(src_i));
}
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;
}
attrset(A_NORMAL);
clrtoeol();
}
// Place terminal cursor at logical position accounting for tabs and coloffs
std::size_t cy = buf->Cury();
@@ -191,71 +284,74 @@ TerminalRenderer::Draw(Editor &ed)
mvaddstr(0, 0, "[no buffer]");
}
// Status line (inverse)
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;
}
// 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 (kind == Editor::PromptKind::Command) {
msg = ": ";
} else 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));
// 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;
}
// 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;
@@ -346,10 +442,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();
@@ -365,7 +461,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).
@@ -374,4 +470,4 @@ TerminalRenderer::Draw(Editor &ed)
}
refresh();
}
}

View File

@@ -1,5 +1,7 @@
#include "UndoSystem.h"
#include "Buffer.h"
#include <cassert>
#include <cstdio>
UndoSystem::UndoSystem(Buffer &owner, UndoTree &tree)
@@ -9,21 +11,24 @@ UndoSystem::UndoSystem(Buffer &owner, UndoTree &tree)
void
UndoSystem::Begin(UndoType type)
{
#ifdef KTE_UNDO_DEBUG
debug_log("Begin");
#endif
// Reuse pending if batching conditions are met
const int row = static_cast<int>(buf_->Cury());
const int col = static_cast<int>(buf_->Curx());
if (tree_.pending && tree_.pending->type == type && tree_.pending->row == row) {
if (type == UndoType::Delete) {
// Support batching both forward deletes (DeleteChar) and backspace (prepend case)
// Forward delete: cursor stays at anchor col; expected == col
// Forward delete: cursor stays at anchor col; keep batching when col == anchor
const auto anchor = static_cast<std::size_t>(tree_.pending->col);
if (anchor + tree_.pending->text.size() == static_cast<std::size_t>(col)) {
if (anchor == static_cast<std::size_t>(col)) {
pending_prepend_ = false;
return; // keep batching forward delete
}
// Backspace: cursor moved left by 1; allow extend if col + text.size() == anchor
if (static_cast<std::size_t>(col) + tree_.pending->text.size() == anchor) {
// Move anchor one left to new cursor column; next Append should prepend
// Backspace: cursor moved left by exactly one position relative to current anchor.
// Extend batch by shifting anchor left and prepending the deleted byte.
if (static_cast<std::size_t>(col) + 1 == anchor) {
tree_.pending->col = col;
pending_prepend_ = true;
return;
@@ -47,6 +52,16 @@ UndoSystem::Begin(UndoType type)
node->next = nullptr;
tree_.pending = node;
pending_prepend_ = false;
#ifdef KTE_UNDO_DEBUG
debug_log("Begin:new");
#endif
// Assert pending is detached from the tree
assert(tree_.pending && "pending must exist after Begin");
assert(tree_.pending != tree_.root);
assert(tree_.pending != tree_.current);
assert(tree_.pending != tree_.saved);
assert(!is_descendant(tree_.root, tree_.pending));
}
@@ -61,6 +76,9 @@ UndoSystem::Append(char ch)
} else {
tree_.pending->text.push_back(ch);
}
#ifdef KTE_UNDO_DEBUG
debug_log("Append:ch");
#endif
}
@@ -70,12 +88,18 @@ UndoSystem::Append(std::string_view text)
if (!tree_.pending)
return;
tree_.pending->text.append(text.data(), text.size());
#ifdef KTE_UNDO_DEBUG
debug_log("Append:sv");
#endif
}
void
UndoSystem::commit()
{
#ifdef KTE_UNDO_DEBUG
debug_log("commit:enter");
#endif
if (!tree_.pending)
return;
@@ -105,6 +129,11 @@ UndoSystem::commit()
}
tree_.pending = nullptr;
update_dirty_flag();
#ifdef KTE_UNDO_DEBUG
debug_log("commit:done");
#endif
// post-conditions
assert(tree_.pending == nullptr && "pending must be cleared after commit");
}
@@ -121,6 +150,9 @@ UndoSystem::undo()
apply(node, -1);
tree_.current = parent;
update_dirty_flag();
#ifdef KTE_UNDO_DEBUG
debug_log("undo");
#endif
}
@@ -143,6 +175,9 @@ UndoSystem::redo()
apply(next, +1);
tree_.current = next;
update_dirty_flag();
#ifdef KTE_UNDO_DEBUG
debug_log("redo");
#endif
}
@@ -151,6 +186,9 @@ UndoSystem::mark_saved()
{
tree_.saved = tree_.current;
update_dirty_flag();
#ifdef KTE_UNDO_DEBUG
debug_log("mark_saved");
#endif
}
@@ -161,6 +199,9 @@ UndoSystem::discard_pending()
delete tree_.pending;
tree_.pending = nullptr;
}
#ifdef KTE_UNDO_DEBUG
debug_log("discard_pending");
#endif
}
@@ -175,6 +216,9 @@ UndoSystem::clear()
}
tree_.root = tree_.current = tree_.saved = tree_.pending = nullptr;
update_dirty_flag();
#ifdef KTE_UNDO_DEBUG
debug_log("clear");
#endif
}
@@ -293,3 +337,62 @@ UndoSystem::UpdateBufferReference(Buffer &new_buf)
{
buf_ = &new_buf;
}
// ---- Debug helpers ----
const char *
UndoSystem::type_str(UndoType t)
{
switch (t) {
case UndoType::Insert:
return "Insert";
case UndoType::Delete:
return "Delete";
case UndoType::Paste:
return "Paste";
case UndoType::Newline:
return "Newline";
case UndoType::DeleteRow:
return "DeleteRow";
}
return "?";
}
bool
UndoSystem::is_descendant(UndoNode *root, const UndoNode *target)
{
if (!root || !target)
return false;
if (root == target)
return true;
for (UndoNode *child = root->child; child != nullptr; child = child->next) {
if (is_descendant(child, target))
return true;
}
return false;
}
void
UndoSystem::debug_log(const char *op) const
{
#ifdef KTE_UNDO_DEBUG
int row = static_cast<int>(buf_->Cury());
int col = static_cast<int>(buf_->Curx());
const UndoNode *p = tree_.pending;
std::fprintf(stderr,
"[UNDO] %s cur=(%d,%d) pending=%p t=%s r=%d c=%d nlen=%zu current=%p saved=%p\n",
op,
row, col,
(const void *) p,
p ? type_str(p->type) : "-",
p ? p->row : -1,
p ? p->col : -1,
p ? p->text.size() : 0,
(void *) tree_.current,
(void *) tree_.saved);
#else
(void) op;
#endif
}

View File

@@ -2,6 +2,8 @@
#define KTE_UNDOSYSTEM_H
#include <string_view>
#include <cstddef>
#include <cstdint>
#include "UndoTree.h"
@@ -39,6 +41,13 @@ private:
void free_branch(UndoNode *node); // frees redo siblings only
UndoNode *find_parent(UndoNode *from, UndoNode *target);
// Debug helpers (compiled only when KTE_UNDO_DEBUG is defined)
void debug_log(const char *op) const;
static const char *type_str(UndoType t);
static bool is_descendant(UndoNode *root, const UndoNode *target);
void update_dirty_flag();
Buffer *buf_;

View File

@@ -23,6 +23,5 @@
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

View File

@@ -77,5 +77,4 @@ k-command mode can be exited with ESC or C-g.
The find operation is an incremental search. The up or left arrow
keys will go to the previous result, while the down or right arrow keys
will go to the next result. Unfortunately, the search starts from the
top of the file each time. This is a known bug.
will go to the next result.

View File

@@ -1,7 +1,7 @@
.\" kge(1) — Kyle's Graphical Editor (GUI-first)
.\"
.\" Project homepage: https://github.com/wntrmute/kte
.TH KGE 1 "2025-11-30" "kte 0.1.0" "User Commands"
.TH KGE 1 "2025-12-01" "kte 0.1.0" "User Commands"
.SH NAME
kge \- Kyle's Graphical Editor (GUI-first)
.SH SYNOPSIS
@@ -52,11 +52,8 @@ tree for the canonical reference and notes:
.PP
Enter K-command mode with Ctrl-K. Exit K-command mode with ESC or Ctrl-G.
.TP
.B C-k BACKSPACE
Delete from the cursor to the beginning of the line.
.TP
.B C-k SPACE
Toggle the mark.
.B C-k '
Toggle read-only for the current buffer.
.TP
.B C-k -
If the mark is set, unindent the region.
@@ -64,6 +61,9 @@ If the mark is set, unindent the region.
.B C-k =
If the mark is set, indent the region.
.TP
.B C-k ;
Open the generic command prompt (": ").
.TP
.B C-k a
Set the mark at the beginning of the file, then jump to the end of the file.
.TP
@@ -80,7 +80,7 @@ Delete from the cursor to the end of the line.
Delete the entire line.
.TP
.B C-k e
Edit a new file.
Edit (open) a new file.
.TP
.B C-k f
Flush the kill ring.
@@ -88,14 +88,20 @@ Flush the kill ring.
.B C-k g
Go to a specific line.
.TP
.B C-k h
Show the built-in help (+HELP+ buffer).
.TP
.B C-k j
Jump to the mark.
.TP
.B C-k l
Reload the current buffer from disk.
.TP
.B C-k m
Run make(1), reporting success or failure.
.B C-k n
Switch to the previous buffer.
.TP
.B C-k o
Change working directory (prompt).
.TP
.B C-k p
Switch to the next buffer.
@@ -106,14 +112,20 @@ Exit the editor. If the file has unsaved changes, a warning will be printed; a s
.B C-k C-q
Immediately exit the editor.
.TP
.B C-k r
Redo changes.
.TP
.B C-k s
Save the file, prompting for a filename if needed.
.TP
.B C-k u
Undo.
.TP
.B C-k r
Redo changes.
.B C-k v
Toggle visual file picker (GUI).
.TP
.B C-k w
Show the current working directory.
.TP
.B C-k x
Save the file and exit. Also C-k C-x.
@@ -121,23 +133,50 @@ Save the file and exit. Also C-k C-x.
.B C-k y
Yank the kill ring.
.TP
.B C-k \e
Dump core.
.B C-k C-x
Save the file and exit.
.SS Other keybindings
.TP
.B C-g
Cancel the current operation.
.TP
.B C-a
Move to the beginning of the line.
.TP
.B C-e
Move to the end of the line.
.TP
.B C-b
Move left.
.TP
.B C-f
Move right.
.TP
.B C-n
Move down.
.TP
.B C-p
Move up.
.TP
.B C-l
Refresh the display.
.TP
.B C-d
Delete the character at the cursor.
.TP
.B C-r
Regex search.
.TP
.B C-s
Incremental find.
.TP
.B C-t
Regex search and replace.
.TP
.B C-h
Search and replace.
.TP
.B C-u
Universal argument. C-u followed by numbers will repeat an operation n times.
.TP
@@ -147,6 +186,15 @@ Kill the region if the mark is set.
.B C-y
Yank the kill ring.
.TP
.B ESC <
Move to the beginning of the file.
.TP
.B ESC >
Move to the end of the file.
.TP
.B ESC m
Toggle the mark.
.TP
.B ESC BACKSPACE
Delete the previous word.
.TP
@@ -204,9 +252,6 @@ Open using the terminal frontend from kge:
(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.
.SH AUTHORS
Kyle (wntrmute) and contributors.
.SH COPYRIGHT

View File

@@ -1,7 +1,7 @@
.\" kte(1) — Kyle's Text Editor (terminal-first)
.\"
.\" Project homepage: https://github.com/wntrmute/kte
.TH KTE 1 "2025-11-30" "kte 0.1.0" "User Commands"
.TH KTE 1 "2025-12-01" "kte 0.1.0" "User Commands"
.SH NAME
kte \- Kyle's Text Editor (terminal-first)
.SH SYNOPSIS
@@ -57,11 +57,8 @@ in the source tree for the canonical reference and notes.
.PP
Enter K-command mode with Ctrl-K. Exit K-command mode with ESC or Ctrl-G.
.TP
.B C-k BACKSPACE
Delete from the cursor to the beginning of the line.
.TP
.B C-k SPACE
Toggle the mark.
.B C-k '
Toggle read-only for the current buffer.
.TP
.B C-k -
If the mark is set, unindent the region.
@@ -69,6 +66,9 @@ If the mark is set, unindent the region.
.B C-k =
If the mark is set, indent the region.
.TP
.B C-k ;
Open the generic command prompt (": ").
.TP
.B C-k a
Set the mark at the beginning of the file, then jump to the end of the file.
.TP
@@ -85,7 +85,7 @@ Delete from the cursor to the end of the line.
Delete the entire line.
.TP
.B C-k e
Edit a new file.
Edit (open) a new file.
.TP
.B C-k f
Flush the kill ring.
@@ -93,14 +93,20 @@ Flush the kill ring.
.B C-k g
Go to a specific line.
.TP
.B C-k h
Show the built-in help (+HELP+ buffer).
.TP
.B C-k j
Jump to the mark.
.TP
.B C-k l
Reload the current buffer from disk.
.TP
.B C-k m
Run make(1), reporting success or failure.
.B C-k n
Switch to the previous buffer.
.TP
.B C-k o
Change working directory (prompt).
.TP
.B C-k p
Switch to the next buffer.
@@ -111,14 +117,20 @@ Exit the editor. If the file has unsaved changes, a warning will be printed; a s
.B C-k C-q
Immediately exit the editor.
.TP
.B C-k r
Redo changes.
.TP
.B C-k s
Save the file, prompting for a filename if needed.
.TP
.B C-k u
Undo.
.TP
.B C-k r
Redo changes.
.B C-k v
Toggle visual file picker (GUI).
.TP
.B C-k w
Show the current working directory.
.TP
.B C-k x
Save the file and exit. Also C-k C-x.
@@ -126,23 +138,78 @@ Save the file and exit. Also C-k C-x.
.B C-k y
Yank the kill ring.
.TP
.B C-k \e
Dump core.
.B C-k C-x
Save the file and exit.
.SH GUI APPEARANCE
When running the GUI frontend, you can control appearance via the generic
command prompt (type "C-k ;" then enter commands):
.TP
.B : theme NAME
Set the GUI theme. Available names: "amber", "eink", "everforest", "gruvbox", "kanagawa-paper", "lcars", "nord", "old-book", "orbital", "plan9", "solarized", "weyland-yutani", "zenburn".
Compatibility aliases are also accepted: "gruvbox-dark", "gruvbox-light",
"solarized-dark", "solarized-light", "eink-dark", "eink-light",
"everforest-hard", "oldbook", "old-book-dark", "old-book-light",
"kanagawa", "kanagawa-light", "kanagawa-paper-light", "vim-amber", "weyland".
.TP
.B : background MODE
Set background mode for supported themes. MODE is either "light" or "dark".
Themes that respond to background: eink, gruvbox, kanagawa-paper, old-book, solarized. The
"lcars", "nord" and "plan9" themes do not vary with background.
.SH CONFIGURATION
The GUI reads a simple configuration file at
~/.config/kte/kge.ini. Recognized keys include:
.IP "fullscreen=on|off"
.IP "columns=NUM"
.IP "rows=NUM"
.IP "font_size=NUM"
.IP "theme=NAME"
.IP "background=light|dark"
The theme name accepts the values listed above. The background key controls
light/dark variants when the selected theme supports it.
.SS Other keybindings
.TP
.B C-g
Cancel the current operation.
.TP
.B C-a
Move to the beginning of the line.
.TP
.B C-e
Move to the end of the line.
.TP
.B C-b
Move left.
.TP
.B C-f
Move right.
.TP
.B C-n
Move down.
.TP
.B C-p
Move up.
.TP
.B C-l
Refresh the display.
.TP
.B C-d
Delete the character at the cursor.
.TP
.B C-r
Regex search.
.TP
.B C-s
Incremental find.
.TP
.B C-t
Regex search and replace.
.TP
.B C-h
Search and replace.
.TP
.B C-u
Universal argument. C-u followed by numbers will repeat an operation n times.
.TP
@@ -152,6 +219,15 @@ Kill the region if the mark is set.
.B C-y
Yank the kill ring.
.TP
.B ESC <
Move to the beginning of the file.
.TP
.B ESC >
Move to the end of the file.
.TP
.B ESC m
Toggle the mark.
.TP
.B ESC BACKSPACE
Delete the previous word.
.TP
@@ -203,9 +279,6 @@ Force GUI frontend (if available):
(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.
.SH AUTHORS
Kyle (wntrmute) and contributors.
.SH COPYRIGHT

525
docs/lsp plan.md Normal file
View File

@@ -0,0 +1,525 @@
# LSP Support Implementation Plan for kte
## Executive Summary
This plan outlines a comprehensive approach to integrating Language Server Protocol (LSP) support into kte while
respecting its core architectural principles: **frontend/backend separation**, **testability**, and **dual terminal/GUI
support**.
---
## 1. Core Architecture
### 1.1 LSP Client Module Structure
```c++
// LspClient.h - Core LSP client abstraction
class LspClient {
public:
virtual ~LspClient() = default;
// Lifecycle
virtual bool initialize(const std::string& rootPath) = 0;
virtual void shutdown() = 0;
// Document Synchronization
virtual void didOpen(const std::string& uri, const std::string& languageId,
int version, const std::string& text) = 0;
virtual void didChange(const std::string& uri, int version,
const std::vector<TextDocumentContentChangeEvent>& changes) = 0;
virtual void didClose(const std::string& uri) = 0;
virtual void didSave(const std::string& uri) = 0;
// Language Features
virtual void completion(const std::string& uri, Position pos,
CompletionCallback callback) = 0;
virtual void hover(const std::string& uri, Position pos,
HoverCallback callback) = 0;
virtual void definition(const std::string& uri, Position pos,
LocationCallback callback) = 0;
virtual void references(const std::string& uri, Position pos,
LocationsCallback callback) = 0;
virtual void diagnostics(DiagnosticsCallback callback) = 0;
// Process Management
virtual bool isRunning() const = 0;
virtual std::string getServerName() const = 0;
};
```
### 1.2 Process-based LSP Implementation
```c++
// LspProcessClient.h - Manages LSP server subprocess
class LspProcessClient : public LspClient {
private:
std::string serverCommand_;
std::vector<std::string> serverArgs_;
std::unique_ptr<Process> process_;
std::unique_ptr<JsonRpcTransport> transport_;
std::unordered_map<int, PendingRequest> pendingRequests_;
int nextRequestId_ = 1;
// Async I/O handling
std::thread readerThread_;
std::mutex mutex_;
std::condition_variable cv_;
public:
LspProcessClient(const std::string& command,
const std::vector<std::string>& args);
// ... implementation of LspClient interface
};
```
### 1.3 JSON-RPC Transport Layer
```c++
// JsonRpcTransport.h
class JsonRpcTransport {
public:
// Send a request and get the request ID
int sendRequest(const std::string& method, const nlohmann::json& params);
// Send a notification (no response expected)
void sendNotification(const std::string& method, const nlohmann::json& params);
// Read next message (blocking)
std::optional<JsonRpcMessage> readMessage();
private:
void writeMessage(const nlohmann::json& message);
std::string readContentLength();
int fdIn_; // stdin to server
int fdOut_; // stdout from server
};
```
---
## 2. Incremental Document Updates
### 2.1 Change Tracking in Buffer
The key to efficient LSP integration is tracking changes incrementally. This integrates with the existing `Buffer`
class:
```c++
// TextDocumentContentChangeEvent.h
struct TextDocumentContentChangeEvent {
std::optional<Range> range; // If nullopt, entire document changed
std::optional<int> rangeLength; // Deprecated but some servers use it
std::string text;
};
// BufferChangeTracker.h - Integrates with Buffer to track changes
class BufferChangeTracker {
public:
explicit BufferChangeTracker(Buffer* buffer);
// Called by Buffer on each edit operation
void recordInsertion(Position pos, const std::string& text);
void recordDeletion(Range range, const std::string& deletedText);
// Get accumulated changes since last sync
std::vector<TextDocumentContentChangeEvent> getChanges();
// Clear changes after sending to LSP
void clearChanges();
// Get current document version
int getVersion() const { return version_; }
private:
Buffer* buffer_;
int version_ = 0;
std::vector<TextDocumentContentChangeEvent> pendingChanges_;
// Optional: Coalesce adjacent changes
void coalesceChanges();
};
```
### 2.2 Integration with Buffer Operations
```c++
// Buffer.h additions
class Buffer {
// ... existing code ...
// LSP integration
void setChangeTracker(std::unique_ptr<BufferChangeTracker> tracker);
BufferChangeTracker* getChangeTracker() { return changeTracker_.get(); }
// These methods should call tracker when present
void insertText(Position pos, const std::string& text);
void deleteRange(Range range);
private:
std::unique_ptr<BufferChangeTracker> changeTracker_;
};
```
### 2.3 Sync Strategy Selection
```c++
// LspSyncMode.h
enum class LspSyncMode {
None, // No sync
Full, // Send full document on each change
Incremental // Send only changes (preferred)
};
// Determined during server capability negotiation
LspSyncMode negotiateSyncMode(const ServerCapabilities& caps);
```
---
## 3. Diagnostics Display System
### 3.1 Diagnostic Data Model
```c++
// Diagnostic.h
enum class DiagnosticSeverity {
Error = 1,
Warning = 2,
Information = 3,
Hint = 4
};
struct Diagnostic {
Range range;
DiagnosticSeverity severity;
std::optional<std::string> code;
std::optional<std::string> source;
std::string message;
std::vector<DiagnosticRelatedInformation> relatedInfo;
};
// DiagnosticStore.h - Central storage for diagnostics
class DiagnosticStore {
public:
void setDiagnostics(const std::string& uri,
std::vector<Diagnostic> diagnostics);
const std::vector<Diagnostic>& getDiagnostics(const std::string& uri) const;
std::vector<Diagnostic> getDiagnosticsAtLine(const std::string& uri,
int line) const;
std::optional<Diagnostic> getDiagnosticAtPosition(const std::string& uri,
Position pos) const;
void clear(const std::string& uri);
void clearAll();
// Statistics
int getErrorCount(const std::string& uri) const;
int getWarningCount(const std::string& uri) const;
private:
std::unordered_map<std::string, std::vector<Diagnostic>> diagnostics_;
};
```
### 3.2 Frontend-Agnostic Diagnostic Display Interface
Following kte's existing abstraction pattern with `Frontend`, `Renderer`, and `InputHandler`:
```c++
// DiagnosticDisplay.h - Abstract interface for showing diagnostics
class DiagnosticDisplay {
public:
virtual ~DiagnosticDisplay() = default;
// Update the diagnostic indicators for a buffer
virtual void updateDiagnostics(const std::string& uri,
const std::vector<Diagnostic>& diagnostics) = 0;
// Show inline diagnostic at cursor position
virtual void showInlineDiagnostic(const Diagnostic& diagnostic) = 0;
// Show diagnostic list/panel
virtual void showDiagnosticList(const std::vector<Diagnostic>& diagnostics) = 0;
virtual void hideDiagnosticList() = 0;
// Status bar summary
virtual void updateStatusBar(int errorCount, int warningCount) = 0;
};
```
### 3.3 Terminal Diagnostic Display
```c++
// TerminalDiagnosticDisplay.h
class TerminalDiagnosticDisplay : public DiagnosticDisplay {
public:
explicit TerminalDiagnosticDisplay(TerminalRenderer* renderer);
void updateDiagnostics(const std::string& uri,
const std::vector<Diagnostic>& diagnostics) override;
void showInlineDiagnostic(const Diagnostic& diagnostic) override;
void showDiagnosticList(const std::vector<Diagnostic>& diagnostics) override;
void hideDiagnosticList() override;
void updateStatusBar(int errorCount, int warningCount) override;
private:
TerminalRenderer* renderer_;
// Terminal-specific display strategies
void renderGutterMarkers(const std::vector<Diagnostic>& diagnostics);
void renderUnderlines(const std::vector<Diagnostic>& diagnostics);
void renderVirtualText(const Diagnostic& diagnostic);
};
```
**Terminal Display Strategies:**
1. **Gutter markers**: Show `E` (error), `W` (warning), `I` (info), `H` (hint) in left gutter
2. **Underlines**: Use terminal underline/curly underline capabilities (where supported)
3. **Virtual text**: Display diagnostic message at end of line (configurable)
4. **Status line**: `[E:3 W:5]` summary
5. **Message line**: Full diagnostic on cursor line shown in bottom bar
```
1 │ fn main() {
E 2 │ let x: i32 = "hello";
3 │ }
──────────────────────────────────────
error[E0308]: mismatched types
expected `i32`, found `&str`
[E:1 W:0] main.rs
```
### 3.4 GUI Diagnostic Display
```c++
// GUIDiagnosticDisplay.h
class GUIDiagnosticDisplay : public DiagnosticDisplay {
public:
explicit GUIDiagnosticDisplay(GUIRenderer* renderer, GUITheme* theme);
void updateDiagnostics(const std::string& uri,
const std::vector<Diagnostic>& diagnostics) override;
void showInlineDiagnostic(const Diagnostic& diagnostic) override;
void showDiagnosticList(const std::vector<Diagnostic>& diagnostics) override;
void hideDiagnosticList() override;
void updateStatusBar(int errorCount, int warningCount) override;
private:
GUIRenderer* renderer_;
GUITheme* theme_;
// GUI-specific display
void renderWavyUnderlines(const std::vector<Diagnostic>& diagnostics);
void renderTooltip(Position pos, const Diagnostic& diagnostic);
void renderDiagnosticPanel();
};
```
**GUI Display Features:**
1. **Wavy underlines**: Classic IDE-style (red for errors, yellow for warnings, etc.)
2. **Gutter icons**: Colored icons/dots in the gutter
3. **Hover tooltips**: Rich tooltips on hover showing full diagnostic
4. **Diagnostic panel**: Bottom panel with clickable diagnostic list
5. **Minimap markers**: Colored marks on the minimap (if present)
---
## 4. LspManager - Central Coordination
```c++
// LspManager.h
class LspManager {
public:
explicit LspManager(Editor* editor, DiagnosticDisplay* display);
// Server management
void registerServer(const std::string& languageId,
const LspServerConfig& config);
bool startServerForBuffer(Buffer* buffer);
void stopServer(const std::string& languageId);
void stopAllServers();
// Document sync
void onBufferOpened(Buffer* buffer);
void onBufferChanged(Buffer* buffer);
void onBufferClosed(Buffer* buffer);
void onBufferSaved(Buffer* buffer);
// Feature requests
void requestCompletion(Buffer* buffer, Position pos,
CompletionCallback callback);
void requestHover(Buffer* buffer, Position pos,
HoverCallback callback);
void requestDefinition(Buffer* buffer, Position pos,
LocationCallback callback);
// Configuration
void setDebugLogging(bool enabled);
private:
Editor* editor_;
DiagnosticDisplay* display_;
DiagnosticStore diagnosticStore_;
std::unordered_map<std::string, std::unique_ptr<LspClient>> servers_;
std::unordered_map<std::string, LspServerConfig> serverConfigs_;
void handleDiagnostics(const std::string& uri,
const std::vector<Diagnostic>& diagnostics);
std::string getLanguageId(Buffer* buffer);
std::string getUri(Buffer* buffer);
};
```
---
## 5. Configuration
```c++
// LspServerConfig.h
struct LspServerConfig {
std::string command;
std::vector<std::string> args;
std::vector<std::string> filePatterns; // e.g., {"*.rs", "*.toml"}
std::string rootPatterns; // e.g., "Cargo.toml"
LspSyncMode preferredSyncMode = LspSyncMode::Incremental;
bool autostart = true;
std::unordered_map<std::string, nlohmann::json> initializationOptions;
std::unordered_map<std::string, nlohmann::json> settings;
};
// Default configurations
std::vector<LspServerConfig> getDefaultServerConfigs() {
return {
{
.command = "rust-analyzer",
.filePatterns = {"*.rs"},
.rootPatterns = "Cargo.toml"
},
{
.command = "clangd",
.args = {"--background-index"},
.filePatterns = {"*.c", "*.cc", "*.cpp", "*.h", "*.hpp"},
.rootPatterns = "compile_commands.json"
},
{
.command = "gopls",
.filePatterns = {"*.go"},
.rootPatterns = "go.mod"
},
// ... more servers
};
}
```
---
## 6. Implementation Phases
### Phase 1: Foundation (2-3 weeks)
- [ ] JSON-RPC transport layer
- [ ] Process management for LSP servers
- [ ] Basic `LspClient` with initialize/shutdown
- [ ] `textDocument/didOpen`, `textDocument/didClose` (full sync)
### Phase 2: Incremental Sync (1-2 weeks)
- [ ] `BufferChangeTracker` integration with `Buffer`
- [ ] `textDocument/didChange` with incremental updates
- [ ] Change coalescing for rapid edits
- [ ] Version tracking
### Phase 3: Diagnostics (2-3 weeks)
- [ ] `DiagnosticStore` implementation
- [ ] `TerminalDiagnosticDisplay` with gutter markers & status line
- [ ] `GUIDiagnosticDisplay` with wavy underlines & tooltips
- [ ] `textDocument/publishDiagnostics` handling
### Phase 4: Language Features (3-4 weeks)
- [ ] Completion (`textDocument/completion`)
- [ ] Hover (`textDocument/hover`)
- [ ] Go to definition (`textDocument/definition`)
- [ ] Find references (`textDocument/references`)
- [ ] Code actions (`textDocument/codeAction`)
### Phase 5: Polish & Advanced Features (2-3 weeks)
- [ ] Multiple server support
- [ ] Server auto-detection
- [ ] Configuration file support
- [ ] Workspace symbol search
- [ ] Rename refactoring
---
## 7. Alignment with kte Core Principles
### 7.1 Frontend/Backend Separation
- LSP logic is completely separate from display
- `DiagnosticDisplay` interface allows identical behavior across Terminal/GUI
- Follows existing pattern: `Renderer`, `InputHandler`, `Frontend`
### 7.2 Testability
- `LspClient` is abstract, enabling `MockLspClient` for testing
- `DiagnosticDisplay` can be mocked for testing diagnostic flow
- Change tracking can be unit tested in isolation
### 7.3 Performance
- Incremental sync minimizes data sent to LSP servers
- Async message handling doesn't block UI
- Diagnostic rendering is batched
### 7.4 Simplicity
- Minimal dependencies (nlohmann/json for JSON handling)
- Self-contained process management
- Clear separation of concerns
---
## 8. File Organization
```
kte/
├── lsp/
│ ├── LspClient.h
│ ├── LspProcessClient.h
│ ├── LspProcessClient.cc
│ ├── LspManager.h
│ ├── LspManager.cc
│ ├── LspServerConfig.h
│ ├── JsonRpcTransport.h
│ ├── JsonRpcTransport.cc
│ ├── LspTypes.h # Position, Range, Location, etc.
│ ├── Diagnostic.h
│ ├── DiagnosticStore.h
│ ├── DiagnosticStore.cc
│ └── BufferChangeTracker.h
├── diagnostic/
│ ├── DiagnosticDisplay.h
│ ├── TerminalDiagnosticDisplay.h
│ ├── TerminalDiagnosticDisplay.cc
│ ├── GUIDiagnosticDisplay.h
│ └── GUIDiagnosticDisplay.cc
```
---
## 9. Dependencies
- **nlohmann/json**: JSON parsing/serialization (header-only)
- **POSIX/Windows process APIs**: For spawning LSP servers
- Existing kte infrastructure: `Buffer`, `Renderer`, `Frontend`, etc.
---
This plan provides a solid foundation for LSP support while maintaining kte's clean architecture. The key insight is
that LSP is fundamentally a backend feature that should be displayed through the existing frontend abstraction layer,
ensuring consistent behavior across terminal and GUI modes.

102
docs/syntax on.md Normal file
View File

@@ -0,0 +1,102 @@
### Objective
Introduce fast, minimaldependency syntax highlighting to kte, consistent with current architecture (Editor/Buffer + GUI/Terminal renderers), preserving ke UX and performance.
### Guiding principles
- Keep core small and fast; no heavy deps (C++17 only).
- Start simple (stateless line regex), evolve incrementally (stateful, caching).
- Work in both Terminal (ncurses) and GUI (ImGui) with consistent token classes and theme mapping.
- Integrate without disrupting existing search highlight, selection, or cursor rendering.
### Scope of v1
- Languages: plain text (off), C/C++ minimal set (keywords, types, strings, chars, comments, numbers, preprocessor).
- Stateless perline highlighting; handle singleline comments and strings; defer multiline state to v2.
- Toggle: `:syntax on|off` and perbuffer filetype selection.
### Architecture
1. Core types (new):
- `enum class TokenKind { Default, Keyword, Type, String, Char, Comment, Number, Preproc, Constant, Function, Operator, Punctuation, Identifier, Whitespace, Error };`
- `struct HighlightSpan { int col_start; int col_end; TokenKind kind; };` // 0based columns in buffer indices per rendered line
- `struct LineHighlight { std::vector<HighlightSpan> spans; uint64_t version; };`
2. Interfaces (new):
- `class LanguageHighlighter { public: virtual ~LanguageHighlighter() = default; virtual void HighlightLine(const Buffer& buf, int row, std::vector<HighlightSpan>& out) const = 0; virtual bool Stateful() const { return false; } };`
- `class HighlighterEngine { public: void SetHighlighter(std::unique_ptr<LanguageHighlighter>); const LineHighlight& GetLine(const Buffer&, int row, uint64_t buf_version); void InvalidateFrom(int row); };`
- `class HighlighterRegistry { public: static const LanguageHighlighter& ForFiletype(std::string_view ft); static std::string DetectForPath(std::string_view path, std::string_view first_line); };`
3. Editor/Buffer integration:
- PerBuffer settings: `bool syntax_enabled; std::string filetype; std::unique_ptr<HighlighterEngine> highlighter;`
- Buffer emits a monotonically increasing `version` on edit; renderers request line highlights by `(row, version)`.
- Invalidate cache minimally on edits (v1: current line only; v2: from current line down when stateful constructs present).
### Rendering integration
- TerminalRenderer/GUIRenderer changes:
- During line rendering, query `Editor.CurrentBuffer()->highlighter->GetLine(buf, row, buf_version)` to obtain spans.
- Apply token styles while drawing glyph runs.
- Zorder and blending:
1) Backgrounds (e.g., selection, search highlight rectangles)
2) Text with syntax colors
3) Cursor/IME decorations
- Search highlights must remain visible over syntax colors:
- Terminal: combine color/attr with reverse/bold for search; if color conflicts, prefer search.
- GUI: draw semitransparent rects behind text (already present); keep syntax color for text.
### Theme and color mapping
- Extend `GUITheme.h` with a `SyntaxPalette` mapping `TokenKind -> ImVec4 ink` (and optional background tint for comments/strings disabled by default). Provide default Light/Dark palettes.
- Terminal: map `TokenKind` to ncurses color pairs where available; degrade gracefully on 8/16color terminals (e.g., comments=dim, keywords=bold, strings=yellow/green if available).
### Language detection
- v1: by file extension; allow manual `:set filetype=<lang>`.
- v2: add shebang detection for scripts, simple modelines (optional).
### Commands/UX
- `:syntax on|off` — global default; buffer inherits on open.
- `:set filetype=<lang>` — perbuffer override.
- `:syntax reload` — rebuild patterns/themes.
- Status line shows filetype and syntax state when changed.
### Implementation plan (phased)
1. Phase 1 — Minimal regex highlighter for C/C++
- Implement `CppRegexHighlighter : LanguageHighlighter` with precompiled `std::regex` (or handrolled simple scanners to avoid regex backtracking). Classes: line comment `//…`, block comment start `/*` (no state), string `"…"`, char `'…'` (no multiline), numbers, keywords/types, preprocessor `^\s*#\w+`.
- Add `HighlighterEngine` with a simple perrow cache keyed by `(row, buf_version)`; no background worker.
- Integrate into both renderers; add palette to `GUITheme.h`; add terminal color selection.
- Add commands.
2. Phase 2 — Stateful constructs and more languages
- Add state machine for multiline comments `/*…*/` and multiline strings (C++11 raw strings), with invalidation from edit line downward until state stabilizes.
- Add simple highlighters: JSON (strings, numbers, booleans, null, punctuation), Markdown (headers/emphasis/code fences), Shell (comments, strings, keywords), Go (types, constants, keywords), Python (strings, comments, keywords), Rust (strings, comments, keywords), Lisp (comments, strings, keywords),.
- Filetype detection by extension + shebang.
3. Phase 3 — Performance and caching
- Viewportfirst highlighting: compute only visible rows each frame; background task warms cache around viewport.
- Reuse span buffers, avoid allocations; smallvector optimization if needed.
- Bench with large files; ensure O(n_visible) cost per frame.
4. Phase 4 — Extensibility
- Public registration API for external highlighters.
- Optional Treesitter adapter behind a compile flag (off by default) to keep dependencies minimal.
### Data flow (per frame)
- Renderer asks Editor for Buffer and viewport rows.
- For each row: `engine.GetLine(buf, row, buf.version)` → spans.
- Renderer emits runs with style from `SyntaxPalette[kind]`.
- Search highlights are applied as separate background rectangles (GUI) or attribute toggles (Terminal), not overriding text color.
### Testing
- Unit tests for tokenization per language: golden inputs → spans.
- Fuzz/edge cases: escaped quotes, numeric literals, preprocessor lines.
- Renderer tests with `TestRenderer` asserting the sequence of style changes for a line.
- Performance tests: highlight 1k visible lines repeatedly; assert time under threshold.
### Risks and mitigations
- Regex backtracking/perf: prefer linear scans; precompute keyword tables; avoid nested regex.
- Terminal color limitations: featuredetect colors; provide bold/dim fallbacks.
- Stateful correctness: invalidate conservatively (from edit line downward) and cap work per frame.
### Deliverables
- New files: `Highlight.h/.cc`, `HighlighterEngine.h/.cc`, `LanguageHighlighter.h`, `CppHighlighter.h/.cc`, optional `HighlighterRegistry.h/.cc`.
- Renderer updates: `GUIRenderer.cc`, `TerminalRenderer.cc` to consume spans.
- Theming: `GUITheme.h` additions for syntax colors.
- Editor/Buffer: perbuffer syntax settings and highlighter handle.
- Commands in `Command.cc` and help text updates.
- Docs: README/ROADMAP update and a brief `docs/syntax.md`.
- Tests: unit and renderer golden tests.

70
docs/syntax.md Normal file
View File

@@ -0,0 +1,70 @@
Syntax highlighting in kte
==========================
Overview
--------
kte provides lightweight syntax highlighting with a pluggable highlighter interface. The initial implementation targets C/C++ and focuses on speed and responsiveness.
Core types
----------
- `TokenKind` — token categories (keywords, types, strings, comments, numbers, preprocessor, operators, punctuation, identifiers, whitespace, etc.).
- `HighlightSpan` — a half-open column range `[col_start, col_end)` with a `TokenKind`.
- `LineHighlight` — a vector of `HighlightSpan` and the buffer `version` used to compute it.
Engine and caching
------------------
- `HighlighterEngine` maintains a per-line cache of `LineHighlight` keyed by row and buffer version.
- Cache invalidation occurs when the buffer version changes or when the buffer calls `InvalidateFrom(row)`, which clears cached lines and line states from `row` downward.
- The engine supports both stateless and stateful highlighters. For stateful highlighters, it memoizes a simple per-line state and computes lines sequentially when necessary.
Stateful highlighters
---------------------
- `LanguageHighlighter` is the base interface for stateless per-line tokenization.
- `StatefulHighlighter` extends it with a `LineState` and the method `HighlightLineStateful(buf, row, prev_state, out)`.
- The engine detects `StatefulHighlighter` via dynamic_cast and feeds each line the previous lines state, caching the resulting state per line.
C/C++ highlighter
-----------------
- `CppHighlighter` implements `StatefulHighlighter`.
- Stateless constructs: line comments `//`, strings `"..."`, chars `'...'`, numbers, identifiers (keywords/types), preprocessor at beginning of line after leading whitespace, operators/punctuation, and whitespace.
- Stateful constructs (v2):
- Multi-line block comments `/* ... */` — the state records whether the next line continues a comment.
- Raw strings `R"delim(... )delim"` — the state tracks whether we are inside a raw string and its delimiter `delim` until the closing sequence appears.
Limitations and TODOs
---------------------
- Raw string detection is intentionally simple and does not handle all corner cases of the C++ standard.
- Preprocessor handling is line-based; continuation lines with `\\` are not yet tracked.
- No semantic analysis; identifiers are classified via small keyword/type sets.
- Additional languages (JSON, Markdown, Shell, Python, Go, Rust, Lisp, …) are planned.
- Terminal color mapping is conservative to support 8/16-color terminals. Rich color-pair themes can be added later.
Renderer integration
--------------------
- Terminal and GUI renderers request line spans via `Highlighter()->GetLine(buf, row, buf.Version())`.
- Search highlight and cursor overlays take precedence over syntax colors.
Extensibility (Phase 4)
-----------------------
- Public registration API: external code can register custom highlighters by filetype.
- Use `HighlighterRegistry::Register("mylang", []{ return std::make_unique<MyHighlighter>(); });`
- Registered factories are preferred over built-ins for the same filetype key.
- Filetype keys are normalized via `HighlighterRegistry::Normalize()`.
- Optional Tree-sitter adapter: disabled by default to keep dependencies minimal.
- Enable with CMake option `-DKTE_ENABLE_TREESITTER=ON` and provide
`-DTREESITTER_INCLUDE_DIR=...` and `-DTREESITTER_LIBRARY=...` if needed.
- Register a Tree-sitter-backed highlighter for a language (example assumes you link a grammar):
```c++
extern "C" const TSLanguage* tree_sitter_c();
kte::HighlighterRegistry::RegisterTreeSitter("c", &tree_sitter_c);
```
- Current adapter is a stub scaffold; it compiles and integrates cleanly when enabled, but
intentionally emits no spans until Tree-sitter node-to-token mapping is implemented.

279
docs/undo-roadmap.md Normal file
View File

@@ -0,0 +1,279 @@
Undo System Overhaul Roadmap (emacs-style undo-tree)
Context: macOS, C++17 project, ncurses terminal and SDL2/ImGui GUI frontends. Date: 2025-12-01.
Purpose
- Define a clear, incremental plan to implement a robust, non-linear undo system inspired by emacs' undo-tree.
- Align implementation with docs/undo.md and fix gaps observed in docs/undo-state.md.
- Provide test cases and acceptance criteria so a junior engineer or agentic coding system can execute the plan safely.
References
- Specification: docs/undo.md (API, invariants, batching rules, raw buffer ops)
- Current snapshot and recent fix: docs/undo-state.md (GUI mapping notes; Begin/Append ordering fix)
- Code: UndoSystem.{h,cc}, UndoTree.{h,cc}, UndoNode.{h,cc}, Buffer.{h,cc}, Command.{h,cc}, GUI/Terminal InputHandlers,
KKeymap.
Instrumentation (KTE_UNDO_DEBUG)
- How to enable
- Build with the CMake option `-DKTE_UNDO_DEBUG=ON` to enable concise instrumentation logs from `UndoSystem`.
- The following targets receive the `KTE_UNDO_DEBUG` compile definition when ON:
- `kte` (terminal), `kge` (GUI), and `test_undo` (tests).
- Examples:
```sh
# Terminal build with tests and instrumentation ON
cmake -S . -B cmake-build-term -DBUILD_TESTS=ON -DBUILD_GUI=OFF -DKTE_UNDO_DEBUG=ON
cmake --build cmake-build-term --target test_undo -j
./cmake-build-term/test_undo 2> undo.log
# GUI build (requires SDL2/OpenGL/Freetype toolchain) with instrumentation ON
cmake -S . -B cmake-build-gui -DBUILD_GUI=ON -DKTE_UNDO_DEBUG=ON
cmake --build cmake-build-gui --target kge -j
# Run kge and perform actions; logs go to stderr
```
- What it logs
- Each Begin/Append/commit/undo/redo operation prints a single `[UNDO]` line with:
- current cursor `(row,col)`, pointer to `pending`, its type/row/col/text-size, and pointers to `current`/`saved`.
- Example fields: `[UNDO] Begin cur=(0,0) pending=0x... t=Insert r=0 c=0 nlen=2 current=0x... saved=0x...`
- Example trace snippets
- Typing a contiguous word ("Hello") batches into a single Insert node; one commit occurs before the subsequent undo:
```text
[UNDO] Begin cur=(0,0) pending=0x0 t=- r=-1 c=-1 nlen=0 current=0x0 saved=0x0
[UNDO] commit:enter cur=(0,0) pending=0x0 t=- r=-1 c=-1 nlen=0 current=0x0 saved=0x0
[UNDO] Begin:new cur=(0,0) pending=0x... t=Insert r=0 c=0 nlen=0 current=0x0 saved=0x0
[UNDO] Append:sv cur=(0,0) pending=0x... t=Insert r=0 c=0 nlen=1 current=0x0 saved=0x0
... (more Append as characters are typed) ...
[UNDO] commit:enter cur=(0,5) pending=0x... t=Insert r=0 c=0 nlen=5 current=0x0 saved=0x0
[UNDO] commit:done cur=(0,5) pending=0x0 t=- r=-1 c=-1 nlen=0 current=0x... saved=0x0
```
- Undo then Redo across that batch:
```text
[UNDO] commit:enter cur=(0,5) pending=0x0 t=- r=-1 c=-1 nlen=0 current=0x... saved=0x0
[UNDO] undo cur=(0,5) pending=0x0 t=- r=-1 c=-1 nlen=0 current=0x0 saved=0x0
[UNDO] commit:enter cur=(0,5) pending=0x0 t=- r=-1 c=-1 nlen=0 current=0x0 saved=0x0
[UNDO] redo cur=(0,5) pending=0x0 t=- r=-1 c=-1 nlen=0 current=0x... saved=0x0
```
- Newline and backspace/delete traces follow the same pattern with `t=Newline` or `t=Delete` and immediate commit for newline.
Capture by running `kge`/`kte` with `KTE_UNDO_DEBUG=ON` and performing the actions; append representative 36 line snippets to docs.
Notes
- Pointer values and exact cursor positions in the logs depend on the runtime and actions; this is expected.
- Keep `KTE_UNDO_DEBUG` OFF by default in CI/release to avoid noisy logs and any performance impact.
̄1) Current State Summary (from docs/undo-state.md)
- Terminal (kte): Keybindings and UndoSystem integration have been stable.
- GUI (kge): Previously, C-k u/U mapping and SDL TEXTINPUT suppression had issues on macOS; these were debugged. The
core root cause of “status shows Undone but no change” was fixed by moving UndoSystem::Begin/Append/commit to occur
after buffer modifications/cursor updates so batching conditions see the correct cursor.
- Undo core exists with tree invariants, saved marker/dirty flag mirroring, batching for Insert/Delete, and Newline as a
single-step undo.
Gaps/Risks
- Event-path unification between KEYDOWN and TEXTINPUT across platforms (macOS specifics).
- Comprehensive tests for branching, GC/limits, multi-line operations, and UTF-8 text input.
- Advanced/compound command grouping and future region operations.
2) Design Goals (emacs-like undo-tree)
- Per-buffer, non-linear undo tree: new edits after undo create a branch; existing redo branches are discarded.
- Batching: insert/backspace/paste/newline grouped into sensible units to match user expectations.
- Silent apply during undo/redo (no re-recording), using raw Buffer methods only.
- Correct saved/dirty tracking and robust pending node lifecycle (detached until commit).
- Efficient memory behavior; optional pruning limits similar to emacs (undo-limit, undo-strong-limit).
- Deterministic behavior across terminal and GUI frontends.
3) Invariants and API (must align with docs/undo.md)
- UndoTree holds root/current/saved/pending; pending is detached and only linked on commit.
- Begin(type) reuses pending only if: same type, same row, and pending->col + pending->text.size() == current cursor
col (or prepend rules for backspace sequences); otherwise it commits and starts a new node.
- commit(): frees redo siblings from current, attaches pending as current->child, advances current, clears pending;
nullifies saved marker if diverged.
- undo()/redo(): move current and apply the node using low-level Buffer APIs that do not trigger undo recording.
- mark_saved(): updates saved pointer and dirty flag (dirty ⇔ current != saved).
- discard_pending()/clear(): lifecycle for buffer close/reset/new file.
4) Phased Roadmap
Phase 0 — Baseline & Instrumentation (1 day)
- Audit UndoSystem against docs/undo.md invariants; ensure apply() uses only raw Buffer ops.
- Verify Begin/Append ordering across all edit commands: insert, backspace, delete, newline, paste.
- Add a temporary debug toggle (compile-time or editor flag) to log Begin/Append/commit/undo/redo, cursor(row,col), node
sizes, and pending state. Include assertions for: pending detached, commit clears pending, redo branch freed on new
commit, and correct batching preconditions.
- Deliverables: Short log from typing/undo/redo scenarios; instrumentation behind a macro so it can be removed.
Phase 1 — Input Path Unification & Batching Rules (12 days)
- Ensure all printable text insertion (terminal and GUI) flows through CommandId::InsertText and reaches UndoSystem
Begin/Append. On SDL, handle KEYDOWN vs TEXTINPUT consistently; always suppress trailing TEXTINPUT after k-prefix
suffix commands.
- Commit boundaries: at k-prefix entry, before Undo/Redo, on cursor movement, on focus/file ops, and before any
non-editing command that should separate undo units.
- Batching heuristics:
- Insert: same row, contiguous columns; Append(std::string_view) handles multi-character text (pastes, IME).
- Backspace: prepend batching in increasing column order (store deleted text in forward order).
- Delete (forward): contiguous at same row/col.
- Newline: record as UndoType::Newline and immediately commit (single-step undo for line splits/joins).
- Deliverables: Manual tests pass for typing/backspace/delete/newline/paste; GUI C-k u/U work as expected on macOS.
Phase 2 — Tree Limits & GC (1 day)
- Add configurable memory/size limits for undo data (soft and strong limits like emacs). Implement pruning of oldest
ancestors or deep redo branches while preserving recent edits. Provide stats (node count, bytes in text storage).
- Deliverables: Config hooks, tests demonstrating pruning without violating apply/undo invariants.
Phase 3 — Compound Commands & Region Ops (23 days)
- Introduce an optional RAII-style UndoTransaction to group multi-step commands (indent region, kill region, rectangle
ops) into a single undo step. Internally this just sequences Begin/Append and ensures commit even on early returns.
- Support row operations (InsertRow/DeleteRow) with proper raw Buffer calls. Ensure join_lines/split_line are handled by
Newline nodes or dedicated types if necessary.
- Deliverables: Commands updated to use transactions when appropriate; tests for region delete/indent and multi-line
paste.
Phase 4 — Developer UX & Diagnostics (1 day)
- Add a dev command to dump the undo tree (preorder) with markers for current/saved and pending (detached). For GUI,
optionally expose a simple ImGui debug window (behind a compile flag) that visualizes the current branch.
- Editor status improvements: show short status codes for undo/redo and when a new branch was created or redo discarded.
- Deliverables: Tree dump command; example output in docs.
Phase 5 — Comprehensive Tests & Property Checks (23 days)
- Unit tests (extend test_undo.cc):
- Insert batching: type "Hello" then one undo removes all; redo restores.
- Backspace batching: type "Hello", backspace 3×, undo → restores the 3; redo → re-applies deletion.
- Delete batching (forward delete) with cursor not moving.
- Newline: split a line and undo to join; join a line (via backspace at col 0) and undo to split.
- Branching: type "abc", undo twice, type "X" → redo history discarded; ensure redo no longer restores 'b'/'c'.
- Saved/dirty: mark_saved after typing; ensure dirty flag toggles correctly after undo/redo; saved marker tracks the
node.
- discard_pending: create pending by typing, then move cursor or invoke commit boundary; ensure pending is attached;
also ensure discard on buffer close clears pending.
- clear(): resets state with no leaks; tree pointers null.
- UTF-8 input: insert multi-byte characters via InsertText with multi-char std::string; ensure counts/col tracking
behave (text stored as bytes; editor col policy consistent within kte).
- Integration tests (TestFrontend):
- Both TerminalFrontend and GUIFrontend: simulate text input and commands, including k-prefix C-k u/U.
- Paste scenarios: multi-character insertions batched as one.
- Property tests (optional but recommended):
- Generate random sequences of edits; record them; then apply undo until root and redo back to the end → buffer
contents match at each step; no crashes; dirty flag transitions consistent. Seed-based to reproduce failures.
- Redo-branch discard property: any new edit after undo must eliminate redo path; redoing should be impossible
afterward.
- Deliverables: Tests merged and passing on CI for both frontends; failures block changes to undo core.
Phase 6 — Performance & Stress (0.51 day)
- Stress test with large files and long edit sequences. Target: smooth typing at 10k+ ops/minute on commodity hardware;
memory growth bounded when GC limits enabled.
- Deliverables: Basic perf notes; optional lightweight benchmarks.
5) Acceptance Criteria
- Conformance to docs/undo.md invariants and API surface (including raw Buffer operations for apply()).
- Repro checklist passes:
- Type text; single-step undo/redo works and respects batching.
- Backspace/delete batching works.
- Newline split/join are single-step undo/redo.
- Branching works: undo, then type → redo path is discarded; no ghost redo.
- Saved/dirty flags accurate across undo/redo and diverge/rejoin paths.
- No pending nodes leaked on buffer close/reload; no re-recording during undo/redo.
- Behavior identical across terminal and GUI input paths.
- Tests added for all above; CI green.
6) Concrete Work Items by File
- UndoSystem.h/cc:
- Re-verify Begin/Append ordering; enforce batching invariants; prepend logic for backspace; immediate commit for
newline.
- Implement/verify apply() uses only Buffer raw methods: insert_text/delete_text/split_line/join_lines/row ops.
- Add limits (configurable) and stats; add discard_pending safety paths.
- Buffer.h/cc:
- Ensure raw methods exist and do not trigger undo; ensure UpdateBufferReference is correctly used when
replacing/renaming the underlying buffer.
- Call undo.commit() on cursor movement and non-editing commands (via Command layer integration).
- Command.cc:
- Ensure all edit commands drive UndoSystem correctly; commit at k-prefix entry and before Undo/Redo.
- Introduce UndoTransaction for compound commands when needed.
- GUIInputHandler.cc / TerminalInputHandler.cc / KKeymap.cc:
- Ensure unified InsertText path; suppress SDL_TEXTINPUT when a k-prefix suffix produced a command; preserve case
mapping.
- Tests: test_undo.cc (extend) + new tests (e.g., test_undo_branching.cc, test_undo_multiline.cc).
7) Example Test Cases (sketches)
- Branch discard after undo:
1) InsertText("abc"); Undo(); Undo(); InsertText("X"); Redo();
Expected: Redo is a no-op (or status indicates no redo), buffer is "aX".
- Newline split/join:
1) InsertText("ab"); Newline(); InsertText("c"); Undo();
Expected: single undo joins lines → buffer "abc" on one line at original join point; Redo() splits again.
- Backspace batching:
1) InsertText("hello"); Backspace×3; Undo();
Expected: restores "hello".
- UTF-8 insertion:
1) InsertText("😀汉"); Undo(); Redo();
Expected: content unchanged across cycles; no crashes.
- Saved/dirty transitions:
1) InsertText("hi"); mark_saved(); InsertText("!"); Undo(); Redo();
Expected: dirty false after mark_saved; dirty true after InsertText("!"); dirty returns to false after Undo();
true again after Redo().
8) Risks & Mitigations
- SDL/macOS event ordering (KEYDOWN vs TEXTINPUT, IME): Mitigate by suppressing TEXTINPUT on mapped k-prefix suffixes;
optionally temporarily disable SDL text input during k-prefix suffix mapping; add targeted diagnostics.
- UTF-8 width vs byte-length: Store bytes in UndoNode::text; keep column logic consistent with existing Buffer
semantics.
- Memory growth: Add GC/limits and provide a way to clear/reduce history for huge sessions.
- Re-entrancy during apply(): Prevent public edit paths from being called; use only raw operations.
9) Nice-to-Have (post-MVP)
- Visual undo-tree navigation (emacs-like time travel and branch selection), at least as a debug tool initially.
- Persistent undo across saves (opt-in; likely out-of-scope initially).
- Time-based batching threshold (e.g., break batches after >500ms pause in typing).
10) Execution Notes for a Junior Engineer/Agentic System
- Start from Phase 0; do not skip instrumentation—assertions will catch subtle batching bugs early.
- Change one surface at a time; when adjusting Begin/Append/commit positions, re-run unit tests immediately.
- Always ensure commit boundaries before invoking commands that move the cursor/state.
- When unsure about apply(), read docs/undo.md and mirror exactly: only raw Buffer methods, never the public editing
APIs.
- Keep diffs small and localized; add tests alongside behavior changes.
Appendix A — Minimal Developer Checklist
- [ ] Begin/Append occur after buffer mutation and cursor updates for all edit commands.
- [ ] Pending detached until commit; freed/cleared on commit/discard/clear.
- [ ] Redo branches freed on new commit after undo.
- [ ] mark_saved updates both saved pointer and dirty flag; dirty mirrors current != saved.
- [ ] apply() uses only raw Buffer methods; no recording during apply.
- [ ] Terminal and GUI both route printable input to InsertText; k-prefix mapping suppresses trailing TEXTINPUT.
- [ ] Unit and integration tests cover batching, branching, newline, saved/dirty, and UTF-8 cases.

View File

@@ -1,128 +1,139 @@
Undo/Redo + C-k GUI status (macOS) — current state snapshot
### Context recap
Context
- Platform: macOS (Darwin)
- Target: GUI build (kge) using SDL2/ImGui path
- Date: 2025-11-30 00:30 local (from user)
- The undo system is now treebased with batching rules and `KTE_UNDO_DEBUG` instrumentation hooks already present in
`UndoSystem.{h,cc}`.
- GUI path uses SDL; printable input now flows exclusively via `SDL_TEXTINPUT` to `CommandId::InsertText`, while
control/meta/movement (incl. Backspace/Delete/Newline and kprefix) come from `SDL_KEYDOWN`.
- Commit boundaries must be enforced at welldefined points (movement, nonediting commands, newline, undo/redo, etc.).
What works right now
- Terminal (kte): C-k keymap and UndoSystem integration have been stable in recent builds.
- GUI: Most C-k mappings work: C-k d (KillToEOL), C-k x (Save+Quit), C-k q (Quit) — confirmed by user.
- UndoSystem core is implemented and integrated for InsertText/Newline/Delete/Backspace. Buffer owns an UndoSystem and raw edit APIs are used by apply().
### Status summary (20251201)
What is broken (GUI, macOS)
- C-k u: Status shows "Undone" but buffer content does not change (no visible undo).
- C-k U: Inserts a literal 'U' into the buffer; does not execute Redo.
- C-k C-u / C-k C-U: No effect (expected unmapped), but the k-prefix prompt can remain in some paths.
- Inputpath unification: Completed. `GUIInputHandler.cc` routes all printable characters through `SDL_TEXTINPUT → InsertText`.
Newlines originate only from `SDL_KEYDOWN → Newline`. CR/LF are filtered out of `SDL_TEXTINPUT` payloads. Suppression
rules prevent stray `TEXTINPUT` after meta/prefix/universalargument flows. Terminal input path remains consistent.
- Tests: `test_undo.cc` expanded to cover branching behavior, UTF8 insertion, multiline newline/join, and typing batching.
All scenarios pass.
- Instrumentation: `KTE_UNDO_DEBUG` hooks exist in `UndoSystem.{h,cc}`; a CMake toggle has not yet been added.
- Commit boundaries: Undo/Redo commit boundaries are in place; newline path commits immediately by design. A final audit
pass across movement/nonediting commands is still pending.
- Docs: This status document updated. Further docs (instrumentation howto and example traces) remain pending in
`docs/undo.md` / `docs/undo-roadmap.md`.
Repro steps (GUI)
1) Type "Hello".
2) Press C-k then press u → status becomes "Undone", but text remains "Hello".
3) Press C-k then press Shift+U → a literal 'U' is inserted (becomes "HelloU").
4) Press C-k then hold Ctrl on the suffix and press u → status "Undone", still no change.
5) Press C-k then hold Ctrl on the suffix and press Shift+U → status shows the k-prefix prompt again ("C-k _").
### Objectives
Keymap and input-layer changes we attempted (and kept)
- KKeymap.cc: Case-sensitive 'U' mapping prioritized before the lowercase table. Added ctrl→non-ctrl fall-through so C-k u/U still map even if SDL reports Ctrl held on the suffix.
- TerminalInputHandler: already preserved case and mapped correctly.
- GUIInputHandler:
- Preserve case for k-prefix suffix letters (Shift → uppercase). Clear esc_meta before k-suffix mapping.
- Strengthened SDL_TEXTINPUT suppression after a k-prefix printable suffix to avoid inserting literal characters.
- Added fallback to map the k-prefix suffix in the SDL_TEXTINPUT path (to catch macOS cases where uppercase arrives in TEXTINPUT rather than KEYDOWN).
- Fixed malformed switch block introduced during iteration.
- Command layer: commit pending undo batch at k-prefix entry and just before Undo/Redo so prior typing can actually be undone/redone.
- Use the existing instrumentation to capture short traces of typing/backspacing/deleting and undo/redo.
- Unify input paths (SDL `KEYDOWN` vs `TEXTINPUT`) and lock down commit boundaries across commands.
- Extend tests to cover branching behavior, UTF8, and multiline operations.
Diagnostics added
- GUIInputHandler logs k-prefix u/U suffix attempts to stderr and (previously) /tmp/kge.log. The users macOS session showed only KEYDOWN logs for 'u':
- "[kge] k-prefix suffix: sym=117 mods=0x0 ascii=117 'u' ctrl2=0 pass_ctrl=0 mapped=1 id=38"
- "[kge] k-prefix suffix: sym=117 mods=0x80 ascii=117 'u' ctrl2=1 pass_ctrl=0 mapped=1 id=38"
- No logs were produced for 'U' (neither KEYDOWN nor TEXTINPUT). The /tmp log file was not created on the users host in the last run (stderr logs were visible earlier from KEYDOWN).
### Plan of action
Hypotheses for current failures
1) Undo appears to be invoked (status "Undone"), but no state change:
- The most likely cause is that no committed node exists at the time of undo (i.e., typing "Hello" is not being recorded as an undo node), because our current typing path in Command.cc directly edits buffer rows without always driving UndoSystem Begin/Append/commit at the right times for every printable char on GUI.
- Although we call u->Begin(Insert) and u->Append(text) in cmd_insert_text for CommandId::InsertText, the GUI printable input might be arriving through a different path or being short-circuited (e.g., via a prompt or suppression), resulting in actual text insertion but no corresponding UndoSystem pending node content, or pending but never committed.
- We now commit at k-prefix entry and before undo; if there is still "nothing to undo", it implies the batch never had text appended (Append not called) or is detached from the real buffer edits.
1. Enable instrumentation and make it easy to toggle
- Add a CMake option in `CMakeLists.txt` (root project):
`option(KTE_UNDO_DEBUG "Enable undo instrumentation logs" OFF)`.
- When ON, add a compile definition `-DKTE_UNDO_DEBUG` to all targets that include the editor core (e.g., `kte`,
`kge`, and test binaries).
- Keep the default OFF so normal builds are quiet; ensure both modes compile in CI.
2) Redo via C-k U inserts a literal 'U':
- On macOS, uppercase letters often arrive as SDL_TEXTINPUT events. We added TEXTINPUT-based k-prefix mapping, but the user's run still showed a literal insertion and no diagnostic lines for TEXTINPUT, which suggests:
a) The TEXTINPUT suppression didnt trigger for that platform/sequence, or
b) The k-prefix flag was already cleared by the time TEXTINPUT arrived, so the TEXTINPUT path defaulted to InsertText, or
c) The GUI windows input focus or SDL event ordering differs from expectations (e.g., IME/text input settings), so our suppression/mapping didnt see the event.
2. Capture short traces to validate current behavior
- Build with `-DKTE_UNDO_DEBUG=ON` and run the GUI frontend:
- Scenario A: type a contiguous word, then move cursor (should show `Begin(Insert)` + multiple `Append`, single
`commit` at a movement boundary).
- Scenario B: hold backspace to delete a run, including backspace batching (prepend rule); verify
`Begin(Delete)` with prepended `Append` behavior, single `commit`.
- Scenario C: forward deletes at a fixed column (anchor batching); expected single `Begin(Delete)` with same
column.
- Scenario D: insert newline (`Newline` node) and immediately commit; type text on the next line; undo/redo
across the boundary.
- Scenario E: undo chain and redo chain; then type new text and confirm redo branch gets discarded in logs.
- Save representative trace snippets and add to `docs/undo.md` or `docs/undo-roadmap.md` for reference.
Relevant code pointers
- Key mapping tables: KKeymap.cc → KLookupKCommand() (C-k suffix), KLookupCtrlCommand(), KLookupEscCommand().
- Terminal input: TerminalInputHandler.cc → map_key_to_command().
- GUI input: GUIInputHandler.cc → map_key() and GUIInputHandler::ProcessSDLEvent() (KEYDOWN + TEXTINPUT handling, suppression, k_prefix_/esc_meta_ flags).
- Command dispatch: Command.cc → cmd_insert_text(), cmd_newline(), cmd_backspace(), cmd_delete_char(), cmd_undo(), cmd_redo(), cmd_kprefix().
- Undo core: UndoSystem.{h,cc}, UndoNode.{h,cc}, UndoTree.{h,cc}. Buffer raw methods used by apply().
3. Inputpath unification (SDL `KEYDOWN` vs `TEXTINPUT`) — Completed 20251201
- In `GUIInputHandler.cc`:
- Ensure printable characters are generated exclusively from `SDL_TEXTINPUT` and mapped to
`CommandId::InsertText`.
- Keep `SDL_KEYDOWN` for control/meta/movement, backspace/delete, newline, and kprefix handling.
- Maintain suppression of stray `SDL_TEXTINPUT` immediately following meta/prefix or universalargument
collection so no accidental text is inserted.
- Confirm that `InsertText` path never carries `"\n"`; newline must only originate from `KEYDOWN`
`CommandId::Newline`.
- If the terminal input path exists, ensure parity: printable insertions go through `InsertText`, control via key
events, and the same commit boundaries apply.
- Status: Implemented. See `GUIInputHandler.cc` changes; tests confirm parity with terminal path.
Immediate next steps (when we return to this)
1) Verify that GUI printable insertion always flows through CommandId::InsertText so UndoSystem::Begin/Append gets called. If SDL_TEXTINPUT delivers multi-byte strings, ensure Append() is given the same text inserted into buffer.
- Add a one-session debug hook in cmd_insert_text to assert/trace: pending node type/text length and current cursor col before/after.
- If GUI sometimes sends CommandId::InsertTextEmpty or another path, unify.
4. Enforce and verify commit boundaries in command execution — In progress
- Audit `Command.cc` and ensure `u->commit()` is called before executing any nonediting command that should end a
batch:
- Movement commands (left/right/up/down/home/end/page).
- Prompt accept/cancel transitions and mode switches (search prompts, replace prompts).
- Buffer/file operations (open/switch/save/close), and focus changes.
- Before running `Undo` or `Redo` (already present).
- Ensure immediate commit at the end of atomic edit operations:
- `Newline` insertion and line joins (`Delete` of newline when backspacing at column 0) should create
`UndoType::Newline` and commit immediately (parts are already implemented; verify all call sites).
- Pastes should be a single `Paste`/`Insert` batch per operation (depending on current design).
2) Ensure batching rules are satisfied so Begin() reuses pending correctly:
- Begin(Insert) must see same row and col == pending->col + pending->text.size() for typing sequences.
- If GUI accumulates multiple characters per TEXTINPUT (e.g., pasted text), Append(std::string_view) is fine, but row/col expectations remain.
5. Extend automated tests (or add them if absent) — Phase 1 completed
- Branching behavior ✓
- Insert `"abc"`, undo twice (back to `"a"`), insert `"X"`, assert redo list is discarded, and new timeline
continues with `aX`.
- Navigate undo/redo along the new branch to ensure correctness.
- UTF8 insertion and deletion ✓
- Insert `"é漢"` (multibyte characters) via `InsertText`; verify buffer content and that a single Insert batch
is created.
- Undo/redo restores/removes the full insertion batch.
- Backspace after typed UTF8 should remove the last inserted codepoint from the batch in a single undo step (
current semantics are byteoriented in buffer ops; test to current behavior and document).
- Multiline operations ✓
- Newline splits a line: verify an `UndoType::Newline` node is created and committed immediately; undo/redo
roundtrip.
- Backspace at column 0 joins with previous line: record as `Newline` deletion (via `UndoType::Newline`
inverse); undo/redo roundtrip.
- Typing and deletion batching ✓ (typing) / Pending (delete batching)
- Typing a contiguous word (no cursor moves) yields one `Insert` node with accumulated text.
- Forward delete at a fixed anchor column yields one `Delete` batch. (Pending test)
- Backspace batching uses the prepend rule when the cursor moves left. (Pending test)
- Place tests near existing test suite files (e.g., `tests/test_undo.cc`) or create them if not present. Prefer
using `Buffer` + `UndoSystem` directly for tight unit tests; add higherlevel integration tests as needed.
3) For C-k U uppercase mapping on macOS:
- Add a temporary status dump when k-prefix suffix mapping falls back to TEXTINPUT path (we added stderr prints; also set Editor status with a short code like "K-TI U" during one session) to confirm path is used and suppression is working.
- If TEXTINPUT never arrives, force suppression: when we detect k-prefix and KEYDOWN of a letter with Shift, preemptively handle via KEYDOWN-derived uppercase ASCII rather than deferring.
6. Documentation updates — In progress
- In `docs/undo.md` and `docs/undo-roadmap.md`:
- Describe how to enable instrumentation (`KTE_UNDO_DEBUG`) and an example of trace logs.
- List batching rules and commit boundaries clearly with examples.
- Document current UTF8 semantics (bytewise vs codepointwise) and any known limitations.
- Current status: this `undo-state.md` updated; instrumentation howto and example traces pending.
4) Consolidate k-prefix handling:
- After mapping a k-prefix suffix to a command (Undo/Redo/etc.), always set suppress_text_input_once_ = true to avoid any trailing TEXTINPUT.
- Clear k_prefix_ reliably on both KEYDOWN and TEXTINPUT paths.
7. CI and build hygiene — Pending
- Default builds: `KTE_UNDO_DEBUG` OFF.
- Add a CI job that builds and runs tests with `KTE_UNDO_DEBUG=ON` to ensure the instrumentation path remains
healthy.
- Ensure no performance regressions or excessive logging in release builds.
5) Once mapping is solid, remove all diagnostics and keep the minimal, deterministic logic.
8. Stretch goals (optional, timeboxed) — Pending
- IME composition: confirm that `SDL_TEXTINPUT` behavior during IME composition does not produce partial/broken
insertions; if needed, buffer composition updates into a single commit.
- Ensure paste operations (multiline/UTF8) remain atomic in undo history.
Open questions for future debugging
- Does SDL on this macOS setup deliver Shift+U as KEYDOWN+TEXTINPUT consistently, or only TEXTINPUT? We need a small on-screen debug to avoid relying on stderr.
- Are there any IME/TextInput SDL hints on macOS we should set for raw key handling during k-prefix?
- Should we temporarily disable SDL text input (SDL_StopTextInput) during k-prefix suffix processing to eliminate TEXTINPUT races on macOS?
### How to run the tests
Notes on UndoSystem correctness (unrelated to the GUI mapping bug)
- Undo tree invariants are implemented: pending is detached; commit attaches and clears redo branches; undo/redo apply low-level Buffer edits with no public editor paths; saved marker updated via mark_saved().
- Dirty flag mirrors (current != saved).
- Delete batching supports prepend for backspace sequences (stored text is in increasing column order from anchor).
- Newline joins/splits are recorded as UndoType::Newline and committed immediately for single-step undo of line joins.
- Configure with `-DBUILD_TESTS=ON` and build the `test_undo` target. Run the produced binary (e.g., `./test_undo`).
The test prints progress and uses assertions to validate behavior.
Owner pointers & file locations
- GUI mapping and suppression: GUIInputHandler.cc
- Command layer commit boundaries: Command.cc (cmd_kprefix, cmd_undo, cmd_redo)
- Undo batching entry points: Command.cc (cmd_insert_text, cmd_backspace, cmd_delete_char, cmd_newline)
### Deliverables
End of snapshot — safe to resume from here.
- CMake toggle for instrumentation and verified logs for core scenarios. (Pending)
- Updated `GUIInputHandler.cc` solidifying `KEYDOWN` vs `TEXTINPUT` separation and suppression rules. (Completed)
- Verified commit boundaries in `Command.cc` with comments where appropriate. (In progress)
- New tests for branching, UTF8, and multiline operations; all passing. (Completed for listed scenarios)
- Docs updated with howto and example traces. (Pending)
---
### Acceptance criteria
RESOLUTION (2025-11-30)
### Current status (20251201) vs acceptance criteria
Root Cause Identified and Fixed
The undo system failure was caused by incorrect timing of UndoSystem::Begin() and Append() calls relative to buffer modifications in Command.cc.
Problem:
- In cmd_insert_text, cmd_backspace, cmd_delete_char, and cmd_newline, the undo recording (Begin/Append) was called BEFORE the actual buffer modification and cursor update.
- UndoSystem::Begin() checks the current cursor position to determine if it can batch with the pending node.
- For Insert type: Begin() checks if col == pending->col + pending->text.size()
- For Delete type: Begin() checks if the cursor is at the expected position based on whether it's forward delete or backspace
- When Begin/Append were called before cursor updates, the batching condition would fail on the second character because the cursor hadn't moved yet from the first insertion.
- This caused each character to create a separate batch, but since commit() was never called between characters (only at k-prefix or undo), the pending node would be overwritten rather than committed, resulting in no undo history.
Fix Applied:
- cmd_insert_text: Moved Begin/Append to AFTER buffer insertion (lines 854-856) and cursor update (line 857).
- cmd_backspace: Moved Begin/Append to AFTER character deletion (lines 1024-1025) and cursor decrement (line 1026).
- cmd_delete_char: Moved Begin/Append to AFTER character deletion (lines 1074-1076).
- cmd_newline: Moved Begin/commit to AFTER line split (lines 956-966) and cursor update (lines 963-967).
Result:
- Begin() now sees the correct cursor position after each edit, allowing proper batching of consecutive characters.
- Typing "Hello" will now create a single pending batch with all 5 characters that can be undone as one unit.
- The fix applies to both terminal (kte) and GUI (kge) builds.
Testing Recommendation:
- Type several characters (e.g., "Hello")
- Press C-k u to undo - the entire word should disappear
- Press C-k U to redo - the word should reappear
- Test backspace batching: type several characters, then backspace multiple times, then undo - should undo the backspace batch
- Test delete batching similarly
- Short instrumentation traces match expected batching and commit behavior for typing, backspace/delete, newline, and
undo/redo. — Pending (instrumentation toggle + capture not done)
- Printable input comes exclusively from `SDL_TEXTINPUT`; no stray inserts after meta/prefix/universalargument flows.
— Satisfied (GUI path updated; terminal path consistent)
- Undo branching behaves correctly; redo is discarded upon new commits after undo. — Satisfied (tested)
- UTF8 and multiline scenarios roundtrip via undo/redo according to the documented semantics. — Satisfied (tested)
- Tests pass with `KTE_UNDO_DEBUG` both OFF and ON. — Pending (no CMake toggle yet; default OFF passes)

View File

@@ -1,8 +1,11 @@
#ifndef KGE_FONTS_B612_MONO_H
#define KGE_FONTS_B612_MONO_H
#pragma once
#include "Font.h"
// File: 'B612_Mono/B612Mono-Bold.ttf' (135904 bytes)
namespace kte::Fonts {
namespace B612Mono {
// File: 'B612Mono/B612Mono-Bold.ttf' (135904 bytes)
// Exported using binary_to_compressed_c.cpp
static const unsigned int DefaultFontBoldCompressedSize = 74748;
static const unsigned int DefaultFontBoldCompressedData[74748 / 4] =
@@ -3125,7 +3128,7 @@ static const unsigned int DefaultFontBoldCompressedData[74748 / 4] =
};
// File: 'B612_Mono/B612Mono-Italic.ttf' (118888 bytes)
// File: 'B612Mono/B612Mono-Italic.ttf' (118888 bytes)
// Exported using binary_to_compressed_c.cpp
static const unsigned int DefaultFontItalicCompressedSize = 67763;
static const unsigned int DefaultFontItalicCompressedData[67764 / 4] =
@@ -5956,7 +5959,7 @@ static const unsigned int DefaultFontItalicCompressedData[67764 / 4] =
};
// File: 'B612_Mono/B612Mono-BoldItalic.ttf' (121732 bytes)
// File: 'B612Mono/B612Mono-BoldItalic.ttf' (121732 bytes)
// Exported using binary_to_compressed_c.cpp
static const unsigned int DefaultFontBoldItalicCompressedSize = 69211;
static const unsigned int DefaultFontBoldItalicCompressedData[69212 / 4] =
@@ -8848,7 +8851,7 @@ static const unsigned int DefaultFontBoldItalicCompressedData[69212 / 4] =
};
// File: 'B612_Mono/B612Mono-Regular.ttf' (136712 bytes)
// File: 'B612Mono/B612Mono-Regular.ttf' (136712 bytes)
// Exported using binary_to_compressed_c.cpp
static const unsigned int DefaultFontRegularCompressedSize = 72615;
static const unsigned int DefaultFontRegularCompressedData[72616 / 4] =
@@ -11880,6 +11883,5 @@ static const unsigned int DefaultFontRegularCompressedData[72616 / 4] =
0x534c1701, 0x17042a0f, 0x1efd613e, 0x0130511f, 0x2a568e22, 0x3e611404, 0x51301ffd, 0x8f22041f, 0x00fa055c,
0x0070a96e,
};
#endif
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,93 +0,0 @@
Copyright 2012 The B612 Project Authors (https://github.com/polarsys/b612)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

5589
fonts/BrassMono.h Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@@ -1,435 +0,0 @@
OFL FAQ - Frequently Asked Questions about the SIL Open Font License (OFL)
Version 1.1-update5 - April 2017
The OFL FAQ is copyright (c) 2005-2017 SIL International.
(See http://scripts.sil.org/OFL for updates)
CONTENTS OF THIS FAQ
1 USING AND DISTRIBUTING FONTS LICENSED UNDER THE OFL
2 USING OFL FONTS FOR WEB PAGES AND ONLINE WEB FONT SERVICES
3 MODIFYING OFL-LICENSED FONTS
4 LICENSING YOUR ORIGINAL FONTS UNDER THE OFL
5 CHOOSING RESERVED FONT NAMES
6 ABOUT THE FONTLOG
7 MAKING CONTRIBUTIONS TO OFL PROJECTS
8 ABOUT THE LICENSE ITSELF
9 ABOUT SIL INTERNATIONAL
APPENDIX A - FONTLOG EXAMPLE
1 USING AND DISTRIBUTING FONTS LICENSED UNDER THE OFL
1.1 Can I use the fonts for a book or other print publication, to create logos or other graphics or even to manufacture objects based on their outlines?
Yes. You are very welcome to do so. Authors of fonts released under the OFL allow you to use their font software as such for any kind of design work. No additional license or permission is required, unlike with some other licenses. Some examples of these uses are: logos, posters, business cards, stationery, video titling, signage, t-shirts, personalised fabric, 3D-printed/laser-cut shapes, sculptures, rubber stamps, cookie cutters and lead type.
1.1.1 Does that restrict the license or distribution of that artwork?
No. You remain the author and copyright holder of that newly derived graphic or object. You are simply using an open font in the design process. It is only when you redistribute, bundle or modify the font itself that other conditions of the license have to be respected (see below for more details).
1.1.2 Is any kind of acknowledgement required?
No. Font authors may appreciate being mentioned in your artwork's acknowledgements alongside the name of the font, possibly with a link to their website, but that is not required.
1.2 Can the fonts be included with Free/Libre and Open Source Software collections such as GNU/Linux and BSD distributions and repositories?
Yes! Fonts licensed under the OFL can be freely included alongside other software under FLOSS (Free/Libre and Open Source Software) licenses. Since fonts are typically aggregated with, not merged into, existing software, there is little need to be concerned about incompatibility with existing software licenses. You may also repackage the fonts and the accompanying components in a .rpm or .deb package (or other similar package formats or installers) and include them in distribution CD/DVDs and online repositories. (Also see section 5.9 about rebuilding from source.)
1.3 I want to distribute the fonts with my program. Does this mean my program also has to be Free/Libre and Open Source Software?
No. Only the portions based on the Font Software are required to be released under the OFL. The intent of the license is to allow aggregation or bundling with software under restricted licensing as well.
1.4 Can I sell a software package that includes these fonts?
Yes, you can do this with both the Original Version and a Modified Version of the fonts. Examples of bundling made possible by the OFL would include: word processors, design and publishing applications, training and educational software, games and entertainment software, mobile device applications, etc.
1.5 Can I include the fonts on a CD of freeware or commercial fonts?
Yes, as long some other font or software is also on the disk, so the OFL font is not sold by itself.
1.6 Why won't the OFL let me sell the fonts alone?
The intent is to keep people from making money by simply redistributing the fonts. The only people who ought to profit directly from the fonts should be the original authors, and those authors have kindly given up potential direct income to distribute their fonts under the OFL. Please honour and respect their contribution!
1.7 What about sharing OFL fonts with friends on a CD, DVD or USB stick?
You are very welcome to share open fonts with friends, family and colleagues through removable media. Just remember to include the full font package, including any copyright notices and licensing information as available in OFL.txt. In the case where you sell the font, it has to come bundled with software.
1.8 Can I host the fonts on a web site for others to use?
Yes, as long as you make the full font package available. In most cases it may be best to point users to the main site that distributes the Original Version so they always get the most recent stable and complete version. See also discussion of web fonts in Section 2.
1.9 Can I host the fonts on a server for use over our internal network?
Yes. If the fonts are transferred from the server to the client computer by means that allow them to be used even if the computer is no longer attached to the network, the full package (copyright notices, licensing information, etc.) should be included.
1.10 Does the full OFL license text always need to accompany the font?
The only situation in which an OFL font can be distributed without the text of the OFL (either in a separate file or in font metadata), is when a font is embedded in a document or bundled within a program. In the case of metadata included within a font, it is legally sufficient to include only a link to the text of the OFL on http://scripts.sil.org/OFL, but we strongly recommend against this. Most modern font formats include metadata fields that will accept the full OFL text, and full inclusion increases the likelihood that users will understand and properly apply the license.
1.11 What do you mean by 'embedding'? How does that differ from other means of distribution?
By 'embedding' we mean inclusion of the font in a document or file in a way that makes extraction (and redistribution) difficult or clearly discouraged. In many cases the names of embedded fonts might also not be obvious to those reading the document, the font data format might be altered, and only a subset of the font - only the glyphs required for the text - might be included. Any other means of delivering a font to another person is considered 'distribution', and needs to be accompanied by any copyright notices and licensing information available in OFL.txt.
1.12 So can I embed OFL fonts in my document?
Yes, either in full or a subset. The restrictions regarding font modification and redistribution do not apply, as the font is not intended for use outside the document.
1.13 Does embedding alter the license of the document itself?
No. Referencing or embedding an OFL font in any document does not change the license of the document itself. The requirement for fonts to remain under the OFL does not apply to any document created using the fonts and their derivatives. Similarly, creating any kind of graphic using a font under OFL does not make the resulting artwork subject to the OFL.
1.14 If OFL fonts are extracted from a document in which they are embedded (such as a PDF file), what can be done with them? Is this a risk to author(s)?
The few utilities that can extract fonts embedded in a PDF will typically output limited amounts of outlines - not a complete font. To create a working font from this method is much more difficult and time consuming than finding the source of the original OFL font. So there is little chance that an OFL font would be extracted and redistributed inappropriately through this method. Even so, copyright laws address any misrepresentation of authorship. All Font Software released under the OFL and marked as such by the author(s) is intended to remain under this license regardless of the distribution method, and cannot be redistributed under any other license. We strongly discourage any font extraction - we recommend directly using the font sources instead - but if you extract font outlines from a document, please be considerate: respect the work of the author(s) and the licensing model.
1.15 What about distributing fonts with a document? Within a compressed folder structure? Is it distribution, bundling or embedding?
Certain document formats may allow the inclusion of an unmodified font within their file structure which may consist of a compressed folder containing the various resources forming the document (such as pictures and thumbnails). Including fonts within such a structure is understood as being different from embedding but rather similar to bundling (or mere aggregation) which the license explicitly allows. In this case the font is conveyed unchanged whereas embedding a font usually transforms it from the original format. The OFL does not allow anyone to extract the font from such a structure to then redistribute it under another license. The explicit permission to redistribute and embed does not cancel the requirement for the Font Software to remain under the license chosen by its author(s). Even if the font travels inside the document as one of its assets, it should not lose its authorship information and licensing.
1.16 What about ebooks shipping with open fonts?
The requirements differ depending on whether the fonts are linked, embedded or distributed (bundled or aggregated). Some ebook formats use web technologies to do font linking via @font-face, others are designed for font embedding, some use fonts distributed with the document or reading software, and a few rely solely on the fonts already present on the target system. The license requirements depend on the type of inclusion as discussed in 1.15.
1.17 Can Font Software released under the OFL be subject to URL-based access restrictions methods or DRM (Digital Rights Management) mechanisms?
Yes, but these issues are out-of-scope for the OFL. The license itself neither encourages their use nor prohibits them since such mechanisms are not implemented in the components of the Font Software but through external software. Such restrictions are put in place for many different purposes corresponding to various usage scenarios. One common example is to limit potentially dangerous cross-site scripting attacks. However, in the spirit of libre/open fonts and unrestricted writing systems, we strongly encourage open sharing and reuse of OFL fonts, and the establishment of an environment where such restrictions are unnecessary. Note that whether you wish to use such mechanisms or you prefer not to, you must still abide by the rules set forth by the OFL when using fonts released by their authors under this license. Derivative fonts must be licensed under the OFL, even if they are part of a service for which you charge fees and/or for which access to source code is restricted. You may not sell the fonts on their own - they must be part of a larger software package, bundle or subscription plan. For example, even if the OFL font is distributed in a software package or via an online service using a DRM mechanism, the user would still have the right to extract that font, use, study, modify and redistribute it under the OFL.
1.18 I've come across a font released under the OFL. How can I easily get more information about the Original Version? How can I know where it stands compared to the Original Version or other Modified Versions?
Consult the copyright statement(s) in the license for ways to contact the original authors. Consult the FONTLOG (see section 6 for more details and examples) for information on how the font differs from the Original Version, and get in touch with the various contributors via the information in the acknowledgement section. Please consider using the Original Versions of the fonts whenever possible.
1.19 What do you mean in condition 4 of the OFL's permissions and conditions? Can you provide examples of abusive promotion / endorsement / advertisement vs. normal acknowledgement?
The intent is that the goodwill and reputation of the author(s) should not be used in a way that makes it sound like the original author(s) endorse or approve of a specific Modified Version or software bundle. For example, it would not be right to advertise a word processor by naming the author(s) in a listing of software features, or to promote a Modified Version on a web site by saying "designed by ...". However, it would be appropriate to acknowledge the author(s) if your software package has a list of people who deserve thanks. We realize that this can seem to be a grey area, but the standard used to judge an acknowledgement is that if the acknowledgement benefits the author(s) it is allowed, but if it primarily benefits other parties, or could reflect poorly on the author(s), then it is not.
1.20 I'm writing a small app for mobile platforms, do I need to include the whole package?
If you bundle a font under the OFL with your mobile app you must comply with the terms of the license. At a minimum you must include the copyright statement, the license notice and the license text. A mention of this information in your About box or Changelog, with a link to where the font package is from, is good practice, and the extra space needed to carry these items is very small. You do not, however, need to include the full contents of the font package - only the fonts you use and the copyright and license that apply to them. For example, if you only use the regular weight in your app, you do not need to include the italic and bold versions.
1.21 What about including OFL fonts by default in my firmware or dedicated operating system?
Many such systems are restricted and turned into appliances so that users cannot study or modify them. Using open fonts to increase quality and language coverage is a great idea, but you need to be aware that if there is a way for users to extract fonts you cannot legally prevent them from doing that. The fonts themselves, including any changes you make to them, must be distributed under the OFL even if your firmware has a more restrictive license. If you do transform the fonts and change their formats when you include them in your firmware you must respect any names reserved by the font authors via the RFN mechanism and pick your own font name. Alternatively if you directly add a font under the OFL to the font folder of your firmware without modifying or optimizing it you are simply bundling the font like with any other software collection, and do not need to make any further changes.
1.22 Can I make and publish CMS themes or templates that use OFL fonts? Can I include the fonts themselves in the themes or templates? Can I sell the whole package?
Yes, you are very welcome to integrate open fonts into themes and templates for your preferred CMS and make them more widely available. Remember that you can only sell the fonts and your CMS add-on as part of a software bundle. (See 1.4 for details and examples about selling bundles).
1.23 Can OFL fonts be included in services that deliver fonts to the desktop from remote repositories? Even if they contain both OFL and non-OFL fonts?
Yes. Some foundries have set up services to deliver fonts to subscribers directly to desktops from their online repositories; similarly, plugins are available to preview and use fonts directly in your design tool or publishing suite. These services may mix open and restricted fonts in the same channel, however they should make a clear distinction between them to users. These services should also not hinder users (such as through DRM or obfuscation mechanisms) from extracting and using the OFL fonts in other environments, or continuing to use OFL fonts after subscription terms have ended, as those uses are specifically allowed by the OFL.
1.24 Can services that provide or distribute OFL fonts restrict my use of them?
No. The terms of use of such services cannot replace or restrict the terms of the OFL, as that would be the same as distributing the fonts under a different license, which is not allowed. You are still entitled to use, modify and redistribute them as the original authors have intended outside of the sole control of that particular distribution channel. Note, however, that the fonts provided by these services may differ from the Original Versions.
2 USING OFL FONTS FOR WEBPAGES AND ONLINE WEB FONT SERVICES
NOTE: This section often refers to a separate paper on 'Web Fonts & RFNs'. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs
2.1 Can I make webpages using these fonts?
Yes! Go ahead! Using CSS (Cascading Style Sheets) is recommended. Your three best options are:
- referring directly in your stylesheet to open fonts which may be available on the user's system
- providing links to download the full package of the font - either from your own website or from elsewhere - so users can install it themselves
- using @font-face to distribute the font directly to browsers. This is recommended and explicitly allowed by the licensing model because it is distribution. The font file itself is distributed with other components of the webpage. It is not embedded in the webpage but referenced through a web address which will cause the browser to retrieve and use the corresponding font to render the webpage (see 1.11 and 1.15 for details related to embedding fonts into documents). As you take advantage of the @font-face cross-platform standard, be aware that web fonts are often tuned for a web environment and not intended for installation and use outside a browser. The reasons in favour of using web fonts are to allow design of dynamic text elements instead of static graphics, to make it easier for content to be localized and translated, indexed and searched, and all this with cross-platform open standards without depending on restricted extensions or plugins. You should check the CSS cascade (the order in which fonts are being called or delivered to your users) when testing.
2.2 Can I make and use WOFF (Web Open Font Format) versions of OFL fonts?
Yes, but you need to be careful. A change in font format normally is considered modification, and Reserved Font Names (RFNs) cannot be used. Because of the design of the WOFF format, however, it is possible to create a WOFF version that is not considered modification, and so would not require a name change. You are allowed to create, use and distribute a WOFF version of an OFL font without changing the font name, but only if:
- the original font data remains unchanged except for WOFF compression, and
- WOFF-specific metadata is either omitted altogether or present and includes, unaltered, the contents of all equivalent metadata in the original font.
If the original font data or metadata is changed, or the WOFF-specific metadata is incomplete, the font must be considered a Modified Version, the OFL restrictions would apply and the name of the font must be changed: any RFNs cannot be used and copyright notices and licensing information must be included and cannot be deleted or modified. You must come up with a unique name - we recommend one corresponding to your domain or your particular web application. Be aware that only the original author(s) can use RFNs. This is to prevent collisions between a derivative tuned to your audience and the original upstream version and so to reduce confusion.
Please note that most WOFF conversion tools and online services do not meet the two requirements listed above, and so their output must be considered a Modified Version. So be very careful and check to be sure that the tool or service you're using is compressing unchanged data and completely and accurately reflecting the original font metadata.
2.3 What about other web font formats such as EOT/EOTLite/CWT/etc.?
In most cases these formats alter the original font data more than WOFF, and do not completely support appropriate metadata, so their use must be considered modification and RFNs may not be used. However, there may be certain formats or usage scenarios that may allow the use of RFNs. See http://scripts.sil.org/OFL_web_fonts_and_RFNs
2.4 Can I make OFL fonts available through web font online services?
Yes, you are welcome to include OFL fonts in online web font services as long as you properly meet all the conditions of the license. The origin and open status of the font should be clear among the other fonts you are hosting. Authorship, copyright notices and license information must be sufficiently visible to your users or subscribers so they know where the font comes from and the rights granted by the author(s). Make sure the font file contains the needed copyright notice(s) and licensing information in its metadata. Please double-check the accuracy of every field to prevent contradictory information. Other font formats, including EOT/EOTLite/CWT and superior alternatives like WOFF, already provide fields for this information. Remember that if you modify the font within your library or convert it to another format for any reason the OFL restrictions apply and you need to change the names accordingly. Please respect the author's wishes as expressed in the OFL and do not misrepresent original designers and their work. Don't lump quality open fonts together with dubious freeware or public domain fonts. Consider how you can best work with the original designers and foundries, support their efforts and generate goodwill that will benefit your service. (See 1.17 for details related to URL-based access restrictions methods or DRM mechanisms).
2.5 Some web font formats and services provide ways of "optimizing" the font for a particular website or web application; is that allowed?
Yes, it is permitted, but remember that these optimized versions are Modified Versions and so must follow OFL requirements like appropriate renaming. Also you need to bear in mind the other important parameters beyond compression, speed and responsiveness: you need to consider the audience of your particular website or web application, as choosing some optimization parameters may turn out to be less than ideal for them. Subsetting by removing certain glyphs or features may seriously limit functionality of the font in various languages that your users expect. It may also introduce degradation of quality in the rendering or specific bugs on the various target platforms compared to the original font from upstream. In other words, remember that one person's optimized font may be another person's missing feature. Various advanced typographic features (OpenType, Graphite or AAT) are also available through CSS and may provide the desired effects without the need to modify the font.
2.6 Is subsetting a web font considered modification?
Yes. Removing any parts of the font when delivering a web font to a browser, including unused glyphs and smart font code, is considered modification. This is permitted by the OFL but would not normally allow the use of RFNs. Some newer subsetting technologies may be able to subset in a way that allows users to effectively have access to the complete font, including smart font behaviour. See 2.8 and http://scripts.sil.org/OFL_web_fonts_and_RFNs
2.7 Are there any situations in which a modified web font could use RFNs?
Yes. If a web font is optimized only in ways that preserve Functional Equivalence (see 2.8), then it may use RFNs, as it reasonably represents the Original Version and respects the intentions of the author(s) and the main purposes of the RFN mechanism (avoids collisions, protects authors, minimizes support, encourages derivatives). However this is technically very difficult and often impractical, so a much better scenario is for the web font service or provider to sign a separate agreement with the author(s) that allows the use of RFNs for Modified Versions.
2.8 How do you know if an optimization to a web font preserves Functional Equivalence?
Functional Equivalence is described in full in the 'Web fonts and RFNs' paper at http://scripts.sil.org/OFL_web_fonts_and_RFNs, in general, an optimized font is deemed to be Functionally Equivalent (FE) to the Original Version if it:
- Supports the same full character inventory. If a character can be properly displayed using the Original Version, then that same character, encoded correctly on a web page, will display properly.
- Provides the same smart font behavior. Any dynamic shaping behavior that works with the Original Version should work when optimized, unless the browser or environment does not support it. There does not need to be guaranteed support in the client, but there should be no forced degradation of smart font or shaping behavior, such as the removal or obfuscation of OpenType, Graphite or AAT tables.
- Presents text with no obvious degradation in visual quality. The lettershapes should be equally (or more) readable, within limits of the rendering platform.
- Preserves original author, project and license metadata. At a minimum, this should include: Copyright and authorship; The license as stated in the Original Version, whether that is the full text of the OFL or a link to the web version; Any RFN declarations; Information already present in the font or documentation that points back to the Original Version, such as a link to the project or the author's website.
If an optimized font meets these requirements, and so is considered to be FE, then it's very likely that the original author would feel that the optimized font is a good and reasonable equivalent. If it falls short of any of these requirements, the optimized font does not reasonably represent the Original Version, and so should be considered to be a Modified Version. Like other Modified Versions, it would not be allowed to use any RFNs and you simply need to pick your own font name.
2.9 Isn't use of web fonts another form of embedding?
No. Unlike embedded fonts in a PDF, web fonts are not an integrated part of the document itself. They are not specific to a single document and are often applied to thousands of documents around the world. The font data is not stored alongside the document data and often originates from a different location. The ease by which the web fonts used by a document may be identified and downloaded for desktop use demonstrates that they are philosophically and technically separate from the web pages that specify them. See http://scripts.sil.org/OFL_web_fonts_and_RFNs
2.10 So would it be better to not use RFNs at all if you want your font to be distributed by a web fonts service?
No. Although the OFL does not require authors to use RFNs, the RFN mechanism is an important part of the OFL model and completely compatible with web font services. If that web font service modifies the fonts, then the best solution is to sign a separate agreement for the use of any RFNs. It is perfectly valid for an author to not declare any RFNs, but before they do so they need to fully understand the benefits they are giving up, and the overall negative effect of allowing many different versions bearing the same name to be widely distributed. As a result, we don't generally recommend it.
2.11 What should an agreement for the use of RFNs say? Are there any examples?
There is no prescribed format for this agreement, as legal systems vary, and no recommended examples. Authors may wish to add specific clauses to further restrict use, require author review of Modified Versions, establish user support mechanisms or provide terms for ending the agreement. Such agreements are usually not public, and apply only to the main parties. However, it would be very beneficial for web font services to clearly state when they have established such agreements, so that the public understands clearly that their service is operating appropriately.
See the separate paper on 'Web Fonts & RFNs' for in-depth discussion of issues related to the use of RFNs for web fonts. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs
3 MODIFYING OFL-LICENSED FONTS
3.1 Can I change the fonts? Are there any limitations to what things I can and cannot change?
You are allowed to change anything, as long as such changes do not violate the terms of the license. In other words, you are not allowed to remove the copyright statement(s) from the font, but you could put additional information into it that covers your contribution. See the placeholders in the OFL header template for recommendations on where to add your own statements. (Remember that, when authors have reserved names via the RFN mechanism, you need to change the internal names of the font to your own font name when making your modified version even if it is just a small change.)
3.2 I have a font that needs a few extra glyphs - can I take them from an OFL licensed font and copy them into mine?
Yes, but if you distribute that font to others it must be under the OFL, and include the information mentioned in condition 2 of the license.
3.3 Can I charge people for my additional work? In other words, if I add a bunch of special glyphs or OpenType/Graphite/AAT code, can I sell the enhanced font?
Not by itself. Derivative fonts must be released under the OFL and cannot be sold by themselves. It is permitted, however, to include them in a larger software package (such as text editors, office suites or operating systems), even if the larger package is sold. In that case, you are strongly encouraged, but not required, to also make that derived font easily and freely available outside of the larger package.
3.4 Can I pay someone to enhance the fonts for my use and distribution?
Yes. This is a good way to fund the further development of the fonts. Keep in mind, however, that if the font is distributed to others it must be under the OFL. You won't be able to recover your investment by exclusively selling the font, but you will be making a valuable contribution to the community. Please remember how you have benefited from the contributions of others.
3.5 I need to make substantial revisions to the font to make it work with my program. It will be a lot of work, and a big investment, and I want to be sure that it can only be distributed with my program. Can I restrict its use?
No. If you redistribute a Modified Version of the font it must be under the OFL. You may not restrict it in any way beyond what the OFL permits and requires. This is intended to ensure that all released improvements to the fonts become available to everyone. But you will likely get an edge over competitors by being the first to distribute a bundle with the enhancements. Again, please remember how you have benefited from the contributions of others.
3.6 Do I have to make any derivative fonts (including extended source files, build scripts, documentation, etc.) publicly available?
No, but please consider sharing your improvements with others. You may find that you receive in return more than what you gave.
3.7 If a trademark is claimed in the OFL font, does that trademark need to remain in modified fonts?
Yes. Any trademark notices must remain in any derivative fonts to respect trademark laws, but you may add any additional trademarks you claim, officially registered or not. For example if an OFL font called "Foo" contains a notice that "Foo is a trademark of Acme", then if you rename the font to "Bar" when creating a Modified Version, the new trademark notice could say "Foo is a trademark of Acme Inc. - Bar is a trademark of Roadrunner Technologies Ltd.". Trademarks work alongside the OFL and are not subject to the terms of the licensing agreement. The OFL does not grant any rights under trademark law. Bear in mind that trademark law varies from country to country and that there are no international trademark conventions as there are for copyright. You may need to significantly invest in registering and defending a trademark for it to remain valid in the countries you are interested in. This may be costly for an individual independent designer.
3.8 If I commit changes to a font (or publish a branch in a DVCS) as part of a public open source software project, do I have to change the internal font names?
Only if there are declared RFNs. Making a public commit or publishing a public branch is effectively redistributing your modifications, so any change to the font will require that you do not use the RFNs. Even if there are no RFNs, it may be useful to change the name or add a suffix indicating that a particular version of the font is still in development and not released yet. This will clearly indicate to users and fellow designers that this particular font is not ready for release yet. See section 5 for more details.
4 LICENSING YOUR ORIGINAL FONTS UNDER THE OFL
4.1 Can I use the SIL OFL for my own fonts?
Yes! We heartily encourage everyone to use the OFL to distribute their own original fonts. It is a carefully constructed license that allows great freedom along with enough artistic integrity protection for the work of the authors as well as clear rules for other contributors and those who redistribute the fonts. The licensing model is used successfully by various organisations, both for-profit and not-for-profit, to release fonts of varying levels of scope and complexity.
4.2 What do I have to do to apply the OFL to my font?
If you want to release your fonts under the OFL, we recommend you do the following:
4.2.1 Put your copyright and Reserved Font Names information at the beginning of the main OFL.txt file in place of the dedicated placeholders (marked with the <> characters). Include this file in your release package.
4.2.2 Put your copyright and the OFL text with your chosen Reserved Font Name(s) into your font files (the copyright and license fields). A link to the OFL text on the OFL web site is an acceptable (but not recommended) alternative. Also add this information to any other components (build scripts, glyph databases, documentation, test files, etc). Accurate metadata in your font files is beneficial to you as an increasing number of applications are exposing this information to the user. For example, clickable links can bring users back to your website and let them know about other work you have done or services you provide. Depending on the format of your fonts and sources, you can use template human-readable headers or machine-readable metadata. You should also double-check that there is no conflicting metadata in the font itself contradicting the license, such as the fstype bits in the os2 table or fields in the name table.
4.2.3 Write an initial FONTLOG.txt for your font and include it in the release package (see Section 6 and Appendix A for details including a template).
4.2.4 Include the relevant practical documentation on the license by adding the current OFL-FAQ.txt file in your package.
4.2.5 If you wish you can use the OFL graphics (http://scripts.sil.org/OFL_logo) on your website.
4.3 Will you make my font OFL for me?
We won't do the work for you. We can, however, try to answer your questions, unfortunately we do not have the resources to review and check your font packages for correct use of the OFL. We recommend you turn to designers, foundries or consulting companies with experience in doing open font design to provide this service to you.
4.4 Will you distribute my OFL font for me?
No, although if the font is of sufficient quality and general interest we may include a link to it on our partial list of OFL fonts on the OFL web site. You may wish to consider other open font catalogs or hosting services, such as the Unifont Font Guide (http://unifont.org/fontguide), The League of Movable Type (http://theleagueofmovabletype.com) or the Open Font Library (http://openfontlibrary.org/), which despite the name has no direct relationship to the OFL or SIL. We do not endorse any particular catalog or hosting service - it is your responsibility to determine if the service is right for you and if it treats authors with fairness.
4.5 Why should I use the OFL for my fonts?
- to meet needs for fonts that can be modified to support lesser-known languages
- to provide a legal and clear way for people to respect your work but still use it (and reduce piracy)
- to involve others in your font project
- to enable your fonts to be expanded with new weights and improved writing system/language support
- to allow more technical font developers to add features to your design (such as OpenType, Graphite or AAT support)
- to renew the life of an old font lying on your hard drive with no business model
- to allow your font to be included in Libre Software operating systems like Ubuntu
- to give your font world status and wide, unrestricted distribution
- to educate students about quality typeface and font design
- to expand your test base and get more useful feedback
- to extend your reach to new markets when users see your metadata and go to your website
- to get your font more easily into one of the web font online services
- to attract attention for your commercial fonts
- to make money through web font services
- to make money by bundling fonts with applications
- to make money adjusting and extending existing open fonts
- to get a better chance that foundations/NGOs/charities/companies who commission fonts will pick you
- to be part of a sharing design and development community
- to give back and contribute to a growing body of font sources
5 CHOOSING RESERVED FONT NAMES
5.1 What are Reserved Font Names?
These are font names, or portions of font names, that the author has chosen to reserve for use only with the Original Version of the font, or for Modified Version(s) created by the original author.
5.2 Why can't I use the Reserved Font Names in my derivative font names? I'd like people to know where the design came from.
The best way to acknowledge the source of the design is to thank the original authors and any other contributors in the files that are distributed with your revised font (although no acknowledgement is required). The FONTLOG is a natural place to do this. Reserved Font Names ensure that the only fonts that have the original names are the unmodified Original Versions. This allows designers to maintain artistic integrity while allowing collaboration to happen. It eliminates potential confusion and name conflicts. When choosing a name, be creative and avoid names that reuse almost all the same letters in the same order or sound like the original. It will help everyone if Original Versions and Modified Versions can easily be distinguished from one another and from other derivatives. Any substitution and matching mechanism is outside the scope of the license.
5.3 What do you mean by "primary name as presented to the user"? Are you referring to the font menu name?
Yes, this applies to the font menu name and other mechanisms that specify a font in a document. It would be fine, however, to keep a text reference to the original fonts in the description field, in your modified source file or in documentation provided alongside your derivative as long as no one could be confused that your modified source is the original. But you cannot use the Reserved Font Names in any way to identify the font to the user (unless the Copyright Holder(s) allow(s) it through a separate agreement). Users who install derivatives (Modified Versions) on their systems should not see any of the original Reserved Font Names in their font menus, for example. Again, this is to ensure that users are not confused and do not mistake one font for another and so expect features only another derivative or the Original Version can actually offer.
5.4 Am I not allowed to use any part of the Reserved Font Names?
You may not use individual words from the Reserved Font Names, but you would be allowed to use parts of words, as long as you do not use any word from the Reserved Font Names entirely. We do not recommend using parts of words because of potential confusion, but it is allowed. For example, if "Foobar" was a Reserved Font Name, you would be allowed to use "Foo" or "bar", although we would not recommend it. Such an unfortunate choice would confuse the users of your fonts as well as make it harder for other designers to contribute.
5.5 So what should I, as an author, identify as Reserved Font Names?
Original authors are encouraged to name their fonts using clear, distinct names, and only declare the unique parts of the name as Reserved Font Names. For example, the author of a font called "Foobar Sans" would declare "Foobar" as a Reserved Font Name, but not "Sans", as that is a common typographical term, and may be a useful word to use in a derivative font name. Reserved Font Names should also be single words for simplicity and legibility. A font called "Flowing River" should have Reserved Font Names "Flowing" and "River", not "Flowing River". You also need to be very careful about reserving font names which are already linked to trademarks (whether registered or not) which you do not own.
5.6 Do I, as an author, have to identify any Reserved Font Names?
No. RFNs are optional and not required, but we encourage you to use them. This is primarily to avoid confusion between your work and Modified Versions. As an author you can release a font under the OFL and not declare any Reserved Font Names. There may be situations where you find that using no RFNs and letting your font be changed and modified - including any kind of modification - without having to change the original name is desirable. However you need to be fully aware of the consequences. There will be no direct way for end-users and other designers to distinguish your Original Version from many Modified Versions that may be created. You have to trust whoever is making the changes and the optimizations to not introduce problematic changes. The RFNs you choose for your own creation have value to you as an author because they allow you to maintain artistic integrity and keep some control over the distribution channel to your end-users. For discussion of RFNs and web fonts see section 2.
5.7 Are any names (such as the main font name) reserved by default?
No. That is a change to the license as of version 1.1. If you want any names to be Reserved Font Names, they must be specified after the copyright statement(s).
5.8 Is there any situation in which I can use Reserved Font Names for a Modified Version?
The Copyright Holder(s) can give certain trusted parties the right to use any of the Reserved Font Names through separate written agreements. For example, even if "Foobar" is a RFN, you could write up an agreement to give company "XYZ" the right to distribute a modified version with a name that includes "Foobar". This allows for freedom without confusion. The existence of such an agreement should be made as clear as possible to downstream users and designers in the distribution package and the relevant documentation. They need to know if they are a party to the agreement or not and what they are practically allowed to do or not even if all the details of the agreement are not public.
5.9 Do font rebuilds require a name change? Do I have to change the name of the font when my packaging workflow includes a full rebuild from source?
Yes, all rebuilds which change the font data and the smart code are Modified Versions and the requirements of the OFL apply: you need to respect what the Author(s) have chosen in terms of Reserved Font Names. However if a package (or installer) is simply a wrapper or a compressed structure around the final font - leaving them intact on the inside - then no name change is required. Please get in touch with the author(s) and copyright holder(s) to inquire about the presence of font sources beyond the final font file(s) and the recommended build path. That build path may very well be non-trivial and hard to reproduce accurately by the maintainer. If a full font build path is made available by the upstream author(s) please be aware that any regressions and changes you may introduce when doing a rebuild for packaging purposes is your own responsibility as a package maintainer since you are effectively creating a separate branch. You should make it very clear to your users that your rebuilt version is not the canonical one from upstream.
5.10 Can I add other Reserved Font Names when making a derivative font?
Yes. List your additional Reserved Font Names after your additional copyright statement, as indicated with example placeholders at the top of the OFL.txt file. Be sure you do not remove any existing RFNs but only add your own. RFN statements should be placed next to the copyright statement of the relevant author as indicated in the OFL.txt template to make them visible to designers wishing to make their separate version.
6 ABOUT THE FONTLOG
6.1 What is this FONTLOG thing exactly?
It has three purposes: 1) to provide basic information on the font to users and other designers and developers, 2) to document changes that have been made to the font or accompanying files, either by the original authors or others, and 3) to provide a place to acknowledge authors and other contributors. Please use it!
6.2 Is the FONTLOG required?
It is not a requirement of the license, but we strongly recommend you have one.
6.3 Am I required to update the FONTLOG when making Modified Versions?
No, but users, designers and other developers might get very frustrated with you if you don't. People need to know how derivative fonts differ from the original, and how to take advantage of the changes, or build on them. There are utilities that can help create and maintain a FONTLOG, such as the FONTLOG support in FontForge.
6.4 What should the FONTLOG look like?
It is typically a separate text file (FONTLOG.txt), but can take other formats. It commonly includes these four sections:
- brief header describing the FONTLOG itself and name of the font family
- Basic Font Information - description of the font family, purpose and breadth
- ChangeLog - chronological listing of changes
- Acknowledgements - list of authors and contributors with contact information
It could also include other sections, such as: where to find documentation, how to make contributions, information on contributing organizations, source code details, and a short design guide. See Appendix A for an example FONTLOG.
7 MAKING CONTRIBUTIONS TO OFL PROJECTS
7.1 Can I contribute work to OFL projects?
In many cases, yes. It is common for OFL fonts to be developed by a team of people who welcome contributions from the wider community. Contact the original authors for specific information on how to participate in their projects.
7.2 Why should I contribute my changes back to the original authors?
It would benefit many people if you contributed back in response to what you've received. Your contributions and improvements to the fonts and other components could be a tremendous help and would encourage others to contribute as well and 'give back'. You will then benefit from other people's contributions as well. Sometimes maintaining your own separate version takes more effort than merging back with the original. Be aware that any contributions, however, must be either your own original creation or work that you own, and you may be asked to affirm that clearly when you contribute.
7.3 I've made some very nice improvements to the font. Will you consider adopting them and putting them into future Original Versions?
Most authors would be very happy to receive such contributions. Keep in mind that it is unlikely that they would want to incorporate major changes that would require additional work on their end. Any contributions would likely need to be made for all the fonts in a family and match the overall design and style. Authors are encouraged to include a guide to the design with the fonts. It would also help to have contributions submitted as patches or clearly marked changes - the use of smart source revision control systems like subversion, mercurial, git or bzr is a good idea. Please follow the recommendations given by the author(s) in terms of preferred source formats and configuration parameters for sending contributions. If this is not indicated in a FONTLOG or other documentation of the font, consider asking them directly. Examples of useful contributions are bug fixes, additional glyphs, stylistic alternates (and the smart font code to access them) or improved hinting. Keep in mind that some kinds of changes (esp. hinting) may be technically difficult to integrate.
7.4 How can I financially support the development of OFL fonts?
It is likely that most authors of OFL fonts would accept financial contributions - contact them for instructions on how to do this. Such contributions would support future development. You can also pay for others to enhance the fonts and contribute the results back to the original authors for inclusion in the Original Version.
8 ABOUT THE LICENSE ITSELF
8.1 I see that this is version 1.1 of the license. Will there be later changes?
Version 1.1 is the first minor revision of the OFL. We are confident that version 1.1 will meet most needs, but are open to future improvements. Any revisions would be for future font releases, and previously existing licenses would remain in effect. No retroactive changes are possible, although the Copyright Holder(s) can re-release the font under a revised OFL. All versions will be available on our web site: http://scripts.sil.org/OFL.
8.2 Does this license restrict the rights of the Copyright Holder(s)?
No. The Copyright Holder(s) still retain(s) all the rights to their creation; they are only releasing a portion of it for use in a specific way. For example, the Copyright Holder(s) may choose to release a 'basic' version of their font under the OFL, but sell a restricted 'enhanced' version under a different license. They may also choose to release the same font under both the OFL and some other license. Only the Copyright Holder(s) can do this, and doing so does not change the terms of the OFL as it applies to that font.
8.3 Is the OFL a contract or a license?
The OFL is a worldwide license based on international copyright agreements and conventions. It is not a contract and so does not require you to sign it to have legal validity. By using, modifying and redistributing components under the OFL you indicate that you accept the license.
8.4 I really like the terms of the OFL, but want to change it a little. Am I allowed to take ideas and actual wording from the OFL and put them into my own custom license for distributing my fonts?
We strongly recommend against creating your very own unique open licensing model. Using a modified or derivative license will likely cut you off - along with the font(s) under that license - from the community of designers using the OFL, potentially expose you and your users to legal liabilities, and possibly put your work and rights at risk. The OFL went though a community and legal review process that took years of effort, and that review is only applicable to an unmodified OFL. The text of the OFL has been written by SIL (with review and consultation from the community) and is copyright (c) 2005-2017 SIL International. You may re-use the ideas and wording (in part, not in whole) in another non-proprietary license provided that you call your license by another unambiguous name, that you do not use the preamble, that you do not mention SIL and that you clearly present your license as different from the OFL so as not to cause confusion by being too similar to the original. If you feel the OFL does not meet your needs for an open license, please contact us.
8.5 Can I quote from the OFL FAQ?
Yes, SIL gives permission to quote from the OFL FAQ (OFL-FAQ.txt), in whole or in part, provided that the quoted text is:
- unmodified,
- used to help explain the intent of the OFL, rather than cause misunderstanding, and
- accompanied with the following attribution: "From the OFL FAQ (OFL-FAQ.txt), copyright (c) 2005-2017 SIL International. Used by permission. http://scripts.sil.org/OFL-FAQ_web".
8.6 Can I translate the license and the FAQ into other languages?
SIL certainly recognises the need for people who are not familiar with English to be able to understand the OFL and its use. Making the license very clear and readable has been a key goal for the OFL, but we know that people understand their own language best.
If you are an experienced translator, you are very welcome to translate the OFL and OFL-FAQ so that designers and users in your language community can understand the license better. But only the original English version of the license has legal value and has been approved by the community. Translations do not count as legal substitutes and should only serve as a way to explain the original license. SIL - as the author and steward of the license for the community at large - does not approve any translation of the OFL as legally valid because even small translation ambiguities could be abused and create problems.
SIL gives permission to publish unofficial translations into other languages provided that they comply with the following guidelines:
- Put the following disclaimer in both English and the target language stating clearly that the translation is unofficial:
"This is an unofficial translation of the SIL Open Font License into <language_name>. It was not published by SIL International, and does not legally state the distribution terms for fonts that use the OFL. A release under the OFL is only valid when using the original English text. However, we recognize that this unofficial translation will help users and designers not familiar with English to better understand and use the OFL. We encourage designers who consider releasing their creation under the OFL to read the OFL-FAQ in their own language if it is available. Please go to http://scripts.sil.org/OFL for the official version of the license and the accompanying OFL-FAQ."
- Keep your unofficial translation current and update it at our request if needed, for example if there is any ambiguity which could lead to confusion.
If you start such a unofficial translation effort of the OFL and OFL-FAQ please let us know.
8.7 Does the OFL have an explicit expiration term?
No, the implicit intent of the OFL is that the permissions granted are perpetual and irrevocable.
9 ABOUT SIL INTERNATIONAL
9.1 Who is SIL International and what do they do?
SIL serves language communities worldwide, building their capacity for sustainable language development, by means of research, translation, training and materials development. SIL makes its services available to all without regard to religious belief, political ideology, gender, race, or ethnic background. SIL's members and volunteers share a Christian commitment.
9.2 What does this have to do with font licensing?
The ability to read, write, type and publish in one's own language is one of the most critical needs for millions of people around the world. This requires fonts that are widely available and support lesser-known languages. SIL develops - and encourages others to develop - a complete stack of writing systems implementation components available under open licenses. This open stack includes input methods, smart fonts, smart rendering libraries and smart applications. There has been a need for a common open license that is specifically applicable to fonts and related software (a crucial component of this stack), so SIL developed the SIL Open Font License with the help of the Free/Libre and Open Source Software community.
9.3 How can I contact SIL?
Our main web site is: http://www.sil.org/
Our site about complex scripts is: http://scripts.sil.org/
Information about this license (and contact information) is at: http://scripts.sil.org/OFL
APPENDIX A - FONTLOG EXAMPLE
Here is an example of the recommended format for a FONTLOG, although other formats are allowed.
-----
FONTLOG for the GlobalFontFamily fonts
This file provides detailed information on the GlobalFontFamily Font Software. This information should be distributed along with the GlobalFontFamily fonts and any derivative works.
Basic Font Information
GlobalFontFamily is a Unicode typeface family that supports all languages that use the Latin script and its variants, and could be expanded to support other scripts.
NewWorldFontFamily is based on the GlobalFontFamily and also supports Greek, Hebrew, Cyrillic and Armenian.
More specifically, this release supports the following Unicode ranges...
This release contains...
Documentation can be found at...
To contribute to the project...
ChangeLog
10 December 2010 (Fred Foobar) GlobalFontFamily-devel version 1.4
- fix new build and testing system (bug #123456)
1 August 2008 (Tom Parker) GlobalFontFamily version 1.2.1
- Tweaked the smart font code (Branch merged with trunk version)
- Provided improved build and debugging environment for smart behaviours
7 February 2007 (Pat Johnson) NewWorldFontFamily Version 1.3
- Added Greek and Cyrillic glyphs
7 March 2006 (Fred Foobar) NewWorldFontFamily Version 1.2
- Tweaked contextual behaviours
1 Feb 2005 (Jane Doe) NewWorldFontFamily Version 1.1
- Improved build script performance and verbosity
- Extended the smart code documentation
- Corrected minor typos in the documentation
- Fixed position of combining inverted breve below (U+032F)
- Added OpenType/Graphite smart code for Armenian
- Added Armenian glyphs (U+0531 -> U+0587)
- Released as "NewWorldFontFamily"
1 Jan 2005 (Joe Smith) GlobalFontFamily Version 1.0
- Initial release
Acknowledgements
If you make modifications be sure to add your name (N), email (E), web-address (if you have one) (W) and description (D). This list is in alphabetical order.
N: Jane Doe
E: jane@university.edu
W: http://art.university.edu/projects/fonts
D: Contributor - Armenian glyphs and code
N: Fred Foobar
E: fred@foobar.org
W: http://foobar.org
D: Contributor - misc Graphite fixes
N: Pat Johnson
E: pat@fontstudio.org
W: http://pat.fontstudio.org
D: Designer - Greek & Cyrillic glyphs based on Roman design
N: Tom Parker
E: tom@company.com
W: http://www.company.com/tom/projects/fonts
D: Engineer - original smart font code
N: Joe Smith
E: joe@fontstudio.org
W: http://joe.fontstudio.org
D: Designer - original Roman glyphs
Fontstudio.org is an not-for-profit design group whose purpose is...
Foobar.org is a distributed community of developers...
Company.com is a small business who likes to support community designers...
University.edu is a renowned educational institution with a strong design department...
-----

View File

@@ -1,93 +0,0 @@
Copyright 2017 The Brass Mono Project Authors (github.com/fonsecapeter/brass_mono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

5921
fonts/BrassMonoCode.h Normal file

File diff suppressed because it is too large Load Diff

2854
fonts/FiraCode.h Normal file

File diff suppressed because it is too large Load Diff

23
fonts/Font.cc Normal file
View File

@@ -0,0 +1,23 @@
#include "Font.h"
#include "imgui.h"
namespace kte::Fonts {
void
Font::Load(const float size) const
{
const ImGuiIO &io = ImGui::GetIO();
io.Fonts->Clear();
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
this->data_,
this->size_,
size);
if (!font) {
font = io.Fonts->AddFontDefault();
}
(void) font;
io.Fonts->Build();
}
} // namespace kte::Fonts

34
fonts/Font.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <utility>
#include "BrassMonoCode.h"
namespace kte::Fonts {
// Provide default embedded font aliases used by GUIFrontend fallback loader
inline const unsigned int DefaultFontSize = BrassMonoCode::DefaultFontBoldCompressedSize;
inline const unsigned int *DefaultFontData = BrassMonoCode::DefaultFontBoldCompressedData;
class Font {
public:
Font(std::string name, unsigned int *data, const unsigned int size)
: name_(std::move(name)),
data_(data),
size_(size) {}
std::string Name()
{
return name_;
}
void Load(float size) const;
private:
std::string name_;
unsigned int *data_{nullptr};
unsigned int size_{0};
};
}

17
fonts/FontList.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "B612Mono.h"
#include "BrassMono.h"
#include "BrassMonoCode.h"
#include "FiraCode.h"
#include "Go.h"
#include "IBMPlexMono.h"
#include "Idealist.h"
#include "Inconsolata.h"
#include "InconsolataExpanded.h"
#include "Iosevka.h"
#include "IosevkaExtended.h"
#include "ShareTech.h"
#include "SpaceMono.h"
#include "Syne.h"
#include "Triplicate.h"
#include "Unispace.h"

94
fonts/FontRegistry.cc Normal file
View File

@@ -0,0 +1,94 @@
#include "FontRegistry.h"
#include "FontList.h"
namespace kte::Fonts {
void
InstallDefaultFonts()
{
FontRegistry::Instance().Register(std::make_unique<Font>(
"default",
const_cast<unsigned int *>(BrassMono::DefaultFontBoldCompressedData),
BrassMono::DefaultFontBoldCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"b612",
const_cast<unsigned int *>(B612Mono::DefaultFontRegularCompressedData),
B612Mono::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"brassmono",
const_cast<unsigned int *>(BrassMono::DefaultFontBoldCompressedData),
BrassMono::DefaultFontBoldCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"brassmonocode",
const_cast<unsigned int *>(BrassMonoCode::DefaultFontBoldCompressedData),
BrassMonoCode::DefaultFontBoldCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"fira",
const_cast<unsigned int *>(FiraCode::DefaultFontRegularCompressedData),
FiraCode::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"go",
const_cast<unsigned int *>(Go::DefaultFontRegularCompressedData),
Go::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"ibm",
const_cast<unsigned int *>(IBMPlexMono::DefaultFontRegularCompressedData),
IBMPlexMono::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"idealist",
const_cast<unsigned int *>(Idealist::DefaultFontRegularCompressedData),
Idealist::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"inconsolata",
const_cast<unsigned int *>(Inconsolata::DefaultFontRegularCompressedData),
Inconsolata::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"inconsolataex",
const_cast<unsigned int *>(InconsolataExpanded::DefaultFontRegularCompressedData),
InconsolataExpanded::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"iosevka",
const_cast<unsigned int *>(Iosoveka::DefaultFontRegularCompressedData),
Iosoveka::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"iosevkaex",
const_cast<unsigned int *>(IosevkaExtended::DefaultFontRegularCompressedData),
IosevkaExtended::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"sharetech",
const_cast<unsigned int *>(ShareTech::DefaultFontRegularCompressedData),
ShareTech::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"space",
const_cast<unsigned int *>(SpaceMono::DefaultFontRegularCompressedData),
SpaceMono::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"syne",
const_cast<unsigned int *>(Syne::DefaultFontRegularCompressedData),
Syne::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"triplicate",
const_cast<unsigned int *>(Triplicate::DefaultFontRegularCompressedData),
Triplicate::DefaultFontRegularCompressedSize
));
FontRegistry::Instance().Register(std::make_unique<Font>(
"unispace",
const_cast<unsigned int *>(Unispace::DefaultFontRegularCompressedData),
Unispace::DefaultFontRegularCompressedSize
));
}
}

122
fonts/FontRegistry.h Normal file
View File

@@ -0,0 +1,122 @@
#pragma once
#include <cassert>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include "Font.h"
namespace kte::Fonts {
class FontRegistry {
public:
// Get the global instance
static FontRegistry &Instance()
{
static FontRegistry instance;
return instance;
}
// Register a font (usually done at startup or static initialization)
void Register(std::unique_ptr<Font> font)
{
std::lock_guard lock(mutex_);
auto name = font->Name();
assert(fonts_.find(name) == fonts_.end() && "Font already registered!");
fonts_[std::move(name)] = std::move(font);
}
// Get a font by name (const access)
const Font *Get(const std::string &name) const
{
std::lock_guard lock(mutex_);
const auto it = fonts_.find(name);
return it != fonts_.end() ? it->second.get() : nullptr;
}
// Convenience: load a font by name and size
bool LoadFont(const std::string &name, const float size)
{
if (auto *font = Get(name)) {
font->Load(size);
// Track current selection
{
std::lock_guard lock(mutex_);
current_name_ = name;
current_size_ = size;
}
return true;
}
return false;
}
// Request font load to be applied at a safe time (e.g., before starting a new frame)
// Thread-safe. Frontend should call ConsumePendingFontRequest() and then LoadFont().
void RequestLoadFont(const std::string &name, float size)
{
std::lock_guard lock(mutex_);
pending_name_ = name;
pending_size_ = size;
has_pending_ = true;
}
// Retrieve and clear a pending font request. Returns true if there was one.
bool ConsumePendingFontRequest(std::string &name, float &size)
{
std::lock_guard lock(mutex_);
if (!has_pending_)
return false;
name = pending_name_;
size = pending_size_;
has_pending_ = false;
return true;
}
// Check if font exists
bool HasFont(const std::string &name) const
{
std::lock_guard lock(mutex_);
return fonts_.count(name) > 0;
}
// Current font name/size as last successfully loaded via LoadFont()
std::string CurrentFontName() const
{
std::lock_guard lock(mutex_);
return current_name_;
}
float CurrentFontSize() const
{
std::lock_guard lock(mutex_);
return current_size_;
}
private:
FontRegistry() = default;
mutable std::mutex mutex_;
std::unordered_map<std::string, std::unique_ptr<Font> > fonts_;
// Pending font change request (applied by frontend between frames)
bool has_pending_ = false;
std::string pending_name_;
float pending_size_ = 0.0f;
// Track last applied font
std::string current_name_;
float current_size_ = 0.0f;
};
void InstallDefaultFonts();
}

8613
fonts/Go.h Normal file

File diff suppressed because it is too large Load Diff

13674
fonts/IBMPlexMono.h Normal file

File diff suppressed because it is too large Load Diff

6234
fonts/Idealist.h Normal file

File diff suppressed because it is too large Load Diff

5635
fonts/Inconsolata.h Normal file

File diff suppressed because it is too large Load Diff

5740
fonts/InconsolataExpanded.h Normal file

File diff suppressed because it is too large Load Diff

66576
fonts/Iosevka.h Normal file

File diff suppressed because it is too large Load Diff

66699
fonts/IosevkaExtended.h Normal file

File diff suppressed because it is too large Load Diff

1190
fonts/ShareTech.h Normal file

File diff suppressed because it is too large Load Diff

10477
fonts/SpaceMono.h Normal file

File diff suppressed because it is too large Load Diff

1887
fonts/Syne.h Normal file

File diff suppressed because it is too large Load Diff

7387
fonts/Triplicate.h Normal file

File diff suppressed because it is too large Load Diff

880
fonts/Unispace.h Normal file
View File

@@ -0,0 +1,880 @@
#pragma once
namespace kte::Fonts::Unispace {
// File: 'Unispace/Unispace-Regular.ttf' (33884 bytes)
// Exported using binary_to_compressed_c.cpp
static const unsigned int DefaultFontRegularCompressedSize = 20899;
static const unsigned int DefaultFontRegularCompressedData[20900 / 4] =
{
0x0000bc57, 0x00000000, 0x5c840000, 0x00000400, 0x00010037, 0x000d0000, 0x00030080, 0x54464650, 0x3238394d,
0x84000031, 0x2c158240, 0x4544471c,
0x09f10746, 0x83000001, 0x2c0f82e4, 0x2f534f5a, 0x4c088b32, 0x0100009a, 0x360f8258, 0x616d6360, 0xfa0ed770,
0x04000030, 0x03000098, 0x736167a6,
0x82ffff70, 0x83002249, 0x381f82dc, 0x796c6708, 0xab179366, 0x0b00001d, 0x6800001c, 0x61656824, 0x9ee0bf64,
0x831b821a, 0x6836211f, 0x06231082,
0x82720124, 0x8214204f, 0x68242813, 0x2f78746d, 0x82872510, 0x00b82b0f, 0x6ce00200, 0x3161636f, 0x5f8217aa,
0x8f820820, 0x6dda0229, 0x01707861,
0x825b00b5, 0x8238201f, 0x6e202b2f, 0x5b656d61, 0x00c6f841, 0x1f827300, 0x701a0a31, 0xde74736f, 0x006ff819,
0x005c7d00, 0x847e0600, 0x00052edb,
0x8b588000, 0x3c0f5fde, 0x030b00f5, 0x243782e8, 0x31257c00, 0x2b0783f0, 0x7b2834e2, 0x0dfffeff, 0xbd036602,
0x08220f82, 0x05820200, 0xf1820283,
0xffbd0323, 0x2083820d, 0x221f8263, 0x846602fd, 0x87198349, 0x83042003, 0x6c012611, 0x07005800, 0x24008300,
0x00000002, 0x82258401, 0x2e0b8289,
0x02040000, 0x00900163, 0x02000005, 0x8258028a, 0x024b2411, 0x828a02bc, 0x00c52424, 0x82fa0032, 0x05002315,
0x06830609, 0x00a00428, 0x00002f00,
0x23820a20, 0x542b0284, 0x004f5059, 0x25170040, 0x824d03ca, 0x82c82083, 0x20f3249b, 0x83930000, 0x8102231c,
0x07822803, 0x01002026, 0x1b006302,
0x00260982, 0x00004d01, 0x09836302, 0x00e40025, 0x824800aa, 0x001b2615, 0x00020128, 0x2201828d, 0x82510057,
0x00302e15, 0x002c00ee, 0x002b0023,
0x00280024, 0x22018221, 0x821c0023, 0x82242009, 0x00e42c1b, 0x0062007f, 0x0030007e, 0x82200035, 0x823c2019,
0x00332217, 0x2201822e, 0x82330021,
0x82282013, 0x0025220f, 0x831b8225, 0x82262039, 0x82382009, 0x00202649, 0x00260002, 0x201b821a, 0x2459828d,
0xff05008d, 0x209f82fe, 0x2013821e,
0x22138230, 0x822d0024, 0x00242409, 0x8272002e, 0x002d2271, 0x2035821e, 0x22058219, 0x823a0028, 0x822d209d,
0x00153013, 0x001f0009, 0x0037000e,
0x0001016f, 0x8256006f, 0x00e422bf, 0x266f8278, 0x00fe001a, 0x82670039, 0x00782289, 0x20bd825a, 0x22458230,
0x82740053, 0x00b52409, 0x82e300b2,
0x00ee245d, 0x82cc00f4, 0x005a2811, 0x00150017, 0x8230001e, 0x8820209b, 0x00172203, 0x20b7823c, 0x20038c33,
0x87bf8419, 0x827a2023, 0x82232077,
0x830383c3, 0x821a20c1, 0x881e2043, 0x82102003, 0x8224204f, 0x200383a1, 0x837b822e, 0x84232003, 0x001921bf,
0x51200185, 0x21870782, 0x37820e20,
0x7b820e20, 0x03881e20, 0x3f823c20, 0x28220387, 0x2b820a00, 0x8b822820, 0x03902420, 0x26002e22, 0x03855d82,
0x1f82fc82, 0x038c2e20, 0x28002622,
0x24200182, 0x2d200382, 0x25200390, 0x038b8f82, 0x65822020, 0x21220387, 0x59820500, 0x03883a20, 0x1b002522,
0x03852d82, 0x2d003823, 0x20038700,
0x93e38223, 0x00022603, 0x001a0009, 0x2203820e, 0x82370028, 0x20038579, 0x8343841e, 0x006c2e3b, 0x0055006c,
0x00bb00ee, 0x00570095, 0x20f7823e,
0x21498226, 0x37830026, 0x3f830387, 0x30003024, 0x0184d500, 0x83005d21, 0x82552001, 0x008e2235, 0x2c59821c,
0x00cd00cd, 0x00350021, 0x00170029,
0x22098223, 0x82510033, 0x0049266d, 0x003c000b, 0x230b822c, 0x005c0051, 0x03200085, 0x03830682, 0x01001c22,
0x00240982, 0x03009c01, 0x1c2a0984,
0x80010400, 0x5c000000, 0xed824000, 0x09821c20, 0x00175808, 0x00a3007e, 0x010701b4, 0x0123011b, 0x01330127,
0x01480137, 0x017e015b, 0x021b0292,
0x03dd02c7, 0x03a90394, 0x1ec003bc, 0x20f31e85, 0x201a2014, 0x2022201e, 0x20302026, 0x2044203a, 0x21ac20a4,
0x22022222, 0x2212220f, 0x221e221a,
0x2248222b, 0x25652260, 0x82ffffca, 0x00003a5b, 0x00200017, 0x00a500a0, 0x010a01b6, 0x0126011e, 0x0136012a,
0x014a0139, 0x245d825e, 0x02c60218,
0x2a5d88d8, 0x20f21e80, 0x20182013, 0x8420201c, 0x8c39205d, 0x8a11205d, 0x8464205d, 0x015a085d, 0xe4ffecff,
0xc2ffc3ff, 0xbfffc1ff, 0xbbffbdff,
0xb7ffb9ff, 0xb5ffb6ff, 0xa0ffb3ff, 0x71fe1bff, 0xabfd61fe, 0x85fd97fd, 0xc3e282fd, 0x38e157e2, 0x34e135e1,
0x30e133e1, 0x1fe127e1, 0xb7e016e1,
0x3be0b0e0, 0x50df5cdf, 0x48df4fdf, 0x39df45df, 0x06df1ddf, 0x9fdb03df, 0xb9840100, 0x062204d7, 0x5a830a02,
0x678f6482, 0x02000123, 0x87008500,
0x8a002007, 0x8a032000, 0x8401200b, 0x04bc080b, 0x06000500, 0x08000700, 0x0a000900, 0x0c000b00, 0x0e000d00,
0x10000f00, 0x12001100, 0x14001300,
0x16001500, 0x18001700, 0x1a001900, 0x1c001b00, 0x1e001d00, 0x20001f00, 0x22002100, 0x24002300, 0x26002500,
0x28002700, 0x2a002900, 0x2c002b00,
0x2e002d00, 0x30002f00, 0x32003100, 0x34003300, 0x36003500, 0x38003700, 0x3a003900, 0x3c003b00, 0x3e003d00,
0x40003f00, 0x42004100, 0x44004300,
0x46004500, 0x48004700, 0x4a004900, 0x4c004b00, 0x4e004d00, 0x50004f00, 0x52005100, 0x54005300, 0x56005500,
0x58005700, 0x5a005900, 0x5c005b00,
0x5e005d00, 0x60005f00, 0x62006100, 0x8a08c182, 0x00860085, 0x008a0088, 0x00970092, 0x00a2009d, 0x00a300a1,
0x00a400a5, 0x00a800a6, 0x00a900aa,
0x00ac00ab, 0x00ad00ae, 0x00b000af, 0x00b400b2, 0x00b500b3, 0x00b600b7, 0x00ba00bb, 0x01bd00bc, 0x00720053,
0x00660065, 0x00550169, 0x00a00077,
0x016b0070, 0x0076005d, 0x0066016a, 0x01990087, 0x01730063, 0x00680167, 0x01000067, 0x0160015e, 0x0142015f,
0x006c0064, 0x0040017b, 0x00b900a7,
0x00640080, 0x0162016e, 0x82650132, 0x6d2c088d, 0x56017c00, 0x81006300, 0x96008400, 0x08010701, 0x4c014b01,
0x51015001, 0x4e014d01, 0x6901b800,
0x2b01c000, 0x5c015a01, 0x59015801, 0x20082f82, 0x00540100, 0x014f0178, 0x00570152, 0x008b0083, 0x008c0082,
0x008e0089, 0x0090008f, 0x0094008d,
0x3c238295, 0x009b0093, 0x009a009c, 0x013701ea, 0x0171003d, 0x013a0139, 0x0179003b, 0x013c013e, 0x221f8238,
0x883c0000, 0x004e2a01, 0x008a0060,
0x011c01ca, 0x0863824e, 0x9c017cac, 0xd401c001, 0xf001e201, 0x0a02fc01, 0x58024402, 0xb2028202, 0xf002ce02,
0x34032203, 0xa6037403, 0xca03b803,
0xf003dc03, 0x32040404, 0x80046604, 0xd204b004, 0x0e05f604, 0x4a052405, 0x78056205, 0xae059205, 0xda05be05,
0x2206f205, 0x7a064406, 0xd006a406,
0x0207e206, 0x36071607, 0x68075207, 0x92078007, 0xb207a007, 0xd207c407, 0x0a08e007, 0x4e082e08, 0xa2087408,
0xec08be08, 0x22090809, 0x60094409,
0x92097409, 0xda09ac09, 0x220afe09, 0x640a380a, 0x9c0a820a, 0xca0aae0a, 0xf80ae40a, 0x3e0b100b, 0x780b4c0b,
0x01829e0b, 0x0bb00a0a, 0x0cfe0bdc,
0x0c340c22, 0x0c900c7e, 0x0d1c0df4, 0x0d480d38, 0x0d960d56, 0x0dd20da4, 0x0e140eee, 0x0e480e3a, 0x0e700e64,
0x0e900e7c, 0x0fda0ebe, 0x0f440f08,
0x0fb40f86, 0x10f80fd6, 0x1050101e, 0x10ae1076, 0x11f810d0, 0x11381118, 0x1180115c, 0x11bc119e, 0x12fe11dc,
0x1258122a, 0x12c4128e, 0x134413fe,
0x139a1380, 0x140214da, 0x1454142a, 0x149c1480, 0x15f614be, 0x155a1528, 0x16d21590, 0x1658160a, 0x16c4169e,
0x173017fa, 0x17a21768, 0x17da17be,
0x181a18fa, 0x187c184c, 0x19e418b0, 0x1960191c, 0x19b2199a, 0x1a101af0, 0x1a541a30, 0x1a961a7a, 0x1adc1abc,
0x1b301bfe, 0x1b9c1b5e, 0x1c101cce,
0x1c5e1c38, 0x1cac1c86, 0x1d021dd8, 0x1d5c1d30, 0x1db61d88, 0x1e0c1ed6, 0x1e781e38, 0x1eca1e96, 0x1f401ffa,
0x1f9c1f64, 0x201420d4, 0x20742040,
0x20d620a2, 0x212021fc, 0x215a213e, 0x21aa2182, 0x220e22da, 0x223e222a, 0x228c225e, 0x22d022ae, 0x230423e8,
0x2336231a, 0x236a234e, 0x239c2380,
0x23d223b2, 0x241024f0, 0x244e242e, 0x24942470, 0x25de24b8, 0x25482514, 0x26ca258a, 0x26422608, 0x26ba266c,
0x270627ea, 0x27522736, 0x27a62786,
0x280a28d8, 0x286e283c, 0x28da28a4, 0x291629f2, 0x29562932, 0x29962972, 0x2afc29cc, 0x2a442a24, 0x2aa22a76,
0x2b222be6, 0x2b762b50, 0x2ce02bae,
0x2c322c0a, 0x2c722c52, 0x2cb22c94, 0x2dee2cd0, 0x2d2e2d0c, 0x2d762d50, 0x2dda2da8, 0x2e162ef2, 0x2e3a2e28,
0x2e602e54, 0x2eac2e8a, 0x2ede2eca,
0x2f2a2ff4, 0x2f5c2f44, 0x2fa62f82, 0x30f02fcc, 0x3044301c, 0x307c3060, 0x3098308a, 0x30b430a6, 0x30d630c2,
0x310031ec, 0x31343116, 0x315a3142,
0x31e031ce, 0x320032f2, 0x3254322a, 0x32ac3278, 0x32d832c0, 0x33fc32e6, 0x335e3340, 0x33c0339e, 0x34f433da,
0x08018312, 0x00000064, 0xff1b0003,
0x03490238, 0x000b0095, 0x00290025, 0x22210500, 0x33341135, 0x11153221, 0x15210314, 0x15163233, 0x012b0614,
0x14150622, 0x36343317, 0x3632013b,
0x03263435, 0x01331523, 0x6195fee7, 0x626b0161, 0xfcbbfeb9, 0x2b36372a, 0x1e523d21, 0x41383b27, 0xc6555344,
0x54c88787, 0x5555b403, 0x06824cfc,
0x3f54433b, 0x3b5b5241, 0x5f542739, 0x765c5b7f, 0x00744dfd, 0x00e40002, 0x037f0100, 0x36818228, 0x13000007,
0x07231133, 0xee231533, 0x9b0a8787,
0xfd28039b, 0x82786aba, 0x02aa2623, 0x03b90165, 0x2223875a, 0x82372315, 0x5eaa2b23, 0x5d5db25e, 0xf5f55a03,
0x238200f5, 0x04824820, 0x47841a20,
0x47821f20, 0x35331524, 0x04823303, 0x15200382, 0x0382a382, 0x23352323, 0x2c038215, 0x23353335, 0x96eb3335,
0x4f964ee4, 0x2300824a, 0x554e964f,
0x012c0082, 0x01d7d7ee, 0xf2f2f23a, 0xd146d748, 0x46220082, 0x6d8248d7, 0x53821b20, 0x53824920, 0x26001d24,
0x55822f00, 0x86353321, 0x060d414f,
0x57085087, 0x34352622, 0x33151336, 0x34353632, 0x23032326, 0x14150622, 0x9d013b16, 0xcccc3d73, 0x3a3c2f91,
0xde3d9131, 0x46347bde, 0x1c4aed45,
0x871c2323, 0x1e1e1845, 0xc5024518, 0xc8446363, 0x634e5073, 0xd1434545, 0x5d4f4e72, 0x3dd194fe, 0x013f2a2b,
0x2a2c3928, 0x00050039, 0x48207f83,
0x1322d384, 0x81821f00, 0x00003b3c, 0x23013301, 0x16323303, 0x0614011d, 0x2622012b, 0x3634013d, 0x3b141517,
0x09823201, 0x01201082, 0x54081b9b,
0xf4fe4997, 0x2861244a, 0x61282424, 0x26212229, 0x1b1b341a, 0x39011a34, 0x23232862, 0x22286228, 0x331b2422,
0x1b331c1c, 0xd8fc2803, 0x40342803,
0x3435407b, 0x33417b41, 0x3d3e7a74, 0x73fe3d7b, 0x3f7c4034, 0x7c3f3535, 0x7a743440, 0x3c7c3d3f, 0x00020000,
0x20048228, 0x22a3823c, 0x82210008,
0x22fa879d, 0x82153303, 0x16142e0a, 0x23152133, 0x21153311, 0x34352622, 0x08038237, 0x6d01363e, 0x36332e66,
0xe0a8662b, 0x30362b9b, 0x7838012b,
0x4483fe78, 0x534a5053, 0x54579901, 0xe502614a, 0x48744d43, 0x43aafe43, 0x309a668b, 0x6d537538, 0x02010100,
0x61016502, 0x03005a03, 0x58826182,
0x5f020127, 0xf55a035f, 0x2a008200, 0xff8d0001, 0x03d6010d, 0x851300bc, 0x0622271b, 0x16141115, 0x7e82013b,
0x35262108, 0x01363411, 0x2d67577f,
0x246b4b46, 0x8f8f7545, 0x5a44bc03, 0x4debfc31, 0x5a69443a, 0x79571c03, 0x3f8b3282, 0x32331323, 0x263d8216,
0x35012b06, 0x82363233, 0x2b263e3d,
0x61578d01, 0x44759091, 0x484b6a25, 0xbc03662c, 0xe4fc567a, 0x3c44695a, 0x3115034b, 0x247f845a, 0x02730157,
0x27ff820b, 0x13000011, 0x07173717,
0x17250282, 0x27072707, 0x08028237, 0x55c42725, 0x7b2c6e18, 0x4f617e1d, 0x296d1955, 0x607d1d79, 0x7f620b03,
0x6c2a7a1d, 0x614f5619, 0x2a791e7f,
0x8254186f, 0x01002b46, 0xe4005100, 0x82021102, 0x67420b00, 0x3523300a, 0x4cbb5123, 0xbb4cb9b9, 0xa6a6dc01,
0x82b3b345, 0xffe43527, 0x007f0149,
0x00030078, 0x03333700, 0x2e9be423, 0xd1fe786d, 0x30264385, 0x31025301, 0x1b839901, 0x1521132b, 0x01023021,
0x9901fffd, 0x281c8246, 0x00ee0001,
0x00750100, 0x26378575, 0x87ee2315, 0x82757587, 0x2c002512, 0x38020000, 0x4f82bf82, 0x0133012d, 0x55e30123,
0x03584cfe, 0x82d8fc28, 0x82232029,
0x8241201b, 0x0009231b, 0x0b820013, 0x16030134, 0x3632013b, 0x05341135, 0x13171411, 0x22012b26, 0x33413706,
0x41222008, 0x3908056f, 0x140fd7c3,
0xfe362b5e, 0x10db0cc6, 0x372b6015, 0x5443f125, 0x41f14255, 0x99025355, 0x5407b1fd, 0x2581013b, 0x267ffe25,
0x094f021f, 0x597c9855, 0x785a7ffe,
0x81015979, 0xa6827c59, 0x00010023, 0x2073822b, 0x3a738439, 0x11211300, 0x35211533, 0x73231133, 0xfdbe0801,
0x0383cbf2, 0x431bfd28, 0x82a20243,
0x86242027, 0x001b219b, 0x9d442782, 0x011d2108, 0x3520f782, 0x08089f44, 0x77012b28, 0x55432f01, 0x29a84553,
0xfd99012d, 0xa74350e3, 0x2a37352c,
0x7c2803e7, 0x4e795a5d, 0xec43a942, 0x3f54765d, 0x7b85553e, 0x53822820, 0x7b823a20, 0x53871f20, 0x011e0729,
0x23061415, 0x82213521, 0x823420f6,
0x433520f0, 0x2f0806e3, 0x6b013021, 0x274c5543, 0xfe43552c, 0x2a320186, 0x862b3637, 0x362e2c86, 0x03ddfe2b,
0x845d7728, 0x3f6e182f, 0x5c43815b,
0x445a3f42, 0x54404350, 0x02225f83, 0xb3862100, 0x0d000229, 0x03010000, 0x82330333, 0x15233ce0, 0x35213523,
0xf7f76f01, 0x5a5ab33b, 0x02b2fe78,
0x02effde9, 0x3fb0fd50, 0x835a9899, 0x88012037, 0x83142037, 0x21152597, 0x16323311, 0x2508958a, 0x01212123,
0xe6a2fedd, 0x545b5962, 0x2d018ffe,
0x3630363e, 0x2803c5fe, 0x6f00ff43, 0x5c4386f0, 0x00435c63, 0xcb410002, 0x83162007, 0x2113284d, 0x06222315,
0x4421011d, 0x24080948, 0x36341135,
0x16141513, 0x3d32013b, 0xc8233401, 0x2ed81001, 0x3c170128, 0xd35e4844, 0x4047475e, 0x54642e28, 0x35548238,
0x5180473b, 0x4e6d8f48, 0xb2016d4e,
0x78fe4d6e, 0x6c3b49d9, 0xa7855b96, 0xdf821c20, 0x28034824, 0xa7830500, 0x01230135, 0x2c021c21, 0x017ea8fe,
0x0367fe43, 0x02d8fc28, 0x440300e5,
0x1720082f, 0x2d2e8782, 0x23210000, 0x34352622, 0x012e3736, 0x7a823435, 0x06141522, 0x26057341, 0x012b3427,
0x82141522, 0x054e4512, 0x35370882,
0x92012634, 0x304e5bc1, 0x97252e35, 0x2c2697c1, 0x304f3035, 0x82615561, 0x6e250802, 0x6333303a, 0x6e34633a,
0x145b616d, 0xd1445315, 0x155344d1,
0x6d605b15, 0x9f9fe06e, 0x41a2029d, 0x4f919150, 0x21a48242, 0x37420002, 0x002c2105, 0x42050741, 0xe14106bd,
0x013d2205, 0x0a504521, 0x82826e82,
0xd4c72e08, 0x5e48475f, 0x1701b2fe, 0xeafe272d, 0x3f45403e, 0x2d27d63a, 0x2c035666, 0x4afe6d4e, 0x3a434e6d,
0x4a4e6248, 0xb44d6eb2, 0x49f87b93,
0x4662823a, 0x012107d3, 0x05af46d8, 0x15333724, 0x03821123, 0x829be421, 0x78782400, 0x8276d801, 0x00022624,
0x0149ffe4, 0x2023877f, 0x231f8213,
0x23033315, 0x2e212383, 0x0822826d, 0xd1feea30, 0x7f000100, 0xe5017c00, 0x0600d702, 0x01130000, 0x17150715,
0x66017f15, 0xa401eeee, 0xca5b3301,
0x005bca11, 0x62000200, 0x02023301, 0x6b854e02, 0x15211324, 0x03821121, 0xa0016224, 0x038460fe, 0x01457825,
0x8200471b, 0x8a7e204b, 0x0109324b,
0x27353735, 0xfe67017e, 0x02eeee99, 0xfecdfed7, 0x844f83d8, 0x82302097, 0x03342654, 0x00190028, 0x0c59431d,
0x26231522, 0x22095743, 0x82132123,
0x302a08b1, 0x55426d01, 0x38414453, 0x521e273b, 0x362b213d, 0xdcfe2a37, 0x03878762, 0x5b5c7628, 0x27545f7f,
0x525b3b39, 0xfd543f41, 0x5a827590,
0x0002002d, 0x0259ff35, 0x0081022c, 0x8423001c, 0x1115325b, 0x34352223, 0x3311013b, 0x21233411, 0x14111522,
0x08bc8233, 0x1135223c, 0x22230134,
0x013b1415, 0x651f01a8, 0x616d63fe, 0xe7fe2547, 0x8a012121, 0x01737cfe, 0x20202d28, 0x9181022d, 0xa2ab62fe,
0x5b01f3fe, 0xe6fd5151, 0x02784337,
0xe6fe911f, 0x68826c69, 0x20000226, 0x44020000, 0x0322c382, 0xfa820b00, 0x33032608, 0x13332703, 0x07232723,
0x5b2f0123, 0xb75f5bbc, 0xd62186b7,
0xd1028621, 0xdc0124fe, 0xb0d8fc57, 0x000300b0, 0x20338221, 0x22338243, 0x84100008, 0x431120f9, 0x032006e9,
0x34290883, 0x21252326, 0x14151632,
0x08038207, 0x21230636, 0x362abda6, 0xb2bd2b35, 0x2a36342c, 0x7f01c9fe, 0x53485543, 0x76fe4355, 0xc7fe7c01,
0x5c40435a, 0xdcfe6901, 0x4355824d,
0x3d745d78, 0x845c883a, 0x3c000100, 0x28205f82, 0x13205f82, 0x1521f983, 0x05a74621, 0x2620ee84, 0x363cef82,
0xfe5401d4, 0x36362af3, 0xfe0d012a,
0x555444ac, 0x55432803, 0x3b7ffe3d, 0x5a784354, 0x47061c45, 0x3d20056b, 0x09214382, 0x84458300, 0x871120a1,
0x1411219a, 0xad239782, 0x823729b0,
0x01cb2491, 0x8255437d, 0x0283328f, 0x535efde5, 0x3d81013c, 0x597c4355, 0x79597ffe, 0x22008200, 0x82330001,
0x822f208b, 0x850b2047, 0x4211208b,
0x332c0635, 0x8cfefc01, 0xf2fe0e01, 0x04fe7401, 0xfe298082, 0xd0fe44d2, 0x00010043, 0x202f822e, 0x20778435,
0x07614200, 0x2e23112b, 0x82fe0702,
0xe7fe1901, 0x232a8389, 0x99fe44c6, 0x2b875b84, 0xe78b1720, 0x11013b27, 0x11213523, 0x05234621, 0x6101c731,
0x352ce7fe, 0x92ac2a37, 0x92fe0c01,
0x86545742, 0x533c2eed, 0xfe453001, 0x01587a48, 0x007b5a81, 0x08934101, 0x3326a783, 0x33112111, 0x05822311,
0x87212336, 0x88881301, 0x0387edfe,
0x018ffe28, 0x01d8fc71, 0x008dfe73, 0xd78d2f82, 0x3d462320, 0x25d78207, 0x04febaba, 0x6682baba, 0x465efd21,
0x3520063f, 0x2e20d382, 0x0d20d382,
0x1120a783, 0x38084e45, 0x01d42311, 0xfe41565a, 0x2b19019e, 0x2803dc37, 0x7959aafd, 0x023c5343, 0x485f8313,
0x0c2007eb, 0x33373382, 0x33133311,
0x03230109, 0x28231107, 0x8afb0887, 0x1401ecfe, 0x8308ee97, 0x01a12b94, 0xfe84fe5f, 0x02810154, 0x378781fe,
0x17453a20, 0x27c78206, 0x87282115,
0xeefd8b01, 0x1b215d82, 0x201f8343, 0x208b8225, 0x841f823e, 0x011b2157, 0x032ee683, 0x23110323, 0x75739825,
0x746b6899, 0x2a82686a, 0xb002502d,
0x6a02d8fc, 0x660296fd, 0x82009afd, 0x00012100, 0x09203787, 0x01218f83, 0x2b378211, 0x25231101, 0x6a1d0192,
0x66f9feac, 0x69223382, 0x33829702,
0xaffd5122, 0x00202e82, 0x20051743, 0x22678243, 0x821f000f, 0x14112331, 0xce473b16, 0x14c94705, 0x2a399d3a,
0x36362a64, 0x382b642a, 0x5542f51a,
0x43f54156, 0x53025454, 0x533c7ffe, 0x21055242, 0x53429956, 0x07a04205, 0x22099f42, 0x4412000a, 0x11220a3b,
0x43431323, 0x01283d06, 0x5456427d,
0x8686f744, 0x34342caf, 0x8e28032c, 0xfe876769, 0xfee502bd, 0x659860a3, 0x2123a385, 0x850284ff, 0x9b2320a3,
0x231723a3, 0x4d422327, 0x379f2305,
0xa783622b, 0x372b622f, 0x5543f418, 0x3b314157, 0x41583b6b, 0x2aab8355, 0x53523c7e, 0x3d82013b, 0x82985555,
0x587e28ab, 0x787c7c79, 0x48820159,
0x022c056b, 0x00002600, 0x28033d02, 0x19001100, 0x2808f147, 0x23011d16, 0x2b263435, 0x3fb68901, 0x457b0126,
0x7f555a57, 0x85ae2b35, 0x342baf85,
0x28032937, 0x3c85597e, 0xbbbbaa2b, 0x93fe664c, 0xcd22bf82, 0xab478257, 0x07934106, 0x00001d26, 0x22211501,
0x44055147, 0x35220535, 0x59843221,
0x2d08b782, 0x02333634, 0x5af0fe0c, 0x6a643936, 0xfe575f5f, 0x651601bb, 0x534a6143, 0x034d5358, 0x4c8d4328,
0x6e6d703e, 0x55a44383, 0x6e637633,
0x4a820072, 0x82380021, 0x822c20ab, 0x830720ab, 0x231522a9, 0x25018211, 0xb4f40138, 0x2f82bb85, 0x021bfd23,
0x202383e5, 0x20238223, 0x06634441,
0xde413320, 0x22098208, 0x82012b06, 0x7d233c74, 0x2b602c35, 0x42568035, 0x035541f0, 0x42aafd28, 0x02414e4d,
0x59aafd56, 0x83597979, 0x8220203f,
0x8644203f, 0x13332163, 0x03290182, 0x86862023, 0xb7868909, 0x242e82b7, 0xfcd1022f, 0x208b84d8, 0x20278202,
0x2027825f, 0x848b820f, 0x332b8527,
0x23032303, 0x05318602, 0x054f4850, 0x945e8431, 0x9339043a, 0x412c3782, 0x1cfee401, 0xd8fcbf02, 0x9ffe6101,
0x26203f84, 0x2006ef42, 0x083f830b,
0x33011b25, 0x0b231303, 0x31132301, 0x7d867e8c, 0x7b8eb9ba, 0x03c58a84, 0x01cafe28, 0xfe75fe36, 0xfe470163,
0x849d01b9, 0x821a2037, 0x82492037,
0x87082077, 0x23112937, 0x8a8f1a11, 0x87d98690, 0x68282f82, 0xfafd9801, 0x2401defe, 0xcb492b84, 0x82092007,
0x15213e2b, 0x21152101, 0x28210135,
0x85fe1202, 0xeefd7b01, 0x72fe8e01, 0xfd3f2803, 0x022c435a, 0x0c474cb9, 0x2f075f41, 0x8d211533, 0xdada4901,
0xbc03b7fe, 0x44d9fb44, 0x270e2b4b,
0x23013313, 0xb801542c, 0x20052a4b, 0x09874c00, 0x11253f84, 0x11333521, 0x2a3f8223, 0xdadab7fe, 0x51fbbc03,
0x82270444, 0x01052423, 0x825e02c3,
0x000624bf, 0x46010900, 0x30270590, 0xcb592e01, 0x825aca11, 0xed9b31bf, 0xff0100ed, 0x0217fffe, 0x006dff66,
0x07000003, 0x0226b482, 0x98fd6802,
0xa2825693, 0x93263f82, 0x81019d02, 0x1b831603, 0x1517132b, 0xeeee9327, 0x45341603, 0x2b1c821a, 0x001e0002,
0x02450200, 0x00110081, 0x13320f82,
0x15163221, 0x35222111, 0x21333634, 0x2b263435, 0x994d0101, 0x76290807, 0x475e2a01, 0x428b64fe, 0x26150149,
0x4801f230, 0x2c333cb2, 0x8102c035,
0x3cfe6e4f, 0x516061be, 0xfffe313d, 0x3c434437, 0x051f4300, 0x4d033e2c, 0x15000b00, 0x33130000, 0x2e4a3315,
0x232b0805, 0x33111321, 0x013d3632,
0x26232634, 0x4c58ed87, 0x8cfe574d, 0x292cb587, 0x4d032d27, 0xd27761cc, 0x3d026077, 0x513506fe, 0x823d4aed,
0x0001229b, 0x209b8230, 0x469b8231,
0x1d2608fb, 0x3b161401, 0x6e491501, 0x01d43c06, 0x30defe5d, 0xcf694026, 0x4a5aa3fe, 0x4381024a, 0x4ffb4a3b,
0x7158432c, 0x4e5871ef, 0x87830963,
0x1d4d1620, 0x4eda8205, 0x1e2407f9, 0x11013b01, 0x2008dc82, 0xfe86eacc, 0x4a4d5790, 0x3b371a3b, 0x282eb37d,
0xfccc8102, 0xd27760b3, 0xa3cb6078,
0x011c575d, 0x828a83fa, 0x822420d3, 0x84412050, 0x831f208b, 0x1532254b, 0x15212314, 0x17208d8b, 0x3806c74f,
0x0622012b, 0x8686f4c7, 0x3f2df0fe,
0x59c4feec, 0xd03d4a4a, 0x3025262f, 0x365b837a, 0x5145b7be, 0x72574333, 0xc85872ee, 0x4543316a, 0x00003c36,
0x822d0001, 0x0336245b, 0x8310004d,
0x36342159, 0x2323e182, 0x43331522, 0x2d3605ac, 0xc65a4a9f, 0x9a9a598a, 0x81029f87, 0x88446666, 0x02c2fd43,
0x0082003e, 0x26000226, 0x3e020dff,
0x1d209384, 0x25098d46, 0x3d363233, 0x904a2301, 0x83112006, 0x08e683a1, 0x7301cb2b, 0xfffe5a4a, 0xed396383,
0x3a4b485d, 0x88b62f28, 0x8102394c,
0x567456fd, 0x344c3043, 0x72ed7457, 0xfcfebb57, 0xfb013946, 0x205b8233, 0x06ef4a01, 0x0f004d22, 0x33205982,
0x2108da82, 0x23111516, 0x2b263411,
0x24231101, 0x475df287, 0xbc2d2786, 0xcc4d0387, 0x3bfe6d4f, 0x3d49b901, 0x9385c1fd, 0x2305c747, 0x0009004d,
0x4d052147, 0x13200689, 0x2e2ace82,
0xfdc04701, 0xb6c0c0f9, 0x79829b9b, 0x4343c22f, 0x1001fa01, 0x00020070, 0x010dff72, 0x22ff82fb, 0x8613000f,
0x012b29c7, 0x3e323335, 0x23113502,
0x722c3883, 0x5c487f01, 0x292c79db, 0xeef80921, 0x472c3c84, 0x06434f6c, 0x0233361e, 0x700f0161, 0x0c20af8c,
0x33294182, 0x33373311, 0x03230109,
0x29ac8323, 0xfe88f009, 0x9e2301f2, 0xaf8207f1, 0xfb39fe2a, 0xa0fedffe, 0xd8fe2801, 0x7b413682, 0x82352006,
0x8409207b, 0x2fad8679, 0xc444012d,
0xbebef8fd, 0xf6fc4d03, 0xc6024343, 0x27432782, 0x05374e09, 0x82080d41, 0x1e233c08, 0x475d8301, 0x232e2884,
0x0284584e, 0xfe6d4e81, 0x49b8013a,
0x02c2fd3d, 0x82c2fd3e, 0x0025263b, 0x023f0200, 0x05114181, 0x49413220, 0x01252d0a, 0x84465e76, 0x85bc2d28,
0x6c4f8102, 0x4b213583, 0x8231823b,
0x1900212e, 0x4b200482, 0x0f223382, 0x99821f00, 0x16141524, 0x3a43013b, 0x073d4f05, 0xc9511d20, 0x9f25080a,
0x3f4c402d, 0x7a2e272d, 0xea1e282e,
0x5a4a4a5a, 0x49495bea, 0x58e5b901, 0xf2513339, 0x803d3d48, 0x73ef7356, 0x82048456, 0x1e002556, 0x46020dff,
0x9f435b82, 0x08cc4d05, 0x13231522,
0x32081b4a, 0x5982011e, 0xfc5b4b4d, 0x2fc48686, 0x025a3827, 0x82705981, 0x03f32a41, 0x3905fe31, 0x4a050146,
0x2047832d, 0x20478228, 0x2347873c,
0x35231101, 0x22067642, 0x4a231733, 0x3b2705a7, 0x863c0201, 0x82475dea, 0x4b862a90, 0xb32e2838, 0x8cfc8102,
0x2e8782f3, 0x32435871, 0x46fbfe45,
0x00000039, 0x823a0001, 0x8229204c, 0x820a2047, 0x1521210c, 0x23343b84, 0x22013411, 0x4eef0701, 0x8102882a,
0xfe5f2f43, 0xc2bf0150, 0x00251e82,
0x0200001b, 0x202b8249, 0x210c821d, 0x31532115, 0x47322006, 0x22080828, 0x2b263436, 0x35262201, 0x19023334,
0x2218b2fe, 0x46f01822, 0xfe434348,
0x1d65016f, 0xf5222025, 0x829c4a3d, 0x2b3b2e4f, 0x4c673a29, 0x4143665c, 0x446b3958, 0x204a82bd, 0x2fd08300,
0x00280335, 0x13000012, 0x15331533,
0x16141123, 0x4a820582, 0x3339d782, 0xdddd86d2, 0x6760542a, 0x03a5a53d, 0xfe43a728, 0x43294977, 0x9f01623d,
0x097b4243, 0x4706df41, 0x1123053f,
0x82211133, 0x8424353a, 0x88bb2e28, 0x455e86fe, 0x46fe8102, 0x3e023c48, 0x6d4e7ffd, 0x15203384, 0x4f20c782,
0x06203382, 0x33079346, 0x93881523,
0xbcbf8897, 0xc6fd8102, 0x7ffd3a02, 0x09000100, 0x5a202382, 0x0c202382, 0x1b342386, 0x23033301, 0x0923010b,
0x48423b88, 0x65883d3f, 0x982a2c98,
0x01232f83, 0x8371fe8f, 0x30012333, 0x3782d0fe, 0x37821f20, 0x00217182, 0x0837830b, 0x33371721, 0x27231303,
0x1f132307, 0x7a989084, 0x8f84cecf,
0x02cc8190, 0xfef5f581, 0xf8b7fec8, 0x844401f8, 0xff0e248f, 0x8256020d, 0x8607206b, 0x23012b6b, 0xa1950e37,
0xb5fe6aa8, 0xbc82656e, 0xe6011a29,
0x00f38cfc, 0x82370001, 0x822c205b, 0x0c1f4727, 0xf5013735, 0x5301adfe, 0x66010bfe, 0x81029afe, 0x43fdfd3b,
0x84160228, 0xff6f2c57, 0x03f5010d,
0x001e00bc, 0x50330100, 0x1423053d, 0x45160706, 0x8b4906c9, 0x36273605, 0x3634013d, 0x3727ce01, 0x2f40462c,
0x25552f6f, 0x6d8f7515, 0x0577536d,
0x512bf134, 0xfc49471c, 0x69443750, 0x5e04015a, 0xf05d3b32, 0x5a827957, 0x01010023, 0x205b8201, 0x215b8262,
0x5b820003, 0x01231129, 0x03616101,
0x8f51fbbc, 0x46132077, 0x1721057f, 0x09a45306, 0x37226f82, 0x0582012e, 0x2b267d08, 0x62256f01, 0x8f6f6f90,
0x52221276, 0x3e306e34, 0x03352d46,
0xf05779bc, 0x5f313a5e, 0x695afcfe, 0xfc4e3944, 0x511c454b, 0x5a31f12b, 0x56000100, 0x0c024d01, 0x1900ec01,
0x33130000, 0x33021e32, 0x33353632,
0x012b0614, 0x23022e22, 0x23150622, 0x4bb03634, 0x23192816, 0x4e1d1913, 0x1c372b3a, 0x18241429, 0x2f4e1b1c,
0x211bec01, 0x3c27301b, 0x1e241e62,
0x5c43273a, 0xe4000200, 0x7f22bf82, 0x834f3302, 0x23053305, 0x23373311, 0x75013335, 0x9b0a8787, 0x4602f39b,
0x2382766a, 0xdb827820, 0x2803e926,
0x1d001500, 0x99420982, 0x56332005, 0x22330597, 0x34113526, 0x07013b36, 0x33161411, 0x01222311, 0x82822f38,
0x2f210800, 0x3535404b, 0x235f4b40,
0x033d223c, 0xfe307428, 0x75753021, 0x2101523d, 0xfe8d3d52, 0x011f38d5, 0x204882df, 0x07c74c00, 0x97411520,
0x24568209, 0x21152111, 0x08848211,
0x36343521, 0xaff82801, 0xa4a4372a, 0x63fe1e01, 0x03555c5c, 0x3d514328, 0xc5fe4691, 0x46800145, 0x82775a91,
0x49002036, 0x18200757, 0x21063342,
0x42821503, 0x31570384, 0x1a352708, 0x8e8a898e, 0x00827ed9, 0x82718721, 0x28033100, 0xa3015dfe, 0x2f12fafd,
0x51512e62, 0x142f622e, 0xfe26e382,
0x660124ff, 0x0741bc03, 0x82372005, 0x821120ca, 0x68fe2103, 0xb7260082, 0x98046dfe, 0x23826efe, 0x80ff392a,
0x28032a02, 0x33002700, 0xa6826d82,
0x4f0afa43, 0x6d53065d, 0x08355608, 0x23133208, 0x1614010e, 0x3536013b, 0x01c32634, 0x18effe37, 0xc2182121,
0x3737423d, 0xa1fe3c3e, 0x261d2701,
0x3cb81d26, 0x483f3f4b, 0x1f1891f7, 0x27ab1920, 0x08e78226, 0x292a3b23, 0x6053703b, 0x4f6d2c30, 0x2c414361,
0x4f713b29, 0x6b2f305e, 0x93fe5c50,
0x39543801, 0x3a29471c, 0x28008200, 0x02670002, 0x03fc01b0, 0x20b78516, 0x20fb8213, 0x2b038225, 0x01878767,
0x0387870e, 0x66666616, 0x212c1982,
0x4102e9ff, 0x13003f03, 0x4c002500, 0x83096b41, 0x45152095, 0x9f08062e, 0x020e2237, 0x031e1415, 0x013e3233,
0x27012e10, 0x14071e32, 0x2223070e,
0x3435082e, 0x0f01063e, 0x231c5886, 0x86581c23, 0x4b38372a, 0x13354e44, 0x44361c0e, 0x29585a36, 0x2b5b5828,
0x1e273742, 0x02060c13, 0x130c0602,
0x4237271e, 0x333c272b, 0x0f152026, 0x03020509, 0x271c1009, 0x7602473a, 0xda233125, 0x42262f23, 0x4534da36,
0x8b521a8b, 0x547d5f76, 0x9a35112b,
0x37993a01, 0x1d0d073e, 0x5936381f, 0x59548054, 0x1d1e3837, 0x0d05070d, 0x37212011, 0x394f5236, 0x385f5d47,
0x08161c36, 0x782eeb84, 0xeb016d01,
0x12002903, 0x00001a00, 0x274a3313, 0x05da5105, 0x34354808, 0x17012b26, 0x15062223, 0xb0013b14, 0xfe3340c8,
0x39392cf2, 0x1812a32c, 0x2a56cfa6,
0x03811d1e, 0xfe4e3b29, 0x3a3c4fcd, 0x18232650, 0x4c241da4, 0x5a000200, 0x0902a400, 0x06003602, 0x00000d00,
0x07153701, 0x852f1517, 0x01273406,
0x8f8fc841, 0x8fc9e7c8, 0x8d01c98f, 0x6a6a5fa9, 0x853cad5f, 0x01002b06, 0xf1005100, 0xdc011102, 0x35820500,
0x3523152e, 0x11023521, 0x0184fe44,
0x45a6ebdc, 0x0121a782, 0x19cb5600, 0x2e000431, 0x35021601, 0x10003f03, 0x21001600, 0x85002c00, 0x071421c7,
0x2a05bb4d, 0x2315012b, 0x32331513,
0x83272334, 0x33162aca, 0x26343632, 0x14163227, 0x056b5406, 0xb2b53d08, 0x282a2920, 0x3e512d3d, 0x2e2e523e,
0x7e7e5614, 0x7f7f5756, 0x98986c57,
0x9a986b6c, 0x242fc402, 0x430f1833, 0x8d464747, 0x76761d01, 0x5d5c8d67, 0x8abe8b8d, 0xa0e8a12e, 0xa27374a0,
0x0026ae82, 0x02bb0253, 0x874b0310,
0x15212a05, 0xbd015321, 0x160343fe, 0x2bb7825b, 0x01740002, 0x03f00191, 0x000f004d, 0x59055f4a, 0xff470fa9,
0x95e72f0c, 0x3e36363e, 0x35353e95,
0x7f17131f, 0x04841317, 0x414d033a, 0x434f9a4f, 0x4f9a5042, 0x2ab08441, 0xb2292526, 0x00222228, 0x51000200,
0x23580482, 0x580f2005, 0x11200d25,
0x51218582, 0x052958bb, 0x40fec02c, 0xa6a6dc01, 0xfeb3b345, 0x928245ae, 0x00010029, 0x01a001b5, 0x412a03ae,
0x142108f1, 0x05cd5b07, 0x32333526,
0x012b3435, 0x20080686, 0x2823aeb5, 0x22292a2a, 0x21218eae, 0x21214545, 0x402a038e, 0x2224352f, 0x303c3034,
0x3d313d3e, 0x224c823e, 0x82b20001,
0x03b2224b, 0x06374528, 0x22254785, 0x1533011d, 0x061e5421, 0x26342508, 0x8fd7012b, 0x21282920, 0xffb61d4d,
0x49232600, 0x0f14140f, 0x4028036d,
0x3a3a2e2f, 0x31773047, 0x23322238, 0xe3244b84, 0xd1019d02, 0x2a064741, 0xe3071537, 0xe202eeee, 0x841a5f34,
0xff28241b, 0x823d020d, 0x820e2067,
0x112122e9, 0x21018523, 0x64822622, 0x7e01bf37, 0x2a6d6c7b, 0x03545344, 0x03e5fb28, 0x023afcc6, 0x6a688636,
0x28eb858d, 0x014a01ee, 0x00bf0175,
0x32378203, 0xee231533, 0xbf018787, 0x00010075, 0x010dfff4, 0x83ceff6f, 0x33172917, 0x7bf42307, 0xc1324f2c,
0xcc2d1782, 0x9101a101, 0x09002a03,
0x33130000, 0x31318211, 0x23113335, 0xc53a83d4, 0x2a033a42, 0x2f2fa6fe, 0x58822901, 0x74000228, 0xf0016c01,
0xd7412903, 0x1338262f, 0x12174e17,
0x37048212, 0x42290313, 0x4251994f, 0x50995142, 0x2b979141, 0x992a2425, 0x00242428, 0x270f4343, 0x07151713,
0x37273735, 0x5a320685, 0x8f8fc9c9,
0x8fc8c8e7, 0xac36028f, 0x695faa3c, 0x06855f6b, 0x04000024, 0x3c821700, 0xbb824e20, 0x0d000328, 0x1b001800,
0x1d5c0000, 0x20c58705, 0x2ecf8401,
0x23352315, 0x33073735, 0xfe49fc01, 0x82064c2c, 0x39412fd9, 0x295aac01, 0x99924529, 0x28036363, 0xe787d8fc,
0xfe8ffe2d, 0x45472dec, 0x00cbc535,
0x82150003, 0x864f205b, 0x9128205b, 0x41322059, 0x23200af9, 0x0806f941, 0x26343523, 0xfa01012b, 0x4b2bfe4a,
0xc63b8206, 0x57013942, 0x2929218e,
0xb51d4c21, 0x492325fe, 0x1014150f, 0x2e6f8d6d, 0x3b2e303f, 0x77304739, 0x18233930, 0x83002219, 0x821e20d3,
0x84462077, 0x000e2477, 0x852c0011,
0x20c98e79, 0x088a5533, 0x4208bc42, 0x012106c3, 0x24e483f5, 0x285ba501, 0x2cde8228, 0x40fe6464, 0x2a2822a2,
0xa221292a, 0x05d44282, 0x80838220,
0x87880121, 0xb60221e8, 0x2910e242, 0x30000200, 0x34020dff, 0x03573402, 0x21052305, 0xe0872622, 0x055f3320,
0x33162a09, 0x35230321, 0xfe340233,
0x0f045793, 0x62240125, 0x57f38787, 0x02220d03, 0xde827470, 0x56030021, 0xbd20069b, 0x0b220982, 0x9d560f00,
0x1713230d, 0xa1562715, 0xee73220c,
0x0aa456ee, 0x4fbd0321, 0x439d05eb, 0x07153722, 0xc320438c, 0x8920438d, 0x8d055f43, 0x8e122043, 0x17332387,
0x2c572723, 0x98c6260e, 0x4f527579,
0x228e8c75, 0x914e4e76, 0x8e1d20d3, 0x3423244b, 0x49163233, 0x2324065c, 0x06222622, 0x8c2e9d8c, 0x5a2d8855,
0x55181a1e, 0x57334342, 0x5e8b163a,
0x2c655828, 0x392b1c10, 0x63831732, 0x37410420, 0x8e13200e, 0x06b34765, 0x47205b8c, 0x8c05c047, 0x05cb47b2,
0x0000202c, 0xbd034402, 0x17001300,
0x49822300, 0x2705574a, 0x27231307, 0x13230723, 0x28055950, 0x27033303, 0x33171415, 0x06575f36, 0x5e023708,
0xb2172227, 0x21d62186, 0x2117b186,
0x5bbc5b54, 0x19351a38, 0x031b311c, 0x39211cbd, 0xeefc0d28, 0x1203b0b0, 0x2239280d, 0x24feec1b, 0x35afdc01,
0x1d06071c, 0x6a821f35, 0x00266582,
0x28034b02, 0x71820300, 0x03130025, 0x5f271133, 0x2f080516, 0x15331523, 0x03231121, 0x752cd023, 0xcad1019f,
0xfeca7d7d, 0x651f7ece, 0x60fee902,
0x3f3fa001, 0xbe4360fe, 0xfe010143, 0x000200fa, 0x020dff3c, 0x57053f58, 0x33220c59, 0x55571521, 0x33132306,
0x45582307, 0x7b63230f, 0x49584f2c,
0xa6fc220f, 0x579383c1, 0xbd24052b, 0x0f000b00, 0x230d0558, 0x27151713, 0x220c0958, 0x58eeee60, 0x8342080c,
0x58022007, 0x3f930643, 0x07153722,
0xb0203f8c, 0x7f423f8b, 0x203f8b06, 0x427f8e12, 0x428c057d, 0x7d42b320, 0x42868a05, 0xc78b087b, 0x498f1320,
0x25231525, 0x8c231533, 0x4234204a,
0x4a8a0515, 0x66666623, 0x58cf9100, 0x3720083d, 0x58050f41, 0x0b410541, 0x05445805, 0x8e43d820, 0x233b9b05,
0x0715013f, 0x41087d58, 0x3b850507,
0x8643a420, 0x413b8c05, 0x77890507, 0x85080741, 0x0903417a, 0xd8254285, 0x004e4e76, 0x0c874103, 0x418aff85,
0x4285ff88, 0x4286fb89, 0x8382fa83,
0xfeff192e, 0x28034a02, 0x1b000d00, 0x21130000, 0x29050561, 0x23112123, 0x11133335, 0x50821533, 0x36323208,
0x26341135, 0x7e013423, 0x42565543,
0x1b1b82fe, 0xaf7e7e87, 0x2b36362b, 0x597c2803, 0x7a597efe, 0x01428301, 0x42defe22, 0x3a55c1fe, 0x553d8201,
0x57578200, 0xbd22050b, 0x57840900,
0xa1583320, 0x44132007, 0xb3581013, 0x44872008, 0xc2580d0f, 0x4403200b, 0x03220b10, 0x60822000, 0xbd034327,
0x1f000f00, 0x1c295800, 0x82262221,
0x373624ba, 0x58271517, 0x1f2214d1, 0xd458eeee, 0x34952319, 0xb3451a45, 0x236baa06, 0x0715013f, 0x6f206b94,
0x61226b9b, 0x6b8f5f34, 0x01592620,
0x42d7861b, 0x6e940525, 0x35437220, 0x23de9a05, 0x004e4e76, 0x3120df8e, 0xbf4173a2, 0x207e9410, 0x0dcb4138,
0x30208699, 0x4509e945, 0x6b4105e7,
0xa1272009, 0x4527208d, 0x839406fb, 0xed430d20, 0x317b9905, 0x66666695, 0x01000000, 0x02017a00, 0x6d02e901,
0x71820b00, 0x63173721, 0x280808c5,
0x8584317a, 0x2f7f8335, 0x7f367f82, 0x842f3e02, 0x81853484, 0x357f8231, 0x0300007f, 0xbeff0e00, 0x56035602,
0x1f001700, 0x25a98300, 0x37173233,
0x43640733, 0x225d0806, 0x37230727, 0x34113526, 0x01111736, 0x22012b26, 0x16010506, 0x3632013b, 0x1df7bb35,
0x3b551f13, 0xf741552d, 0x58271915,
0x29542a41, 0x271d0a01, 0x01372c63, 0x1af7fe27, 0x362b632b, 0x380a2803, 0xfe59406a, 0x097a597e, 0x5b3d7b49,
0x7c598201, 0x017efed5, 0x3d552aea,
0x542615fe, 0x61b3823b, 0xc74607d3, 0x13002105, 0x2312f159, 0x27151713, 0x220cf559, 0x59eeee70, 0x02210ff8,
0x08c744eb, 0x9b053f5a, 0x1537224f,
0x204f8c07, 0x204f92c0, 0x08db44b7, 0x1a214f89, 0x429f9500, 0x528c05bd, 0xb542c320, 0x44a69105, 0x538905ef,
0x55441720, 0x11e75a05, 0x8c07c14f,
0x42442056, 0x56910509, 0x66666625, 0x51020000, 0xbd230603, 0x53000800, 0x032407a5, 0x03112311, 0x615af082,
0x83062006, 0x5afe20ea, 0x02210864,
0x5ce68365, 0x0c2209f3, 0x61571400, 0x06142308, 0x8455012b, 0x26342906, 0xf7862823, 0x43555642, 0x2c09f55c,
0x67698ea7, 0x3d029b88, 0x9861a3fe,
0x097f5164, 0x28004d22, 0x2407c94e, 0x011e0706, 0x64458315, 0x2b20066f, 0x26059166, 0x012b2634, 0x82150622,
0x343e0897, 0x4eb9e036, 0x2b2b315a,
0xb54b5d39, 0x373c2d7b, 0x20606028, 0x5b2d2921, 0x4f8a302d, 0x5c724d03, 0x0a087049, 0x91614d75, 0x41457443,
0x4151435d, 0x3a3f4952, 0x6c026ffd,
0x03005988, 0xf0821e00, 0x48034524, 0xf5821100, 0x2d5a0b82, 0x1701231a, 0x315a2715, 0xe9fe2316, 0x355aeeee,
0x05032511, 0x001a4534, 0x032363ac,
0x96071537, 0x93c72063, 0xd1022162, 0x8d056042, 0x002121c7, 0x5a06b946, 0x032014f5, 0x96056842, 0x42c42066,
0xcd930572, 0x41086748, 0x2c200b33,
0x1e456b9d, 0x20769610, 0x0e2045fe, 0x2110825b, 0x034ba002, 0x0db7410d, 0x859c2220, 0x16450120, 0x207b9706,
0x050f43bd, 0x6623f393, 0x4d006666,
0x452a088b, 0x11006603, 0x2a001a00, 0x6f9c3700, 0x786a0320, 0x064b6611, 0x22012b22, 0xa8318496, 0x2122275e,
0x20285e28, 0x0f152221, 0x311c251f,
0x318e921b, 0x39211c23, 0x231b1b23, 0x3d1b2239, 0x24141035, 0x9b821f35, 0x10000339, 0x56020000, 0x1d008102,
0x32002600, 0x33010000, 0x2b141532,
0x82141501, 0x21153574, 0x36343522, 0x3435013b, 0x35012b26, 0x36173233, 0x06222303, 0xa46b1a84, 0x08938207,
0x7e01062e, 0x88656573, 0xfe6d2f22,
0x36326851, 0x72241c8c, 0x1a1a309c, 0x262d424e, 0x654c2722, 0x1c1c2359, 0x1f211823, 0xb7be8102, 0x43335145,
0x30051a5d, 0xfe111143, 0x434437bc,
0x7581013c, 0x35464331, 0x258b8237, 0xff300002, 0xdf5c020d, 0x089b4b05, 0x4b0de15c, 0xe55c059b, 0x7b98230c,
0xe95c4f2c, 0x4dfd240d, 0x5b0300c1,
0x482206af, 0xd7481300, 0x5cd78306, 0x03231aa1, 0x5c271517, 0x182216a5, 0xbc87eeee, 0x240ba85c, 0x45344601,
0xb1b6821a, 0x3713236b, 0x6b960715,
0x6b963820, 0xaa431220, 0x206b8e05, 0x21795d26, 0xb0431320, 0x206e9605, 0x07b0433b, 0x2611875d, 0x4e764601,
0x4104004e, 0x9d470e47, 0x1ceb5d05,
0x30430320, 0x46729606, 0x5141063f, 0x46662015, 0x335d0543, 0x5d482005, 0x17221033, 0x335d2715, 0xee612507,
0xfd8102ee, 0x2005335d, 0x0588410b,
0x5f650220, 0x23379006, 0x0715013f, 0xab203787, 0xd7203789, 0x89085f47, 0x5c10206f, 0x3b410bf5, 0x203a8706,
0x072c41ae, 0xd3447687, 0x223f8908,
0x8d11000d, 0x05394c41, 0x2f204287, 0x87072c44, 0x05334d42, 0x24058b47, 0x000a004d, 0x2a3d8221, 0x013b1411,
0x11353632, 0x4c032223, 0x2b210642,
0x05a44a01, 0x08068360, 0x6554aa31, 0x38d6272e, 0x5e100120, 0xd35e4949, 0x3c43465e, 0x2d281601, 0xfee301d9,
0x493b6ccc, 0x0f017701, 0x29fe6c4f,
0x6d4e4f6c, 0x51482d01, 0x4c313325, 0x3f260643, 0x0d004803, 0x7f441f00, 0x5e232007, 0x474c06bf, 0x08875d12,
0x5d0e484c, 0x02210b96, 0x095545e3,
0x9f5d0320, 0x4c5f8205, 0xa15d0747, 0x1737231e, 0xa55d2715, 0xee312214, 0x15a85dee, 0x434cc720, 0x00192405,
0xa94b0200, 0x013f2367, 0x67940715,
0x67978120, 0x3f4c9320, 0x20678b05, 0x21715e26, 0x67433720, 0x206a9405, 0x05384284, 0x7624d696, 0x00004e4e,
0x200d3f41, 0x476fa231, 0x7a94101c,
0x1a474a20, 0x2082950d, 0x09c74162, 0xc7410420, 0xa227200e, 0x06ef4389, 0x05207f94, 0x9505f042, 0x66c72477,
0x83006666, 0x00512cfb, 0x021102d1,
0x00030099, 0x820b0007, 0x20518271, 0x25038211, 0x21152107, 0x008287ee, 0xc0019d2c, 0x2a0140fe, 0x58c80159,
0x25824565, 0xb1ff192c, 0xd9024b02,
0x1e001400, 0x2f822800, 0x33372123, 0x061f5807, 0x5c230721, 0x362205c7, 0x5a6f1517, 0x6f052007, 0x3008056e,
0xbd34013d, 0x54300401, 0x5a4a413b,
0x32582cea, 0x3c4a252d, 0x1810e810, 0x01272f7a, 0x1611dd23, 0x022d3f4c, 0x236d5881, 0x5673ef91, 0x55125a4f,
0x31078257, 0x1d2fe5c8, 0x3b09ad01,
0x0967fe1e, 0x12f25133, 0x0f450200, 0x05e94308, 0x230cdd5e, 0x27151713, 0x2209e15e, 0x5eeeee6f, 0x02240be4,
0x1a45348d, 0x37223f9e, 0x3f890715,
0x3f8ebf20, 0x274b5920, 0x00242805, 0x03410200, 0x4b0d0048, 0x11240527, 0x013b1614, 0x44065d5f, 0x428906af,
0x7842c220, 0x42868d05, 0x4789066f,
0x95641120, 0x44498d05, 0x4a8905b7, 0xc8414320, 0x414a8d05, 0x022605bf, 0x0dff0e00, 0x93825602, 0x2606bd41,
0x0133011b, 0x82033723, 0x08315fcd,
0xcc841f20, 0x2105345f, 0x78451403, 0x00022805, 0x0212ff1e, 0x824d0346, 0x651720cb, 0xf44b0b17, 0x11352708,
0x1e232634, 0x7961fc86, 0x4d03360b,
0xef7059cc, 0x03ee5673, 0x3808fe2b, 0x4b030146, 0x0000002c, 0x20838c03, 0x067f5d0f, 0x9d428583, 0x20898806,
0x86c8889b, 0x5448208d, 0x03200dcb,
0x2005c953, 0x0aed6c01, 0x15211323, 0x0c115521, 0xbd013324, 0x0f5543fe, 0x845b200c, 0x0d574a83, 0x201b474b,
0x49528201, 0xfe2116cc, 0x4a5d84a9,
0x032110c4, 0x20648205, 0x0cbf5503, 0xbd551920, 0x5614200f, 0x0e240521, 0x26222301, 0x352bb18c, 0x2d2b4173,
0x7a0c723b, 0x8c7b5152, 0x322526b8,
0x4a402730, 0x4cbe8249, 0x28200e6b, 0x211ca54a, 0x6a8b3301, 0xab20c997, 0xd092758b, 0x20087c89, 0xff200002,
0x034a020d, 0x001a0028, 0x2100001e,
0x23072327, 0x07133313, 0x011d010e, 0x3b011e14, 0x081b5e01, 0x010b3c08, 0xbe010333, 0x8621d621, 0x74b7b7b6,
0x360f1a1f, 0x64953539, 0x191f1840,
0xb05bbc5b, 0xfc2803b0, 0x180920d8, 0x1c1e1214, 0x3d2f430f, 0x091f1e20, 0x24fef202, 0x0000dc01, 0x821e0002,
0x024b2b63, 0x00250081, 0x2900002e,
0xd6672201, 0x21352509, 0x11151632, 0x13206e92, 0x2307f14a, 0xeafebf01, 0x2208ec67, 0x8b475e2a, 0x6775207a,
0xda4a05fb, 0x6e4f2306, 0x828e3cfe,
0x375e0129, 0x003c4344, 0x6e020000, 0x1b5006bf, 0x05b75f07, 0x16141122, 0x23097f56, 0x0715013f, 0x220f7f56,
0x56eeee52, 0x61230f7e, 0x831a5f34,
0x8230204f, 0x03312504, 0x00130048, 0x8516334b, 0x0c334b4f, 0xeeee6922, 0x200d324b, 0x055b4493, 0x0222a182,
0x9b9a0328, 0x15333722, 0x22101b57,
0x8f87875d, 0x6695219b, 0xeb82ea82, 0x4f859ba0, 0x74229b8c, 0x9b8d8787, 0x4c83c720, 0x820b3741, 0x411320f6,
0x37261237, 0x17332723, 0x3a413337,
0x56ec200f, 0x077008f2, 0x761f230c, 0xa7714e4e, 0x053f4108, 0x734c5788, 0x8725200d, 0x21a68c57, 0x57850301,
0x5120ab8d, 0x2b055b51, 0x02000028,
0x00bd033d, 0x00130009, 0x45665583, 0x56252008, 0x01200813, 0x68705585, 0x5501210f, 0x70705686, 0x4703210f,
0x032cb084, 0x00000a00, 0x4d035702,
0x16000b00, 0x33225b84, 0xc5693335, 0x33013812, 0xc9972307, 0x4bc4fe73, 0x16333f42, 0x9a6b3330, 0x71012327,
0x6944266a, 0x3e2310ce, 0x82c14e01,
0x56002052, 0x282057bb, 0x64205c82, 0x1322af82, 0x6b5f1e00, 0x08b18709, 0x35013b32, 0x14151323, 0x013b011e,
0x06222311, 0x4686f1a7, 0x57aefe46,
0xcc5a4a4d, 0x371a06f1, 0x2e955f3b, 0x37160328, 0x45fd5b37, 0x78d27760, 0xfbfe3a60, 0x5809846a, 0x2122116b,
0xb7732111, 0x82132005, 0x0cad5807,
0xbd012024, 0xab5843fe, 0x005b220a, 0x0e4b4d00, 0x4b4d2320, 0x82032021, 0x16d54c55, 0xb3455820, 0x13464d05,
0x00206a83, 0x1920ab8b, 0x450f5d59,
0xb58c0b4a, 0x3f452220, 0x45bc8a0a, 0xc7470cb3, 0x00132407, 0xa22d001f, 0x0cb845c3, 0x5620cd96, 0x930cb745,
0x418287d4, 0x315a1b83, 0x87bb220f,
0x20c48a87, 0x317f4166, 0x51821320, 0x4322b196, 0x514e8787, 0x00002816, 0xff330001, 0x7835020d, 0x2921058f,
0x07234201, 0xef450782, 0xa9012312,
0xbe738afe, 0x0be44509, 0xdb45bb88, 0x0200230e, 0x5f822400, 0x81024122, 0x21058b65, 0x6b6c2321, 0x013b2105,
0x920a136d, 0x6d032069, 0x01260a1f,
0x4a59b67d, 0x256d594a, 0x21768b07, 0x2c6dd05c, 0x72572406, 0x415872ee, 0x818e058b, 0x6dda0121, 0x6742073a,
0x0e0f5c0c, 0x42067144, 0x01210c60,
0x0f105c4a, 0x41056644, 0x67500e9b, 0x067d4522, 0x20169e41, 0x196750d2, 0x4e76d024, 0xb782004e, 0x2405434f,
0x001700bd, 0x08cf6625, 0x16141122,
0x200df174, 0x0dbd4227, 0x2010ff74, 0x0d30432d, 0xfe3d5523, 0x0c0b757f, 0xb7429520, 0x6e032007, 0x0327052b,
0x00130048, 0x832b001d, 0x05bb7471,
0x20152d6e, 0x6e778c03, 0x3343143b, 0x14476e0d, 0x433d0121, 0xab5008b9, 0x20ef8307, 0x23ef991b, 0x23153337,
0x2111e575, 0x7147876c, 0x22dc8d07,
0x8d000066, 0x9f2120d7, 0x821320d7, 0x22cd945f, 0x6f878743, 0x0121160c, 0x058f473d, 0x53432e20, 0x00172106,
0x1322bf9a, 0xbf920733, 0x4d5f7220,
0x23c08c09, 0x00c1a6fc, 0xc3870082, 0x13007f25, 0xa2001d00, 0x230721c3, 0x8320c394, 0xc4976682, 0x69827420,
0x82000221, 0x63022300, 0x3b492803,
0x33112505, 0x21153335, 0x33220382, 0x1f772315, 0x23113905, 0x21352117, 0x13018721, 0x88202088, 0x2187edfe,
0xfe1301a8, 0x8d9b02ed, 0x5b2a0082,
0x7301c0fd, 0x40028dfe, 0x46828989, 0x4b840120, 0x4d034126, 0x00001700, 0x45824984, 0x340d2570, 0x87462311,
0x5dd0f0f0, 0x2d278647, 0x0346879a,
0x5b373716, 0x092d703a, 0x84bb0221, 0x0c875e42, 0x60130021, 0x958205df, 0x37231122, 0x5e05d346, 0xcf460581,
0x067f5e07, 0x53066346, 0x39821737,
0x20078152, 0x05a8461b, 0x83077f52, 0x119b4638, 0x460af95e, 0x7d850e9b, 0x860e9746, 0x09134684, 0x17208789,
0x8b0d0d53, 0x2091874d, 0x0c6c4613,
0x29439887, 0x07bf4508, 0x28032f2d, 0x00002000, 0x33350129, 0x41352311, 0x23200517, 0x2a125645, 0xf1fe4201,
0xfc01baba, 0x4567baba, 0x43240b46,
0x4343a202, 0x3a45a882, 0x0573410e, 0x2405cb42, 0x001e004d, 0x22618922, 0x93153311, 0x8213205f, 0x26638216,
0x01c0c0ec, 0x8b6dc047, 0x9b182662,
0xfa01439b, 0x8ec18244, 0x6e032264, 0x62678570, 0x61600f2b, 0x08df600c, 0x41052747, 0x66210654, 0x07377a00,
0x00810222, 0x410c9371, 0x0220073f,
0x2d060655, 0x26000200, 0x42020000, 0x0d002803, 0x36821100, 0x3805af76, 0x36323335, 0x25231135, 0x01231133,
0x4156e260, 0x372b79c2, 0x88c6fe64,
0x0a937988, 0xd8fc4322, 0x0433a082, 0x0dff2800, 0x4d033802, 0x07000300, 0x1b001700, 0x82130000, 0x82032035,
0x880520f0, 0x0883724b, 0x87873234,
0x019b9b0a, 0x5c48f313, 0x292c51b3, 0x626c0921, 0x8b829b9b, 0x4d037f25, 0x72fd5c70, 0x02200f8f, 0x3c205b84,
0x0c209b82, 0x7905d573, 0x17220af5,
0xf9790733, 0x7bc2260d, 0x28034f2c, 0x0bfd79fe, 0x82c13221, 0x056747da, 0x43889f82, 0x8308d572, 0x87242143,
0x200ad972, 0x204383c6, 0x0add724d,
0x43834183, 0x2505cf76, 0x000500bd, 0xdf830009, 0x21152126, 0x07153713, 0x2105497a, 0x4c7aee02, 0x63032005,
0x0021065e, 0x06077102, 0x8b56bd20,
0x013f230f, 0x15730715, 0xeea22b07, 0xfdf802ee, 0x023c3c44, 0x5356ce7f, 0xff282308, 0xab7a020d, 0x85678805,
0x8b0124e8, 0x84e0eefd, 0x1bfd22e1,
0x20d78543, 0x732b822d, 0x23740573, 0x2307210e, 0xc8206387, 0x7d73cf84, 0xc5fc2305, 0x008200c1, 0x85000221,
0x8b4d20cb, 0x330123cb, 0xcb852307,
0x87060121, 0x4d032164, 0x2d252f86, 0x3f020000, 0x108b7403, 0x33822520, 0x01216787, 0x20688a97, 0x20678c44,
0x0c334128, 0x86153321, 0x87e52467,
0x82280387, 0xd10122ca, 0x415e8266, 0x6390062f, 0x2f820120, 0x80206388, 0x49743282, 0xeafe2106, 0xdb7b3582,
0x052d410b, 0x05152526, 0x21152111,
0x1022c982, 0xe37bf0fe, 0xacfe2905, 0xfe7f517f, 0x010045c2, 0x11286388, 0x21130000, 0x07153711, 0x2406a17c,
0x11373507, 0x3b658223, 0xfdc4c0c0,
0xbbbbbef8, 0xfe4d03be, 0x354635a4, 0x434398fe, 0x46334301, 0x003e0132, 0x200d4f63, 0x6341820d, 0x4982094f,
0x22084163, 0x63eeeebe, 0x11420c35,
0x00022205, 0x57408225, 0x7d840547, 0x200bbd74, 0x573f8313, 0x40820839, 0x520b2d57, 0x3f8205f5, 0x020dff25,
0x4228033e, 0x7b88064f, 0x07331722,
0x2009717c, 0x05b941cf, 0x4208757c, 0x252007bb, 0x37753b82, 0x847b9005, 0x837b883f, 0x847c8b40, 0x22bb8540,
0x82bd033e, 0x8b10207b, 0x06194af7,
0x0121fa88, 0x083a6058, 0x7f4e8288, 0x20ff8b06, 0x0f475814, 0x86884787, 0x8a8b4887, 0x76d20227, 0x01004e4e,
0x22cb8300, 0x5428033e, 0x0121057b,
0x0a604411, 0x23013d22, 0x28077e7d, 0x96db5c48, 0x0921292c, 0x09867d42, 0xe976a020, 0x02232406, 0x86affd51,
0x023f2247, 0x05fd5081, 0x77057165,
0x3422082c, 0x49832b26, 0x49828f83, 0x49837c20, 0x20085b76, 0x21478602, 0x6276db01, 0x0ebf6306, 0x23220b65,
0x21152127, 0x24142d63, 0xfebd0121,
0x1a2b6343, 0xef575b20, 0x596b830e, 0x6b831e2f, 0x20146157, 0x576b830f, 0x6783165f, 0xdf652020, 0x002d230a,
0xad7e1300, 0x0d614b1e, 0x1f20dd94,
0x7e0bb047, 0x6a4b18c7, 0x83eb8e08, 0x4beb9f83, 0xf5940c6d, 0x838a0d20, 0x4148fc96, 0x0b0b6508, 0x17001326,
0x00002700, 0x09a14818, 0x2405585b,
0x23073337, 0x20038225, 0x0e1d4103, 0xb07fb720, 0x6a432508, 0x7c01697a, 0x2a080482, 0x642a39a4, 0x2b35362a,
0x02382b64, 0xfe5070d7, 0x6c6d50a6,
0x515a0151, 0x9595e66f, 0xfeeffe95, 0x4b4b36a6, 0x375a0136, 0x82004d4d, 0x344f5900, 0xf7948b85, 0x86875f20,
0x8382f496, 0x18020021, 0x2d08ef41,
0x00190007, 0x22230100, 0x3b141115, 0x33490301, 0x33112206, 0x06e96a15, 0x58012508, 0x5c56565c, 0x887e0193,
0xfe887171, 0x46465e82, 0xfe7be502,
0xe5027d56, 0x44d2fe43, 0x4e43d0fe, 0x6bb4016d, 0x2c069b6c, 0x02000005, 0x0081025a, 0x002d001d, 0x0e276039,
0x27222322, 0x08444b18, 0x32013b25,
0x41073617, 0x05200ee1, 0x080beb4e, 0x63797e2a, 0x2f218e63, 0x182fae73, 0x43722f18, 0x72433636, 0xe91a1a2d,
0x2f11221a, 0x1f221d21, 0x01011d22,
0x1c1c235f, 0x1e221f23, 0x2c07d34f, 0x73561313, 0x135673ef, 0x3fffc813, 0x05c27938, 0xf34e4820, 0x00032208,
0x249b8226, 0x00bd033d, 0x05696e11,
0x6e211321, 0x1d2005d7, 0x10814018, 0x15372723, 0x85401807, 0xee762211, 0x884018ee, 0x065c6a14, 0x8f790220,
0x48032505, 0x0e000a00, 0x230c9179,
0x0715013f, 0x22079579, 0x79eeeeb3, 0x93200998, 0x2005435c, 0x067b7126, 0x032397a0, 0x91230733, 0x7b652397,
0x98944f2c, 0xc1e9fc23, 0x21f28200,
0x5f823a00, 0x8d05277a, 0x82132097, 0x2097874e, 0x89448206, 0x051b6198, 0x210b2f41, 0xa7580020, 0x07f57305,
0x182b2621, 0x4f0ab141, 0x9a9106d5,
0x21462420, 0x187e2007, 0x2311bf41, 0x4e4e7662, 0x00219e82, 0x0a374102, 0x37411120, 0x0652550c, 0x0121a287,
0x084b554d, 0x2006d87a, 0x46428351,
0x1d2209af, 0x42182100, 0x3f231f05, 0x18071501, 0x22170942, 0x18eeee1e, 0x57110c42, 0x1b26073f, 0x49020000,
0x694e4803, 0x15012105, 0x841b417b,
0x17457b63, 0xeeee2c22, 0x2011487b, 0x05575793, 0xa207eb46, 0x331323c7, 0xc7972307, 0x2c7b2f23, 0x20c8914f,
0x05876efc, 0x0dff1b22, 0x4106077c,
0xa863062b, 0x13097c05, 0xc7976384, 0x2f423d20, 0x0e117c05, 0xc14dfd22, 0x220a876c, 0x4124001d, 0x39421f8f,
0x20ca9706, 0x05f141b8, 0x9757cd91,
0x09974108, 0xcf996b86, 0xd2976b87, 0x21077371, 0xd58e4381, 0x52765121, 0x3826057b, 0x2c020dff, 0x7d5e2803,
0x15212306, 0x01821123, 0x0733132a,
0xf4013823, 0xbcbb85b4, 0x29057741, 0xe5021bfd, 0x00c1e9fc, 0xaf4b0002, 0x00282405, 0x7d160012, 0x3a83141d,
0x200a217d, 0x7d3f844a, 0xfd230b25,
0x8200c14d, 0x82382047, 0x032c2204, 0x207782bd, 0x8647820e, 0x061c4377, 0x01217a85, 0x08284145, 0x62207e83,
0x20051743, 0x2237822d, 0x98610353,
0x8225207f, 0x4c7f8aba, 0x808b0609, 0x7f82e020, 0x45180120, 0x0f20080b, 0x554f7d82, 0x22fd830a, 0x82bb9063,
0x888831fb, 0xd2019085, 0x43431301,
0xfe46edfe, 0x008c0174, 0x7f4b0082, 0x03392205, 0x067f5828, 0x18352321, 0x2308cf51, 0x14152315, 0x0805257e,
0x23013d21, 0xa5a5a131, 0xe1dddd86,
0x60542ae1, 0x01a13d67, 0xa743af8f, 0x46af43a7, 0x43294994, 0x82aa623d, 0x09836aff, 0xd76a2520, 0x10e56016,
0x200c8b6a, 0x0ddd6089, 0x2010936a,
0x08d86086, 0xb75f0220, 0x881f200a, 0x3311266b, 0x26222111, 0x11ff6235, 0x2009f75e, 0x5e628d88, 0x28200cff,
0xcb8c5e88, 0xcb961720, 0x21152122,
0x3020bd8c, 0x18059358, 0x230d3e46, 0x005beb02, 0x0a5f4d18, 0xcb4f4820, 0x06936c05, 0x4982af87, 0x2f20a189,
0x978c4683, 0x8c5b8d21, 0x9621208f,
0x0c65498f, 0x3220998c, 0x8f0c4655, 0x09e751a0, 0x50094360, 0x8b60052b, 0x0beb510c, 0x3120ad89, 0x180ced51,
0x21090340, 0xaf498d02, 0x00032108,
0x22091742, 0x97300023, 0x7a3220bd, 0x3d251311, 0x012b3401, 0x20cc8c22, 0x101c69df, 0x20102742, 0x131a69eb,
0x26080357, 0x000d0066, 0x422a001d,
0xa8691035, 0x20f0891b, 0x417e90de, 0xab200cab, 0xff8b7a91, 0x9b6d1720, 0x05254a19, 0x9420ea8c, 0x35071d4a,
0xe6fdd702, 0x3b46463b, 0xe6fd1a02,
0x506d6d50, 0x95950003, 0xf35a0095, 0x000d210c, 0x8514f761, 0x20be8955, 0x185288a7, 0x210ab041, 0x4e838d02,
0x23000132, 0x41020dff, 0x27002803,
0x23210000, 0x11352622, 0x530d3a6f, 0x012d133e, 0x55416b24, 0x602c357d, 0x5680352b, 0x0b3e5341, 0x02597923,
0x0b6a4256, 0x240e4453, 0x00010000,
0x246f8224, 0x00810247, 0x826f8d21, 0x13085476, 0x5ef4bb28, 0x2e288445, 0x658b88bb, 0x016d4e23, 0x077542c6,
0x00226190, 0x01820200, 0x5f020026,
0x0f00bd03, 0x2105db45, 0x01853313, 0x83230321, 0x06616301, 0x0f9c4918, 0x2f4f8320, 0xa3491808, 0xbd03210c,
0x2a065f46, 0x02000009, 0x0048035a,
0x1813000c, 0x860eb942, 0xc0421850, 0x6877200c, 0xc6200867, 0x0bc74218, 0x49480321, 0x1a200857, 0x49204f82,
0x0820a382, 0x002aa582, 0x011b3313,
0x23110333, 0x82660311, 0x06666f05, 0x21069f5e, 0x6a6f2803, 0x8499200a, 0x0baf6341, 0x8f860e20, 0x37230122,
0x28633e86, 0x871c2008, 0x63fe2086,
0xef680728, 0x207f8908, 0x05cd530c, 0x36648188, 0x20828605, 0x056a6382, 0x3364828d, 0x05bb5207, 0x18085753,
0x2307594a, 0x0715013f, 0x0c5d4a18,
0xeeeebb22, 0x08604a18, 0x2205904b, 0x82370002, 0x032c21ff, 0x8b08e769, 0x7943183b, 0xeeac220c, 0x7c4318ee,
0x34d62108, 0x96052f61, 0x33372377,
0x778c2315, 0x8787c622, 0xd8227788, 0x00820066, 0x13207798, 0x778c3b82, 0x8787b722, 0x01217788, 0x533c830a,
0x7b5107ab, 0x494b1807, 0x06ed4708,
0x04607a8c, 0x217f8807, 0xa7487662, 0x23f78905, 0x13000010, 0x828c438f, 0x41460121, 0x3b2707be, 0x2843fdfd,
0x84941602, 0x00012a43, 0x020dff1e,
0x004d0344, 0x2241821a, 0x18333523, 0x710b5d49, 0x3e240628, 0x9fda3502, 0x07654918, 0x9f5c4831, 0x21292c3d,
0x433e0209, 0x88446666, 0x518afd43,
0x00210637, 0x09cb5200, 0x49bfd74a, 0x012c75ff, 0xa0026c00, 0x1603f701, 0x00000600, 0x20060844, 0x05b541e6,
0x76160322, 0x8c05434d, 0x065f5323,
0x857d0121, 0xa0022124, 0x01262485, 0x8c025500, 0x47820e02, 0x47830d20, 0x200b7747, 0x0a6d4755, 0x59160321,
0xee240c53, 0x7501b002, 0xdf7f3382,
0x16032a09, 0x00020066, 0x016202bb, 0x221782aa, 0x7b1c000f, 0xdc4f08d1, 0x18172008, 0x2408d856, 0x0122012b,
0x10da4603, 0x46160321, 0x002f11ce,
0xff950001, 0x00ce010d, 0x00140000, 0x45332100, 0x422113aa, 0x0ba34586, 0x26129745, 0x02570001, 0x820b02b1,
0x82112097, 0x10154ae3, 0xa549ac20,
0x49b1200e, 0x00200999, 0x3e24d382, 0x24028102, 0x09974418, 0x2005e346, 0x07d946b7, 0x47160321, 0x0220051c,
0x08065b67, 0x03002820, 0x00000700,
0x33130129, 0x03210307, 0xdcfd4402, 0x7c5fb7b6, 0x037b0001, 0x5efd4328, 0xc982a202, 0x26000122, 0x3c205882,
0x3205fb46, 0x35231525, 0x013d013e,
0x012b2634, 0x011d0622, 0x82171614, 0x35332211, 0x2213832e, 0x48013b36, 0x370805a9, 0x3c021507, 0x372d26c8,
0x372c662c, 0x97c8262d, 0x43544146,
0x415443c8, 0xee434346, 0xe1364608, 0x3d55553d, 0x084636e1, 0x1f6743ee, 0x59e14c5d, 0xe1597c7c, 0x671f5d4c,
0x2006f746, 0x4f491841, 0x693b2009,
0x273805f0, 0x85242311, 0xfe887898, 0x851b63e6, 0x87fe8102, 0xfd3c02c3, 0xfafe137f, 0x3d249f88, 0x0b008002,
0x3209594c, 0x23112311, 0x6a170226,
0x72617a60, 0xfd438002, 0x833d02c3, 0x462e8203, 0x13200cf7, 0x118d5018, 0x15171323, 0x0ff44627, 0xeeee3025,
0x46fd2803, 0xe36a0ef0, 0x09ef4605,
0xef461020, 0x4648820f, 0x24220cec, 0x4918eeee, 0x03210fb0, 0x47458348, 0x93930d8b, 0x07153722, 0x8020938f,
0x89209392, 0x9a05834f, 0x8c488293,
0x92742093, 0x06b36a93, 0x1f480320, 0x0571670a, 0x21061f48, 0x21483313, 0x05024707, 0x0420998f, 0x41070b47,
0x0f470f31, 0x00032105, 0x14209f8b,
0x710e3541, 0xa58c0752, 0x4b700820, 0x0e264808, 0x87775183, 0x15172218, 0x069d4727, 0x87775620, 0x4199200e,
0xcb6b05bb, 0x48368215, 0xd86c0818,
0x07144805, 0x1805ab41, 0x9d1b6746, 0x02d5331b, 0x038e011e, 0x0003004d, 0x13230100, 0x9b700133, 0x12826d4c,
0x0e822f20, 0x1b8a0020, 0x03331324,
0x1a82f323, 0xfe4d0324, 0x1b8400d1, 0x0146ff25, 0x8375008e, 0x86372037, 0x8475201b, 0x0200271a, 0x1e025d00,
0x53840602, 0x00000723, 0x83558213,
0x82f82003, 0x86d2203d, 0xd1fe235c, 0x278f2f01, 0x45820120, 0x03820320, 0x866b0121, 0x216a8328, 0x53882f01,
0x0246ff23, 0x826f8406, 0x8f252053,
0x847a822b, 0x00012a2a, 0x02000055, 0x0028030e, 0x9d4a180b, 0x11233508, 0x01333523, 0xafaf5c03, 0x03aeae5c,
0xfd4bb428, 0x4b2902d7, 0x2b881e82,
0x4a181320, 0x35210fc9, 0x84338833, 0x27378535, 0xab4bcdfe, 0x33014bab, 0x8e263b84, 0xd401fa00, 0xff834802,
0x1121132d, 0x46018e21, 0x4802bafe,
0x8200b2fe, 0x821c2011, 0x86472083, 0x248782ab, 0x23153325, 0x24038627, 0x8787c001, 0x200284d2, 0x21008475,
0x7d500000, 0x56022105, 0x0f2cb382,
0x1f001b00, 0x3b002f00, 0x57004b00, 0x2a12fd45, 0x011d2223, 0x32013b14, 0x4134013d, 0x834d052d, 0x201f8a0e,
0x0f1d4d13, 0x20081b8a, 0x2320f501,
0x20231e1e, 0x391d1e21, 0x0c17170c, 0x3ee7fe18, 0x211440a7, 0x211e1f20, 0x1e1e2221, 0x27148337, 0x2120bf17,
0x20221c1d, 0x0d2d0f83, 0x190d1717,
0x40346401, 0x3534407c, 0x3804823f, 0x3f7a3c38, 0x013c7c3d, 0x03d8fcfc, 0x7b3f3528, 0x40353540, 0x3734407b,
0x6061183d, 0x82298d09, 0x010035e6,
0xa400cd00, 0x36029601, 0x00000600, 0x07153713, 0xcd271517, 0x0b544918, 0x238d2482, 0x15173531, 0xcd373507,
0x0190c9c9, 0x3ca95fd7, 0x826a5fad,
0x01002122, 0x08475e18, 0x00000330, 0x23013301, 0xfe49f801, 0x28034c2c, 0x1b82d8fc, 0x00003528, 0x28032e02,
0x63821d00, 0x49353321, 0x0626065e,
0x1533011d, 0x03831523, 0x21152131, 0x33352311, 0x5c352335, 0xaff84255, 0x82a4372a, 0x1e013800, 0x5c5c63fe,
0x2d2a025c, 0x5143775a, 0x82462d3d,
0x0145d746, 0x8582461c, 0xff29246f, 0x823a02fe, 0x001e2753, 0x15210100, 0x4d892223, 0x8606a677, 0x10332851,
0x2f010b01, 0x82a862e7, 0x2d352600,
0x98d1fee7, 0x3e00824a, 0xc1432803, 0x4391450d, 0x44513e2d, 0x91430001, 0x00110145, 0x01170002, 0x034e0273,
0x7007002b, 0xf3520547, 0x33252405,
0x8233011b, 0x0726084c, 0x23152723, 0x6134cf17, 0x5c08013a, 0x425e3a3b, 0x44314236, 0xfe362b03, 0x3582017e,
0x4101bffe, 0xd3d349fe, 0xea82d1d1,
0x2406ab4f, 0x000b004d, 0x29f18222, 0x3b011e15, 0x3d363201, 0xbc742101, 0x013d230c, 0xbc743634, 0x02912a06,
0x2e952a29, 0x38f9fe27, 0x0bbf7407,
0x2d282e2d, 0x944301f1, 0x493b3537, 0x74b001d6, 0x8d2607be, 0x33c65048, 0xf6820031, 0x82210021, 0x0343226c,
0x22af8228, 0x63211300, 0x212e059f,
0xfe6f2202, 0x28036ebb, 0xe502d8fc, 0x26821bfd, 0x0abb5a18, 0x8d820920, 0x21152124, 0x04820313, 0x01330138,
0xee8cfefc, 0xfe7401ee, 0x03200104,
0xaafe4328, 0x0143b4fe, 0x2f82008f, 0x97015126, 0xdc011102, 0x29054743, 0x01512115, 0x0140fec0, 0x571845dc,
0x01381427, 0x1a230323, 0x70cd6290,
0x7873fefe, 0xfe700142, 0xfce102d7, 0x002001d8, 0x492c3d82, 0x1a022401, 0x15005a02, 0x2f002200, 0x35207b82,
0x3222f182, 0x06433617, 0x22233406,
0x22230627, 0x14152526, 0x36323316, 0x2334013d, 0x8b070622, 0x492a080c, 0x2344354a, 0x4b384127, 0x293f384b,
0x4a354126, 0x23260401, 0x234a2822,
0x2229cc26, 0x22472720, 0x867c0129, 0x20203226, 0x06842830, 0x3985322a, 0x12161612, 0x13172a39, 0xb2820887,
0x00010029, 0x020dff0b, 0x624d0358,
0x15220501, 0x4f182223, 0x352a08e3, 0x01363411, 0x598ac692, 0x04835a4a, 0x444d032d, 0x6557fd88, 0x02874466,
0x836666a9, 0x00022c3b, 0x02e5003c,
0x00510228, 0x822d0016, 0x832320c1, 0xd04f18c1, 0x22232708, 0x2223022e, 0x14920703, 0x484d8d3f, 0x1d37234f,
0x24211a2c, 0x2445494f, 0x1429203c,
0x484e0350, 0x1c372450, 0x2521192c, 0x3d13834e, 0x4f15291f, 0x5a45b201, 0x2f1b211b, 0x1e5f4028, 0xd5fe1e23,
0x1c594601, 0x28301c20, 0x0e825e40,
0xbb820020, 0x00002c28, 0x28033802, 0x7d821300, 0x33372126, 0x23153307, 0x26080383, 0x23132303, 0x23373335,
0x770a0162, 0x65417755, 0xa5d9b44f,
0x9770a658, 0x4e02e54e, 0x8f47dada, 0x01cdfe45, 0x858f4533, 0x00512ac3, 0x02110200, 0x000600d7, 0x3445820a,
0x15071501, 0x21051517, 0x017f2115,
0xfeeeee66, 0xfec0016c, 0xf75f1840, 0x45372108, 0x09263393, 0x35373501, 0x33820327, 0x67017e27, 0xeeee99fe,
0x1834832d, 0x2a09de5f, 0x0045c9fd,
0x005c0002, 0x82070200, 0x000522ab, 0x26ba820b, 0x13032303, 0x83130333, 0x07022e06, 0x6d6cd16e, 0x4a4e4dd1,
0x014c4f3b, 0x2d728294, 0xfd940194,
0x01560116, 0xfeaafe56, 0x008200aa, 0x26011824, 0x07820100, 0x37220283, 0x0b867000, 0x08000124, 0x0b86ba00,
0x07000224, 0x0b86d300, 0x2e000324,
0x0b863901, 0x10000424, 0x0b868a01, 0x0d000524, 0x0b86b701, 0x17820620, 0x0b86e720, 0x0b000824, 0x0b861002,
0x0b820920, 0x0b863420, 0x1b000c24,
0x0b867802, 0xec010d24, 0x0b866e06, 0x32000e2a, 0x0300c108, 0x09040100, 0x6e209082, 0x0b850382, 0x10000124,
0x1786a800, 0x0e000224, 0x0b86c300,
0xfb820320, 0x0b86db20, 0x20000424, 0x0b866801, 0x1a000524, 0x0b869b01, 0x17820620, 0x0b86c520, 0x16000824,
0x0b86f801, 0x16000924, 0x0b861c02,
0x36000c24, 0x0b864002, 0xd8030d24, 0x0b869402, 0x64000e2a, 0x52005b08, 0x6c006500, 0x61220382, 0x05827300,
0x20006426, 0x6e006900, 0x32260582,
0x32003000, 0x09823400, 0x0f827520, 0x1b826420, 0x0b827220, 0x43004322, 0x20201782, 0x69223182, 0x13826300,
0x35846e20, 0x19822e20, 0x6f004e22,
0x72200582, 0x67243d82, 0x74006800, 0x20221782, 0x21827200, 0x39847320, 0x5b847620, 0x002e3808, 0x6c655200,
0x65736165, 0x6e692064, 0x32303220,
0x6e752034, 0x20726564, 0x20304343, 0x6563696c, 0x2e65736e, 0x206f4e20, 0x68676972, 0x72207374, 0x72657365,
0x82646576, 0x82552038, 0x82692085,
0x00702255, 0x28798461, 0x696e5500, 0x63617073, 0x25538265, 0x67006500, 0x94827500, 0x72826120, 0x67276282,
0x72616c75, 0x84560000, 0x8273207a,
0x846f2092, 0x003522d2, 0x20bc822e, 0x2b038230, 0x0054003b, 0x00500059, 0x003b004f, 0x2d22588f, 0x4f8c5200,
0x31003b26, 0x36003900, 0x3b280382,
0x4c004600, 0x33003800, 0x00334182, 0x73726556, 0x206e6f69, 0x30302e35, 0x59543b30, 0x873b4f50, 0x522d2291,
0x2b818465, 0x3639313b, 0x4c463b39,
0x00303338, 0xcf8866a0, 0xbf9b3d87, 0x5cb47d8d, 0x61005226, 0x20007900, 0x6122d082, 0x0b827200, 0x69006222,
0x002df682, 0x20796152, 0x6172614c,
0x00656962, 0x2223a300, 0x82740068, 0x00702601, 0x003a0073, 0x2001822f, 0x23558274, 0x006f0070, 0x20053142,
0x2255826d, 0x82660063, 0x826e200f,
0x00732425, 0x8263002e, 0x006d3c0b, 0x74746800, 0x2f3a7370, 0x7079742f, 0x7265646f, 0x6663696d, 0x73746e6f,
0x826f632e, 0x0054221c, 0x20378268,
0x879d8273, 0x82202039, 0x82612011, 0x00202263, 0x22a58262, 0x826e0065, 0x4272201b, 0xa94210b9, 0x8261200b,
0x426e2021, 0x20222293, 0xff824300,
0x61006522, 0x69229f82, 0x53827600, 0x6f221183, 0x01826d00, 0x67826f20, 0x5a208583, 0x72201582, 0x6c204b84,
0x7f83cf84, 0x13827320, 0x63822e20,
0x50002024, 0x3f436c00, 0x82202008, 0x826f20f7, 0x826e2015, 0x8274202d, 0x82612007, 0x826b20b3, 0x84702007,
0x846d2041, 0x099042d5, 0x53847420,
0x05846420, 0x6b826120, 0x83827920, 0x23826820, 0x67006e22, 0x77203382, 0x74200982, 0x2020f782, 0x65201584,
0x65204982, 0x210a1141, 0x7d830073,
0x1f825720, 0x1f826120, 0x76006522, 0x20226184, 0x4d827900, 0x3d847520, 0x6e006122, 0x3d831982, 0x65859183,
0x6920538d, 0x20205382, 0x20089d41,
0x8335822c, 0x2065836f, 0x20f98461, 0x20538677, 0x20898277, 0x41e5826c, 0x6383057d, 0x39826520, 0x2f822e20,
0x7220fb8d, 0x61222f82, 0x17826400,
0x62006122, 0x74208784, 0x3b44538a, 0x00502207, 0x411d8275, 0x20220549, 0x27824400, 0x61006d22, 0x5744f584,
0x206f850f, 0x83218266, 0x00202259,
0x06474263, 0x09826120, 0x27417420, 0x416d2008, 0x89420689, 0x4163200f, 0x79420ed7, 0x846d2005, 0x00732243,
0x2455842e, 0x002f0067, 0x208f8a70,
0x228d8a64, 0x847a002f, 0x826f20ff, 0x82312023, 0x8230202f, 0x00202207, 0x20218254, 0x06914120, 0x1f822020,
0x0b827820, 0x69416520, 0x82702006,
0x0073221b, 0x22998273, 0x826c0062, 0x8220201b, 0x436e2059, 0xdb830609, 0x77006126, 0x20002c00, 0x85065743,
0x43642089, 0xeb421083, 0x00772209,
0x454b8261, 0x2020053b, 0x83410b82, 0x82632005, 0x00702265, 0x24dd8279, 0x00670069, 0x837f8268, 0x2067831b,
0x20f18420, 0x2029826c, 0x837d8274,
0x826f2059, 0x82202011, 0x8265209d, 0x202b8347, 0x20f58462, 0x20118269, 0x10ad4567, 0x20058d42, 0x107b4374,
0x29822e20, 0x20099143, 0x20418477,
0x4213826b, 0x3d41053d, 0x82732009, 0x82652039, 0x002022ed, 0x22758266, 0x826d006f, 0x824a2023, 0x8270208b,
0x006e3a03, 0x000a002e, 0x69685400,
0x6f662073, 0x6820746e, 0x62207361, 0x206e6565, 0x07104672, 0x22050846, 0x456e2061, 0x203510fd, 0x61657243,
0x65766974, 0x6d6f4320, 0x736e6f6d,
0x72655a20, 0x092f466f, 0x6c502029, 0x65736165, 0x826f6420, 0x2074323c, 0x206b7361, 0x6d726570, 0x69737369,
0x74206e6f, 0x3018836f, 0x74796e61,
0x676e6968, 0x74697720, 0x68742068, 0x832f8265, 0x2e733188, 0x61685720, 0x65766574, 0x6f792072, 0x61772075,
0x32859c82, 0xb1862986, 0x34832c20,
0x6e612024, 0x29827773, 0x6c697723, 0x22be826c, 0x82657920, 0x827d863f, 0x206426a3, 0x756f6261, 0x30298474,
0x20304343, 0x6c627550, 0x44206369,
0x69616d6f, 0x82af876e, 0x6f662b37, 0x63206572, 0x61746e6f, 0x93837463, 0x2e656d23, 0x07104520, 0xeb866320,
0xea856320, 0x726f2e25, 0x84702f67,
0x84642047, 0x7a2f2146, 0x2f27fb82, 0x2f302e31, 0x846f5420, 0x78652369, 0xb4826574, 0x826f7021, 0x6c6222ef,
0x06514765, 0x77616c2b, 0x6152202c,
0x6e6f6d79, 0x07d14564, 0x61682023, 0x22e18273, 0x82657669, 0x28c182a9, 0x79706f63, 0x68676972, 0x26d78274,
0x65722064, 0x8274616c, 0x726f2419,
0x82656e20, 0x6f622215, 0x849c8372, 0x20732220, 0x416c8374, 0x2e23060b, 0x82685420, 0x6f77240a, 0x82206b72,
0x219e8407, 0x3a826873, 0x6f72662d,
0x614a206d, 0x2e6e6170, 0x4300000a, 0x48466390, 0x29374108, 0x00820020, 0x84000221, 0x67ff2300, 0x08841400,
0x012e048e, 0x0100006c, 0x01020002,
0x00030003, 0x77180004, 0x160ab809, 0xa3000401, 0x85008400, 0xe8009600, 0x8e008600, 0x9d008b00, 0xa400a900,
0x8a000501, 0x8300da00, 0xf2009300,
0x8d00f300, 0xc3008800, 0xf100de00, 0xaa009e00, 0xf400f500, 0xa200f600, 0xc900ad00, 0xae00c700, 0x63006200,
0x64009000, 0x6500cb00, 0xca00c800,
0xcc00cf00, 0xce00cd00, 0x6600e900, 0xd000d300, 0xaf00d100, 0xf0006700, 0xd6009100, 0xd500d400, 0xeb006800,
0x8900ed00, 0x69006a00, 0x6d006b00,
0x6e006c00, 0x6f00a000, 0x70007100, 0x73007200, 0x74007500, 0x77007600, 0x7800ea00, 0x79007a00, 0x7d007b00,
0xb8007c00, 0x7f00a100, 0x80007e00,
0xec008100, 0xba00ee00, 0x07010601, 0x09010801, 0x0b010a01, 0xfe00fd00, 0x0d010c01, 0x0001ff00, 0x0f010e01,
0x01011001, 0x12011101, 0x14011301,
0x16011501, 0x18011701, 0x1a011901, 0xf900f800, 0x1c011b01, 0x1e011d01, 0x20011f01, 0x22012101, 0x24012301,
0x26012501, 0xd700fa00, 0x28012701,
0x2a012901, 0x2c012b01, 0x2e012d01, 0x30012f01, 0x32013101, 0xe300e200, 0x34013301, 0x36013501, 0x38013701,
0x3a013901, 0x3c013b01, 0x3e013d01,
0x40013f01, 0xb100b000, 0x42014101, 0x44014301, 0x46014501, 0x48014701, 0xfc00fb00, 0xe500e400, 0x4a014901,
0x4c014b01, 0x4e014d01, 0x50014f01,
0x52015101, 0x54015301, 0x56015501, 0x58015701, 0x5a015901, 0x5c015b01, 0x5e015d01, 0x5f01bb00, 0x61016001,
0xe6006201, 0xa600e700, 0x64016301,
0x66016501, 0xe100d800, 0xdc00db00, 0xe000dd00, 0xdf00d900, 0x68016701, 0x9b006901, 0x6b016a01, 0x6d016c01,
0x6f016e01, 0x71017001, 0xb300b200,
0xb700b600, 0xb400c400, 0xc500b500, 0xc2008200, 0xab008700, 0xbe00c600, 0xbc00bf00, 0x73017201, 0x98008c00,
0x99009a00, 0xa500ef00, 0x9c009200,
0x8f00a700, 0x95009400, 0x7401b900, 0x75077501, 0x8230696e, 0x21078500, 0x07853731, 0x86304121, 0x07442a07,
0x63616d41, 0x076e6f72, 0x28078561,
0x72624106, 0x06657665, 0x29068461, 0x676f4107, 0x6b656e6f, 0x07856107, 0x64430a2c, 0x6361746f, 0x746e6563,
0x0a88630a, 0x63440623, 0x21428261,
0x06856406, 0x72634427, 0x0774616f, 0x21508545, 0x07856507, 0x84450621, 0x65062151, 0x0a210684, 0x213d8845,
0x0a88650a, 0x85450721, 0x65072166,
0x06210785, 0x20518545, 0x21068465, 0x3389470a, 0x0a886720, 0x3122c884, 0x07863232, 0x48043327, 0x04726162,
0x21048268, 0x79854907, 0x85690721,
0x49062107, 0x06217a84, 0x21068469, 0x63854907, 0x85690721, 0x49022507, 0x6a69024a, 0x33214585, 0x29078636,
0x614c0637, 0x65747563, 0x06846c06,
0x42201586, 0x43220786, 0x98844c06, 0x846c0621, 0x4c042106, 0x04219482, 0x2104826c, 0x35854e06, 0x358a6e20,
0x85353421, 0x36342335, 0x2e844e06,
0x846e0621, 0x45032906, 0x6503676e, 0x4f07676e, 0x0721a585, 0x2107856f, 0xa6844f06, 0x846f0621, 0x4f0d2f06,
0x676e7568, 0x6d757261, 0x7475616c,
0x0d8b6f0d, 0x84520621, 0x72062166, 0x35206d8a, 0x3523c186, 0x84520637, 0x72062166, 0x53200685, 0x06212484,
0x202b8a73, 0x063b4136, 0x06333623,
0x20248554, 0x26068474, 0x61625404, 0x82740472, 0x55062804, 0x646c6974, 0x84750665, 0x55072106, 0x0721a185,
0x21078575, 0xa2845506, 0x84750621,
0x55052706, 0x676e6972, 0x05837505, 0x8c550d21, 0x8b7520b5, 0x5507210d, 0x21057741, 0x07857507, 0x63570b2d,
0x75637269, 0x656c666d, 0x8a770b78,
0x8a59200b, 0x8979200b, 0x5a06210b, 0x0621c284, 0x2106847a, 0x10425a0a, 0x7a0a2108, 0x2a0d1b42, 0x07383132,
0x30696e75, 0x86393132, 0x86412007,
0x05c74107, 0x34393322, 0x33210f84, 0x2b1f8541, 0x06434233, 0x61726757, 0x77066576, 0x57200685, 0x06216284,
0x2b068477, 0x69645709, 0x73657265,
0x77097369, 0x06210987, 0x20288559, 0x31068479, 0x72696c04, 0x75450461, 0x43026f72, 0x45440352, 0x0083004c,
0xffff0126, 0x01000200, 0x0c200a82,
0x52200382, 0x02220382, 0x0f820b00, 0x03828620, 0x87008728, 0x88000200, 0x0b829f00, 0xa000a022, 0xa1220b82,
0x0b82a600, 0xa700a722, 0xa8220b82,
0x0b82ea00, 0xec00eb22, 0xed2e0b82, 0x01000601, 0x08010701, 0x09010200, 0x17826b01, 0x49840420, 0x02820482,
0x03820120, 0xdeda0024, 0x0783c5d7,
0x31257c2b, 0x000000f0, 0x2834e200, 0x33fa057b, 0x0082f2a5,
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +0,0 @@
#!/usr/bin/env python3
import os
import re
import sys
import subprocess
DEFAULT_FONTS = ['B612_Mono', 'BrassMono']
def generate_font(header_file, path):
symbol_name=os.path.splitext(os.path.basename(path))[0]
symbol_name=symbol_name.replace('-', '_')
output = subprocess.check_output(
f'binary_to_compressed_c "{path}" {symbol_name}',
shell=True)
header_file.write('\n\n')
header_file.write(output.decode('utf-8'))
def generate_header(header, guard, files):
try:
os.remove(header)
except FileNotFoundError:
pass
except:
raise
with open(header, 'wt') as header_file:
header_file.write(f"""#ifndef {guard}
#define {guard}
""")
for file in files:
generate_font(header_file, file)
header_file.write('\n\n#endif\n')
def generate_dir(path):
filelist = [os.path.join(path, file) for file in os.listdir(path)
if file.endswith('ttf')]
guard = f'KGE_FONTS_{path.upper()}_H'
header = f"{path.lower().replace('-', '_')}.h"
generate_header(header, guard, filelist)
def main(fonts=None):
if fonts is None:
fonts = DEFAULT_FONTS
for font in fonts:
generate_dir(font)
if __name__ == '__main__':
fonts = None
if len(sys.argv) > 1:
fonts = sys.argv[1:]
main(fonts)

View File

@@ -1,3 +1,9 @@
#!/usr/bin/env bash
ls -1 *.cc *.h | grep -v '^Font.h$' | xargs cloc -fmt 3
fmt_arg=""
if [ "${V}" = "1" ]
then
fmt_args="-fmt 3"
fi
ls -1 *.cc *.h | grep -v '^Font.h$' | xargs cloc ${fmt_args}

279
syntax/CppHighlighter.cc Normal file
View File

@@ -0,0 +1,279 @@
#include "CppHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static bool
is_digit(char c)
{
return c >= '0' && c <= '9';
}
CppHighlighter::CppHighlighter()
{
const char *kw[] = {
"if", "else", "for", "while", "do", "switch", "case", "default", "break", "continue",
"return", "goto", "struct", "class", "namespace", "using", "template", "typename",
"public", "private", "protected", "virtual", "override", "const", "constexpr", "auto",
"static", "inline", "operator", "new", "delete", "try", "catch", "throw", "friend",
"enum", "union", "extern", "volatile", "mutable", "noexcept", "sizeof", "this"
};
for (auto s: kw)
keywords_.insert(s);
const char *types[] = {
"int", "long", "short", "char", "signed", "unsigned", "float", "double", "void",
"bool", "wchar_t", "size_t", "ptrdiff_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t",
"int8_t", "int16_t", "int32_t", "int64_t"
};
for (auto s: types)
types_.insert(s);
}
bool
CppHighlighter::is_ident_start(char c)
{
return std::isalpha(static_cast<unsigned char>(c)) || c == '_';
}
bool
CppHighlighter::is_ident_char(char c)
{
return std::isalnum(static_cast<unsigned char>(c)) || c == '_';
}
void
CppHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
// Stateless entry simply delegates to stateful with a clean previous state
StatefulHighlighter::LineState prev;
(void) HighlightLineStateful(buf, row, prev, out);
}
StatefulHighlighter::LineState
CppHighlighter::HighlightLineStateful(const Buffer &buf,
int row,
const LineState &prev,
std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
StatefulHighlighter::LineState state = prev;
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return state;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
if (s.empty())
return state;
auto push = [&](int a, int b, TokenKind k) {
if (b > a)
out.push_back({a, b, k});
};
int n = static_cast<int>(s.size());
int bol = 0;
while (bol < n && (s[bol] == ' ' || s[bol] == '\t'))
++bol;
int i = 0;
// Continue multi-line raw string from previous line
if (state.in_raw_string) {
std::string needle = ")" + state.raw_delim + "\"";
auto pos = s.find(needle);
if (pos == std::string::npos) {
push(0, n, TokenKind::String);
state.in_raw_string = true;
return state;
} else {
int end = static_cast<int>(pos + needle.size());
push(0, end, TokenKind::String);
i = end;
state.in_raw_string = false;
state.raw_delim.clear();
}
}
// Continue multi-line block comment from previous line
if (state.in_block_comment) {
int j = i;
while (i + 1 < n) {
if (s[i] == '*' && s[i + 1] == '/') {
i += 2;
push(j, i, TokenKind::Comment);
state.in_block_comment = false;
break;
}
++i;
}
if (state.in_block_comment) {
push(j, n, TokenKind::Comment);
return state;
}
}
while (i < n) {
char c = s[i];
// Preprocessor at beginning of line (after leading whitespace)
if (i == bol && c == '#') {
push(0, n, TokenKind::Preproc);
break;
}
// Whitespace
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(i, j, TokenKind::Whitespace);
i = j;
continue;
}
// Line comment
if (c == '/' && i + 1 < n && s[i + 1] == '/') {
push(i, n, TokenKind::Comment);
break;
}
// Block comment
if (c == '/' && i + 1 < n && s[i + 1] == '*') {
int j = i + 2;
bool closed = false;
while (j + 1 <= n) {
if (j + 1 < n && s[j] == '*' && s[j + 1] == '/') {
j += 2;
closed = true;
break;
}
++j;
}
if (closed) {
push(i, j, TokenKind::Comment);
i = j;
continue;
}
// Spill to next lines
push(i, n, TokenKind::Comment);
state.in_block_comment = true;
return state;
}
// Raw string start: very simple detection: R"delim(
if (c == 'R' && i + 1 < n && s[i + 1] == '"') {
int k = i + 2;
std::string delim;
while (k < n && s[k] != '(') {
delim.push_back(s[k]);
++k;
}
if (k < n && s[k] == '(') {
int body_start = k + 1;
std::string needle = ")" + delim + "\"";
auto pos = s.find(needle, static_cast<std::size_t>(body_start));
if (pos == std::string::npos) {
push(i, n, TokenKind::String);
state.in_raw_string = true;
state.raw_delim = delim;
return state;
} else {
int end = static_cast<int>(pos + needle.size());
push(i, end, TokenKind::String);
i = end;
continue;
}
}
// If malformed, just treat 'R' as identifier fallback
}
// Regular string literal
if (c == '"') {
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == '"')
break;
}
push(i, j, TokenKind::String);
i = j;
continue;
}
// Char literal
if (c == '\'') {
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == '\'')
break;
}
push(i, j, TokenKind::Char);
i = j;
continue;
}
// Number literal (simple)
if (is_digit(c) || (c == '.' && i + 1 < n && is_digit(s[i + 1]))) {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '.' || s[j] == 'x' ||
s[j] == 'X' || s[j] == 'b' || s[j] == 'B' || s[j] == '_'))
++j;
push(i, j, TokenKind::Number);
i = j;
continue;
}
// Identifier / keyword / type
if (is_ident_start(c)) {
int j = i + 1;
while (j < n && is_ident_char(s[j]))
++j;
std::string id = s.substr(i, j - i);
TokenKind k = TokenKind::Identifier;
if (keywords_.count(id))
k = TokenKind::Keyword;
else if (types_.count(id))
k = TokenKind::Type;
push(i, j, k);
i = j;
continue;
}
// Operators and punctuation (single char for now)
TokenKind kind = TokenKind::Operator;
if (std::ispunct(static_cast<unsigned char>(c)) && c != '_' && c != '#') {
if (c == ';' || c == ',' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c ==
']')
kind = TokenKind::Punctuation;
push(i, i + 1, kind);
++i;
continue;
}
// Fallback
push(i, i + 1, TokenKind::Default);
++i;
}
return state;
}
} // namespace kte

35
syntax/CppHighlighter.h Normal file
View File

@@ -0,0 +1,35 @@
// CppHighlighter.h - minimal stateless C/C++ line highlighter
#pragma once
#include <regex>
#include <string>
#include <unordered_set>
#include <vector>
#include "LanguageHighlighter.h"
class Buffer;
namespace kte {
class CppHighlighter final : public StatefulHighlighter {
public:
CppHighlighter();
~CppHighlighter() override = default;
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
LineState HighlightLineStateful(const Buffer &buf,
int row,
const LineState &prev,
std::vector<HighlightSpan> &out) const override;
private:
std::unordered_set<std::string> keywords_;
std::unordered_set<std::string> types_;
static bool is_ident_start(char c);
static bool is_ident_char(char c);
};
} // namespace kte

159
syntax/ErlangHighlighter.cc Normal file
View File

@@ -0,0 +1,159 @@
#include "ErlangHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
static bool
is_ident_start(char c)
{
return std::isalpha(static_cast<unsigned char>(c)) || c == '_' || c == '\'';
}
static bool
is_ident_char(char c)
{
return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '@' || c == ':' || c == '?';
}
ErlangHighlighter::ErlangHighlighter()
{
const char *kw[] = {
"after", "begin", "case", "catch", "cond", "div", "end", "fun", "if", "let", "of",
"receive", "when", "try", "rem", "and", "andalso", "orelse", "not", "band", "bor", "bxor",
"bnot", "xor", "module", "export", "import", "record", "define", "undef", "include", "include_lib"
};
for (auto s: kw)
kws_.insert(s);
}
void
ErlangHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
int i = 0;
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
// comment
if (c == '%') {
push(out, i, n, TokenKind::Comment);
break;
}
// strings
if (c == '"') {
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == '"')
break;
}
push(out, i, j, TokenKind::String);
i = j;
continue;
}
// char literal $X
if (c == '$') {
int j = i + 1;
if (j < n && s[j] == '\\' && j + 1 < n)
j += 2;
else if (j < n)
++j;
push(out, i, j, TokenKind::Char);
i = j;
continue;
}
// numbers
if (std::isdigit(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '#' || s[j] == '.' ||
s[j] == '_'))
++j;
push(out, i, j, TokenKind::Number);
i = j;
continue;
}
// atoms/variables/identifiers (including quoted atoms)
if (is_ident_start(c)) {
// quoted atom: '...'
if (c == '\'') {
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (d == '\'') {
if (j < n && s[j] == '\'') {
++j;
continue;
}
break;
}
if (d == '\\')
esc = !esc;
}
push(out, i, j, TokenKind::Identifier);
i = j;
continue;
}
int j = i + 1;
while (j < n && is_ident_char(s[j]))
++j;
std::string id = s.substr(i, j - i);
// lowercase leading -> atom/function/module; uppercase or '_' -> variable
TokenKind k = TokenKind::Identifier;
// keyword check (lowercase)
std::string lower;
lower.reserve(id.size());
for (char ch: id)
lower.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(ch))));
if (kws_.count(lower))
k = TokenKind::Keyword;
push(out, i, j, k);
i = j;
continue;
}
if (std::ispunct(static_cast<unsigned char>(c))) {
TokenKind k = TokenKind::Operator;
if (c == ',' || c == ';' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c ==
'}')
k = TokenKind::Punctuation;
push(out, i, i + 1, k);
++i;
continue;
}
push(out, i, i + 1, TokenKind::Default);
++i;
}
}
} // namespace kte

View File

@@ -0,0 +1,17 @@
// ErlangHighlighter.h - simple Erlang highlighter
#pragma once
#include "LanguageHighlighter.h"
#include <unordered_set>
namespace kte {
class ErlangHighlighter final : public LanguageHighlighter {
public:
ErlangHighlighter();
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
private:
std::unordered_set<std::string> kws_;
};
} // namespace kte

121
syntax/ForthHighlighter.cc Normal file
View File

@@ -0,0 +1,121 @@
#include "ForthHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
static bool
is_word_char(char c)
{
return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '>' || c == '<' || c == '?';
}
ForthHighlighter::ForthHighlighter()
{
const char *kw[] = {
":", ";", "if", "else", "then", "begin", "until", "while", "repeat",
"do", "loop", "+loop", "leave", "again", "case", "of", "endof", "endcase",
".", ".r", ".s", ".\"", ",", "cr", "emit", "type", "key",
"+", "-", "*", "/", "mod", "/mod", "+-", "abs", "min", "max",
"dup", "drop", "swap", "over", "rot", "-rot", "nip", "tuck", "pick", "roll",
"and", "or", "xor", "invert", "lshift", "rshift",
"variable", "constant", "value", "to", "create", "does>", "allot", ",",
"cells", "cell+", "chars", "char+",
"[", "]", "immediate",
"s\"", ".\""
};
for (auto s: kw)
kws_.insert(s);
}
void
ForthHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
int i = 0;
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
// backslash comment to end of line
if (c == '\\') {
push(out, i, n, TokenKind::Comment);
break;
}
// parenthesis comment ( ... ) if at word boundary
if (c == '(') {
int j = i + 1;
while (j < n && s[j] != ')')
++j;
if (j < n)
++j;
push(out, i, j, TokenKind::Comment);
i = j;
continue;
}
// strings: ." ... " and S" ... " and raw "..."
if (c == '"') {
int j = i + 1;
while (j < n && s[j] != '"')
++j;
if (j < n)
++j;
push(out, i, j, TokenKind::String);
i = j;
continue;
}
if (std::isdigit(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '.' || s[j] == '#'))
++j;
push(out, i, j, TokenKind::Number);
i = j;
continue;
}
// word/identifier
if (std::isalpha(static_cast<unsigned char>(c)) || std::ispunct(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && is_word_char(s[j]))
++j;
std::string w = s.substr(i, j - i);
// normalize to lowercase for keyword compare (Forth is case-insensitive typically)
std::string lower;
lower.reserve(w.size());
for (char ch: w)
lower.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(ch))));
TokenKind k = kws_.count(lower) ? TokenKind::Keyword : TokenKind::Identifier;
// Single-char punctuation fallback
if (w.size() == 1 && std::ispunct(static_cast<unsigned char>(w[0])) && !kws_.count(lower)) {
k = (w[0] == '(' || w[0] == ')' || w[0] == ',')
? TokenKind::Punctuation
: TokenKind::Operator;
}
push(out, i, j, k);
i = j;
continue;
}
push(out, i, i + 1, TokenKind::Default);
++i;
}
}
} // namespace kte

17
syntax/ForthHighlighter.h Normal file
View File

@@ -0,0 +1,17 @@
// ForthHighlighter.h - simple Forth highlighter
#pragma once
#include "LanguageHighlighter.h"
#include <unordered_set>
namespace kte {
class ForthHighlighter final : public LanguageHighlighter {
public:
ForthHighlighter();
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
private:
std::unordered_set<std::string> kws_;
};
} // namespace kte

157
syntax/GoHighlighter.cc Normal file
View File

@@ -0,0 +1,157 @@
#include "GoHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
static bool
is_ident_start(char c)
{
return std::isalpha(static_cast<unsigned char>(c)) || c == '_';
}
static bool
is_ident_char(char c)
{
return std::isalnum(static_cast<unsigned char>(c)) || c == '_';
}
GoHighlighter::GoHighlighter()
{
const char *kw[] = {
"break", "case", "chan", "const", "continue", "default", "defer", "else", "fallthrough", "for", "func",
"go", "goto", "if", "import", "interface", "map", "package", "range", "return", "select", "struct",
"switch", "type", "var"
};
for (auto s: kw)
kws_.insert(s);
const char *tp[] = {
"bool", "byte", "complex64", "complex128", "error", "float32", "float64", "int", "int8", "int16",
"int32", "int64", "rune", "string", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr"
};
for (auto s: tp)
types_.insert(s);
}
void
GoHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
int i = 0;
int bol = 0;
while (bol < n && (s[bol] == ' ' || s[bol] == '\t'))
++bol;
// line comment
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
if (c == '/' && i + 1 < n && s[i + 1] == '/') {
push(out, i, n, TokenKind::Comment);
break;
}
if (c == '/' && i + 1 < n && s[i + 1] == '*') {
int j = i + 2;
bool closed = false;
while (j + 1 <= n) {
if (j + 1 < n && s[j] == '*' && s[j + 1] == '/') {
j += 2;
closed = true;
break;
}
++j;
}
if (!closed) {
push(out, i, n, TokenKind::Comment);
break;
} else {
push(out, i, j, TokenKind::Comment);
i = j;
continue;
}
}
if (c == '"' || c == '`') {
char q = c;
int j = i + 1;
bool esc = false;
if (q == '`') {
while (j < n && s[j] != '`')
++j;
if (j < n)
++j;
} else {
while (j < n) {
char d = s[j++];
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == '"')
break;
}
}
push(out, i, j, TokenKind::String);
i = j;
continue;
}
if (std::isdigit(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '.' || s[j] == 'x' ||
s[j] == 'X' || s[j] == '_'))
++j;
push(out, i, j, TokenKind::Number);
i = j;
continue;
}
if (is_ident_start(c)) {
int j = i + 1;
while (j < n && is_ident_char(s[j]))
++j;
std::string id = s.substr(i, j - i);
TokenKind k = TokenKind::Identifier;
if (kws_.count(id))
k = TokenKind::Keyword;
else if (types_.count(id))
k = TokenKind::Type;
push(out, i, j, k);
i = j;
continue;
}
if (std::ispunct(static_cast<unsigned char>(c))) {
TokenKind k = TokenKind::Operator;
if (c == ';' || c == ',' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c ==
']')
k = TokenKind::Punctuation;
push(out, i, i + 1, k);
++i;
continue;
}
push(out, i, i + 1, TokenKind::Default);
++i;
}
}
} // namespace kte

18
syntax/GoHighlighter.h Normal file
View File

@@ -0,0 +1,18 @@
// GoHighlighter.h - simple Go highlighter
#pragma once
#include "LanguageHighlighter.h"
#include <unordered_set>
namespace kte {
class GoHighlighter final : public LanguageHighlighter {
public:
GoHighlighter();
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
private:
std::unordered_set<std::string> kws_;
std::unordered_set<std::string> types_;
};
} // namespace kte

209
syntax/HighlighterEngine.cc Normal file
View File

@@ -0,0 +1,209 @@
#include "HighlighterEngine.h"
#include "../Buffer.h"
#include "LanguageHighlighter.h"
#include <thread>
namespace kte {
HighlighterEngine::HighlighterEngine() = default;
HighlighterEngine::~HighlighterEngine()
{
// stop background worker
if (worker_running_.load()) {
{
std::lock_guard<std::mutex> lock(mtx_);
worker_running_.store(false);
has_request_ = true; // wake it up to exit
}
cv_.notify_one();
if (worker_.joinable())
worker_.join();
}
}
void
HighlighterEngine::SetHighlighter(std::unique_ptr<LanguageHighlighter> hl)
{
std::lock_guard<std::mutex> lock(mtx_);
hl_ = std::move(hl);
cache_.clear();
state_cache_.clear();
state_last_contig_.clear();
}
const LineHighlight &
HighlighterEngine::GetLine(const Buffer &buf, int row, std::uint64_t buf_version) const
{
std::unique_lock<std::mutex> lock(mtx_);
auto it = cache_.find(row);
if (it != cache_.end() && it->second.version == buf_version) {
return it->second;
}
// Prepare destination slot to reuse its capacity and avoid allocations
LineHighlight &slot = cache_[row];
slot.version = buf_version;
slot.spans.clear();
if (!hl_) {
return slot;
}
// Copy shared_ptr-like raw pointer for use outside critical sections
LanguageHighlighter *hl_ptr = hl_.get();
bool is_stateful = dynamic_cast<StatefulHighlighter *>(hl_ptr) != nullptr;
if (!is_stateful) {
// Stateless fast path: we can release the lock while computing to reduce contention
auto &out = slot.spans;
lock.unlock();
hl_ptr->HighlightLine(buf, row, out);
return cache_.at(row);
}
// Stateful path: we need to walk from a known previous state. Keep lock while consulting caches,
// but release during heavy computation.
auto *stateful = static_cast<StatefulHighlighter *>(hl_ptr);
StatefulHighlighter::LineState prev_state;
int start_row = -1;
if (!state_cache_.empty()) {
// linear search over map (unordered), track best candidate
int best = -1;
for (const auto &kv: state_cache_) {
int r = kv.first;
if (r <= row - 1 && kv.second.version == buf_version) {
if (r > best)
best = r;
}
}
if (best >= 0) {
start_row = best;
prev_state = state_cache_.at(best).state;
}
}
// We'll compute states and the target line's spans without holding the lock for most of the work.
// Create a local copy of prev_state and iterate rows; we will update caches under lock.
lock.unlock();
StatefulHighlighter::LineState cur_state = prev_state;
for (int r = start_row + 1; r <= row; ++r) {
std::vector<HighlightSpan> tmp;
std::vector<HighlightSpan> &out = (r == row) ? slot.spans : tmp;
auto next_state = stateful->HighlightLineStateful(buf, r, cur_state, out);
// Update state cache for r
std::lock_guard<std::mutex> gl(mtx_);
StateEntry se;
se.version = buf_version;
se.state = next_state;
state_cache_[r] = se;
cur_state = next_state;
}
// Return reference under lock to ensure slot's address stability in map
lock.lock();
return cache_.at(row);
}
void
HighlighterEngine::InvalidateFrom(int row)
{
std::lock_guard<std::mutex> lock(mtx_);
if (cache_.empty())
return;
// Simple implementation: erase all rows >= row
for (auto it = cache_.begin(); it != cache_.end();) {
if (it->first >= row)
it = cache_.erase(it);
else
++it;
}
if (!state_cache_.empty()) {
for (auto it = state_cache_.begin(); it != state_cache_.end();) {
if (it->first >= row)
it = state_cache_.erase(it);
else
++it;
}
}
}
void
HighlighterEngine::ensure_worker_started() const
{
if (worker_running_.load())
return;
worker_running_.store(true);
worker_ = std::thread([this]() {
this->worker_loop();
});
}
void
HighlighterEngine::worker_loop() const
{
std::unique_lock<std::mutex> lock(mtx_);
while (worker_running_.load()) {
cv_.wait(lock, [this]() {
return has_request_ || !worker_running_.load();
});
if (!worker_running_.load())
break;
WarmRequest req = pending_;
has_request_ = false;
// Copy locals then release lock while computing
lock.unlock();
if (req.buf) {
int start = std::max(0, req.start_row);
int end = std::max(start, req.end_row);
for (int r = start; r <= end; ++r) {
// Re-check version staleness quickly by peeking cache version; not strictly necessary
// Compute line; GetLine is thread-safe
(void) this->GetLine(*req.buf, r, req.version);
}
}
lock.lock();
}
}
void
HighlighterEngine::PrefetchViewport(const Buffer &buf, int first_row, int row_count, std::uint64_t buf_version,
int warm_margin) const
{
if (row_count <= 0)
return;
// Synchronously compute visible rows to ensure cache hits during draw
int start = std::max(0, first_row);
int end = start + row_count - 1;
int max_rows = static_cast<int>(buf.Nrows());
if (start >= max_rows)
return;
if (end >= max_rows)
end = max_rows - 1;
for (int r = start; r <= end; ++r) {
(void) GetLine(buf, r, buf_version);
}
// Enqueue background warm-around
int warm_start = std::max(0, start - warm_margin);
int warm_end = std::min(max_rows - 1, end + warm_margin);
{
std::lock_guard<std::mutex> lock(mtx_);
pending_.buf = &buf;
pending_.version = buf_version;
pending_.start_row = warm_start;
pending_.end_row = warm_end;
has_request_ = true;
}
ensure_worker_started();
cv_.notify_one();
}
} // namespace kte

View File

@@ -0,0 +1,85 @@
// HighlighterEngine.h - caching layer for per-line highlights
#pragma once
#include <cstdint>
#include <memory>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <thread>
#include "../Highlight.h"
#include "LanguageHighlighter.h"
class Buffer;
namespace kte {
class HighlighterEngine {
public:
HighlighterEngine();
~HighlighterEngine();
void SetHighlighter(std::unique_ptr<LanguageHighlighter> hl);
// Retrieve highlights for a given line and buffer version.
// If cache is stale, recompute using the current highlighter.
const LineHighlight &GetLine(const Buffer &buf, int row, std::uint64_t buf_version) const;
// Invalidate cached lines from row (inclusive)
void InvalidateFrom(int row);
bool HasHighlighter() const
{
return static_cast<bool>(hl_);
}
// Phase 3: viewport-first prefetch and background warming
// Compute only the visible range now, and enqueue a background warm-around task.
// warm_margin: how many extra lines above/below to warm in the background.
void PrefetchViewport(const Buffer &buf, int first_row, int row_count, std::uint64_t buf_version,
int warm_margin = 200) const;
private:
std::unique_ptr<LanguageHighlighter> hl_;
// Simple cache by row index (mutable to allow caching in const GetLine)
mutable std::unordered_map<int, LineHighlight> cache_;
// For stateful highlighters, remember per-line state (state after finishing that row)
struct StateEntry {
std::uint64_t version{0};
// Using the interface type; forward-declare via header
StatefulHighlighter::LineState state;
};
mutable std::unordered_map<int, StateEntry> state_cache_;
// Track best known contiguous state row for a given version to avoid O(n) scans
mutable std::unordered_map<std::uint64_t, int> state_last_contig_;
// Thread-safety for caches and background worker state
mutable std::mutex mtx_;
// Background warmer
struct WarmRequest {
const Buffer *buf{nullptr};
std::uint64_t version{0};
int start_row{0};
int end_row{0}; // inclusive
};
mutable std::condition_variable cv_;
mutable std::thread worker_;
mutable std::atomic<bool> worker_running_{false};
mutable bool has_request_{false};
mutable WarmRequest pending_{};
void ensure_worker_started() const;
void worker_loop() const;
};
} // namespace kte

View File

@@ -0,0 +1,247 @@
#include "HighlighterRegistry.h"
#include "CppHighlighter.h"
#include <algorithm>
#include <filesystem>
#include <vector>
#include <cctype>
// Forward declare simple highlighters implemented in this project
namespace kte {
// Registration storage
struct RegEntry {
std::string ft; // normalized
HighlighterRegistry::Factory factory;
};
static std::vector<RegEntry> &
registry()
{
static std::vector<RegEntry> reg;
return reg;
}
class JSONHighlighter;
class MarkdownHighlighter;
class ShellHighlighter;
class GoHighlighter;
class PythonHighlighter;
class RustHighlighter;
class LispHighlighter;
class SqlHighlighter;
class ErlangHighlighter;
class ForthHighlighter;
}
// Headers for the above
#include "JsonHighlighter.h"
#include "MarkdownHighlighter.h"
#include "ShellHighlighter.h"
#include "GoHighlighter.h"
#include "PythonHighlighter.h"
#include "RustHighlighter.h"
#include "LispHighlighter.h"
#include "SqlHighlighter.h"
#include "ErlangHighlighter.h"
#include "ForthHighlighter.h"
namespace kte {
static std::string
to_lower(std::string_view s)
{
std::string r(s);
std::transform(r.begin(), r.end(), r.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return r;
}
std::string
HighlighterRegistry::Normalize(std::string_view ft)
{
std::string f = to_lower(ft);
if (f == "c" || f == "c++" || f == "cc" || f == "hpp" || f == "hh" || f == "h" || f == "cxx")
return "cpp";
if (f == "cpp")
return "cpp";
if (f == "json")
return "json";
if (f == "markdown" || f == "md" || f == "mkd" || f == "mdown")
return "markdown";
if (f == "shell" || f == "sh" || f == "bash" || f == "zsh" || f == "ksh" || f == "fish")
return "shell";
if (f == "go" || f == "golang")
return "go";
if (f == "py" || f == "python")
return "python";
if (f == "rs" || f == "rust")
return "rust";
if (f == "lisp" || f == "scheme" || f == "scm" || f == "rkt" || f == "el" || f == "clj" || f == "cljc" || f ==
"cl")
return "lisp";
if (f == "sql" || f == "sqlite" || f == "sqlite3")
return "sql";
if (f == "erlang" || f == "erl" || f == "hrl")
return "erlang";
if (f == "forth" || f == "fth" || f == "4th" || f == "fs")
return "forth";
return f;
}
std::unique_ptr<LanguageHighlighter>
HighlighterRegistry::CreateFor(std::string_view filetype)
{
std::string ft = Normalize(filetype);
// Prefer externally registered factories
for (const auto &e: registry()) {
if (e.ft == ft && e.factory)
return e.factory();
}
if (ft == "cpp")
return std::make_unique<CppHighlighter>();
if (ft == "json")
return std::make_unique<JSONHighlighter>();
if (ft == "markdown")
return std::make_unique<MarkdownHighlighter>();
if (ft == "shell")
return std::make_unique<ShellHighlighter>();
if (ft == "go")
return std::make_unique<GoHighlighter>();
if (ft == "python")
return std::make_unique<PythonHighlighter>();
if (ft == "rust")
return std::make_unique<RustHighlighter>();
if (ft == "lisp")
return std::make_unique<LispHighlighter>();
if (ft == "sql")
return std::make_unique<SqlHighlighter>();
if (ft == "erlang")
return std::make_unique<ErlangHighlighter>();
if (ft == "forth")
return std::make_unique<ForthHighlighter>();
return nullptr;
}
static std::string
shebang_to_ft(std::string_view first_line)
{
if (first_line.size() < 2 || first_line.substr(0, 2) != "#!")
return "";
std::string low = to_lower(first_line);
if (low.find("python") != std::string::npos)
return "python";
if (low.find("bash") != std::string::npos)
return "shell";
if (low.find("sh") != std::string::npos)
return "shell";
if (low.find("zsh") != std::string::npos)
return "shell";
if (low.find("fish") != std::string::npos)
return "shell";
if (low.find("scheme") != std::string::npos || low.find("racket") != std::string::npos || low.find("guile") !=
std::string::npos)
return "lisp";
return "";
}
std::string
HighlighterRegistry::DetectForPath(std::string_view path, std::string_view first_line)
{
// Extension
std::string p(path);
std::error_code ec;
std::string ext = std::filesystem::path(p).extension().string();
for (auto &ch: ext)
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
if (!ext.empty()) {
if (ext == ".c" || ext == ".cc" || ext == ".cpp" || ext == ".cxx" || ext == ".h" || ext == ".hpp" || ext
== ".hh")
return "cpp";
if (ext == ".json")
return "json";
if (ext == ".md" || ext == ".markdown" || ext == ".mkd")
return "markdown";
if (ext == ".sh" || ext == ".bash" || ext == ".zsh" || ext == ".ksh" || ext == ".fish")
return "shell";
if (ext == ".go")
return "go";
if (ext == ".py")
return "python";
if (ext == ".rs")
return "rust";
if (ext == ".lisp" || ext == ".scm" || ext == ".rkt" || ext == ".el" || ext == ".clj" || ext == ".cljc"
|| ext == ".cl")
return "lisp";
if (ext == ".sql" || ext == ".sqlite")
return "sql";
if (ext == ".erl" || ext == ".hrl")
return "erlang";
if (ext == ".forth" || ext == ".fth" || ext == ".4th" || ext == ".fs")
return "forth";
}
// Shebang
std::string ft = shebang_to_ft(first_line);
return ft;
}
} // namespace kte
// Extensibility API implementations
namespace kte {
void
HighlighterRegistry::Register(std::string_view filetype, Factory factory, bool override_existing)
{
std::string ft = Normalize(filetype);
for (auto &e: registry()) {
if (e.ft == ft) {
if (override_existing)
e.factory = std::move(factory);
return;
}
}
registry().push_back(RegEntry{ft, std::move(factory)});
}
bool
HighlighterRegistry::IsRegistered(std::string_view filetype)
{
std::string ft = Normalize(filetype);
for (const auto &e: registry())
if (e.ft == ft)
return true;
return false;
}
std::vector<std::string>
HighlighterRegistry::RegisteredFiletypes()
{
std::vector<std::string> out;
out.reserve(registry().size());
for (const auto &e: registry())
out.push_back(e.ft);
return out;
}
#ifdef KTE_ENABLE_TREESITTER
// Forward declare adapter factory
std::unique_ptr<LanguageHighlighter> CreateTreeSitterHighlighter(const char *filetype,
const void * (*get_lang)());
void
HighlighterRegistry::RegisterTreeSitter(std::string_view filetype,
const TSLanguage * (*get_language)())
{
std::string ft = Normalize(filetype);
Register(ft, [ft, get_language]() {
return CreateTreeSitterHighlighter(ft.c_str(), reinterpret_cast<const void* (*)()>(get_language));
}, /*override_existing=*/true);
}
#endif
} // namespace kte

View File

@@ -0,0 +1,47 @@
// HighlighterRegistry.h - create/detect language highlighters and allow external registration
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "LanguageHighlighter.h"
namespace kte {
class HighlighterRegistry {
public:
using Factory = std::function<std::unique_ptr<LanguageHighlighter>()>;
// Create a highlighter for normalized filetype id (e.g., "cpp", "json", "markdown", "shell", "go", "python", "rust", "lisp").
static std::unique_ptr<LanguageHighlighter> CreateFor(std::string_view filetype);
// Detect filetype by path extension and shebang (first line).
// Returns normalized id or empty string if unknown.
static std::string DetectForPath(std::string_view path, std::string_view first_line);
// Normalize various aliases/extensions to canonical ids.
static std::string Normalize(std::string_view ft);
// Extensibility: allow external code to register highlighters at runtime.
// The filetype key is normalized via Normalize(). If a factory is already registered for the
// normalized key and override=false, the existing factory is kept.
static void Register(std::string_view filetype, Factory factory, bool override_existing = true);
// Returns true if a factory is registered for the (normalized) filetype.
static bool IsRegistered(std::string_view filetype);
// Return a list of currently registered (normalized) filetypes. Primarily for diagnostics/tests.
static std::vector<std::string> RegisteredFiletypes();
#ifdef KTE_ENABLE_TREESITTER
// Forward declaration to avoid hard dependency when disabled.
struct TSLanguage;
// Convenience: register a Tree-sitter-backed highlighter for a filetype.
// The getter should return a non-null language pointer for the grammar.
static void RegisterTreeSitter(std::string_view filetype,
const TSLanguage * (*get_language)());
#endif
};
} // namespace kte

90
syntax/JsonHighlighter.cc Normal file
View File

@@ -0,0 +1,90 @@
#include "JsonHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static bool
is_digit(char c)
{
return c >= '0' && c <= '9';
}
void
JSONHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
auto push = [&](int a, int b, TokenKind k) {
if (b > a)
out.push_back({a, b, k});
};
int i = 0;
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(i, j, TokenKind::Whitespace);
i = j;
continue;
}
if (c == '"') {
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == '"')
break;
}
push(i, j, TokenKind::String);
i = j;
continue;
}
if (is_digit(c) || (c == '-' && i + 1 < n && is_digit(s[i + 1]))) {
int j = i + 1;
while (j < n && (std::isdigit(static_cast<unsigned char>(s[j])) || s[j] == '.' || s[j] == 'e' ||
s[j] == 'E' || s[j] == '+' || s[j] == '-' || s[j] == '_'))
++j;
push(i, j, TokenKind::Number);
i = j;
continue;
}
// booleans/null
if (std::isalpha(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && std::isalpha(static_cast<unsigned char>(s[j])))
++j;
std::string id = s.substr(i, j - i);
if (id == "true" || id == "false" || id == "null")
push(i, j, TokenKind::Constant);
else
push(i, j, TokenKind::Identifier);
i = j;
continue;
}
// punctuation
if (c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':') {
push(i, i + 1, TokenKind::Punctuation);
++i;
continue;
}
// fallback
push(i, i + 1, TokenKind::Default);
++i;
}
}
} // namespace kte

12
syntax/JsonHighlighter.h Normal file
View File

@@ -0,0 +1,12 @@
// JsonHighlighter.h - simple JSON line highlighter
#pragma once
#include "LanguageHighlighter.h"
#include <vector>
namespace kte {
class JSONHighlighter final : public LanguageHighlighter {
public:
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
};
} // namespace kte

View File

@@ -0,0 +1,51 @@
// LanguageHighlighter.h - interface for line-based highlighters
#pragma once
#include <memory>
#include <vector>
#include <string>
#include "../Highlight.h"
class Buffer;
namespace kte {
class LanguageHighlighter {
public:
virtual ~LanguageHighlighter() = default;
// Produce highlight spans for a given buffer row. Implementations should append to out.
virtual void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const = 0;
virtual bool Stateful() const
{
return false;
}
};
// Optional extension for stateful highlighters (e.g., multi-line comments/strings).
// Engines may detect and use this via dynamic_cast without breaking stateless impls.
class StatefulHighlighter : public LanguageHighlighter {
public:
struct LineState {
bool in_block_comment{false};
bool in_raw_string{false};
// For raw strings, remember the delimiter between the opening R"delim( and closing )delim"
std::string raw_delim;
};
// Highlight one line given the previous line state; return the resulting state after this line.
// Implementations should append spans for this line to out and compute the next state.
virtual LineState HighlightLineStateful(const Buffer &buf,
int row,
const LineState &prev,
std::vector<HighlightSpan> &out) const = 0;
bool Stateful() const override
{
return true;
}
};
} // namespace kte

107
syntax/LispHighlighter.cc Normal file
View File

@@ -0,0 +1,107 @@
#include "LispHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
LispHighlighter::LispHighlighter()
{
const char *kw[] = {
"defun", "lambda", "let", "let*", "define", "set!", "if", "cond", "begin", "quote", "quasiquote",
"unquote", "unquote-splicing", "loop", "do", "and", "or", "not"
};
for (auto s: kw)
kws_.insert(s);
}
void
LispHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
int i = 0;
int bol = 0;
while (bol < n && (s[bol] == ' ' || s[bol] == '\t'))
++bol;
if (bol < n && s[bol] == ';') {
push(out, bol, n, TokenKind::Comment);
if (bol > 0)
push(out, 0, bol, TokenKind::Whitespace);
return;
}
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
if (c == ';') {
push(out, i, n, TokenKind::Comment);
break;
}
if (c == '"') {
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == '"')
break;
}
push(out, i, j, TokenKind::String);
i = j;
continue;
}
if (std::isalpha(static_cast<unsigned char>(c)) || c == '*' || c == '-' || c == '+' || c == '/' || c ==
'_') {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '*' || s[j] == '-' ||
s[j] == '+' || s[j] == '/' || s[j] == '_' || s[j] == '!'))
++j;
std::string id = s.substr(i, j - i);
TokenKind k = kws_.count(id) ? TokenKind::Keyword : TokenKind::Identifier;
push(out, i, j, k);
i = j;
continue;
}
if (std::isdigit(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && (std::isdigit(static_cast<unsigned char>(s[j])) || s[j] == '.'))
++j;
push(out, i, j, TokenKind::Number);
i = j;
continue;
}
if (std::ispunct(static_cast<unsigned char>(c))) {
TokenKind k = TokenKind::Punctuation;
push(out, i, i + 1, k);
++i;
continue;
}
push(out, i, i + 1, TokenKind::Default);
++i;
}
}
} // namespace kte

17
syntax/LispHighlighter.h Normal file
View File

@@ -0,0 +1,17 @@
// LispHighlighter.h - simple Lisp/Scheme family highlighter
#pragma once
#include "LanguageHighlighter.h"
#include <unordered_set>
namespace kte {
class LispHighlighter final : public LanguageHighlighter {
public:
LispHighlighter();
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
private:
std::unordered_set<std::string> kws_;
};
} // namespace kte

View File

@@ -0,0 +1,132 @@
#include "MarkdownHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push_span(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
void
MarkdownHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
LineState st; // not used in stateless entry
(void) HighlightLineStateful(buf, row, st, out);
}
StatefulHighlighter::LineState
MarkdownHighlighter::HighlightLineStateful(const Buffer &buf, int row, const LineState &prev,
std::vector<HighlightSpan> &out) const
{
StatefulHighlighter::LineState state = prev;
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return state;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
// Reuse in_block_comment flag as "in fenced code" state.
if (state.in_block_comment) {
// If line contains closing fence ``` then close after it
auto pos = s.find("```");
if (pos == std::string::npos) {
push_span(out, 0, n, TokenKind::String);
state.in_block_comment = true;
return state;
} else {
int end = static_cast<int>(pos + 3);
push_span(out, 0, end, TokenKind::String);
// rest of line processed normally after fence
int i = end;
// whitespace
if (i < n)
push_span(out, i, n, TokenKind::Default);
state.in_block_comment = false;
return state;
}
}
// Detect fenced code block start at beginning (allow leading spaces)
int bol = 0;
while (bol < n && (s[bol] == ' ' || s[bol] == '\t'))
++bol;
if (bol + 3 <= n && s.compare(bol, 3, "```") == 0) {
push_span(out, bol, n, TokenKind::String);
state.in_block_comment = true; // enter fenced mode
return state;
}
// Headings: lines starting with 1-6 '#'
if (bol < n && s[bol] == '#') {
int j = bol;
while (j < n && s[j] == '#')
++j; // hashes
// include following space and text as Keyword to stand out
push_span(out, bol, n, TokenKind::Keyword);
return state;
}
// Process inline: emphasis and code spans
int i = 0;
while (i < n) {
char c = s[i];
if (c == '`') {
int j = i + 1;
while (j < n && s[j] != '`')
++j;
if (j < n)
++j;
push_span(out, i, j, TokenKind::String);
i = j;
continue;
}
if (c == '*' || c == '_') {
// bold/italic markers: treat the marker and until next same marker as Type to highlight
char m = c;
int j = i + 1;
while (j < n && s[j] != m)
++j;
if (j < n)
++j;
push_span(out, i, j, TokenKind::Type);
i = j;
continue;
}
// links []() minimal: treat [text](url) as Function
if (c == '[') {
int j = i + 1;
while (j < n && s[j] != ']')
++j;
if (j < n)
++j; // include ]
if (j < n && s[j] == '(') {
while (j < n && s[j] != ')')
++j;
if (j < n)
++j;
}
push_span(out, i, j, TokenKind::Function);
i = j;
continue;
}
// whitespace
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push_span(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
// fallback: default single char
push_span(out, i, i + 1, TokenKind::Default);
++i;
}
return state;
}
} // namespace kte

View File

@@ -0,0 +1,14 @@
// MarkdownHighlighter.h - simple Markdown highlighter
#pragma once
#include "LanguageHighlighter.h"
namespace kte {
class MarkdownHighlighter final : public StatefulHighlighter {
public:
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
LineState HighlightLineStateful(const Buffer &buf, int row, const LineState &prev,
std::vector<HighlightSpan> &out) const override;
};
} // namespace kte

17
syntax/NullHighlighter.cc Normal file
View File

@@ -0,0 +1,17 @@
#include "NullHighlighter.h"
#include "../Buffer.h"
namespace kte {
void
NullHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
if (n <= 0)
return;
out.push_back({0, n, TokenKind::Default});
}
} // namespace kte

11
syntax/NullHighlighter.h Normal file
View File

@@ -0,0 +1,11 @@
// NullHighlighter.h - default highlighter that emits a single Default span per line
#pragma once
#include "LanguageHighlighter.h"
namespace kte {
class NullHighlighter final : public LanguageHighlighter {
public:
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
};
} // namespace kte

172
syntax/PythonHighlighter.cc Normal file
View File

@@ -0,0 +1,172 @@
#include "PythonHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
static bool
is_ident_start(char c)
{
return std::isalpha(static_cast<unsigned char>(c)) || c == '_';
}
static bool
is_ident_char(char c)
{
return std::isalnum(static_cast<unsigned char>(c)) || c == '_';
}
PythonHighlighter::PythonHighlighter()
{
const char *kw[] = {
"and", "as", "assert", "break", "class", "continue", "def", "del", "elif", "else", "except", "False",
"finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "None", "nonlocal", "not",
"or", "pass", "raise", "return", "True", "try", "while", "with", "yield"
};
for (auto s: kw)
kws_.insert(s);
}
void
PythonHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
LineState st;
(void) HighlightLineStateful(buf, row, st, out);
}
StatefulHighlighter::LineState
PythonHighlighter::HighlightLineStateful(const Buffer &buf, int row, const LineState &prev,
std::vector<HighlightSpan> &out) const
{
StatefulHighlighter::LineState state = prev;
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return state;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
// Triple-quoted string continuation uses in_raw_string with raw_delim either "'''" or "\"\"\""
if (state.in_raw_string && (state.raw_delim == "'''" || state.raw_delim == "\"\"\"")) {
auto pos = s.find(state.raw_delim);
if (pos == std::string::npos) {
push(out, 0, n, TokenKind::String);
return state; // still inside
} else {
int end = static_cast<int>(pos + static_cast<int>(state.raw_delim.size()));
push(out, 0, end, TokenKind::String);
// remainder processed normally
s = s.substr(end);
n = static_cast<int>(s.size());
state.in_raw_string = false;
state.raw_delim.clear();
// Continue parsing remainder as a separate small loop
int base = end;
// original offset, but we already emitted to 'out' with base=0; following spans should be from 'end'
// For simplicity, mark rest as Default
if (n > 0)
push(out, base, base + n, TokenKind::Default);
return state;
}
}
int i = 0;
// Detect comment start '#', ignoring inside strings
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
if (c == '#') {
push(out, i, n, TokenKind::Comment);
break;
}
// Strings: triple quotes and single-line
if (c == '"' || c == '\'') {
char q = c;
// triple?
if (i + 2 < n && s[i + 1] == q && s[i + 2] == q) {
std::string delim(3, q);
int j = i + 3; // search for closing triple
auto pos = s.find(delim, static_cast<std::size_t>(j));
if (pos == std::string::npos) {
push(out, i, n, TokenKind::String);
state.in_raw_string = true;
state.raw_delim = delim;
return state;
} else {
int end = static_cast<int>(pos + 3);
push(out, i, end, TokenKind::String);
i = end;
continue;
}
} else {
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == q)
break;
}
push(out, i, j, TokenKind::String);
i = j;
continue;
}
}
if (std::isdigit(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '.' || s[j] == '_'))
++j;
push(out, i, j, TokenKind::Number);
i = j;
continue;
}
if (is_ident_start(c)) {
int j = i + 1;
while (j < n && is_ident_char(s[j]))
++j;
std::string id = s.substr(i, j - i);
TokenKind k = TokenKind::Identifier;
if (kws_.count(id))
k = TokenKind::Keyword;
push(out, i, j, k);
i = j;
continue;
}
if (std::ispunct(static_cast<unsigned char>(c))) {
TokenKind k = TokenKind::Operator;
if (c == ':' || c == ',' || c == '(' || c == ')' || c == '[' || c == ']')
k = TokenKind::Punctuation;
push(out, i, i + 1, k);
++i;
continue;
}
push(out, i, i + 1, TokenKind::Default);
++i;
}
return state;
}
} // namespace kte

View File

@@ -0,0 +1,20 @@
// PythonHighlighter.h - simple Python highlighter with triple-quote state
#pragma once
#include "LanguageHighlighter.h"
#include <unordered_set>
namespace kte {
class PythonHighlighter final : public StatefulHighlighter {
public:
PythonHighlighter();
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
LineState HighlightLineStateful(const Buffer &buf, int row, const LineState &prev,
std::vector<HighlightSpan> &out) const override;
private:
std::unordered_set<std::string> kws_;
};
} // namespace kte

145
syntax/RustHighlighter.cc Normal file
View File

@@ -0,0 +1,145 @@
#include "RustHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
static bool
is_ident_start(char c)
{
return std::isalpha(static_cast<unsigned char>(c)) || c == '_';
}
static bool
is_ident_char(char c)
{
return std::isalnum(static_cast<unsigned char>(c)) || c == '_';
}
RustHighlighter::RustHighlighter()
{
const char *kw[] = {
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for", "if",
"impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self",
"static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where", "while", "dyn", "async",
"await", "try"
};
for (auto s: kw)
kws_.insert(s);
const char *tp[] = {
"u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize", "f32", "f64",
"bool", "char", "str"
};
for (auto s: tp)
types_.insert(s);
}
void
RustHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
int i = 0;
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
if (c == '/' && i + 1 < n && s[i + 1] == '/') {
push(out, i, n, TokenKind::Comment);
break;
}
if (c == '/' && i + 1 < n && s[i + 1] == '*') {
int j = i + 2;
bool closed = false;
while (j + 1 <= n) {
if (j + 1 < n && s[j] == '*' && s[j + 1] == '/') {
j += 2;
closed = true;
break;
}
++j;
}
if (!closed) {
push(out, i, n, TokenKind::Comment);
break;
} else {
push(out, i, j, TokenKind::Comment);
i = j;
continue;
}
}
if (c == '"') {
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == '"')
break;
}
push(out, i, j, TokenKind::String);
i = j;
continue;
}
if (std::isdigit(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '.' || s[j] == '_'))
++j;
push(out, i, j, TokenKind::Number);
i = j;
continue;
}
if (is_ident_start(c)) {
int j = i + 1;
while (j < n && is_ident_char(s[j]))
++j;
std::string id = s.substr(i, j - i);
TokenKind k = TokenKind::Identifier;
if (kws_.count(id))
k = TokenKind::Keyword;
else if (types_.count(id))
k = TokenKind::Type;
push(out, i, j, k);
i = j;
continue;
}
if (std::ispunct(static_cast<unsigned char>(c))) {
TokenKind k = TokenKind::Operator;
if (c == ';' || c == ',' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c ==
']')
k = TokenKind::Punctuation;
push(out, i, i + 1, k);
++i;
continue;
}
push(out, i, i + 1, TokenKind::Default);
++i;
}
}
} // namespace kte

18
syntax/RustHighlighter.h Normal file
View File

@@ -0,0 +1,18 @@
// RustHighlighter.h - simple Rust highlighter
#pragma once
#include "LanguageHighlighter.h"
#include <unordered_set>
namespace kte {
class RustHighlighter final : public LanguageHighlighter {
public:
RustHighlighter();
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
private:
std::unordered_set<std::string> kws_;
std::unordered_set<std::string> types_;
};
} // namespace kte

105
syntax/ShellHighlighter.cc Normal file
View File

@@ -0,0 +1,105 @@
#include "ShellHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
void
ShellHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
int i = 0;
// if first non-space is '#', whole line is comment
int bol = 0;
while (bol < n && (s[bol] == ' ' || s[bol] == '\t'))
++bol;
if (bol < n && s[bol] == '#') {
push(out, bol, n, TokenKind::Comment);
if (bol > 0)
push(out, 0, bol, TokenKind::Whitespace);
return;
}
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
if (c == '#') {
push(out, i, n, TokenKind::Comment);
break;
}
if (c == '\'' || c == '"') {
char q = c;
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (q == '"') {
if (esc) {
esc = false;
continue;
}
if (d == '\\') {
esc = true;
continue;
}
if (d == '"')
break;
} else {
if (d == '\'')
break;
}
}
push(out, i, j, TokenKind::String);
i = j;
continue;
}
// simple keywords
if (std::isalpha(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '_'))
++j;
std::string id = s.substr(i, j - i);
static const char *kws[] = {
"if", "then", "fi", "for", "in", "do", "done", "case", "esac", "while", "function",
"elif", "else"
};
bool kw = false;
for (auto k: kws)
if (id == k) {
kw = true;
break;
}
push(out, i, j, kw ? TokenKind::Keyword : TokenKind::Identifier);
i = j;
continue;
}
if (std::ispunct(static_cast<unsigned char>(c))) {
TokenKind k = TokenKind::Operator;
if (c == '(' || c == ')' || c == '{' || c == '}' || c == ',' || c == ';')
k = TokenKind::Punctuation;
push(out, i, i + 1, k);
++i;
continue;
}
push(out, i, i + 1, TokenKind::Default);
++i;
}
}
} // namespace kte

11
syntax/ShellHighlighter.h Normal file
View File

@@ -0,0 +1,11 @@
// ShellHighlighter.h - simple POSIX shell highlighter
#pragma once
#include "LanguageHighlighter.h"
namespace kte {
class ShellHighlighter final : public LanguageHighlighter {
public:
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
};
} // namespace kte

156
syntax/SqlHighlighter.cc Normal file
View File

@@ -0,0 +1,156 @@
#include "SqlHighlighter.h"
#include "../Buffer.h"
#include <cctype>
namespace kte {
static void
push(std::vector<HighlightSpan> &out, int a, int b, TokenKind k)
{
if (b > a)
out.push_back({a, b, k});
}
static bool
is_ident_start(char c)
{
return std::isalpha(static_cast<unsigned char>(c)) || c == '_';
}
static bool
is_ident_char(char c)
{
return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '$';
}
SqlHighlighter::SqlHighlighter()
{
const char *kw[] = {
"select", "insert", "update", "delete", "from", "where", "group", "by", "order", "limit",
"offset", "values", "into", "create", "table", "index", "unique", "on", "as", "and", "or",
"not", "null", "is", "primary", "key", "constraint", "foreign", "references", "drop", "alter",
"add", "column", "rename", "to", "if", "exists", "join", "left", "right", "inner", "outer",
"cross", "using", "set", "distinct", "having", "union", "all", "case", "when", "then", "else",
"end", "pragma", "transaction", "begin", "commit", "rollback", "replace"
};
for (auto s: kw)
kws_.insert(s);
const char *types[] = {"integer", "real", "text", "blob", "numeric", "boolean", "date", "datetime"};
for (auto s: types)
types_.insert(s);
}
void
SqlHighlighter::HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const
{
const auto &rows = buf.Rows();
if (row < 0 || static_cast<std::size_t>(row) >= rows.size())
return;
std::string s = static_cast<std::string>(rows[static_cast<std::size_t>(row)]);
int n = static_cast<int>(s.size());
int i = 0;
while (i < n) {
char c = s[i];
if (c == ' ' || c == '\t') {
int j = i + 1;
while (j < n && (s[j] == ' ' || s[j] == '\t'))
++j;
push(out, i, j, TokenKind::Whitespace);
i = j;
continue;
}
// line comments: -- ...
if (c == '-' && i + 1 < n && s[i + 1] == '-') {
push(out, i, n, TokenKind::Comment);
break;
}
// simple block comment on same line: /* ... */
if (c == '/' && i + 1 < n && s[i + 1] == '*') {
int j = i + 2;
bool closed = false;
while (j + 1 <= n) {
if (j + 1 < n && s[j] == '*' && s[j + 1] == '/') {
j += 2;
closed = true;
break;
}
++j;
}
if (!closed) {
push(out, i, n, TokenKind::Comment);
break;
} else {
push(out, i, j, TokenKind::Comment);
i = j;
continue;
}
}
// strings: '...' or "..."
if (c == '\'' || c == '"') {
char q = c;
int j = i + 1;
bool esc = false;
while (j < n) {
char d = s[j++];
if (d == q) {
// Handle doubled quote escaping for SQL single quotes
if (q == '\'' && j < n && s[j] == '\'') {
++j;
continue;
}
break;
}
if (d == '\\') {
esc = !esc;
} else {
esc = false;
}
}
push(out, i, j, TokenKind::String);
i = j;
continue;
}
if (std::isdigit(static_cast<unsigned char>(c))) {
int j = i + 1;
while (j < n && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '.' || s[j] == '_'))
++j;
push(out, i, j, TokenKind::Number);
i = j;
continue;
}
if (is_ident_start(c)) {
int j = i + 1;
while (j < n && is_ident_char(s[j]))
++j;
std::string id = s.substr(i, j - i);
std::string lower;
lower.reserve(id.size());
for (char ch: id)
lower.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(ch))));
TokenKind k = TokenKind::Identifier;
if (kws_.count(lower))
k = TokenKind::Keyword;
else if (types_.count(lower))
k = TokenKind::Type;
push(out, i, j, k);
i = j;
continue;
}
if (std::ispunct(static_cast<unsigned char>(c))) {
TokenKind k = TokenKind::Operator;
if (c == ',' || c == ';' || c == '(' || c == ')')
k = TokenKind::Punctuation;
push(out, i, i + 1, k);
++i;
continue;
}
push(out, i, i + 1, TokenKind::Default);
++i;
}
}
} // namespace kte

18
syntax/SqlHighlighter.h Normal file
View File

@@ -0,0 +1,18 @@
// SqlHighlighter.h - simple SQL/SQLite highlighter
#pragma once
#include "LanguageHighlighter.h"
#include <unordered_set>
namespace kte {
class SqlHighlighter final : public LanguageHighlighter {
public:
SqlHighlighter();
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
private:
std::unordered_set<std::string> kws_;
std::unordered_set<std::string> types_;
};
} // namespace kte

View File

@@ -0,0 +1,51 @@
#include "TreeSitterHighlighter.h"
#ifdef KTE_ENABLE_TREESITTER
#include "Buffer.h"
#include <utility>
namespace kte {
TreeSitterHighlighter::TreeSitterHighlighter(const TSLanguage *lang, std::string filetype)
: language_(lang), filetype_(std::move(filetype)) {}
TreeSitterHighlighter::~TreeSitterHighlighter()
{
disposeParser();
}
void
TreeSitterHighlighter::ensureParsed(const Buffer & /*buf*/) const
{
// Intentionally a stub to avoid pulling the Tree-sitter API and library by default.
// In future, when linking against tree-sitter, initialize parser_, set language_,
// and build tree_ from the buffer contents.
}
void
TreeSitterHighlighter::disposeParser() const
{
// Stub; nothing to dispose when not actually creating parser/tree
}
void
TreeSitterHighlighter::HighlightLine(const Buffer &/*buf*/, int /*row*/, std::vector<HighlightSpan> &/*out*/) const
{
// For now, no-op. When tree-sitter is wired, map nodes to TokenKind spans per line.
}
std::unique_ptr<LanguageHighlighter>
CreateTreeSitterHighlighter(const char *filetype,
const void * (*get_lang)())
{
const auto *lang = reinterpret_cast<const TSLanguage *>(get_lang ? get_lang() : nullptr);
return std::make_unique < TreeSitterHighlighter > (lang, filetype ? std::string(filetype) : std::string());
}
} // namespace kte
#endif // KTE_ENABLE_TREESITTER

View File

@@ -0,0 +1,48 @@
// TreeSitterHighlighter.h - optional adapter for Tree-sitter (behind KTE_ENABLE_TREESITTER)
#pragma once
#ifdef KTE_ENABLE_TREESITTER
#include <memory>
#include <string>
#include <vector>
#include "LanguageHighlighter.h"
// Forward-declare Tree-sitter C API to avoid hard coupling in headers if includes are not present
extern "C" {
struct TSLanguage;
struct TSParser;
struct TSTree;
}
namespace kte {
// A minimal adapter that uses Tree-sitter to parse the whole buffer and then, for now,
// does very limited token classification. This acts as a scaffold for future richer
// queries. If no queries are provided, it currently produces no spans (safe fallback).
class TreeSitterHighlighter : public LanguageHighlighter {
public:
explicit TreeSitterHighlighter(const TSLanguage *lang, std::string filetype);
~TreeSitterHighlighter() override;
void HighlightLine(const Buffer &buf, int row, std::vector<HighlightSpan> &out) const override;
private:
const TSLanguage *language_{nullptr};
std::string filetype_;
// Lazy parser to avoid startup cost; mutable to allow creation in const method
mutable TSParser *parser_{nullptr};
mutable TSTree *tree_{nullptr};
void ensureParsed(const Buffer &buf) const;
void disposeParser() const;
};
// Factory used by HighlighterRegistry when registering via RegisterTreeSitter.
std::unique_ptr<LanguageHighlighter> CreateTreeSitterHighlighter(const char *filetype,
const void * (*get_lang)());
} // namespace kte
#endif // KTE_ENABLE_TREESITTER

Some files were not shown because too many files have changed in this diff Show More