diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index f1b3720..876f8ed 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -141,6 +141,13 @@
+
+
+
+
+
+
+
diff --git a/Buffer.cc b/Buffer.cc
index bceb1da..7a2b419 100644
--- a/Buffer.cc
+++ b/Buffer.cc
@@ -9,6 +9,7 @@
// For reconstructing highlighter state on copies
#include "HighlighterRegistry.h"
#include "NullHighlighter.h"
+#include "lsp/BufferChangeTracker.h"
Buffer::Buffer()
@@ -19,6 +20,9 @@ Buffer::Buffer()
}
+Buffer::~Buffer() = default;
+
+
Buffer::Buffer(const std::string &path)
{
std::string err;
@@ -394,6 +398,30 @@ Buffer::AsString() const
}
+std::string
+Buffer::FullText() const
+{
+ std::string out;
+ // Precompute size for fewer reallocations
+ std::size_t total = 0;
+ for (std::size_t i = 0; i < rows_.size(); ++i) {
+ total += rows_[i].Size();
+ if (i + 1 < rows_.size())
+ total += 1; // for '\n'
+ }
+ out.reserve(total);
+ for (std::size_t i = 0; i < rows_.size(); ++i) {
+ const char *d = rows_[i].Data();
+ std::size_t n = rows_[i].Size();
+ if (d && n)
+ out.append(d, n);
+ if (i + 1 < rows_.size())
+ out.push_back('\n');
+ }
+ return out;
+}
+
+
// --- Raw editing APIs (no undo recording, cursor untouched) ---
void
Buffer::insert_text(int row, int col, std::string_view text)
@@ -432,6 +460,9 @@ Buffer::insert_text(int row, int col, std::string_view text)
remain.erase(0, pos + 1);
}
// Do not set dirty here; UndoSystem will manage state/dirty externally
+ if (change_tracker_) {
+ change_tracker_->recordInsertion(row, col, std::string(text));
+ }
}
@@ -470,6 +501,9 @@ Buffer::delete_text(int row, int col, std::size_t len)
break;
}
}
+ if (change_tracker_) {
+ change_tracker_->recordDeletion(row, col, len);
+ }
}
@@ -543,3 +577,17 @@ Buffer::Undo() const
{
return undo_sys_.get();
}
+
+
+void
+Buffer::SetChangeTracker(std::unique_ptr tracker)
+{
+ change_tracker_ = std::move(tracker);
+}
+
+
+kte::lsp::BufferChangeTracker *
+Buffer::GetChangeTracker()
+{
+ return change_tracker_.get();
+}
\ No newline at end of file
diff --git a/Buffer.h b/Buffer.h
index 926f67c..3cfb3b0 100644
--- a/Buffer.h
+++ b/Buffer.h
@@ -17,11 +17,20 @@
#include "HighlighterEngine.h"
#include "Highlight.h"
+// Forward declarations to avoid heavy includes
+namespace kte {
+namespace lsp {
+class BufferChangeTracker;
+}
+}
+
class Buffer {
public:
Buffer();
+ ~Buffer();
+
Buffer(const Buffer &other);
Buffer &operator=(const Buffer &other);
@@ -374,23 +383,59 @@ public:
[[nodiscard]] std::string AsString() const;
+ // Compose full text of this buffer with newlines between rows
+ [[nodiscard]] std::string FullText() const;
+
// Syntax highlighting integration (per-buffer)
- [[nodiscard]] std::uint64_t Version() const { return version_; }
+ [[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_; }
+ 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();
+ }
- kte::HighlighterEngine *Highlighter() { return highlighter_.get(); }
- const kte::HighlighterEngine *Highlighter() const { return highlighter_.get(); }
void EnsureHighlighter()
{
- if (!highlighter_) highlighter_ = std::make_unique();
+ if (!highlighter_)
+ highlighter_ = std::make_unique();
}
+
// 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);
@@ -410,6 +455,11 @@ public:
[[nodiscard]] const UndoSystem *Undo() const;
+ // LSP integration: optional change tracker
+ void SetChangeTracker(std::unique_ptr tracker);
+
+ kte::lsp::BufferChangeTracker *GetChangeTracker();
+
private:
// State mirroring original C struct (without undo_tree)
std::size_t curx_ = 0, cury_ = 0; // cursor position in characters
@@ -430,9 +480,12 @@ private:
// Syntax/highlighting state
std::uint64_t version_ = 0; // increment on edits
- bool syntax_enabled_ = true;
+ bool syntax_enabled_ = true;
std::string filetype_;
std::unique_ptr highlighter_;
+
+ // Optional LSP change tracker (absent by default)
+ std::unique_ptr change_tracker_;
};
-#endif // KTE_BUFFER_H
+#endif // KTE_BUFFER_H
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 32d0bf2..e09ea3c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,36 +16,36 @@ 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.")
+ message(STATUS "Build system is POSIX.")
else ()
- message(STATUS "Build system is NOT POSIX.")
+ message(STATUS "Build system is NOT POSIX.")
endif ()
if (MSVC)
- add_compile_options("/W4" "$<$:/O2>")
+ add_compile_options("/W4" "$<$:/O2>")
else ()
- add_compile_options(
- "-Wall"
- "-Wextra"
- "-Werror"
- "$<$:-g>"
- "$<$:-O2>")
- if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
- add_compile_options("-stdlib=libc++")
- else ()
- # nothing special for gcc at the moment
- endif ()
+ add_compile_options(
+ "-Wall"
+ "-Wextra"
+ "-Werror"
+ "$<$:-g>"
+ "$<$:-O2>")
+ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ add_compile_options("-stdlib=libc++")
+ else ()
+ # nothing special for gcc at the moment
+ endif ()
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)
+ add_compile_definitions(KTE_ENABLE_TREESITTER)
endif ()
message(STATUS "Build system: ${CMAKE_HOST_SYSTEM_NAME}")
if (${BUILD_GUI})
- include(cmake/imgui.cmake)
+ include(cmake/imgui.cmake)
endif ()
# NCurses for terminal mode
@@ -55,222 +55,239 @@ find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR})
set(COMMON_SOURCES
- GapBuffer.cc
- PieceTable.cc
- Buffer.cc
- Editor.cc
- Command.cc
- HelpText.cc
- KKeymap.cc
- TerminalInputHandler.cc
- TerminalRenderer.cc
- TerminalFrontend.cc
- TestInputHandler.cc
- TestRenderer.cc
- TestFrontend.cc
- UndoNode.cc
- UndoTree.cc
- UndoSystem.cc
- HighlighterEngine.cc
- CppHighlighter.cc
- HighlighterRegistry.cc
- NullHighlighter.cc
- JsonHighlighter.cc
- MarkdownHighlighter.cc
- ShellHighlighter.cc
- GoHighlighter.cc
- PythonHighlighter.cc
- RustHighlighter.cc
- LispHighlighter.cc
+ GapBuffer.cc
+ PieceTable.cc
+ Buffer.cc
+ Editor.cc
+ Command.cc
+ HelpText.cc
+ KKeymap.cc
+ TerminalInputHandler.cc
+ TerminalRenderer.cc
+ TerminalFrontend.cc
+ TestInputHandler.cc
+ TestRenderer.cc
+ TestFrontend.cc
+ UndoNode.cc
+ UndoTree.cc
+ UndoSystem.cc
+ HighlighterEngine.cc
+ CppHighlighter.cc
+ HighlighterRegistry.cc
+ NullHighlighter.cc
+ JsonHighlighter.cc
+ MarkdownHighlighter.cc
+ ShellHighlighter.cc
+ GoHighlighter.cc
+ PythonHighlighter.cc
+ RustHighlighter.cc
+ LispHighlighter.cc
+ lsp/BufferChangeTracker.cc
+ lsp/JsonRpcTransport.cc
+ lsp/LspProcessClient.cc
+ lsp/DiagnosticStore.cc
+ lsp/TerminalDiagnosticDisplay.cc
+ lsp/LspManager.cc
)
if (KTE_ENABLE_TREESITTER)
- list(APPEND COMMON_SOURCES
- TreeSitterHighlighter.cc)
+ list(APPEND COMMON_SOURCES
+ TreeSitterHighlighter.cc)
endif ()
set(COMMON_HEADERS
- GapBuffer.h
- PieceTable.h
- Buffer.h
- Editor.h
- AppendBuffer.h
- Command.h
- HelpText.h
- KKeymap.h
- InputHandler.h
- TerminalInputHandler.h
- Renderer.h
- TerminalRenderer.h
- Frontend.h
- TerminalFrontend.h
- TestInputHandler.h
- TestRenderer.h
- TestFrontend.h
- UndoNode.h
- UndoTree.h
- UndoSystem.h
- Highlight.h
- LanguageHighlighter.h
- HighlighterEngine.h
- CppHighlighter.h
- HighlighterRegistry.h
- NullHighlighter.h
- JsonHighlighter.h
- MarkdownHighlighter.h
- ShellHighlighter.h
- GoHighlighter.h
- PythonHighlighter.h
- RustHighlighter.h
- LispHighlighter.h
+ GapBuffer.h
+ PieceTable.h
+ Buffer.h
+ Editor.h
+ AppendBuffer.h
+ Command.h
+ HelpText.h
+ KKeymap.h
+ InputHandler.h
+ TerminalInputHandler.h
+ Renderer.h
+ TerminalRenderer.h
+ Frontend.h
+ TerminalFrontend.h
+ TestInputHandler.h
+ TestRenderer.h
+ TestFrontend.h
+ UndoNode.h
+ UndoTree.h
+ UndoSystem.h
+ Highlight.h
+ LanguageHighlighter.h
+ HighlighterEngine.h
+ CppHighlighter.h
+ HighlighterRegistry.h
+ NullHighlighter.h
+ JsonHighlighter.h
+ MarkdownHighlighter.h
+ ShellHighlighter.h
+ GoHighlighter.h
+ PythonHighlighter.h
+ RustHighlighter.h
+ LispHighlighter.h
+ lsp/LspTypes.h
+ lsp/BufferChangeTracker.h
+ lsp/JsonRpcTransport.h
+ lsp/LspClient.h
+ lsp/LspProcessClient.h
+ lsp/Diagnostic.h
+ lsp/DiagnosticStore.h
+ lsp/DiagnosticDisplay.h
+ lsp/TerminalDiagnosticDisplay.h
+ lsp/LspManager.h
+ lsp/LspServerConfig.h
)
if (KTE_ENABLE_TREESITTER)
- list(APPEND COMMON_HEADERS
- TreeSitterHighlighter.h)
+ list(APPEND COMMON_HEADERS
+ TreeSitterHighlighter.h)
endif ()
# kte (terminal-first) executable
add_executable(kte
- main.cc
- ${COMMON_SOURCES}
- ${COMMON_HEADERS}
+ main.cc
+ ${COMMON_SOURCES}
+ ${COMMON_HEADERS}
)
if (KTE_USE_PIECE_TABLE)
- target_compile_definitions(kte PRIVATE KTE_USE_PIECE_TABLE=1)
+ target_compile_definitions(kte PRIVATE KTE_USE_PIECE_TABLE=1)
endif ()
if (KTE_UNDO_DEBUG)
- target_compile_definitions(kte PRIVATE KTE_UNDO_DEBUG=1)
+ target_compile_definitions(kte PRIVATE KTE_UNDO_DEBUG=1)
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 ()
+ # 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}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# Man pages
install(FILES docs/kte.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
if (BUILD_TESTS)
- # test_undo executable for testing undo/redo system
- add_executable(test_undo
- test_undo.cc
- ${COMMON_SOURCES}
- ${COMMON_HEADERS}
- )
+ # test_undo executable for testing undo/redo system
+ add_executable(test_undo
+ test_undo.cc
+ ${COMMON_SOURCES}
+ ${COMMON_HEADERS}
+ )
- if (KTE_USE_PIECE_TABLE)
- target_compile_definitions(test_undo PRIVATE KTE_USE_PIECE_TABLE=1)
- endif ()
+ if (KTE_USE_PIECE_TABLE)
+ target_compile_definitions(test_undo PRIVATE KTE_USE_PIECE_TABLE=1)
+ endif ()
- if (KTE_UNDO_DEBUG)
- target_compile_definitions(test_undo PRIVATE KTE_UNDO_DEBUG=1)
- endif ()
+ if (KTE_UNDO_DEBUG)
+ target_compile_definitions(test_undo PRIVATE KTE_UNDO_DEBUG=1)
+ endif ()
- 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 ()
+ 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)
+ 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)
- # kge (GUI-first) executable
- add_executable(kge
- main.cc
- ${COMMON_SOURCES}
- ${COMMON_HEADERS}
- GUIConfig.cc
- GUIConfig.h
- GUIRenderer.cc
- GUIRenderer.h
- GUIInputHandler.cc
- GUIInputHandler.h
- GUIFrontend.cc
- GUIFrontend.h)
- target_compile_definitions(kge PRIVATE KTE_BUILD_GUI=1 KTE_DEFAULT_GUI=1 KTE_FONT_SIZE=${KTE_FONT_SIZE})
- if (KTE_UNDO_DEBUG)
- target_compile_definitions(kge PRIVATE KTE_UNDO_DEBUG=1)
- endif ()
- target_link_libraries(kge ${CURSES_LIBRARIES} imgui)
+ # kge (GUI-first) executable
+ add_executable(kge
+ main.cc
+ ${COMMON_SOURCES}
+ ${COMMON_HEADERS}
+ GUIConfig.cc
+ GUIConfig.h
+ GUIRenderer.cc
+ GUIRenderer.h
+ GUIInputHandler.cc
+ GUIInputHandler.h
+ GUIFrontend.cc
+ GUIFrontend.h)
+ target_compile_definitions(kge PRIVATE KTE_BUILD_GUI=1 KTE_DEFAULT_GUI=1 KTE_FONT_SIZE=${KTE_FONT_SIZE})
+ if (KTE_UNDO_DEBUG)
+ target_compile_definitions(kge PRIVATE KTE_UNDO_DEBUG=1)
+ endif ()
+ target_link_libraries(kge ${CURSES_LIBRARIES} imgui)
- # On macOS, build kge as a proper .app bundle
- if (APPLE)
- # Define the icon file
- set(MACOSX_BUNDLE_ICON_FILE kge.icns)
- set(kge_ICON "${CMAKE_CURRENT_SOURCE_DIR}/${MACOSX_BUNDLE_ICON_FILE}")
+ # On macOS, build kge as a proper .app bundle
+ if (APPLE)
+ # Define the icon file
+ set(MACOSX_BUNDLE_ICON_FILE kge.icns)
+ set(kge_ICON "${CMAKE_CURRENT_SOURCE_DIR}/${MACOSX_BUNDLE_ICON_FILE}")
- # Add icon to the target sources and mark it as a resource
- target_sources(kge PRIVATE ${kge_ICON})
- set_source_files_properties(${kge_ICON} PROPERTIES
- MACOSX_PACKAGE_LOCATION Resources)
+ # Add icon to the target sources and mark it as a resource
+ target_sources(kge PRIVATE ${kge_ICON})
+ set_source_files_properties(${kge_ICON} PROPERTIES
+ MACOSX_PACKAGE_LOCATION Resources)
- # Configure Info.plist with version and identifiers
- set(KGE_BUNDLE_ID "dev.wntrmute.kge")
- configure_file(
- ${CMAKE_CURRENT_LIST_DIR}/cmake/Info.plist.in
- ${CMAKE_CURRENT_BINARY_DIR}/kge-Info.plist
- @ONLY)
+ # Configure Info.plist with version and identifiers
+ set(KGE_BUNDLE_ID "dev.wntrmute.kge")
+ configure_file(
+ ${CMAKE_CURRENT_LIST_DIR}/cmake/Info.plist.in
+ ${CMAKE_CURRENT_BINARY_DIR}/kge-Info.plist
+ @ONLY)
- set_target_properties(kge PROPERTIES
- MACOSX_BUNDLE TRUE
- MACOSX_BUNDLE_GUI_IDENTIFIER ${KGE_BUNDLE_ID}
- MACOSX_BUNDLE_BUNDLE_NAME "kge"
- MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE}
- MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/kge-Info.plist")
+ set_target_properties(kge PROPERTIES
+ MACOSX_BUNDLE TRUE
+ MACOSX_BUNDLE_GUI_IDENTIFIER ${KGE_BUNDLE_ID}
+ MACOSX_BUNDLE_BUNDLE_NAME "kge"
+ MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE}
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/kge-Info.plist")
- add_dependencies(kge kte)
- add_custom_command(TARGET kge POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy
- $
- $/kte
- COMMENT "Copying kte binary into kge.app bundle")
+ add_dependencies(kge kte)
+ add_custom_command(TARGET kge POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ $
+ $/kte
+ COMMENT "Copying kte binary into kge.app bundle")
- install(TARGETS kge
- BUNDLE DESTINATION .
- )
+ install(TARGETS kge
+ BUNDLE DESTINATION .
+ )
- install(TARGETS kte
- RUNTIME DESTINATION kge.app/Contents/MacOS
- )
- else ()
- install(TARGETS kge
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- )
- endif ()
- # Install kge man page only when GUI is built
- install(FILES docs/kge.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
- install(FILES kge.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons)
+ install(TARGETS kte
+ RUNTIME DESTINATION kge.app/Contents/MacOS
+ )
+ else ()
+ install(TARGETS kge
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ )
+ endif ()
+ # Install kge man page only when GUI is built
+ install(FILES docs/kge.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
+ install(FILES kge.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons)
endif ()
diff --git a/Command.cc b/Command.cc
index 3b5a12f..144605c 100644
--- a/Command.cc
+++ b/Command.cc
@@ -762,122 +762,140 @@ cmd_unknown_kcommand(CommandContext &ctx)
return true;
}
+
// --- Syntax highlighting commands ---
-static void apply_filetype(Buffer &buf, const std::string &ft)
+static void
+apply_filetype(Buffer &buf, const std::string &ft)
{
- buf.EnsureHighlighter();
- auto *eng = buf.Highlighter();
- if (!eng) return;
- std::string val = ft;
- // trim + lower
- auto trim = [](const std::string &s){
- std::string r = s;
- auto notsp = [](int ch){ return !std::isspace(ch); };
- r.erase(r.begin(), std::find_if(r.begin(), r.end(), notsp));
- r.erase(std::find_if(r.rbegin(), r.rend(), notsp).base(), r.end());
- return r;
- };
- val = trim(val);
- for (auto &ch: val) ch = static_cast(std::tolower(static_cast(ch)));
- if (val == "off") {
- eng->SetHighlighter(nullptr);
- buf.SetFiletype("");
- buf.SetSyntaxEnabled(false);
- return;
- }
- if (val.empty()) {
- // Empty means unknown/unspecified -> use NullHighlighter but keep syntax enabled
- buf.SetFiletype("");
- buf.SetSyntaxEnabled(true);
- eng->SetHighlighter(std::make_unique());
- eng->InvalidateFrom(0);
- return;
- }
- // Normalize and create via registry
- std::string norm = kte::HighlighterRegistry::Normalize(val);
- auto hl = kte::HighlighterRegistry::CreateFor(norm);
- if (hl) {
- eng->SetHighlighter(std::move(hl));
- buf.SetFiletype(norm);
- buf.SetSyntaxEnabled(true);
- eng->InvalidateFrom(0);
- } else {
- // Unknown -> install NullHighlighter and keep syntax enabled
- eng->SetHighlighter(std::make_unique());
- buf.SetFiletype(val); // record what user asked even if unsupported
- buf.SetSyntaxEnabled(true);
- eng->InvalidateFrom(0);
- }
+ buf.EnsureHighlighter();
+ auto *eng = buf.Highlighter();
+ if (!eng)
+ return;
+ std::string val = ft;
+ // trim + lower
+ auto trim = [](const std::string &s) {
+ std::string r = s;
+ auto notsp = [](int ch) {
+ return !std::isspace(ch);
+ };
+ r.erase(r.begin(), std::find_if(r.begin(), r.end(), notsp));
+ r.erase(std::find_if(r.rbegin(), r.rend(), notsp).base(), r.end());
+ return r;
+ };
+ val = trim(val);
+ for (auto &ch: val)
+ ch = static_cast(std::tolower(static_cast(ch)));
+ if (val == "off") {
+ eng->SetHighlighter(nullptr);
+ buf.SetFiletype("");
+ buf.SetSyntaxEnabled(false);
+ return;
+ }
+ if (val.empty()) {
+ // Empty means unknown/unspecified -> use NullHighlighter but keep syntax enabled
+ buf.SetFiletype("");
+ buf.SetSyntaxEnabled(true);
+ eng->SetHighlighter(std::make_unique());
+ eng->InvalidateFrom(0);
+ return;
+ }
+ // Normalize and create via registry
+ std::string norm = kte::HighlighterRegistry::Normalize(val);
+ auto hl = kte::HighlighterRegistry::CreateFor(norm);
+ if (hl) {
+ eng->SetHighlighter(std::move(hl));
+ buf.SetFiletype(norm);
+ buf.SetSyntaxEnabled(true);
+ eng->InvalidateFrom(0);
+ } else {
+ // Unknown -> install NullHighlighter and keep syntax enabled
+ eng->SetHighlighter(std::make_unique());
+ buf.SetFiletype(val); // record what user asked even if unsupported
+ buf.SetSyntaxEnabled(true);
+ eng->InvalidateFrom(0);
+ }
}
-static bool cmd_syntax(CommandContext &ctx)
+
+static bool
+cmd_syntax(CommandContext &ctx)
{
- Buffer *b = ctx.editor.CurrentBuffer();
- if (!b) {
- ctx.editor.SetStatus("No buffer");
- return true;
- }
- std::string arg = ctx.arg;
- // trim
- auto trim = [](std::string &s){
- auto notsp = [](int ch){ return !std::isspace(ch); };
- s.erase(s.begin(), std::find_if(s.begin(), s.end(), notsp));
- s.erase(std::find_if(s.rbegin(), s.rend(), notsp).base(), s.end());
- };
- trim(arg);
- if (arg == "on") {
- b->SetSyntaxEnabled(true);
- // If no highlighter but filetype is cpp by extension, set it
- if (!b->Highlighter() || !b->Highlighter()->HasHighlighter()) {
- apply_filetype(*b, b->Filetype().empty() ? std::string("cpp") : b->Filetype());
- }
- ctx.editor.SetStatus("syntax: on");
- } else if (arg == "off") {
- b->SetSyntaxEnabled(false);
- ctx.editor.SetStatus("syntax: off");
- } else if (arg == "reload") {
- if (auto *eng = b->Highlighter()) eng->InvalidateFrom(0);
- ctx.editor.SetStatus("syntax: reloaded");
- } else {
- ctx.editor.SetStatus("usage: :syntax on|off|reload");
- }
- return true;
+ Buffer *b = ctx.editor.CurrentBuffer();
+ if (!b) {
+ ctx.editor.SetStatus("No buffer");
+ return true;
+ }
+ std::string arg = ctx.arg;
+ // trim
+ auto trim = [](std::string &s) {
+ auto notsp = [](int ch) {
+ return !std::isspace(ch);
+ };
+ s.erase(s.begin(), std::find_if(s.begin(), s.end(), notsp));
+ s.erase(std::find_if(s.rbegin(), s.rend(), notsp).base(), s.end());
+ };
+ trim(arg);
+ if (arg == "on") {
+ b->SetSyntaxEnabled(true);
+ // If no highlighter but filetype is cpp by extension, set it
+ if (!b->Highlighter() || !b->Highlighter()->HasHighlighter()) {
+ apply_filetype(*b, b->Filetype().empty() ? std::string("cpp") : b->Filetype());
+ }
+ ctx.editor.SetStatus("syntax: on");
+ } else if (arg == "off") {
+ b->SetSyntaxEnabled(false);
+ ctx.editor.SetStatus("syntax: off");
+ } else if (arg == "reload") {
+ if (auto *eng = b->Highlighter())
+ eng->InvalidateFrom(0);
+ ctx.editor.SetStatus("syntax: reloaded");
+ } else {
+ ctx.editor.SetStatus("usage: :syntax on|off|reload");
+ }
+ return true;
}
-static bool cmd_set_option(CommandContext &ctx)
+
+static bool
+cmd_set_option(CommandContext &ctx)
{
- Buffer *b = ctx.editor.CurrentBuffer();
- if (!b) {
- ctx.editor.SetStatus("No buffer");
- return true;
- }
- // Expect key=value
- auto eq = ctx.arg.find('=');
- if (eq == std::string::npos) {
- ctx.editor.SetStatus("usage: :set key=value");
- return true;
- }
- std::string key = ctx.arg.substr(0, eq);
- std::string val = ctx.arg.substr(eq + 1);
- // trim
- auto trim = [](std::string &s){
- auto notsp = [](int ch){ return !std::isspace(ch); };
- s.erase(s.begin(), std::find_if(s.begin(), s.end(), notsp));
- s.erase(std::find_if(s.rbegin(), s.rend(), notsp).base(), s.end());
- };
- trim(key); trim(val);
- // lower-case value for filetype
- for (auto &ch: val) ch = static_cast(std::tolower(static_cast(ch)));
- if (key == "filetype") {
- apply_filetype(*b, val);
- if (b->SyntaxEnabled())
- ctx.editor.SetStatus(std::string("filetype: ") + (b->Filetype().empty()?"off":b->Filetype()));
- else
- ctx.editor.SetStatus("filetype: off");
- return true;
- }
- ctx.editor.SetStatus("unknown option: " + key);
- return true;
+ Buffer *b = ctx.editor.CurrentBuffer();
+ if (!b) {
+ ctx.editor.SetStatus("No buffer");
+ return true;
+ }
+ // Expect key=value
+ auto eq = ctx.arg.find('=');
+ if (eq == std::string::npos) {
+ ctx.editor.SetStatus("usage: :set key=value");
+ return true;
+ }
+ std::string key = ctx.arg.substr(0, eq);
+ std::string val = ctx.arg.substr(eq + 1);
+ // trim
+ auto trim = [](std::string &s) {
+ auto notsp = [](int ch) {
+ return !std::isspace(ch);
+ };
+ s.erase(s.begin(), std::find_if(s.begin(), s.end(), notsp));
+ s.erase(std::find_if(s.rbegin(), s.rend(), notsp).base(), s.end());
+ };
+ trim(key);
+ trim(val);
+ // lower-case value for filetype
+ for (auto &ch: val)
+ ch = static_cast(std::tolower(static_cast(ch)));
+ if (key == "filetype") {
+ apply_filetype(*b, val);
+ if (b->SyntaxEnabled())
+ ctx.editor.SetStatus(
+ std::string("filetype: ") + (b->Filetype().empty() ? "off" : b->Filetype()));
+ else
+ ctx.editor.SetStatus("filetype: off");
+ return true;
+ }
+ ctx.editor.SetStatus("unknown option: " + key);
+ return true;
}
@@ -907,6 +925,7 @@ cmd_theme_next(CommandContext &ctx)
return true;
}
+
static bool
cmd_theme_prev(CommandContext &ctx)
{
@@ -3734,12 +3753,12 @@ InstallDefaultCommands()
CommandId::ChangeWorkingDirectory, "change-working-directory", "Change current working directory",
cmd_change_working_directory_start
});
- // UI helpers
- CommandRegistry::Register(
- {CommandId::UArgStatus, "uarg-status", "Update universal-arg status", cmd_uarg_status});
- // Syntax highlighting (public commands)
- CommandRegistry::Register({CommandId::Syntax, "syntax", "Syntax: on|off|reload", cmd_syntax, true});
- CommandRegistry::Register({CommandId::SetOption, "set", "Set option: key=value", cmd_set_option, true});
+ // UI helpers
+ CommandRegistry::Register(
+ {CommandId::UArgStatus, "uarg-status", "Update universal-arg status", cmd_uarg_status});
+ // Syntax highlighting (public commands)
+ CommandRegistry::Register({CommandId::Syntax, "syntax", "Syntax: on|off|reload", cmd_syntax, true});
+ CommandRegistry::Register({CommandId::SetOption, "set", "Set option: key=value", cmd_set_option, true});
}
@@ -3781,4 +3800,4 @@ Execute(Editor &ed, const std::string &name, const std::string &arg, int count)
return false;
CommandContext ctx{ed, arg, count};
return cmd->handler ? cmd->handler(ctx) : false;
-}
+}
\ No newline at end of file
diff --git a/Command.h b/Command.h
index 7ee15eb..20ede02 100644
--- a/Command.h
+++ b/Command.h
@@ -94,10 +94,10 @@ enum class CommandId {
// Theme by name
ThemeSetByName,
// Background mode (GUI)
- BackgroundSet,
- // Syntax highlighting
- Syntax, // ":syntax on|off|reload"
- SetOption, // generic ":set key=value" (v1: filetype=)
+ BackgroundSet,
+ // Syntax highlighting
+ Syntax, // ":syntax on|off|reload"
+ SetOption, // generic ":set key=value" (v1: filetype=)
};
@@ -151,4 +151,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
\ No newline at end of file
diff --git a/CppHighlighter.cc b/CppHighlighter.cc
index c1e0f9c..2ba3a93 100644
--- a/CppHighlighter.cc
+++ b/CppHighlighter.cc
@@ -3,168 +3,277 @@
#include
namespace kte {
+static bool
+is_digit(char c)
+{
+ return c >= '0' && c <= '9';
+}
-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);
+ 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(c)) || c == '_'; }
-bool CppHighlighter::is_ident_char(char c) { return std::isalnum(static_cast(c)) || c == '_'; }
-void CppHighlighter::HighlightLine(const Buffer &buf, int row, std::vector &out) const
+bool
+CppHighlighter::is_ident_start(char c)
{
- // Stateless entry simply delegates to stateful with a clean previous state
- StatefulHighlighter::LineState prev;
- (void)HighlightLineStateful(buf, row, prev, out);
+ return std::isalpha(static_cast(c)) || c == '_';
}
-StatefulHighlighter::LineState CppHighlighter::HighlightLineStateful(const Buffer &buf,
- int row,
- const LineState &prev,
- std::vector &out) const
+
+bool
+CppHighlighter::is_ident_char(char c)
{
- const auto &rows = buf.Rows();
- StatefulHighlighter::LineState state = prev;
- if (row < 0 || static_cast(row) >= rows.size()) return state;
- std::string s = static_cast(rows[static_cast(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(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(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(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(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(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(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;
+ return std::isalnum(static_cast(c)) || c == '_';
}
-} // namespace kte
+
+void
+CppHighlighter::HighlightLine(const Buffer &buf, int row, std::vector &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 &out) const
+{
+ const auto &rows = buf.Rows();
+ StatefulHighlighter::LineState state = prev;
+ if (row < 0 || static_cast(row) >= rows.size())
+ return state;
+ std::string s = static_cast(rows[static_cast(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(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(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(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(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(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(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
\ No newline at end of file
diff --git a/CppHighlighter.h b/CppHighlighter.h
index 9b63bff..10a3680 100644
--- a/CppHighlighter.h
+++ b/CppHighlighter.h
@@ -11,24 +11,25 @@
class Buffer;
namespace kte {
-
class CppHighlighter final : public StatefulHighlighter {
public:
- CppHighlighter();
- ~CppHighlighter() override = default;
+ CppHighlighter();
- void HighlightLine(const Buffer &buf, int row, std::vector &out) const override;
- LineState HighlightLineStateful(const Buffer &buf,
- int row,
- const LineState &prev,
- std::vector &out) const override;
+ ~CppHighlighter() override = default;
+
+ void HighlightLine(const Buffer &buf, int row, std::vector &out) const override;
+
+ LineState HighlightLineStateful(const Buffer &buf,
+ int row,
+ const LineState &prev,
+ std::vector &out) const override;
private:
- std::unordered_set keywords_;
- std::unordered_set types_;
+ std::unordered_set keywords_;
+ std::unordered_set types_;
- static bool is_ident_start(char c);
- static bool is_ident_char(char c);
+ static bool is_ident_start(char c);
+
+ static bool is_ident_char(char c);
};
-
-} // namespace kte
+} // namespace kte
\ No newline at end of file
diff --git a/Editor.cc b/Editor.cc
index 1aa82f2..1fc9923 100644
--- a/Editor.cc
+++ b/Editor.cc
@@ -148,104 +148,107 @@ Editor::OpenFile(const std::string &path, std::string &err)
{
// If there is exactly one unnamed, empty, clean buffer, reuse it instead
// of creating a new one.
- if (buffers_.size() == 1) {
- Buffer &cur = buffers_[curbuf_];
- const bool unnamed = cur.Filename().empty() && !cur.IsFileBacked();
- const bool clean = !cur.Dirty();
- const auto &rows = cur.Rows();
- 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)) {
- 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(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());
- eng->InvalidateFrom(0);
- }
- }
- return true;
- }
- }
+ if (buffers_.size() == 1) {
+ Buffer &cur = buffers_[curbuf_];
+ const bool unnamed = cur.Filename().empty() && !cur.IsFileBacked();
+ const bool clean = !cur.Dirty();
+ const auto &rows = cur.Rows();
+ 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)) {
+ 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(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());
+ eng->InvalidateFrom(0);
+ }
+ }
+ return true;
+ }
+ }
- Buffer b;
- 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(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());
- eng->InvalidateFrom(0);
- }
- }
- // Add as a new buffer and switch to it
- std::size_t idx = AddBuffer(std::move(b));
- SwitchTo(idx);
- return true;
+ Buffer b;
+ 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(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());
+ eng->InvalidateFrom(0);
+ }
+ }
+ // Add as a new buffer and switch to it
+ std::size_t idx = AddBuffer(std::move(b));
+ SwitchTo(idx);
+ return true;
}
bool
Editor::SwitchTo(std::size_t index)
{
- if (index >= buffers_.size()) {
- 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());
- }
- } else {
- eng->SetHighlighter(std::make_unique());
- }
- eng->InvalidateFrom(0);
- }
- }
- }
- return true;
+ if (index >= buffers_.size()) {
+ 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());
+ }
+ } else {
+ eng->SetHighlighter(std::make_unique());
+ }
+ eng->InvalidateFrom(0);
+ }
+ }
+ }
+ return true;
}
@@ -281,4 +284,4 @@ Editor::Reset()
quit_confirm_pending_ = false;
buffers_.clear();
curbuf_ = 0;
-}
+}
\ No newline at end of file
diff --git a/GUIConfig.cc b/GUIConfig.cc
index 9820ef0..ef0c913 100644
--- a/GUIConfig.cc
+++ b/GUIConfig.cc
@@ -102,27 +102,27 @@ GUIConfig::LoadFromFile(const std::string &path)
if (v > 0.0f) {
font_size = v;
}
- } 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;
- }
- }
- }
+ } 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;
-}
+ return true;
+}
\ No newline at end of file
diff --git a/GUIConfig.h b/GUIConfig.h
index 63799e1..61da5fa 100644
--- a/GUIConfig.h
+++ b/GUIConfig.h
@@ -12,18 +12,18 @@
class GUIConfig {
public:
- bool fullscreen = false;
- int columns = 80;
- int rows = 42;
- float font_size = (float) KTE_FONT_SIZE;
- std::string theme = "nord";
- // Background mode for themes that support light/dark variants
- // Values: "dark" (default), "light"
- std::string background = "dark";
+ bool fullscreen = false;
+ int columns = 80;
+ int rows = 42;
+ float font_size = (float) KTE_FONT_SIZE;
+ 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
+ // 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();
@@ -32,4 +32,4 @@ public:
bool LoadFromFile(const std::string &path);
};
-#endif // KTE_GUI_CONFIG_H
+#endif // KTE_GUI_CONFIG_H
\ No newline at end of file
diff --git a/GUIFrontend.cc b/GUIFrontend.cc
index afd0dcc..597d81c 100644
--- a/GUIFrontend.cc
+++ b/GUIFrontend.cc
@@ -108,42 +108,44 @@ GUIFrontend::Init(Editor &ed)
(void) io;
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 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(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());
- b->SetFiletype("");
- eng->InvalidateFrom(0);
- }
- }
- }
- } else {
- b->SetSyntaxEnabled(false);
- }
- }
+ // 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(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());
+ b->SetFiletype("");
+ eng->InvalidateFrom(0);
+ }
+ }
+ }
+ } else {
+ b->SetSyntaxEnabled(false);
+ }
+ }
if (!ImGui_ImplSDL2_InitForOpenGL(window_, gl_ctx_))
return false;
@@ -316,4 +318,4 @@ GUIFrontend::LoadGuiFont_(const char * /*path*/, float size_px)
}
-// No runtime font reload or system font resolution in this simplified build.
+// No runtime font reload or system font resolution in this simplified build.
\ No newline at end of file
diff --git a/GUIRenderer.cc b/GUIRenderer.cc
index 9ccf1fa..a607235 100644
--- a/GUIRenderer.cc
+++ b/GUIRenderer.cc
@@ -139,29 +139,29 @@ GUIRenderer::Draw(Editor &ed)
vis_rows = 1;
long last_row = first_row + vis_rows - 1;
- if (!forced_scroll) {
- long cyr = static_cast(cy);
- if (cyr < first_row || cyr > last_row) {
- float target = (static_cast(cyr) - std::max(0L, vis_rows / 2)) * row_h;
- float max_y = ImGui::GetScrollMaxY();
- if (target < 0.f)
- target = 0.f;
- if (max_y >= 0.f && target > max_y)
- target = max_y;
- ImGui::SetScrollY(target);
- // refresh local variables
- scroll_y = ImGui::GetScrollY();
- first_row = static_cast(scroll_y / row_h);
- last_row = first_row + vis_rows - 1;
- }
- }
- // Phase 3: prefetch visible viewport highlights and warm around in background
- if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
- int fr = static_cast(std::max(0L, first_row));
- int rc = static_cast(std::max(1L, vis_rows));
- buf->Highlighter()->PrefetchViewport(*buf, fr, rc, buf->Version());
- }
- }
+ if (!forced_scroll) {
+ long cyr = static_cast(cy);
+ if (cyr < first_row || cyr > last_row) {
+ float target = (static_cast(cyr) - std::max(0L, vis_rows / 2)) * row_h;
+ float max_y = ImGui::GetScrollMaxY();
+ if (target < 0.f)
+ target = 0.f;
+ if (max_y >= 0.f && target > max_y)
+ target = max_y;
+ ImGui::SetScrollY(target);
+ // refresh local variables
+ scroll_y = ImGui::GetScrollY();
+ first_row = static_cast(scroll_y / row_h);
+ last_row = first_row + vis_rows - 1;
+ }
+ }
+ // Phase 3: prefetch visible viewport highlights and warm around in background
+ if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
+ int fr = static_cast(std::max(0L, first_row));
+ int rc = static_cast(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;
@@ -329,50 +329,56 @@ GUIRenderer::Draw(Editor &ed)
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;
- }
- }
+ // 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(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::max(0, sp.col_start)));
- std::size_t rx_e = src_to_rx_full(static_cast(std::max(sp.col_start, sp.col_end)));
- if (rx_e <= coloffs_now)
- continue;
- std::size_t vx0 = (rx_s > coloffs_now) ? (rx_s - coloffs_now) : 0;
- std::size_t vx1 = (rx_e > coloffs_now) ? (rx_e - coloffs_now) : 0;
- if (vx0 >= expanded.size()) continue;
- vx1 = std::min(vx1, expanded.size());
- if (vx1 <= vx0) continue;
- ImU32 col = ImGui::GetColorU32(kte::SyntaxInk(sp.kind));
- ImVec2 p = ImVec2(line_pos.x + static_cast(vx0) * space_w, line_pos.y);
- ImGui::GetWindowDrawList()->AddText(p, col, expanded.c_str() + vx0, expanded.c_str() + vx1);
- }
- // We drew text via draw list (no layout advance). Manually advance the cursor to the next line.
- ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + line_h));
- } else {
- // No syntax: draw as one run
- ImGui::TextUnformatted(expanded.c_str());
- }
+ // 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(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::max(0, sp.col_start)));
+ std::size_t rx_e = src_to_rx_full(
+ static_cast(std::max(sp.col_start, sp.col_end)));
+ if (rx_e <= coloffs_now)
+ continue;
+ std::size_t vx0 = (rx_s > coloffs_now) ? (rx_s - coloffs_now) : 0;
+ std::size_t vx1 = (rx_e > coloffs_now) ? (rx_e - coloffs_now) : 0;
+ if (vx0 >= expanded.size())
+ continue;
+ vx1 = std::min(vx1, expanded.size());
+ if (vx1 <= vx0)
+ continue;
+ ImU32 col = ImGui::GetColorU32(kte::SyntaxInk(sp.kind));
+ ImVec2 p = ImVec2(line_pos.x + static_cast(vx0) * space_w, line_pos.y);
+ ImGui::GetWindowDrawList()->AddText(
+ p, col, expanded.c_str() + vx0, expanded.c_str() + vx1);
+ }
+ // We drew text via draw list (no layout advance). Manually advance the cursor to the next line.
+ ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + line_h));
+ } else {
+ // No syntax: draw as one run
+ ImGui::TextUnformatted(expanded.c_str());
+ }
// Draw a visible cursor indicator on the current line
if (i == cy) {
@@ -761,4 +767,4 @@ GUIRenderer::Draw(Editor &ed)
ed.SetFilePickerVisible(false);
}
}
-}
+}
\ No newline at end of file
diff --git a/GUITheme.h b/GUITheme.h
index 1606c4a..9b41cdb 100644
--- a/GUITheme.h
+++ b/GUITheme.h
@@ -10,16 +10,17 @@
#include
// Small helper to convert packed RGB (0xRRGGBB) + optional alpha to ImVec4
-static inline ImVec4 RGBA(unsigned int rgb, float a = 1.0f)
+static inline ImVec4
+RGBA(unsigned int rgb, float a = 1.0f)
{
- const float r = static_cast((rgb >> 16) & 0xFF) / 255.0f;
- const float g = static_cast((rgb >> 8) & 0xFF) / 255.0f;
- const float b = static_cast(rgb & 0xFF) / 255.0f;
- return ImVec4(r, g, b, a);
+ const float r = static_cast((rgb >> 16) & 0xFF) / 255.0f;
+ const float g = static_cast((rgb >> 8) & 0xFF) / 255.0f;
+ const float b = static_cast(rgb & 0xFF) / 255.0f;
+ return ImVec4(r, g, b, a);
}
-namespace kte {
+namespace kte {
// Background mode selection for light/dark palettes
enum class BackgroundMode { Light, Dark };
@@ -28,20 +29,21 @@ static inline BackgroundMode 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,
+ EInk = 0,
+ GruvboxDarkMedium = 1,
+ GruvboxLightMedium = 1, // alias to unified gruvbox index
+ Nord = 2,
+ Plan9 = 3,
+ Solarized = 4,
};
// Current theme tracking
-static inline ThemeId gCurrentTheme = ThemeId::Nord;
+static inline ThemeId gCurrentTheme = ThemeId::Nord;
static inline std::size_t gCurrentThemeIndex = 0;
// Forward declarations for helpers used below
static inline size_t ThemeIndexFromId(ThemeId id);
+
static inline ThemeId ThemeIdFromIndex(size_t idx);
// Helpers to set/query background mode
@@ -1098,18 +1100,18 @@ CurrentThemeName()
static inline size_t
ThemeIndexFromId(ThemeId id)
{
- switch (id) {
- case ThemeId::EInk:
- return 0;
- case ThemeId::GruvboxDarkMedium:
- return 1;
- case ThemeId::Nord:
- return 2;
- case ThemeId::Plan9:
- return 3;
- case ThemeId::Solarized:
- return 4;
- }
+ switch (id) {
+ case ThemeId::EInk:
+ return 0;
+ case ThemeId::GruvboxDarkMedium:
+ return 1;
+ case ThemeId::Nord:
+ return 2;
+ case ThemeId::Plan9:
+ return 3;
+ case ThemeId::Solarized:
+ return 4;
+ }
return 0;
}
@@ -1132,29 +1134,46 @@ ThemeIdFromIndex(size_t idx)
}
}
+
// --- Syntax palette (v1): map TokenKind to ink color per current theme/background ---
-static inline ImVec4 SyntaxInk(TokenKind k)
+static inline ImVec4
+SyntaxInk(TokenKind k)
{
- // Basic palettes for dark/light backgrounds; tuned for Nord-ish defaults
- const bool dark = (GetBackgroundMode() == BackgroundMode::Dark);
- // Base text
- 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;
- }
+ // Basic palettes for dark/light backgrounds; tuned for Nord-ish defaults
+ const bool dark = (GetBackgroundMode() == BackgroundMode::Dark);
+ // Base text
+ 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
+} // namespace kte
\ No newline at end of file
diff --git a/GoHighlighter.cc b/GoHighlighter.cc
index d63e240..6463c86 100644
--- a/GoHighlighter.cc
+++ b/GoHighlighter.cc
@@ -3,46 +3,155 @@
#include
namespace kte {
+static void
+push(std::vector &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(c)) || c == '_';
+}
+
+
+static bool
+is_ident_char(char c)
+{
+ return std::isalnum(static_cast(c)) || c == '_';
+}
-static void push(std::vector &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(c)) || c=='_'; }
-static bool is_ident_char(char c){ return std::isalnum(static_cast(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);
+ 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 &out) const
+
+void
+GoHighlighter::HighlightLine(const Buffer &buf, int row, std::vector &out) const
{
- const auto &rows = buf.Rows();
- if (row < 0 || static_cast(row) >= rows.size()) return;
- std::string s = static_cast(rows[static_cast(row)]);
- int n = static_cast(s.size());
- int i = 0;
- int bol=0; while (bol(c))) { int j=i+1; while (j(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(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;
- }
+ const auto &rows = buf.Rows();
+ if (row < 0 || static_cast(row) >= rows.size())
+ return;
+ std::string s = static_cast(rows[static_cast(row)]);
+ int n = static_cast(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(c))) {
+ int j = i + 1;
+ while (j < n && (std::isalnum(static_cast(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(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
+} // namespace kte
\ No newline at end of file
diff --git a/GoHighlighter.h b/GoHighlighter.h
index 89ec530..5a8bde4 100644
--- a/GoHighlighter.h
+++ b/GoHighlighter.h
@@ -5,14 +5,14 @@
#include
namespace kte {
-
class GoHighlighter final : public LanguageHighlighter {
public:
- GoHighlighter();
- void HighlightLine(const Buffer &buf, int row, std::vector &out) const override;
-private:
- std::unordered_set kws_;
- std::unordered_set types_;
-};
+ GoHighlighter();
-} // namespace kte
+ void HighlightLine(const Buffer &buf, int row, std::vector &out) const override;
+
+private:
+ std::unordered_set kws_;
+ std::unordered_set types_;
+};
+} // namespace kte
\ No newline at end of file
diff --git a/Highlight.h b/Highlight.h
index f10961b..21537fe 100644
--- a/Highlight.h
+++ b/Highlight.h
@@ -5,35 +5,33 @@
#include
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
+ 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};
+ int col_start{0}; // inclusive, 0-based columns in buffer indices
+ int col_end{0}; // exclusive
+ TokenKind kind{TokenKind::Default};
};
struct LineHighlight {
- std::vector spans;
- std::uint64_t version{0}; // buffer version used for this line
+ std::vector spans;
+ std::uint64_t version{0}; // buffer version used for this line
};
-
-} // namespace kte
+} // namespace kte
\ No newline at end of file
diff --git a/HighlighterEngine.cc b/HighlighterEngine.cc
index abcddff..647405d 100644
--- a/HighlighterEngine.cc
+++ b/HighlighterEngine.cc
@@ -4,178 +4,206 @@
#include
namespace kte {
-
HighlighterEngine::HighlighterEngine() = default;
+
+
HighlighterEngine::~HighlighterEngine()
{
- // stop background worker
- if (worker_running_.load()) {
- {
- std::lock_guard lock(mtx_);
- worker_running_.store(false);
- has_request_ = true; // wake it up to exit
- }
- cv_.notify_one();
- if (worker_.joinable()) worker_.join();
- }
+ // stop background worker
+ if (worker_running_.load()) {
+ {
+ std::lock_guard 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 hl)
{
- std::lock_guard lock(mtx_);
- hl_ = std::move(hl);
- cache_.clear();
- state_cache_.clear();
- state_last_contig_.clear();
+ std::lock_guard 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 lock(mtx_);
- auto it = cache_.find(row);
- if (it != cache_.end() && it->second.version == buf_version) {
- return it->second;
- }
+ std::unique_lock 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();
+ // 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;
- }
+ 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(hl_ptr) != nullptr;
+ // Copy shared_ptr-like raw pointer for use outside critical sections
+ LanguageHighlighter *hl_ptr = hl_.get();
+ bool is_stateful = dynamic_cast(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);
- }
+ 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(hl_ptr);
+ // 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(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;
- }
- }
+ 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 tmp;
- std::vector &out = (r == row) ? slot.spans : tmp;
- auto next_state = stateful->HighlightLineStateful(buf, r, cur_state, out);
- // Update state cache for r
- std::lock_guard gl(mtx_);
- StateEntry se;
- se.version = buf_version;
- se.state = next_state;
- state_cache_[r] = se;
- cur_state = next_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 tmp;
+ std::vector &out = (r == row) ? slot.spans : tmp;
+ auto next_state = stateful->HighlightLineStateful(buf, r, cur_state, out);
+ // Update state cache for r
+ std::lock_guard 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);
+ // 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 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;
- }
- }
+ std::lock_guard 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
+
+void
+HighlighterEngine::ensure_worker_started() const
{
- if (worker_running_.load()) return;
- worker_running_.store(true);
- worker_ = std::thread([this]() { this->worker_loop(); });
+ if (worker_running_.load())
+ return;
+ worker_running_.store(true);
+ worker_ = std::thread([this]() {
+ this->worker_loop();
+ });
}
-void HighlighterEngine::worker_loop() const
+
+void
+HighlighterEngine::worker_loop() const
{
- std::unique_lock 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();
- }
+ std::unique_lock 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
+
+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(buf.Nrows());
- if (start >= max_rows) return;
- if (end >= max_rows) end = max_rows - 1;
+ 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(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);
- }
+ 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 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();
+ // 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 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
+} // namespace kte
\ No newline at end of file
diff --git a/HighlighterEngine.h b/HighlighterEngine.h
index a57af91..b95569d 100644
--- a/HighlighterEngine.h
+++ b/HighlighterEngine.h
@@ -16,61 +16,70 @@
class Buffer;
namespace kte {
-
class HighlighterEngine {
public:
- HighlighterEngine();
- ~HighlighterEngine();
+ HighlighterEngine();
- void SetHighlighter(std::unique_ptr hl);
+ ~HighlighterEngine();
- // 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;
+ void SetHighlighter(std::unique_ptr hl);
- // Invalidate cached lines from row (inclusive)
- void InvalidateFrom(int row);
+ // 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;
- bool HasHighlighter() const { return static_cast(hl_); }
+ // Invalidate cached lines from row (inclusive)
+ void InvalidateFrom(int row);
- // 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;
+
+ bool HasHighlighter() const
+ {
+ return static_cast(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 hl_;
- // Simple cache by row index (mutable to allow caching in const GetLine)
- mutable std::unordered_map 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 state_cache_;
+ std::unique_ptr hl_;
+ // Simple cache by row index (mutable to allow caching in const GetLine)
+ mutable std::unordered_map cache_;
- // Track best known contiguous state row for a given version to avoid O(n) scans
- mutable std::unordered_map state_last_contig_;
+ // 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;
+ };
- // Thread-safety for caches and background worker state
- mutable std::mutex mtx_;
+ mutable std::unordered_map state_cache_;
- // 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 worker_running_{false};
- mutable bool has_request_{false};
- mutable WarmRequest pending_{};
+ // Track best known contiguous state row for a given version to avoid O(n) scans
+ mutable std::unordered_map state_last_contig_;
- void ensure_worker_started() const;
- void worker_loop() const;
+ // 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 worker_running_{false};
+ mutable bool has_request_{false};
+ mutable WarmRequest pending_{};
+
+ void ensure_worker_started() const;
+
+ void worker_loop() const;
};
-
-} // namespace kte
+} // namespace kte
\ No newline at end of file
diff --git a/HighlighterRegistry.cc b/HighlighterRegistry.cc
index b2c67a8..50bea1c 100644
--- a/HighlighterRegistry.cc
+++ b/HighlighterRegistry.cc
@@ -8,19 +8,28 @@
// Forward declare simple highlighters implemented in this project
namespace kte {
-
// Registration storage
struct RegEntry {
- std::string ft; // normalized
- HighlighterRegistry::Factory factory;
+ std::string ft; // normalized
+ HighlighterRegistry::Factory factory;
};
-static std::vector ®istry() {
- static std::vector reg;
- return reg;
+
+static std::vector &
+registry()
+{
+ static std::vector reg;
+ return reg;
}
-class JSONHighlighter; class MarkdownHighlighter; class ShellHighlighter;
-class GoHighlighter; class PythonHighlighter; class RustHighlighter; class LispHighlighter;
+
+
+class JSONHighlighter;
+class MarkdownHighlighter;
+class ShellHighlighter;
+class GoHighlighter;
+class PythonHighlighter;
+class RustHighlighter;
+class LispHighlighter;
}
// Headers for the above
@@ -33,125 +42,182 @@ class GoHighlighter; class PythonHighlighter; class RustHighlighter; class LispH
#include "LispHighlighter.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(std::tolower(c)); });
- return r;
-}
-
-std::string HighlighterRegistry::Normalize(std::string_view ft)
+static std::string
+to_lower(std::string_view s)
{
- 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";
- return f;
+ std::string r(s);
+ std::transform(r.begin(), r.end(), r.begin(), [](unsigned char c) {
+ return static_cast(std::tolower(c));
+ });
+ return r;
}
-std::unique_ptr HighlighterRegistry::CreateFor(std::string_view filetype)
+
+std::string
+HighlighterRegistry::Normalize(std::string_view ft)
{
- 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();
- if (ft == "json") return std::make_unique();
- if (ft == "markdown") return std::make_unique();
- if (ft == "shell") return std::make_unique();
- if (ft == "go") return std::make_unique();
- if (ft == "python") return std::make_unique();
- if (ft == "rust") return std::make_unique();
- if (ft == "lisp") return std::make_unique();
- return nullptr;
+ 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";
+ return f;
}
-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)
+std::unique_ptr
+HighlighterRegistry::CreateFor(std::string_view filetype)
{
- // 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(std::tolower(static_cast(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";
- }
- // Shebang
- std::string ft = shebang_to_ft(first_line);
- return ft;
+ 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();
+ if (ft == "json")
+ return std::make_unique();
+ if (ft == "markdown")
+ return std::make_unique();
+ if (ft == "shell")
+ return std::make_unique();
+ if (ft == "go")
+ return std::make_unique();
+ if (ft == "python")
+ return std::make_unique();
+ if (ft == "rust")
+ return std::make_unique();
+ if (ft == "lisp")
+ return std::make_unique();
+ 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(std::tolower(static_cast(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";
+ }
+ // 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)
+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)});
+ 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)
+
+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::string ft = Normalize(filetype);
+ for (const auto &e: registry())
+ if (e.ft == ft)
+ return true;
+ return false;
}
-std::vector HighlighterRegistry::RegisteredFiletypes()
+
+std::vector
+HighlighterRegistry::RegisteredFiletypes()
{
- std::vector out;
- out.reserve(registry().size());
- for (const auto &e : registry()) out.push_back(e.ft);
- return out;
+ std::vector 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 CreateTreeSitterHighlighter(const char* filetype,
- const void* (*get_lang)());
+std::unique_ptr CreateTreeSitterHighlighter(const char *filetype,
+ const void * (*get_lang)());
-void HighlighterRegistry::RegisterTreeSitter(std::string_view filetype,
- const TSLanguage* (*get_language)())
+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(get_language));
- }, /*override_existing=*/true);
+ std::string ft = Normalize(filetype);
+ Register(ft, [ft, get_language]() {
+ return CreateTreeSitterHighlighter(ft.c_str(), reinterpret_cast(get_language));
+ }, /*override_existing=*/true);
}
#endif
-
-} // namespace kte
+} // namespace kte
\ No newline at end of file
diff --git a/HighlighterRegistry.h b/HighlighterRegistry.h
index 5eb39f7..f44e5c3 100644
--- a/HighlighterRegistry.h
+++ b/HighlighterRegistry.h
@@ -10,40 +10,38 @@
#include "LanguageHighlighter.h"
namespace kte {
-
class HighlighterRegistry {
public:
- using Factory = std::function()>;
+ using Factory = std::function()>;
- // Create a highlighter for normalized filetype id (e.g., "cpp", "json", "markdown", "shell", "go", "python", "rust", "lisp").
- static std::unique_ptr CreateFor(std::string_view filetype);
+ // Create a highlighter for normalized filetype id (e.g., "cpp", "json", "markdown", "shell", "go", "python", "rust", "lisp").
+ static std::unique_ptr 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);
+ // 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);
+ // 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);
+ // 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);
+ // 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 RegisteredFiletypes();
+ // Return a list of currently registered (normalized) filetypes. Primarily for diagnostics/tests.
+ static std::vector 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)());
+ // 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
+} // namespace kte
\ No newline at end of file
diff --git a/JsonHighlighter.cc b/JsonHighlighter.cc
index 693d3ff..140f458 100644
--- a/JsonHighlighter.cc
+++ b/JsonHighlighter.cc
@@ -3,40 +3,88 @@
#include
namespace kte {
-
-static bool is_digit(char c) { return c >= '0' && c <= '9'; }
-
-void JSONHighlighter::HighlightLine(const Buffer &buf, int row, std::vector &out) const
+static bool
+is_digit(char c)
{
- const auto &rows = buf.Rows();
- if (row < 0 || static_cast(row) >= rows.size()) return;
- std::string s = static_cast(rows[static_cast(row)]);
- int n = static_cast(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(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(c))) {
- int j=i+1; while (j(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;
- }
+ return c >= '0' && c <= '9';
}
-} // namespace kte
+
+void
+JSONHighlighter::HighlightLine(const Buffer &buf, int row, std::vector &out) const
+{
+ const auto &rows = buf.Rows();
+ if (row < 0 || static_cast(row) >= rows.size())
+ return;
+ std::string s = static_cast(rows[static_cast(row)]);
+ int n = static_cast(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(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(c))) {
+ int j = i + 1;
+ while (j < n && std::isalpha(static_cast(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
\ No newline at end of file
diff --git a/JsonHighlighter.h b/JsonHighlighter.h
index 314619f..0273d4f 100644
--- a/JsonHighlighter.h
+++ b/JsonHighlighter.h
@@ -5,10 +5,8 @@
#include
namespace kte {
-
class JSONHighlighter final : public LanguageHighlighter {
public:
- void HighlightLine(const Buffer &buf, int row, std::vector &out) const override;
+ void HighlightLine(const Buffer &buf, int row, std::vector &out) const override;
};
-
-} // namespace kte
+} // namespace kte
\ No newline at end of file
diff --git a/LanguageHighlighter.h b/LanguageHighlighter.h
index cdd0d45..a73eb85 100644
--- a/LanguageHighlighter.h
+++ b/LanguageHighlighter.h
@@ -10,34 +10,42 @@
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 &out) const = 0;
- virtual bool Stateful() const { return false; }
+ 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 &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;
- };
+ 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 &out) const = 0;
+ // 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 &out) const = 0;
- bool Stateful() const override { return true; }
+
+ bool Stateful() const override
+ {
+ return true;
+ }
};
-
-} // namespace kte
+} // namespace kte
\ No newline at end of file
diff --git a/LispHighlighter.cc b/LispHighlighter.cc
index e8b0763..f128ec7 100644
--- a/LispHighlighter.cc
+++ b/LispHighlighter.cc
@@ -3,39 +3,105 @@
#include
namespace kte {
+static void
+push(std::vector &out, int a, int b, TokenKind k)
+{
+ if (b > a)
+ out.push_back({a, b, k});
+}
-static void push(std::vector &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);
+ 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 &out) const
+
+void
+LispHighlighter::HighlightLine(const Buffer &buf, int row, std::vector &out) const
{
- const auto &rows = buf.Rows();
- if (row < 0 || static_cast