3 Commits

Author SHA1 Message Date
051106a233 Enable LSP debug logging, expand language feature support, and fix GUI rendering issues.
- Added `--debug` CLI flag to control LSP debug logging and corresponding environment setting.
- Extended LSP capabilities with basic hover, completion, and definition feature support.
- Removed redundant `NoScrollWithMouse` flag, resolving inconsistencies in GUI scrolling behavior.
- Refined variable usage and type consistency across LSP and rendering modules.
- Updated `LspManager` for improved buffer handling and server diagnostics integration.
2025-12-02 01:21:09 -08:00
33bbb5b98f Add SQL, Erlang, and Forth highlighter implementations and tests for LSP process and transport handling.
- Added highlighters for new languages (SQL, Erlang, Forth) with filetype recognition.
- Updated and reorganized syntax files to maintain consistency and modularity.
- Introduced LSP transport framing unit tests and JSON decoding/dispatch tests.
- Refactored `LspManager`, integrating UTF-16/UTF-8 position conversions and robust diagnostics handling.
- Enhanced server start/restart logic with workspace root detection and logging to improve LSP usability.
2025-12-02 00:15:15 -08:00
e089c6e4d1 LSP integration steps 1-4, part of 5. 2025-12-01 20:09:49 -08:00
98 changed files with 40484 additions and 211789 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
!.idea
cmake-build*
build
build-*
/imgui.ini
result

View File

@@ -141,6 +141,13 @@
<pair source="c++m" header="" fileNamingConvention="NONE" />
</extensions>
</files>
<codeStyleSettings language="CMake">
<indentOptions>
<option name="INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="8" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<indentOptions>
<option name="INDENT_SIZE" value="8" />

View File

@@ -9,6 +9,7 @@
// For reconstructing highlighter state on copies
#include "syntax/HighlighterRegistry.h"
#include "syntax/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<kte::lsp::BufferChangeTracker> tracker)
{
change_tracker_ = std::move(tracker);
}
kte::lsp::BufferChangeTracker *
Buffer::GetChangeTracker()
{
return change_tracker_.get();
}

View File

@@ -17,11 +17,20 @@
#include "syntax/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,6 +383,9 @@ 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
{
@@ -443,6 +455,11 @@ public:
[[nodiscard]] const UndoSystem *Undo() const;
// LSP integration: optional change tracker
void SetChangeTracker(std::unique_ptr<kte::lsp::BufferChangeTracker> 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
@@ -466,6 +483,9 @@ private:
bool syntax_enabled_ = true;
std::string filetype_;
std::unique_ptr<kte::HighlighterEngine> highlighter_;
// Optional LSP change tracker (absent by default)
std::unique_ptr<kte::lsp::BufferChangeTracker> change_tracker_;
};
#endif // KTE_BUFFER_H
#endif // KTE_BUFFER_H

View File

@@ -4,7 +4,7 @@ project(kte)
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 17)
set(KTE_VERSION "1.3.1")
set(KTE_VERSION "1.2.0")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.
@@ -16,47 +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 ()
add_compile_options(
"-static"
"-Wall"
"-Wextra"
"-Werror"
"-Wno-unused-function"
"-Wno-unused-parameter"
"-g"
"$<$<CONFIG:RELEASE>:-O2>"
)
if (MSVC)
add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/O2>")
add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/O2>")
else ()
add_compile_options(
"-Wall"
"-Wextra"
"-Werror"
"$<$<CONFIG:DEBUG>:-g>"
"$<$<CONFIG:RELEASE>:-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"
"$<$<CONFIG:DEBUG>:-g>"
"$<$<CONFIG:RELEASE>:-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
@@ -65,300 +54,368 @@ set(CURSES_NEED_WIDE)
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR})
set(SYNTAX_SOURCES
syntax/GoHighlighter.cc
syntax/CppHighlighter.cc
syntax/JsonHighlighter.cc
syntax/ErlangHighlighter.cc
syntax/MarkdownHighlighter.cc
syntax/TreeSitterHighlighter.cc
syntax/LispHighlighter.cc
syntax/HighlighterEngine.cc
syntax/RustHighlighter.cc
syntax/HighlighterRegistry.cc
syntax/SqlHighlighter.cc
syntax/NullHighlighter.cc
syntax/ForthHighlighter.cc
syntax/PythonHighlighter.cc
syntax/ShellHighlighter.cc
)
if (KTE_ENABLE_TREESITTER)
list(APPEND SYNTAX_SOURCES
TreeSitterHighlighter.cc)
# Detect availability of get_wch (wide-char input) in the curses headers
include(CheckSymbolExists)
set(CMAKE_REQUIRED_INCLUDES ${CURSES_INCLUDE_DIR})
check_symbol_exists(get_wch "ncurses.h" KTE_HAVE_GET_WCH_IN_NCURSES)
if (NOT KTE_HAVE_GET_WCH_IN_NCURSES)
# Some systems expose curses headers as <curses.h>
check_symbol_exists(get_wch "curses.h" KTE_HAVE_GET_WCH_IN_CURSES)
endif ()
if (KTE_HAVE_GET_WCH_IN_NCURSES OR KTE_HAVE_GET_WCH_IN_CURSES)
add_compile_definitions(KTE_HAVE_GET_WCH)
endif ()
set(FONT_SOURCES
fonts/Font.cc
fonts/FontRegistry.cc
set(SYNTAX_SOURCES
syntax/HighlighterEngine.cc
syntax/CppHighlighter.cc
syntax/HighlighterRegistry.cc
syntax/NullHighlighter.cc
syntax/JsonHighlighter.cc
syntax/MarkdownHighlighter.cc
syntax/ShellHighlighter.cc
syntax/GoHighlighter.cc
syntax/PythonHighlighter.cc
syntax/RustHighlighter.cc
syntax/LispHighlighter.cc
syntax/SqlHighlighter.cc
syntax/ErlangHighlighter.cc
syntax/ForthHighlighter.cc
)
set(GUI_SOURCES
${FONT_SOURCES}
GUIConfig.cc
GUIRenderer.cc
GUIInputHandler.cc
GUIFrontend.cc
)
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
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
lsp/UtfCodec.cc
lsp/BufferChangeTracker.cc
lsp/JsonRpcTransport.cc
lsp/LspProcessClient.cc
lsp/DiagnosticStore.cc
lsp/TerminalDiagnosticDisplay.cc
lsp/LspManager.cc
${SYNTAX_SOURCES}
)
set(SYNTAX_HEADERS
syntax/GoHighlighter.h
syntax/HighlighterEngine.h
syntax/ShellHighlighter.h
syntax/MarkdownHighlighter.h
syntax/LispHighlighter.h
syntax/SqlHighlighter.h
syntax/ForthHighlighter.h
syntax/JsonHighlighter.h
syntax/TreeSitterHighlighter.h
syntax/NullHighlighter.h
syntax/CppHighlighter.h
syntax/ErlangHighlighter.h
syntax/LanguageHighlighter.h
syntax/RustHighlighter.h
syntax/PythonHighlighter.h
${SYNTAX_SOURCES}
)
if (KTE_ENABLE_TREESITTER)
list(APPEND THEME_HEADERS
TreeSitterHighlighter.h)
list(APPEND SYNTAX_SOURCES
syntax/TreeSitterHighlighter.cc)
endif ()
set(THEME_HEADERS
themes/ThemeHelpers.h
themes/EInk.h
themes/Gruvbox.h
themes/Solarized.h
themes/Plan9.h
themes/Nord.h
themes/Everforest.h
themes/KanagawaPaper.h
themes/LCARS.h
themes/OldBook.h
themes/Amber.h
themes/Orbital.h
themes/WeylandYutani.h
themes/Zenburn.h
themes/EInk.h
themes/Gruvbox.h
themes/Nord.h
themes/Plan9.h
themes/Solarized.h
themes/ThemeHelpers.h
)
set(FONT_HEADERS
fonts/Font.h
fonts/FontRegistry.h
fonts/FontRegistry.h
fonts/FontList.h
fonts/B612Mono.h
fonts/BrassMono.h
fonts/BrassMonoCode.h
fonts/FiraCode.h
fonts/Go.h
fonts/IBMPlexMono.h
fonts/Idealist.h
fonts/Inconsolata.h
fonts/InconsolataExpanded.h
fonts/Iosevka.h
fonts/IosevkaExtended.h
fonts/ShareTech.h
fonts/SpaceMono.h
fonts/Syne.h
fonts/Triplicate.h
fonts/Unispace.h
set(SYNTAX_HEADERS
syntax/LanguageHighlighter.h
syntax/HighlighterEngine.h
syntax/CppHighlighter.h
syntax/HighlighterRegistry.h
syntax/NullHighlighter.h
syntax/JsonHighlighter.h
syntax/MarkdownHighlighter.h
syntax/ShellHighlighter.h
syntax/GoHighlighter.h
syntax/PythonHighlighter.h
syntax/RustHighlighter.h
syntax/LispHighlighter.h
)
if (KTE_ENABLE_TREESITTER)
list(APPEND SYNTAX_HEADERS
syntax/TreeSitterHighlighter.h)
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
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
lsp/UtfCodec.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
ext/json.h
ext/json_fwd.h
${SYNTAX_HEADERS}
)
set(GUI_HEADERS
${THEME_HEADERS}
${FONT_HEADERS}
GUIConfig.h
GUIRenderer.h
GUIInputHandler.h
GUIFrontend.h
${THEME_HEADERS}
${SYNTAX_HEADERS}
)
# 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})
# Ensure vendored headers (e.g., ext/json.h) are on the include path
target_include_directories(kte PRIVATE ${CMAKE_SOURCE_DIR}/ext)
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})
# Ensure vendored headers (e.g., ext/json.h) are on the include path for tests as well
target_include_directories(test_undo PRIVATE ${CMAKE_SOURCE_DIR}/ext)
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 ()
# test_utfcodec executable for UTF conversion helpers
add_executable(test_utfcodec
test_utfcodec.cc
${COMMON_SOURCES}
${COMMON_HEADERS}
)
if (KTE_USE_PIECE_TABLE)
target_compile_definitions(test_utfcodec PRIVATE KTE_USE_PIECE_TABLE=1)
endif ()
if (KTE_UNDO_DEBUG)
target_compile_definitions(test_utfcodec PRIVATE KTE_UNDO_DEBUG=1)
endif ()
target_link_libraries(test_utfcodec ${CURSES_LIBRARIES})
# Ensure vendored headers (e.g., ext/json.h) are on the include path for tests as well
target_include_directories(test_utfcodec PRIVATE ${CMAKE_SOURCE_DIR}/ext)
if (KTE_ENABLE_TREESITTER)
if (TREESITTER_INCLUDE_DIR)
target_include_directories(test_utfcodec PRIVATE ${TREESITTER_INCLUDE_DIR})
endif ()
if (TREESITTER_LIBRARY)
target_link_libraries(test_utfcodec ${TREESITTER_LIBRARY})
endif ()
endif ()
# test_transport executable for JSON-RPC framing
add_executable(test_transport
test_transport.cc
${COMMON_SOURCES}
${COMMON_HEADERS}
)
if (KTE_USE_PIECE_TABLE)
target_compile_definitions(test_transport PRIVATE KTE_USE_PIECE_TABLE=1)
endif ()
if (KTE_UNDO_DEBUG)
target_compile_definitions(test_transport PRIVATE KTE_UNDO_DEBUG=1)
endif ()
target_link_libraries(test_transport ${CURSES_LIBRARIES})
# Ensure vendored headers (e.g., ext/json.h) are on the include path for tests as well
target_include_directories(test_transport PRIVATE ${CMAKE_SOURCE_DIR}/ext)
if (KTE_ENABLE_TREESITTER)
if (TREESITTER_INCLUDE_DIR)
target_include_directories(test_transport PRIVATE ${TREESITTER_INCLUDE_DIR})
endif ()
if (TREESITTER_LIBRARY)
target_link_libraries(test_transport ${TREESITTER_LIBRARY})
endif ()
endif ()
# test_lsp_decode executable for dispatcher decoding
add_executable(test_lsp_decode
test_lsp_decode.cc
${COMMON_SOURCES}
${COMMON_HEADERS}
)
if (KTE_USE_PIECE_TABLE)
target_compile_definitions(test_lsp_decode PRIVATE KTE_USE_PIECE_TABLE=1)
endif ()
if (KTE_UNDO_DEBUG)
target_compile_definitions(test_lsp_decode PRIVATE KTE_UNDO_DEBUG=1)
endif ()
target_link_libraries(test_lsp_decode ${CURSES_LIBRARIES})
# Ensure vendored headers (e.g., ext/json.h) are on the include path for tests as well
target_include_directories(test_lsp_decode PRIVATE ${CMAKE_SOURCE_DIR}/ext)
if (KTE_ENABLE_TREESITTER)
if (TREESITTER_INCLUDE_DIR)
target_include_directories(test_lsp_decode PRIVATE ${TREESITTER_INCLUDE_DIR})
endif ()
if (TREESITTER_LIBRARY)
target_link_libraries(test_lsp_decode ${TREESITTER_LIBRARY})
endif ()
endif ()
endif ()
if (${BUILD_GUI})
# ImGui::CreateContext();
# ImGuiIO& io = ImGui::GetIO();
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)
# // Set custom ini filename path to ~/.config/kte/imgui.ini
# if (const char* home = std::getenv("HOME")) {
# static std::string ini_path = std::string(home) + "/.config/kte/imgui.ini";
# io.IniFilename = ini_path.c_str();
# }
# 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)
target_include_directories(kge PRIVATE ${CMAKE_SOURCE_DIR}/ext)
# io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
# io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
# Do not enable GUI in the terminal-first 'kte' binary; GUI is built as separate 'kge'.
# This avoids referencing GUI classes from kte and keeps dependencies minimal.
# 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}")
# kge (GUI-first) executable
add_executable(kge
main.cc
${COMMON_SOURCES}
${GUI_SOURCES}
${COMMON_HEADERS}
${GUI_HEADERS}
# 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)
)
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)
# 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)
# 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}")
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 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_dependencies(kge kte)
add_custom_command(TARGET kge POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:kte>
$<TARGET_FILE_DIR:kge>/kte
COMMENT "Copying kte binary into kge.app bundle")
# 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)
install(TARGETS kge
BUNDLE DESTINATION .
)
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
$<TARGET_FILE:kte>
$<TARGET_FILE_DIR:kge>/kte
COMMENT "Copying kte binary into kge.app bundle")
install(TARGETS kge
BUNDLE DESTINATION .
)
install(TARGETS kte
RUNTIME DESTINATION kge.app/Contents/MacOS
)
else ()
install(TARGETS kge
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
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 ()

View File

@@ -4,7 +4,6 @@
#include <regex>
#include <fstream>
#include <sstream>
#include <cmath>
#include <cctype>
#include "Command.h"
@@ -19,8 +18,6 @@
#include "syntax/CppHighlighter.h"
#ifdef KTE_BUILD_GUI
#include "GUITheme.h"
#include "fonts/FontRegistry.h"
#include "imgui.h"
#endif
@@ -45,11 +42,12 @@ compute_render_x(const std::string &line, const std::size_t curx, const std::siz
static void
ensure_cursor_visible(const Editor &ed, Buffer &buf)
{
const std::size_t rows = ed.Rows();
const std::size_t cols = ed.Cols();
if (cols == 0)
if (rows == 0 || cols == 0)
return;
const std::size_t content_rows = ed.ContentRows();
const std::size_t content_rows = rows > 0 ? rows - 1 : 0; // last row = status
const std::size_t cury = buf.Cury();
const std::size_t curx = buf.Curx();
std::size_t rowoffs = buf.Rowoffs();
@@ -556,6 +554,8 @@ cmd_save(CommandContext &ctx)
ctx.editor.SetStatus("Saved " + buf->Filename());
if (auto *u = buf->Undo())
u->mark_saved();
// Notify LSP of save
ctx.editor.NotifyBufferSaved(buf);
return true;
}
@@ -610,6 +610,8 @@ cmd_save_as(CommandContext &ctx)
ctx.editor.SetStatus("Saved as " + ctx.arg);
if (auto *u = buf->Undo())
u->mark_saved();
// Notify LSP of save
ctx.editor.NotifyBufferSaved(buf);
return true;
}
@@ -987,117 +989,6 @@ cmd_theme_set_by_name(CommandContext &ctx)
#endif
// Font set by name (GUI)
#ifdef KTE_BUILD_GUI
static bool
cmd_font_set_by_name(const CommandContext &ctx)
{
using namespace kte::Fonts;
std::string name = ctx.arg;
// trim
auto ltrim = [](std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
};
auto rtrim = [](std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
};
ltrim(name);
rtrim(name);
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) {
return (char) std::tolower(c);
});
if (name.empty()) {
ctx.editor.SetStatus("font: missing name");
return true;
}
auto &reg = FontRegistry::Instance();
if (!reg.HasFont(name)) {
ctx.editor.SetStatus("font: unknown name");
return true;
}
float size = reg.CurrentFontSize();
if (size <= 0.0f) {
// Fallback to current ImGui font size if available
size = ImGui::GetFontSize();
if (size <= 0.0f)
size = 16.0f;
}
reg.RequestLoadFont(name, size);
ctx.editor.SetStatus(std::string("Font: ") + name + " (" + std::to_string((int) std::round(size)) + ")");
return true;
}
#else
static bool
cmd_font_set_by_name(CommandContext &ctx)
{
(void) ctx;
return true;
}
#endif
// Font size set (GUI)
#ifdef KTE_BUILD_GUI
static bool
cmd_font_set_size(const CommandContext &ctx)
{
using namespace kte::Fonts;
std::string a = ctx.arg;
auto ltrim = [](std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
};
auto rtrim = [](std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
};
ltrim(a);
rtrim(a);
if (a.empty()) {
ctx.editor.SetStatus("font-size: missing value");
return true;
}
char *endp = nullptr;
float size = strtof(a.c_str(), &endp);
if (endp == a.c_str() || !std::isfinite(size)) {
ctx.editor.SetStatus("font-size: expected number");
return true;
}
// Clamp to a reasonable range
if (size < 6.0f)
size = 6.0f;
if (size > 96.0f)
size = 96.0f;
auto &reg = FontRegistry::Instance();
std::string name = reg.CurrentFontName();
if (name.empty())
name = "default";
if (!reg.HasFont(name))
name = "default";
reg.RequestLoadFont(name, size);
ctx.editor.SetStatus(std::string("Font size: ") + std::to_string((int) std::round(size)));
return true;
}
#else
static bool
cmd_font_set_size(CommandContext &ctx)
{
(void) ctx;
return true;
}
#endif
// Background set command (GUI)
#ifdef KTE_BUILD_GUI
static bool
@@ -3117,7 +3008,9 @@ cmd_page_up(CommandContext &ctx)
ensure_at_least_one_line(*buf);
auto &rows = buf->Rows();
int repeat = ctx.count > 0 ? ctx.count : 1;
std::size_t content_rows = std::max<std::size_t>(1, ctx.editor.ContentRows());
std::size_t content_rows = ctx.editor.Rows() > 0 ? ctx.editor.Rows() - 1 : 0;
if (content_rows == 0)
content_rows = 1;
// Base on current top-of-screen (row offset)
std::size_t rowoffs = buf->Rowoffs();
@@ -3141,6 +3034,7 @@ cmd_page_up(CommandContext &ctx)
y = rows.empty() ? 0 : rows.size() - 1;
buf->SetOffsets(rowoffs, 0);
buf->SetCursor(0, y);
ensure_cursor_visible(ctx.editor, *buf);
return true;
}
@@ -3156,7 +3050,9 @@ cmd_page_down(CommandContext &ctx)
ensure_at_least_one_line(*buf);
auto &rows = buf->Rows();
int repeat = ctx.count > 0 ? ctx.count : 1;
std::size_t content_rows = std::max<std::size_t>(1, ctx.editor.ContentRows());
std::size_t content_rows = ctx.editor.Rows() > 0 ? ctx.editor.Rows() - 1 : 0;
if (content_rows == 0)
content_rows = 1;
std::size_t rowoffs = buf->Rowoffs();
// Compute maximum top offset
@@ -3177,74 +3073,7 @@ cmd_page_down(CommandContext &ctx)
std::size_t y = std::min<std::size_t>(rowoffs, rows.empty() ? 0 : rows.size() - 1);
buf->SetOffsets(rowoffs, 0);
buf->SetCursor(0, y);
return true;
}
static bool
cmd_scroll_up(CommandContext &ctx)
{
Buffer *buf = ctx.editor.CurrentBuffer();
if (!buf)
return false;
ensure_at_least_one_line(*buf);
const auto &rows = buf->Rows();
std::size_t content_rows = std::max<std::size_t>(1, ctx.editor.ContentRows());
std::size_t rowoffs = buf->Rowoffs();
// Scroll up by 3 lines (or count if specified), without moving cursor
int scroll_amount = ctx.count > 0 ? ctx.count : 3;
if (rowoffs >= static_cast<std::size_t>(scroll_amount))
rowoffs -= static_cast<std::size_t>(scroll_amount);
else
rowoffs = 0;
buf->SetOffsets(rowoffs, buf->Coloffs());
// If cursor is now below the visible area, move it to the last visible line
std::size_t cury = buf->Cury();
if (cury >= rowoffs + content_rows) {
std::size_t new_y = rowoffs + content_rows - 1;
if (new_y >= rows.size() && !rows.empty())
new_y = rows.size() - 1;
buf->SetCursor(buf->Curx(), new_y);
}
return true;
}
static bool
cmd_scroll_down(CommandContext &ctx)
{
Buffer *buf = ctx.editor.CurrentBuffer();
if (!buf)
return false;
ensure_at_least_one_line(*buf);
const auto &rows = buf->Rows();
std::size_t content_rows = std::max<std::size_t>(1, ctx.editor.ContentRows());
std::size_t rowoffs = buf->Rowoffs();
// Scroll down by 3 lines (or count if specified), without moving cursor
int scroll_amount = ctx.count > 0 ? ctx.count : 3;
// Compute maximum top offset
std::size_t max_top = 0;
if (!rows.empty() && rows.size() > content_rows)
max_top = rows.size() - content_rows;
rowoffs += static_cast<std::size_t>(scroll_amount);
if (rowoffs > max_top)
rowoffs = max_top;
buf->SetOffsets(rowoffs, buf->Coloffs());
// If cursor is now above the visible area, move it to the first visible line
std::size_t cury = buf->Cury();
if (cury < rowoffs) {
buf->SetCursor(buf->Curx(), rowoffs);
}
ensure_cursor_visible(ctx.editor, *buf);
return true;
}
@@ -3857,8 +3686,6 @@ InstallDefaultCommands()
CommandRegistry::Register({CommandId::MoveEnd, "end", "Move to end of line", cmd_move_end});
CommandRegistry::Register({CommandId::PageUp, "page-up", "Page up", cmd_page_up});
CommandRegistry::Register({CommandId::PageDown, "page-down", "Page down", cmd_page_down});
CommandRegistry::Register({CommandId::ScrollUp, "scroll-up", "Scroll viewport up", cmd_scroll_up});
CommandRegistry::Register({CommandId::ScrollDown, "scroll-down", "Scroll viewport down", cmd_scroll_down});
CommandRegistry::Register({CommandId::WordPrev, "word-prev", "Move to previous word", cmd_word_prev});
CommandRegistry::Register({CommandId::WordNext, "word-next", "Move to next word", cmd_word_next});
CommandRegistry::Register({
@@ -3895,14 +3722,6 @@ InstallDefaultCommands()
CommandRegistry::Register({
CommandId::ThemeSetByName, "theme", "Set GUI theme by name", cmd_theme_set_by_name, true
});
// Font by name (public)
CommandRegistry::Register({
CommandId::FontSetByName, "font", "Set GUI font by name", cmd_font_set_by_name, true
});
// Font size (public)
CommandRegistry::Register({
CommandId::FontSetSize, "font-size", "Set GUI font size (pixels)", cmd_font_set_size, true
});
// Background light/dark (public)
CommandRegistry::Register({
CommandId::BackgroundSet, "background", "Set GUI background light|dark", cmd_background_set, true

View File

@@ -58,8 +58,6 @@ enum class CommandId {
MoveEnd,
PageUp,
PageDown,
ScrollUp, // scroll viewport up (towards beginning) without moving cursor
ScrollDown, // scroll viewport down (towards end) without moving cursor
WordPrev,
WordNext,
DeleteWordPrev, // delete previous word (ESC BACKSPACE)
@@ -95,15 +93,14 @@ enum class CommandId {
CommandPromptStart, // begin generic command prompt (C-k ;)
// Theme by name
ThemeSetByName,
// Font by name (GUI)
FontSetByName,
// Font size (GUI)
FontSetSize,
// Background mode (GUI)
BackgroundSet,
// Syntax highlighting
Syntax, // ":syntax on|off|reload"
SetOption, // generic ":set key=value" (v1: filetype=<lang>)
// LSP
LspHover,
LspGotoDefinition,
};

View File

@@ -1,8 +1,11 @@
#include <algorithm>
#include <utility>
#include <filesystem>
#include "syntax/HighlighterRegistry.h"
#include "syntax/NullHighlighter.h"
#include "Editor.h"
#include "lsp/LspManager.h"
#include "syntax/HighlighterRegistry.h"
#include "syntax/CppHighlighter.h"
#include "syntax/NullHighlighter.h"
@@ -27,6 +30,15 @@ Editor::SetStatus(const std::string &message)
}
void
Editor::NotifyBufferSaved(Buffer *buf)
{
if (lsp_manager_ && buf) {
lsp_manager_->onBufferSaved(buf);
}
}
Buffer *
Editor::CurrentBuffer()
{
@@ -179,6 +191,10 @@ Editor::OpenFile(const std::string &path, std::string &err)
eng->InvalidateFrom(0);
}
}
// Notify LSP (if wired) for current buffer open
if (lsp_manager_) {
lsp_manager_->onBufferOpened(&cur);
}
return true;
}
}
@@ -214,6 +230,10 @@ Editor::OpenFile(const std::string &path, std::string &err)
// Add as a new buffer and switch to it
std::size_t idx = AddBuffer(std::move(b));
SwitchTo(idx);
// Notify LSP (if wired) for current buffer open
if (lsp_manager_) {
lsp_manager_->onBufferOpened(&buffers_[curbuf_]);
}
return true;
}
@@ -282,4 +302,4 @@ Editor::Reset()
quit_confirm_pending_ = false;
buffers_.clear();
curbuf_ = 0;
}
}

View File

@@ -11,6 +11,13 @@
#include "Buffer.h"
// fwd decl for LSP wiring
namespace kte {
namespace lsp {
class LspManager;
}
}
class Editor {
public:
@@ -32,16 +39,6 @@ public:
}
[[nodiscard]] std::size_t ContentRows() const
{
// Always compute from current rows_ to avoid stale values.
// Reserve 1 row for status line.
if (rows_ == 0)
return 1;
return std::max<std::size_t>(1, rows_ - 1);
}
// Mode and flags (mirroring legacy fields)
void SetMode(int m)
{
@@ -446,6 +443,22 @@ public:
bool OpenFile(const std::string &path, std::string &err);
// LSP: attach/detach manager
void SetLspManager(kte::lsp::LspManager *mgr)
{
lsp_manager_ = mgr;
}
// LSP helpers: trigger hover/definition at current cursor in current buffer
bool LspHoverAtCursor();
bool LspGotoDefinitionAtCursor();
// LSP: notify buffer saved (used by commands)
void NotifyBufferSaved(Buffer *buf);
// Buffer switching/closing
bool SwitchTo(std::size_t index);
@@ -561,6 +574,9 @@ public:
private:
std::string replace_find_tmp_;
std::string replace_with_tmp_;
// Non-owning pointer to LSP manager (if provided)
kte::lsp::LspManager *lsp_manager_ = nullptr;
};
#endif // KTE_EDITOR_H

4892
Font.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -102,8 +102,6 @@ GUIConfig::LoadFromFile(const std::string &path)
if (v > 0.0f) {
font_size = v;
}
} else if (key == "font") {
font = val;
} else if (key == "theme") {
theme = val;
} else if (key == "background" || key == "bg") {

View File

@@ -16,7 +16,6 @@ public:
int columns = 80;
int rows = 42;
float font_size = (float) KTE_FONT_SIZE;
std::string font = "default";
std::string theme = "nord";
// Background mode for themes that support light/dark variants
// Values: "dark" (default), "light"

View File

@@ -1,23 +1,21 @@
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <string>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <imgui.h>
#include <SDL.h>
#include <SDL_opengl.h>
#include <backends/imgui_impl_opengl3.h>
#include <imgui.h>
#include <backends/imgui_impl_sdl2.h>
#include <backends/imgui_impl_opengl3.h>
#include "GUIFrontend.h"
#include "Command.h"
#include "Editor.h"
#include "Command.h"
#include "GUIFrontend.h"
#include "Font.h" // embedded default font (DefaultFontRegular)
#include "GUIConfig.h"
#include "GUITheme.h"
#include "fonts/Font.h" // embedded default font (DefaultFont)
#include "fonts/FontRegistry.h"
#include "syntax/HighlighterRegistry.h"
#include "syntax/NullHighlighter.h"
@@ -26,7 +24,7 @@
#define KTE_FONT_SIZE 16.0f
#endif
static auto kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
static const char *kGlslVersion = "#version 150"; // GL 3.2 core (macOS compatible)
bool
GUIFrontend::Init(Editor &ed)
@@ -78,17 +76,13 @@ GUIFrontend::Init(Editor &ed)
height_ = std::max(200, h);
}
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
window_ = SDL_CreateWindow(
"kge - kyle's graphical editor " KTE_VERSION_STR,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width_, height_,
win_flags);
if (!window_) {
if (!window_)
return false;
}
SDL_EnableScreenSaver();
#if defined(__APPLE__)
// macOS: when "fullscreen" is requested, position the window at the
@@ -111,25 +105,7 @@ GUIFrontend::Init(Editor &ed)
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
// Set custom ini filename path to ~/.config/kte/imgui.ini
if (const char *home = std::getenv("HOME")) {
namespace fs = std::filesystem;
fs::path config_dir = fs::path(home) / ".config" / "kte";
std::error_code ec;
if (!fs::exists(config_dir)) {
fs::create_directories(config_dir, ec);
}
if (fs::exists(config_dir)) {
static std::string ini_path = (config_dir / "imgui.ini").string();
io.IniFilename = ini_path.c_str();
}
}
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
(void) io;
ImGui::StyleColorsDark();
// Apply background mode and selected theme (default: Nord). Can be changed at runtime via commands.
@@ -197,19 +173,8 @@ GUIFrontend::Init(Editor &ed)
}
#endif
// Install embedded fonts into registry and load configured font
kte::Fonts::InstallDefaultFonts();
// Initialize font atlas using configured font name and size; fallback to embedded default helper
if (!kte::Fonts::FontRegistry::Instance().LoadFont(cfg.font, (float) cfg.font_size)) {
LoadGuiFont_(nullptr, (float) cfg.font_size);
// Record defaults in registry so subsequent size changes have a base
kte::Fonts::FontRegistry::Instance().RequestLoadFont("default", (float) cfg.font_size);
std::string n;
float s = 0.0f;
if (kte::Fonts::FontRegistry::Instance().ConsumePendingFontRequest(n, s)) {
kte::Fonts::FontRegistry::Instance().LoadFont(n, s);
}
}
// Initialize GUI font from embedded default (use configured size or compiled default)
LoadGuiFont_(nullptr, (float) cfg.font_size);
return true;
}
@@ -238,21 +203,28 @@ GUIFrontend::Step(Editor &ed, bool &running)
input_.ProcessSDLEvent(e);
}
// Apply pending font change before starting a new frame
{
std::string fname;
float fsize = 0.0f;
if (kte::Fonts::FontRegistry::Instance().ConsumePendingFontRequest(fname, fsize)) {
if (!fname.empty() && fsize > 0.0f) {
kte::Fonts::FontRegistry::Instance().LoadFont(fname, fsize);
// Recreate backend font texture
ImGui_ImplOpenGL3_DestroyFontsTexture();
ImGui_ImplOpenGL3_CreateFontsTexture();
// Execute pending mapped inputs (drain queue)
for (;;) {
MappedInput mi;
if (!input_.Poll(mi))
break;
if (mi.hasCommand) {
// Track kill ring before and after to sync GUI clipboard when it changes
const std::string before = ed.KillRingHead();
Execute(ed, mi.id, mi.arg, mi.count);
const std::string after = ed.KillRingHead();
if (after != before && !after.empty()) {
// Update the system clipboard to mirror the kill ring head in GUI
SDL_SetClipboardText(after.c_str());
}
}
}
// Start a new ImGui frame BEFORE processing commands so dimensions are correct
if (ed.QuitRequested()) {
running = false;
}
// Start a new ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(window_);
ImGui::NewFrame();
@@ -292,27 +264,6 @@ GUIFrontend::Step(Editor &ed, bool &running)
}
}
// Execute pending mapped inputs (drain queue) AFTER dimensions are updated
for (;;) {
MappedInput mi;
if (!input_.Poll(mi))
break;
if (mi.hasCommand) {
// Track kill ring before and after to sync GUI clipboard when it changes
const std::string before = ed.KillRingHead();
Execute(ed, mi.id, mi.arg, mi.count);
const std::string after = ed.KillRingHead();
if (after != before && !after.empty()) {
// Update the system clipboard to mirror the kill ring head in GUI
SDL_SetClipboardText(after.c_str());
}
}
}
if (ed.QuitRequested()) {
running = false;
}
// No runtime font UI; always use embedded font.
// Draw editor UI
@@ -350,13 +301,13 @@ GUIFrontend::Shutdown()
bool
GUIFrontend::LoadGuiFont_(const char * /*path*/, const float size_px)
GUIFrontend::LoadGuiFont_(const char * /*path*/, float size_px)
{
const ImGuiIO &io = ImGui::GetIO();
io.Fonts->Clear();
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
kte::Fonts::DefaultFontData,
kte::Fonts::DefaultFontSize,
DefaultFontBoldCompressedData,
DefaultFontBoldCompressedSize,
size_px);
if (!font) {
font = io.Fonts->AddFontDefault();
@@ -364,4 +315,7 @@ GUIFrontend::LoadGuiFont_(const char * /*path*/, const float size_px)
(void) font;
io.Fonts->Build();
return true;
}
}
// No runtime font reload or system font resolution in this simplified build.

View File

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

View File

@@ -61,46 +61,35 @@ map_key(const SDL_Keycode key,
}
// Movement and basic keys
// These keys exit k-prefix mode if active (user pressed C-k then a special key).
switch (key) {
case SDLK_LEFT:
k_prefix = false;
out = {true, CommandId::MoveLeft, "", 0};
return true;
case SDLK_RIGHT:
k_prefix = false;
out = {true, CommandId::MoveRight, "", 0};
return true;
case SDLK_UP:
k_prefix = false;
out = {true, CommandId::MoveUp, "", 0};
return true;
case SDLK_DOWN:
k_prefix = false;
out = {true, CommandId::MoveDown, "", 0};
return true;
case SDLK_HOME:
k_prefix = false;
out = {true, CommandId::MoveHome, "", 0};
return true;
case SDLK_END:
k_prefix = false;
out = {true, CommandId::MoveEnd, "", 0};
return true;
case SDLK_PAGEUP:
k_prefix = false;
out = {true, CommandId::PageUp, "", 0};
return true;
case SDLK_PAGEDOWN:
k_prefix = false;
out = {true, CommandId::PageDown, "", 0};
return true;
case SDLK_DELETE:
k_prefix = false;
out = {true, CommandId::DeleteChar, "", 0};
return true;
case SDLK_BACKSPACE:
k_prefix = false;
out = {true, CommandId::Backspace, "", 0};
return true;
case SDLK_TAB:
@@ -296,12 +285,15 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
bool produced = false;
switch (e.type) {
case SDL_MOUSEWHEEL: {
// Let ImGui handle mouse wheel when it wants to capture the mouse
// (e.g., when hovering the editor child window with scrollbars).
// This enables native vertical and horizontal scrolling behavior in GUI.
if (ImGui::GetIO().WantCaptureMouse)
return false;
// Otherwise, fallback to mapping vertical wheel to editor scroll commands.
// If ImGui wants to capture the mouse (e.g., hovering the File Picker list),
// don't translate wheel events into editor scrolling.
// This prevents background buffer scroll while using GUI widgets.
ImGuiIO &io = ImGui::GetIO();
if (io.WantCaptureMouse) {
return true; // consumed by GUI
}
// Map vertical wheel to line-wise cursor movement (MoveUp/MoveDown)
int dy = e.wheel.y;
#ifdef SDL_MOUSEWHEEL_FLIPPED
if (e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
@@ -309,7 +301,7 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
#endif
if (dy != 0) {
int repeat = dy > 0 ? dy : -dy;
CommandId id = dy > 0 ? CommandId::ScrollUp : CommandId::ScrollDown;
CommandId id = dy > 0 ? CommandId::MoveUp : CommandId::MoveDown;
std::lock_guard<std::mutex> lk(mu_);
for (int i = 0; i < repeat; ++i) {
q_.push(MappedInput{true, id, std::string(), 0});
@@ -380,7 +372,7 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
// Digits without shift, or a plain '-'
const bool is_digit_key = (key >= SDLK_0 && key <= SDLK_9) && !(mods & KMOD_SHIFT);
const bool is_minus_key = (key == SDLK_MINUS);
if (uarg_active_ && uarg_collecting_ &&(is_digit_key || is_minus_key)) {
if (uarg_active_ && uarg_collecting_ && (is_digit_key || is_minus_key)) {
suppress_text_input_once_ = true;
}
}
@@ -572,23 +564,16 @@ GUIInputHandler::ProcessSDLEvent(const SDL_Event &e)
if (produced && mi.hasCommand) {
// Attach universal-argument count if present, then clear the state
if (uarg_active_ &&mi
.
id != CommandId::UArgStatus
)
{
if (uarg_active_ && mi.id != CommandId::UArgStatus) {
int count = 0;
if (!uarg_had_digits_ && !uarg_negative_) {
// No explicit digits: use current value (default 4 or 4^n)
count = (uarg_value_ > 0) ? uarg_value_ : 4;
} else {
count = uarg_value_;
if (uarg_negative_)
count = -count;
}
mi.count = count;
// Clear universal-argument state after applying it
mi.count = count;
uarg_active_ = false;
uarg_collecting_ = false;
uarg_negative_ = false;
@@ -612,4 +597,4 @@ GUIInputHandler::Poll(MappedInput &out)
out = q_.front();
q_.pop();
return true;
}
}

View File

@@ -47,7 +47,6 @@ GUIRenderer::Draw(Editor &ed)
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoScrollWithMouse
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoCollapse
@@ -60,85 +59,73 @@ GUIRenderer::Draw(Editor &ed)
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.f, 6.f));
ImGui::Begin("kte", nullptr, flags);
ImGui::Begin("kte", nullptr, flags | ImGuiWindowFlags_NoScrollWithMouse);
const Buffer *buf = ed.CurrentBuffer();
if (!buf) {
ImGui::TextUnformatted("[no buffer]");
} else {
const auto &lines = buf->Rows();
const auto &lines = buf->Rows();
// Reserve space for status bar at bottom
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
ImGuiWindowFlags_HorizontalScrollbar);
// Detect click-to-move inside this scroll region
ImVec2 list_origin = ImGui::GetCursorScreenPos();
float scroll_y = ImGui::GetScrollY();
float scroll_x = ImGui::GetScrollX();
std::size_t rowoffs = 0; // we render from the first line; scrolling is handled by ImGui
std::size_t cy = buf->Cury();
std::size_t cx = buf->Curx();
const float line_h = ImGui::GetTextLineHeight();
const float row_h = ImGui::GetTextLineHeightWithSpacing();
const float space_w = ImGui::CalcTextSize(" ").x;
// Two-way sync between Buffer::Rowoffs and ImGui scroll position:
// - If command layer changed Buffer::Rowoffs since last frame, drive ImGui scroll from it.
// - Otherwise, propagate ImGui scroll to Buffer::Rowoffs so command layer has an up-to-date view.
static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
static long prev_buf_coloffs = -1; // previous frame's Buffer::Coloffs
const long buf_rowoffs = static_cast<long>(buf->Rowoffs());
const long buf_coloffs = static_cast<long>(buf->Coloffs());
// Detect programmatic change (e.g., page_down command changed rowoffs)
// Use SetNextWindowScroll BEFORE BeginChild to set initial scroll position
if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
float target_y = static_cast<float>(buf_rowoffs) * row_h;
ImGui::SetNextWindowScroll(ImVec2(-1.0f, target_y));
}
if (prev_buf_coloffs >= 0 && buf_coloffs != prev_buf_coloffs) {
float target_x = static_cast<float>(buf_coloffs) * space_w;
float target_y = static_cast<float>(buf_rowoffs) * row_h;
ImGui::SetNextWindowScroll(ImVec2(target_x, target_y));
}
// Reserve space for status bar at bottom
ImGui::BeginChild("scroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false,
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
// Get child window position and scroll for click handling
ImVec2 child_window_pos = ImGui::GetWindowPos();
float scroll_y = ImGui::GetScrollY();
float scroll_x = ImGui::GetScrollX();
std::size_t rowoffs = 0; // we render from the first line; scrolling is handled by ImGui
// Synchronize buffer offsets from ImGui scroll if user scrolled manually
// This prevents clicks/wheel from being immediately overridden by stale offsets.
bool forced_scroll = false;
{
static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
static float prev_scroll_x = -1.0f; // previous frame's ImGui scroll X in pixels
static long prev_buf_rowoffs = -1; // previous frame's Buffer::Rowoffs
static long prev_buf_coloffs = -1; // previous frame's Buffer::Coloffs
static float prev_scroll_y = -1.0f; // previous frame's ImGui scroll Y in pixels
static float prev_scroll_x = -1.0f; // previous frame's ImGui scroll X in pixels
const long buf_rowoffs = static_cast<long>(buf->Rowoffs());
const long buf_coloffs = static_cast<long>(buf->Coloffs());
const long scroll_top = static_cast<long>(scroll_y / row_h);
const long scroll_left = static_cast<long>(scroll_x / space_w);
// Check if rowoffs was programmatically changed this frame
// Detect programmatic change (e.g., keyboard navigation ensured visibility)
if (prev_buf_rowoffs >= 0 && buf_rowoffs != prev_buf_rowoffs) {
ImGui::SetScrollY(static_cast<float>(buf_rowoffs) * row_h);
scroll_y = ImGui::GetScrollY();
forced_scroll = true;
}
// If user scrolled (not programmatic), update buffer offsets accordingly
if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y && !forced_scroll) {
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
if (prev_buf_coloffs >= 0 && buf_coloffs != prev_buf_coloffs) {
ImGui::SetScrollX(static_cast<float>(buf_coloffs) * space_w);
scroll_x = ImGui::GetScrollX();
forced_scroll = true;
}
// If user scrolled, update buffer offsets accordingly
if (prev_scroll_y >= 0.0f && scroll_y != prev_scroll_y) {
if (auto mbuf = const_cast<Buffer *>(buf)) {
mbuf->SetOffsets(static_cast<std::size_t>(std::max(0L, scroll_top)),
mbuf->Coloffs());
}
}
if (prev_scroll_x >= 0.0f && scroll_x != prev_scroll_x && !forced_scroll) {
if (Buffer *mbuf = const_cast<Buffer *>(buf)) {
if (prev_scroll_x >= 0.0f && scroll_x != prev_scroll_x) {
if (auto mbuf = const_cast<Buffer *>(buf)) {
mbuf->SetOffsets(mbuf->Rowoffs(),
static_cast<std::size_t>(std::max(0L, scroll_left)));
}
}
// Update trackers for next frame
prev_scroll_y = scroll_y;
prev_scroll_x = scroll_x;
prev_buf_rowoffs = static_cast<long>(buf->Rowoffs());
prev_buf_coloffs = static_cast<long>(buf->Coloffs());
prev_scroll_y = ImGui::GetScrollY();
prev_scroll_x = ImGui::GetScrollX();
}
prev_buf_rowoffs = buf_rowoffs;
prev_buf_coloffs = buf_coloffs;
// Synchronize cursor and scrolling.
// Ensure the cursor is visible even on the first frame or when it didn't move,
// unless we already forced scrolling from Buffer::Rowoffs this frame.
@@ -166,41 +153,6 @@ GUIRenderer::Draw(Editor &ed)
first_row = static_cast<long>(scroll_y / row_h);
last_row = first_row + vis_rows - 1;
}
// Horizontal scroll: ensure cursor column is visible
float child_w = ImGui::GetWindowWidth();
long vis_cols = static_cast<long>(child_w / space_w);
if (vis_cols < 1)
vis_cols = 1;
long first_col = static_cast<long>(scroll_x / space_w);
long last_col = first_col + vis_cols - 1;
// Compute cursor's rendered X position (accounting for tabs)
std::size_t cursor_rx = 0;
if (cy < lines.size()) {
std::string cur_line = static_cast<std::string>(lines[cy]);
const std::size_t tabw = 8;
for (std::size_t i = 0; i < cx && i < cur_line.size(); ++i) {
if (cur_line[i] == '\t') {
cursor_rx += tabw - (cursor_rx % tabw);
} else {
cursor_rx += 1;
}
}
}
long cxr = static_cast<long>(cursor_rx);
if (cxr < first_col || cxr > last_col) {
float target_x = static_cast<float>(cxr) * space_w;
// Center horizontally if possible
target_x -= (child_w / 2.0f);
if (target_x < 0.f)
target_x = 0.f;
float max_x = ImGui::GetScrollMaxX();
if (max_x >= 0.f && target_x > max_x)
target_x = max_x;
ImGui::SetScrollX(target_x);
scroll_x = ImGui::GetScrollX();
}
}
// Phase 3: prefetch visible viewport highlights and warm around in background
if (buf->SyntaxEnabled() && buf->Highlighter() && buf->Highlighter()->HasHighlighter()) {
@@ -209,22 +161,29 @@ GUIRenderer::Draw(Editor &ed)
buf->Highlighter()->PrefetchViewport(*buf, fr, rc, buf->Version());
}
}
// Cache current horizontal offset in rendered columns for click handling
const std::size_t coloffs_now = buf->Coloffs();
// Handle mouse click before rendering to avoid dependent on drawn items
// Handle mouse click before rendering to avoid dependency on drawn items
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
ImVec2 mp = ImGui::GetIO().MousePos;
// Compute content-relative position accounting for scroll
// mp.y - child_window_pos.y gives us pixels from top of child window
// Adding scroll_y gives us pixels from top of content (buffer row 0)
float content_y = (mp.y - child_window_pos.y) + scroll_y;
long by_l = static_cast<long>(content_y / row_h);
if (by_l < 0)
by_l = 0;
// Compute viewport-relative row so (0) is top row of the visible area
// Note: list_origin is already in the scrolled space of the child window,
// so we must NOT subtract scroll_y again (would double-apply).
float vy_f = (mp.y - list_origin.y) / row_h;
long vy = static_cast<long>(vy_f);
if (vy < 0)
vy = 0;
// Convert to buffer row
std::size_t by = static_cast<std::size_t>(by_l);
// Clamp vy within visible content height to avoid huge jumps
ImVec2 cr_min = ImGui::GetWindowContentRegionMin();
ImVec2 cr_max = ImGui::GetWindowContentRegionMax();
float child_h = (cr_max.y - cr_min.y);
long vis_rows = static_cast<long>(child_h / row_h);
if (vis_rows < 1)
vis_rows = 1;
if (vy >= vis_rows)
vy = vis_rows - 1;
// Translate viewport row to buffer row using Buffer::Rowoffs
std::size_t by = buf->Rowoffs() + static_cast<std::size_t>(vy);
if (by >= lines.size()) {
if (!lines.empty())
by = lines.size() - 1;
@@ -232,46 +191,59 @@ GUIRenderer::Draw(Editor &ed)
by = 0;
}
// Compute click X position relative to left edge of child window (in pixels)
// This gives us the visual offset from the start of displayed content
float visual_x = mp.x - child_window_pos.x;
if (visual_x < 0.0f)
visual_x = 0.0f;
// Convert visual pixel offset to rendered column, then add coloffs_now
// to get the absolute rendered column in the buffer
std::size_t clicked_rx = static_cast<std::size_t>(visual_x / space_w) + coloffs_now;
// Compute desired pixel X inside the viewport content.
// list_origin is already scrolled; do not subtract scroll_x here.
float px = (mp.x - list_origin.x);
if (px < 0.0f)
px = 0.0f;
// Empty buffer guard: if there are no lines yet, just move to 0:0
if (lines.empty()) {
Execute(ed, CommandId::MoveCursorTo, std::string("0:0"));
} else {
// Convert rendered column (clicked_rx) to source column accounting for tabs
// Convert pixel X to a render-column target including horizontal col offset
// Use our own tab expansion of width 8 to match command layer logic.
std::string line_clicked = static_cast<std::string>(lines[by]);
const std::size_t tabw = 8;
// We iterate source columns computing absolute rendered column (rx_abs) from 0,
// then translate to viewport-space by subtracting Coloffs.
std::size_t coloffs = buf->Coloffs();
std::size_t rx_abs = 0; // absolute rendered column
std::size_t i = 0; // source column iterator
// Iterate through source columns, computing rendered position, to find closest match
std::size_t rx = 0; // rendered column position
std::size_t best_col = 0;
// Fast-forward i until rx_abs >= coloffs to align with leftmost visible column
if (!line_clicked.empty() && coloffs > 0) {
while (i < line_clicked.size() && rx_abs < coloffs) {
if (line_clicked[i] == '\t') {
rx_abs += (tabw - (rx_abs % tabw));
} else {
rx_abs += 1;
}
++i;
}
}
// Now search for closest source column to clicked px within/after viewport
std::size_t best_col = i; // default to first visible column
float best_dist = std::numeric_limits<float>::infinity();
float clicked_rx_f = static_cast<float>(clicked_rx);
for (std::size_t i = 0; i <= line_clicked.size(); ++i) {
// Check current position
float dist = std::fabs(clicked_rx_f - static_cast<float>(rx));
if (dist < best_dist) {
while (true) {
// For i in [current..size], evaluate candidate including the implicit end position
std::size_t rx_view = (rx_abs >= coloffs) ? (rx_abs - coloffs) : 0;
float rx_px = static_cast<float>(rx_view) * space_w;
float dist = std::fabs(px - rx_px);
if (dist <= best_dist) {
best_dist = dist;
best_col = i;
}
// Advance to next position if not at end
if (i < line_clicked.size()) {
if (line_clicked[i] == '\t') {
rx += (tabw - (rx % tabw));
} else {
rx += 1;
}
if (i == line_clicked.size())
break;
// advance to next source column
if (line_clicked[i] == '\t') {
rx_abs += (tabw - (rx_abs % tabw));
} else {
rx_abs += 1;
}
++i;
}
// Dispatch absolute buffer coordinates (row:col)
@@ -280,13 +252,15 @@ GUIRenderer::Draw(Editor &ed)
Execute(ed, CommandId::MoveCursorTo, std::string(tmp));
}
}
// Cache current horizontal offset in rendered columns
const std::size_t coloffs_now = buf->Coloffs();
for (std::size_t i = rowoffs; i < lines.size(); ++i) {
// Capture the screen position before drawing the line
ImVec2 line_pos = ImGui::GetCursorScreenPos();
std::string line = static_cast<std::string>(lines[i]);
ImVec2 line_pos = ImGui::GetCursorScreenPos();
auto line = static_cast<std::string>(lines[i]);
// Expand tabs to spaces with width=8 and apply horizontal scroll offset
const std::size_t tabw = 8;
constexpr std::size_t tabw = 8;
std::string expanded;
expanded.reserve(line.size() + 16);
std::size_t rx_abs_draw = 0; // rendered column for drawing
@@ -303,7 +277,7 @@ GUIRenderer::Draw(Editor &ed)
for (auto it = std::sregex_iterator(line.begin(), line.end(), rx);
it != std::sregex_iterator(); ++it) {
const auto &m = *it;
std::size_t sx = static_cast<std::size_t>(m.position());
auto sx = static_cast<std::size_t>(m.position());
std::size_t ex = sx + static_cast<std::size_t>(m.length());
hl_src_ranges.emplace_back(sx, ex);
}
@@ -346,9 +320,9 @@ GUIRenderer::Draw(Editor &ed)
continue; // fully left of view
std::size_t vx0 = (rx_start > coloffs_now) ? (rx_start - coloffs_now) : 0;
std::size_t vx1 = rx_end - coloffs_now;
ImVec2 p0 = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
ImVec2 p1 = ImVec2(line_pos.x + static_cast<float>(vx1) * space_w,
line_pos.y + line_h);
auto p0 = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
auto p1 = ImVec2(line_pos.x + static_cast<float>(vx1) * space_w,
line_pos.y + line_h);
// Choose color: current match stronger
bool is_current = has_current && sx == cur_x && ex == cur_end;
ImU32 col = is_current
@@ -375,7 +349,7 @@ GUIRenderer::Draw(Editor &ed)
const kte::LineHighlight &lh = buf->Highlighter()->GetLine(
*buf, static_cast<int>(i), buf->Version());
// Helper to convert a src column to expanded rx position
auto src_to_rx_full = [&](std::size_t sidx) -> std::size_t {
auto src_to_rx_full = [&](const 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;
@@ -389,37 +363,25 @@ GUIRenderer::Draw(Editor &ed)
static_cast<std::size_t>(std::max(sp.col_start, sp.col_end)));
if (rx_e <= coloffs_now)
continue;
// Clamp rx_s/rx_e to the visible portion
std::size_t draw_start = (rx_s > coloffs_now) ? rx_s : coloffs_now;
std::size_t draw_end = rx_e;
if (draw_start >= expanded.size())
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;
draw_end = std::min<std::size_t>(draw_end, expanded.size());
if (draw_end <= draw_start)
vx1 = std::min<std::size_t>(vx1, expanded.size());
if (vx1 <= vx0)
continue;
// Screen position is relative to coloffs_now
std::size_t screen_x = draw_start - coloffs_now;
ImU32 col = ImGui::GetColorU32(kte::SyntaxInk(sp.kind));
ImVec2 p = ImVec2(line_pos.x + static_cast<float>(screen_x) * space_w,
line_pos.y);
auto p = ImVec2(line_pos.x + static_cast<float>(vx0) * space_w, line_pos.y);
ImGui::GetWindowDrawList()->AddText(
p, col, expanded.c_str() + draw_start, expanded.c_str() + draw_end);
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.
// Use row_h (with spacing) to match click calculation and ensure consistent line positions.
// We drew text via draw list (no layout advance). Advance by the same amount
// ImGui uses between lines (line height + spacing) so hit-testing (which
// divides by row_h) aligns with drawing.
ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
} else {
// No syntax: draw as one run, accounting for horizontal scroll offset
if (coloffs_now < expanded.size()) {
ImVec2 p = ImVec2(line_pos.x, line_pos.y);
ImGui::GetWindowDrawList()->AddText(
p, ImGui::GetColorU32(ImGuiCol_Text),
expanded.c_str() + coloffs_now);
ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
} else {
// Line is fully scrolled out of view horizontally
ImGui::SetCursorScreenPos(ImVec2(line_pos.x, line_pos.y + row_h));
}
// No syntax: draw as one run
ImGui::TextUnformatted(expanded.c_str());
}
// Draw a visible cursor indicator on the current line
@@ -459,9 +421,9 @@ GUIRenderer::Draw(Editor &ed)
ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, bg_col);
// If a prompt is active, replace the entire status bar with the prompt text
if (ed.PromptActive()) {
std::string label = ed.PromptLabel();
std::string ptext = ed.PromptText();
auto kind = ed.CurrentPromptKind();
const std::string &label = ed.PromptLabel();
std::string ptext = ed.PromptText();
auto kind = ed.CurrentPromptKind();
if (kind == Editor::PromptKind::OpenFile || kind == Editor::PromptKind::SaveAs ||
kind == Editor::PromptKind::Chdir) {
const char *home_c = std::getenv("HOME");
@@ -508,8 +470,8 @@ GUIRenderer::Draw(Editor &ed)
float ratio = tail_sz.x / avail_px;
size_t skip = ratio > 1.5f
? std::min(tail.size() - start,
(size_t) std::max<size_t>(
1, (size_t) (tail.size() / 4)))
static_cast<size_t>(std::max<size_t>(
1, tail.size() / 4)))
: 1;
start += skip;
std::string candidate = tail.substr(start);
@@ -566,8 +528,7 @@ GUIRenderer::Draw(Editor &ed)
left += " ";
// Insert buffer position prefix "[x/N] " before filename
{
std::size_t total = ed.BufferCount();
if (total > 0) {
if (std::size_t total = ed.BufferCount(); total > 0) {
std::size_t idx1 = ed.CurrentBufferIndex() + 1; // 1-based for display
left += "[";
left += std::to_string(static_cast<unsigned long long>(idx1));
@@ -581,7 +542,7 @@ GUIRenderer::Draw(Editor &ed)
left += " *";
// Append total line count as "<n>L"
{
unsigned long lcount = static_cast<unsigned long>(buf->Rows().size());
auto lcount = buf->Rows().size();
left += " ";
left += std::to_string(lcount);
left += "L";
@@ -667,9 +628,9 @@ GUIRenderer::Draw(Editor &ed)
ImGuiViewport *vp2 = ImGui::GetMainViewport();
// Desired size, min size, and margins
const ImVec2 want(800.0f, 500.0f);
const ImVec2 min_sz(240.0f, 160.0f);
const float margin = 20.0f; // space from viewport edges
constexpr ImVec2 want(800.0f, 500.0f);
constexpr ImVec2 min_sz(240.0f, 160.0f);
constexpr float margin = 20.0f; // space from viewport edges
// Compute the maximum allowed size (viewport minus margins) and make sure it's not negative
ImVec2 max_sz(std::max(32.0f, vp2->Size.x - 2.0f * margin),
@@ -809,4 +770,4 @@ GUIRenderer::Draw(Editor &ed)
ed.SetFilePickerVisible(false);
}
}
}
}

View File

@@ -26,14 +26,6 @@ enum class ThemeId {
Nord = 2,
Plan9 = 3,
Solarized = 4,
Everforest = 5,
KanagawaPaper = 6,
LCARS = 7,
OldBook = 8,
Zenburn = 9,
Amber = 10,
WeylandYutani = 11,
Orbital = 12,
};
// Current theme tracking
@@ -73,14 +65,6 @@ BackgroundModeName()
#include "themes/Solarized.h"
#include "themes/Gruvbox.h"
#include "themes/EInk.h"
#include "themes/Everforest.h"
#include "themes/KanagawaPaper.h"
#include "themes/LCARS.h"
#include "themes/OldBook.h"
#include "themes/Amber.h"
#include "themes/WeylandYutani.h"
#include "themes/Zenburn.h"
#include "themes/Orbital.h"
// Theme abstraction and registry (generalized theme system)
@@ -94,123 +78,6 @@ public:
};
namespace detail {
struct LCARSTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "lcars";
}
void Apply() const override
{
ApplyLcarsTheme();
}
ThemeId Id() override
{
return ThemeId::LCARS;
}
};
struct EverforestTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "everforest";
}
void Apply() const override
{
ApplyEverforestTheme();
}
ThemeId Id() override
{
return ThemeId::Everforest;
}
};
struct KanagawaPaperTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "kanagawa-paper";
}
void Apply() const override
{
ApplyKanagawaPaperTheme();
}
ThemeId Id() override
{
return ThemeId::KanagawaPaper;
}
};
struct OldBookTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "old-book";
}
void Apply() const override
{
if (gBackgroundMode == BackgroundMode::Dark)
ApplyOldBookDarkTheme();
else
ApplyOldBookLightTheme();
}
ThemeId Id() override
{
return ThemeId::OldBook;
}
};
struct OrbitalTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "orbital";
}
void Apply() const override
{
ApplyOrbitalTheme();
}
ThemeId Id() override
{
return ThemeId::Orbital;
}
};
struct ZenburnTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "zenburn";
}
void Apply() const override
{
ApplyZenburnTheme();
}
ThemeId Id() override
{
return ThemeId::Zenburn;
}
};
struct NordTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
@@ -230,44 +97,6 @@ struct NordTheme final : Theme {
}
};
struct AmberTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "amber";
}
void Apply() const override
{
ApplyAmberTheme();
}
ThemeId Id() override
{
return ThemeId::Amber;
}
};
struct WeylandYutaniTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
return "weyland-yutani";
}
void Apply() const override
{
ApplyWeylandYutaniTheme();
}
ThemeId Id() override
{
return ThemeId::WeylandYutani;
}
};
struct GruvboxTheme final : Theme {
[[nodiscard]] const char *Name() const override
{
@@ -360,21 +189,12 @@ ThemeRegistry()
{
static std::vector<std::unique_ptr<Theme> > reg;
if (reg.empty()) {
// Alphabetical by canonical name:
// amber, eink, everforest, gruvbox, kanagawa-paper, lcars, nord, old-book, orbital, plan9, solarized, weyland-yutani, zenburn
reg.emplace_back(std::make_unique<detail::AmberTheme>());
// Alphabetical by canonical name: eink, gruvbox, nord, plan9, solarized
reg.emplace_back(std::make_unique<detail::EInkTheme>());
reg.emplace_back(std::make_unique<detail::EverforestTheme>());
reg.emplace_back(std::make_unique<detail::GruvboxTheme>());
reg.emplace_back(std::make_unique<detail::KanagawaPaperTheme>());
reg.emplace_back(std::make_unique<detail::LCARSTheme>());
reg.emplace_back(std::make_unique<detail::NordTheme>());
reg.emplace_back(std::make_unique<detail::OldBookTheme>());
reg.emplace_back(std::make_unique<detail::OrbitalTheme>());
reg.emplace_back(std::make_unique<detail::Plan9Theme>());
reg.emplace_back(std::make_unique<detail::SolarizedTheme>());
reg.emplace_back(std::make_unique<detail::WeylandYutaniTheme>());
reg.emplace_back(std::make_unique<detail::ZenburnTheme>());
}
return reg;
}
@@ -484,26 +304,6 @@ ApplyThemeByName(const std::string &name)
} else if (n == "eink-light") {
SetBackgroundMode(BackgroundMode::Light);
n = "eink";
} else if (n == "everforest-hard") {
// Request asks for everforest hard; map to canonical name
n = "everforest";
} else if (n == "oldbook") {
// alias to old-book
n = "old-book";
} else if (n == "old-book-dark" || n == "oldbook-dark") {
SetBackgroundMode(BackgroundMode::Dark);
n = "old-book";
} else if (n == "old-book-light" || n == "oldbook-light") {
SetBackgroundMode(BackgroundMode::Light);
n = "old-book";
} else if (n == "kanagawa" || n == "kanagawa-paper-light" || n == "kanagawa-light"
|| n == "kanagawa-dark" || n == "kanagawa-paper-dark") {
// map to canonical kanagawa-paper; background controls light/dark
n = "kanagawa-paper";
} else if (n == "vim-amber") {
n = "amber";
} else if (n == "weyland") {
n = "weyland-yutani";
}
const auto &reg = ThemeRegistry();
@@ -537,32 +337,16 @@ static size_t
ThemeIndexFromId(const ThemeId id)
{
switch (id) {
case ThemeId::Amber:
return 0;
case ThemeId::EInk:
return 1;
case ThemeId::Everforest:
return 2;
return 0;
case ThemeId::GruvboxDarkMedium:
return 3;
case ThemeId::KanagawaPaper:
return 4;
case ThemeId::LCARS:
return 5;
return 1;
case ThemeId::Nord:
return 6;
case ThemeId::OldBook:
return 7;
case ThemeId::Orbital:
return 8;
return 2;
case ThemeId::Plan9:
return 9;
return 3;
case ThemeId::Solarized:
return 10;
case ThemeId::WeylandYutani:
return 11;
case ThemeId::Zenburn:
return 12;
return 4;
}
return 0;
}
@@ -574,31 +358,15 @@ ThemeIdFromIndex(const size_t idx)
switch (idx) {
default:
case 0:
return ThemeId::Amber;
case 1:
return ThemeId::EInk;
case 2:
return ThemeId::Everforest;
case 3:
case 1:
return ThemeId::GruvboxDarkMedium; // unified gruvbox
case 4:
return ThemeId::KanagawaPaper;
case 5:
return ThemeId::LCARS;
case 6:
case 2:
return ThemeId::Nord;
case 7:
return ThemeId::OldBook;
case 8:
return ThemeId::Orbital;
case 9:
case 3:
return ThemeId::Plan9;
case 10:
case 4:
return ThemeId::Solarized;
case 11:
return ThemeId::WeylandYutani;
case 12:
return ThemeId::Zenburn;
}
}

View File

@@ -75,7 +75,7 @@ HelpText::Text()
"Buffers:\n +HELP+ is read-only. Press C-k ' to toggle; C-k h restores it.\n"
"\n"
"GUI appearance (command prompt):\n"
" : theme NAME Set GUI theme (amber, eink, everforest, gruvbox, kanagawa-paper, lcars, nord, old-book, plan9, solarized, weyland-yutani, zenburn)\n"
" : background MODE Set background: light | dark (affects eink, gruvbox, old-book, solarized)\n"
" : theme NAME Set GUI theme (eink, gruvbox, nord, plan9, solarized)\n"
" : background MODE Set background: light | dark (affects eink, gruvbox, solarized)\n"
);
}
}

View File

@@ -2,11 +2,9 @@ ROADMAP / TODO:
- [x] Search + Replace
- [x] Regex search + replace
- [ ] The undo system should actually work
- [x] Able to mark buffers as read-only
- [x] Built-in help text
- [x] Shorten paths in the homedir with ~
- [x] When the filename is longer than the message window, scoot left to
keep it in view
- [x] Syntax highlighting
- [ ] The undo system should actually work
- [ ] LSP integration

View File

@@ -1,6 +1,12 @@
#include <ncurses.h>
#include <clocale>
#include <termios.h>
#include <unistd.h>
#ifdef __APPLE__
#include <xlocale.h>
#endif
#include <langinfo.h>
#include <cctype>
#include "TerminalFrontend.h"
#include "Command.h"
@@ -10,6 +16,35 @@
bool
TerminalFrontend::Init(Editor &ed)
{
// Enable UTF-8 locale so ncurses and the terminal handle multibyte correctly
// This relies on the user's environment (e.g., LANG/LC_ALL) being set to a UTF-8 locale.
// If not set, try a couple of common UTF-8 fallbacks.
const char *loc = std::setlocale(LC_ALL, "");
auto is_utf8_codeset = []() -> bool {
const char *cs = nl_langinfo(CODESET);
if (!cs)
return false;
std::string s(cs);
for (auto &ch: s)
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
return (s.find("UTF-8") != std::string::npos) || (s.find("UTF8") != std::string::npos);
};
bool utf8_ok = (MB_CUR_MAX > 1) && is_utf8_codeset();
if (!utf8_ok) {
// Try common UTF-8 locales
loc = std::setlocale(LC_CTYPE, "C.UTF-8");
utf8_ok = (loc != nullptr) && (MB_CUR_MAX > 1) && is_utf8_codeset();
if (!utf8_ok) {
loc = std::setlocale(LC_CTYPE, "en_US.UTF-8");
utf8_ok = (loc != nullptr) && (MB_CUR_MAX > 1) && is_utf8_codeset();
}
if (!utf8_ok) {
// macOS often uses plain "UTF-8" locale identifier
loc = std::setlocale(LC_CTYPE, "UTF-8");
utf8_ok = (loc != nullptr) && (MB_CUR_MAX > 1) && is_utf8_codeset();
}
}
// Ensure Control keys reach the app: disable XON/XOFF and dsusp/susp bindings (e.g., ^S/^Q, ^Y on macOS)
{
struct termios tio{};
@@ -55,6 +90,9 @@ TerminalFrontend::Init(Editor &ed)
prev_r_ = r;
prev_c_ = c;
ed.SetDimensions(static_cast<std::size_t>(r), static_cast<std::size_t>(c));
// Inform renderer of UTF-8 capability so it can choose proper output path
renderer_.SetUtf8Enabled(utf8_ok);
input_.SetUtf8Enabled(utf8_ok);
return true;
}
@@ -100,4 +138,4 @@ TerminalFrontend::Shutdown()
have_orig_tio_ = false;
}
endwin();
}
}

View File

@@ -1,4 +1,6 @@
#include <cstdio>
#include <cwchar>
#include <climits>
#include <ncurses.h>
#include "TerminalInputHandler.h"
@@ -31,25 +33,53 @@ map_key_to_command(const int ch,
MappedInput &out)
{
// Handle special keys from ncurses
// These keys exit k-prefix mode if active (user pressed C-k then a special key).
switch (ch) {
case KEY_MOUSE: {
k_prefix = false;
MEVENT ev{};
if (getmouse(&ev) == OK) {
// Mouse wheel → scroll viewport without moving cursor
// Mouse wheel → map to MoveUp/MoveDown one line per wheel notch
unsigned long wheel_up_mask = 0;
unsigned long wheel_dn_mask = 0;
#ifdef BUTTON4_PRESSED
if (ev.bstate & (BUTTON4_PRESSED | BUTTON4_RELEASED | BUTTON4_CLICKED)) {
out = {true, CommandId::ScrollUp, "", 0};
return true;
}
wheel_up_mask |= BUTTON4_PRESSED;
#endif
#ifdef BUTTON4_RELEASED
wheel_up_mask |= BUTTON4_RELEASED;
#endif
#ifdef BUTTON4_CLICKED
wheel_up_mask |= BUTTON4_CLICKED;
#endif
#ifdef BUTTON4_DOUBLE_CLICKED
wheel_up_mask |= BUTTON4_DOUBLE_CLICKED;
#endif
#ifdef BUTTON4_TRIPLE_CLICKED
wheel_up_mask |= BUTTON4_TRIPLE_CLICKED;
#endif
#ifdef BUTTON5_PRESSED
if (ev.bstate & (BUTTON5_PRESSED | BUTTON5_RELEASED | BUTTON5_CLICKED)) {
out = {true, CommandId::ScrollDown, "", 0};
wheel_dn_mask |= BUTTON5_PRESSED;
#endif
#ifdef BUTTON5_RELEASED
wheel_dn_mask |= BUTTON5_RELEASED;
#endif
#ifdef BUTTON5_CLICKED
wheel_dn_mask |= BUTTON5_CLICKED;
#endif
#ifdef BUTTON5_DOUBLE_CLICKED
wheel_dn_mask |= BUTTON5_DOUBLE_CLICKED;
#endif
#ifdef BUTTON5_TRIPLE_CLICKED
wheel_dn_mask |= BUTTON5_TRIPLE_CLICKED;
#endif
if (wheel_up_mask && (ev.bstate & wheel_up_mask)) {
// Prefer viewport scrolling for wheel: page up
out = {true, CommandId::PageUp, "", 0};
return true;
}
if (wheel_dn_mask && (ev.bstate & wheel_dn_mask)) {
// Prefer viewport scrolling for wheel: page down
out = {true, CommandId::PageDown, "", 0};
return true;
}
#endif
// React to left button click/press
if (ev.bstate & (BUTTON1_CLICKED | BUTTON1_PRESSED | BUTTON1_RELEASED)) {
char buf[64];
@@ -64,43 +94,33 @@ map_key_to_command(const int ch,
return true;
}
case KEY_LEFT:
k_prefix = false;
out = {true, CommandId::MoveLeft, "", 0};
return true;
case KEY_RIGHT:
k_prefix = false;
out = {true, CommandId::MoveRight, "", 0};
return true;
case KEY_UP:
k_prefix = false;
out = {true, CommandId::MoveUp, "", 0};
return true;
case KEY_DOWN:
k_prefix = false;
out = {true, CommandId::MoveDown, "", 0};
return true;
case KEY_HOME:
k_prefix = false;
out = {true, CommandId::MoveHome, "", 0};
return true;
case KEY_END:
k_prefix = false;
out = {true, CommandId::MoveEnd, "", 0};
return true;
case KEY_PPAGE:
k_prefix = false;
out = {true, CommandId::PageUp, "", 0};
return true;
case KEY_NPAGE:
k_prefix = false;
out = {true, CommandId::PageDown, "", 0};
return true;
case KEY_DC:
k_prefix = false;
out = {true, CommandId::DeleteChar, "", 0};
return true;
case KEY_RESIZE:
k_prefix = false;
out = {true, CommandId::Refresh, "", 0};
return true;
default:
@@ -293,6 +313,77 @@ map_key_to_command(const int ch,
bool
TerminalInputHandler::decode_(MappedInput &out)
{
#if defined(KTE_HAVE_GET_WCH)
if (utf8_enabled_) {
// Prefer wide-character input so we can capture Unicode code points
wint_t wch = 0;
int rc = get_wch(&wch);
if (rc == ERR) {
return false;
}
if (rc == KEY_CODE_YES) {
// Function/special key; pass through existing mapper
int sk = static_cast<int>(wch);
bool consumed = map_key_to_command(
sk,
k_prefix_, esc_meta_,
uarg_active_, uarg_collecting_, uarg_negative_, uarg_had_digits_, uarg_value_,
uarg_text_,
out);
if (!consumed)
return false;
} else {
// Regular character
if (wch <= 0x7F) {
// ASCII path -> reuse existing mapping (handles control, ESC, etc.)
int ch = static_cast<int>(wch);
bool consumed = map_key_to_command(
ch,
k_prefix_, esc_meta_,
uarg_active_, uarg_collecting_, uarg_negative_, uarg_had_digits_, uarg_value_,
uarg_text_,
out);
if (!consumed)
return false;
} else {
// Non-ASCII printable -> insert UTF-8 text directly
if (iswcntrl(static_cast<wint_t>(wch))) {
out.hasCommand = false;
return true;
}
char mb[MB_LEN_MAX];
mbstate_t st{};
std::size_t n = wcrtomb(mb, static_cast<wchar_t>(wch), &st);
if (n == static_cast<std::size_t>(-1)) {
// Fallback placeholder if encoding failed
out.hasCommand = true;
out.id = CommandId::InsertText;
out.arg = "?";
out.count = 0;
} else {
out.hasCommand = true;
out.id = CommandId::InsertText;
out.arg.assign(mb, mb + n);
out.count = 0;
}
}
}
} else {
int ch = getch();
if (ch == ERR) {
return false; // no input
}
bool consumed = map_key_to_command(
ch,
k_prefix_, esc_meta_,
uarg_active_, uarg_collecting_, uarg_negative_, uarg_had_digits_, uarg_value_, uarg_text_,
out);
if (!consumed)
return false;
}
#else
// Wide-character input not available in this curses; fall back to byte-wise getch
(void) utf8_enabled_;
int ch = getch();
if (ch == ERR) {
return false; // no input
@@ -304,6 +395,7 @@ TerminalInputHandler::decode_(MappedInput &out)
out);
if (!consumed)
return false;
#endif
// If a command was produced and a universal argument is active, attach it and clear state
if (out.hasCommand && uarg_active_ && out.id != CommandId::UArgStatus) {
int count = 0;

View File

@@ -15,6 +15,12 @@ public:
bool Poll(MappedInput &out) override;
void SetUtf8Enabled(bool on)
{
utf8_enabled_ = on;
}
private:
bool decode_(MappedInput &out);
@@ -30,6 +36,8 @@ private:
bool uarg_had_digits_ = false; // whether any digits were supplied
int uarg_value_ = 0; // current absolute value (>=0)
std::string uarg_text_; // raw digits/minus typed for status display
bool utf8_enabled_ = true;
};
#endif // KTE_TERMINAL_INPUT_HANDLER_H
#endif // KTE_TERMINAL_INPUT_HANDLER_H

View File

@@ -4,6 +4,7 @@
#include <cstdlib>
#include <ncurses.h>
#include <regex>
#include <cwchar>
#include <string>
#include "TerminalRenderer.h"
@@ -34,8 +35,6 @@ TerminalRenderer::Draw(Editor &ed)
const Buffer *buf = ed.CurrentBuffer();
int content_rows = rows - 1; // last line is status
if (content_rows < 1)
content_rows = 1;
int saved_cur_y = -1, saved_cur_x = -1; // logical cursor position within content area
if (buf) {
@@ -152,8 +151,10 @@ TerminalRenderer::Draw(Editor &ed)
}
};
while (written < cols) {
char ch = ' ';
bool from_src = false;
// Default to space when beyond EOL
bool from_src = false;
int wcw = 1; // display width
std::size_t advance_bytes = 0;
if (src_i < line.size()) {
unsigned char c = static_cast<unsigned char>(line[src_i]);
if (c == '\t') {
@@ -211,18 +212,46 @@ TerminalRenderer::Draw(Editor &ed)
++src_i;
continue;
} else {
// normal char
if (render_col < coloffs) {
++render_col;
++src_i;
continue;
if (!Utf8Enabled()) {
// ASCII fallback: treat each byte as single width
if (render_col + 1 <= coloffs) {
++render_col;
++src_i;
continue;
}
wcw = 1;
advance_bytes = 1;
from_src = true;
} else {
// Decode one UTF-8 codepoint
mbstate_t st{};
const char *p = line.data() + src_i;
std::size_t rem = line.size() - src_i;
wchar_t tmp_wc = 0;
std::size_t n = mbrtowc(&tmp_wc, p, rem, &st);
if (n == static_cast<std::size_t>(-1) || n ==
static_cast<std::size_t>(-2) || n == 0) {
// Invalid/incomplete -> treat as single-byte placeholder
tmp_wc = L'?';
n = 1;
}
int w = wcwidth(tmp_wc);
if (w < 0)
w = 1;
// If this codepoint is scrolled off to the left, skip it
if (render_col + static_cast<std::size_t>(w) <=
coloffs) {
render_col += static_cast<std::size_t>(w);
src_i += n;
continue;
}
wcw = w;
advance_bytes = n;
from_src = true;
}
ch = static_cast<char>(c);
from_src = true;
}
} else {
// beyond EOL, fill spaces
ch = ' ';
from_src = false;
}
bool in_hl = search_mode && from_src && is_src_in_hl(src_i);
@@ -248,11 +277,20 @@ TerminalRenderer::Draw(Editor &ed)
if (!in_hl && from_src) {
apply_token_attr(token_at(src_i));
}
addch(static_cast<unsigned char>(ch));
++written;
++render_col;
if (from_src)
++src_i;
if (written + wcw > cols) {
break;
}
if (from_src) {
// Output original bytes for this unit (UTF-8 codepoint or ASCII byte)
const char *cp = line.data() + (src_i);
int out_n = Utf8Enabled() ? static_cast<int>(advance_bytes) : 1;
addnstr(cp, out_n);
src_i += static_cast<std::size_t>(out_n);
} else {
addch(' ');
}
written += wcw;
render_col += wcw;
if (src_i >= line.size() && written >= cols)
break;
}
@@ -424,6 +462,10 @@ TerminalRenderer::Draw(Editor &ed)
else
std::snprintf(rbuf, sizeof(rbuf), "%d,%d | M: not set", row1, col1);
right = rbuf;
// If UTF-8 is not enabled (ASCII fallback), append a short hint
if (!Utf8Enabled()) {
right += " | ASCII";
}
}
// Compute placements with truncation rules: prioritize left and right; middle gets remaining

View File

@@ -14,6 +14,21 @@ public:
~TerminalRenderer() override;
void Draw(Editor &ed) override;
// Enable/disable UTF-8 aware rendering (set by TerminalFrontend after locale init)
void SetUtf8Enabled(bool on)
{
utf8_enabled_ = on;
}
[[nodiscard]] bool Utf8Enabled() const
{
return utf8_enabled_;
}
private:
bool utf8_enabled_ = true;
};
#endif // KTE_TERMINAL_RENDERER_H
#endif // KTE_TERMINAL_RENDERER_H

View File

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

View File

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

View File

@@ -252,6 +252,9 @@ Open using the terminal frontend from kge:
(project keybinding manual)
.br
Project homepage: https://github.com/wntrmute/kte
.SH BUGS
Report issues on the project tracker. Some behaviors are inherited from
ke and may evolve over time; see the manual for notes.
.SH AUTHORS
Kyle (wntrmute) and contributors.
.SH COPYRIGHT

View File

@@ -146,16 +146,14 @@ When running the GUI frontend, you can control appearance via the generic
command prompt (type "C-k ;" then enter commands):
.TP
.B : theme NAME
Set the GUI theme. Available names: "amber", "eink", "everforest", "gruvbox", "kanagawa-paper", "lcars", "nord", "old-book", "orbital", "plan9", "solarized", "weyland-yutani", "zenburn".
Set the GUI theme. Available names: "nord", "gruvbox", "plan9", "solarized", "eink".
Compatibility aliases are also accepted: "gruvbox-dark", "gruvbox-light",
"solarized-dark", "solarized-light", "eink-dark", "eink-light",
"everforest-hard", "oldbook", "old-book-dark", "old-book-light",
"kanagawa", "kanagawa-light", "kanagawa-paper-light", "vim-amber", "weyland".
"solarized-dark", "solarized-light", "eink-dark", "eink-light".
.TP
.B : background MODE
Set background mode for supported themes. MODE is either "light" or "dark".
Themes that respond to background: eink, gruvbox, kanagawa-paper, old-book, solarized. The
"lcars", "nord" and "plan9" themes do not vary with background.
Themes that respond to background: eink, gruvbox, solarized. The
"nord" and "plan9" themes do not vary with background.
.SH CONFIGURATION
The GUI reads a simple configuration file at
@@ -279,6 +277,9 @@ Force GUI frontend (if available):
(project keybinding manual)
.br
Project homepage: https://github.com/wntrmute/kte
.SH BUGS
Incremental search currently restarts from the top on each invocation; see
\(lqKnown behavior\(rq in the ke manual. Report issues on the project tracker.
.SH AUTHORS
Kyle (wntrmute) and contributors.
.SH COPYRIGHT

25626
ext/json.h Normal file

File diff suppressed because it is too large Load Diff

185
ext/json_fwd.h Normal file
View File

@@ -0,0 +1,185 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_DIAGNOSTIC_POSITIONS
#define JSON_DIAGNOSTIC_POSITIONS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_DIAGNOSTIC_POSITIONS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

93
fonts/B612_Mono/OFL.txt Normal file
View File

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

435
fonts/BrassMono/OFL-FAQ.txt Normal file
View File

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

93
fonts/BrassMono/OFL.txt Normal file
View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

8613
fonts/Go.h

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

4892
fonts/brassmono.h Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -6,4 +6,4 @@ then
fmt_args="-fmt 3"
fi
ls -1 *.cc *.h | grep -v '^Font.h$' | xargs cloc ${fmt_args}
ls -1 *.cc *.h lsp/*.{cc,h} | grep -v '^Font.h$' | xargs cloc ${fmt_args}

View File

@@ -0,0 +1,49 @@
/*
* BufferChangeTracker.cc - minimal initial implementation
*/
#include "BufferChangeTracker.h"
#include "../Buffer.h"
namespace kte::lsp {
BufferChangeTracker::BufferChangeTracker(const Buffer *buffer)
: buffer_(buffer) {}
void
BufferChangeTracker::recordInsertion(int /*row*/, int /*col*/, const std::string &/*text*/)
{
// For Phase 12 bring-up, coalesce to full-document changes
fullChangePending_ = true;
++version_;
}
void
BufferChangeTracker::recordDeletion(int /*row*/, int /*col*/, std::size_t /*len*/)
{
fullChangePending_ = true;
++version_;
}
std::vector<TextDocumentContentChangeEvent>
BufferChangeTracker::getChanges() const
{
std::vector<TextDocumentContentChangeEvent> v;
if (!buffer_)
return v;
if (fullChangePending_) {
TextDocumentContentChangeEvent ev;
ev.text = buffer_->FullText();
v.push_back(std::move(ev));
}
return v;
}
void
BufferChangeTracker::clearChanges()
{
fullChangePending_ = false;
}
} // namespace kte::lsp

44
lsp/BufferChangeTracker.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* BufferChangeTracker.h - integrates with Buffer to accumulate LSP-friendly changes
*/
#ifndef KTE_BUFFER_CHANGE_TRACKER_H
#define KTE_BUFFER_CHANGE_TRACKER_H
#include <memory>
#include <vector>
#include <string>
#include "LspTypes.h"
class Buffer; // forward declare from core
namespace kte::lsp {
class BufferChangeTracker {
public:
explicit BufferChangeTracker(const Buffer *buffer);
// Called by Buffer on each edit operation
void recordInsertion(int row, int col, const std::string &text);
void recordDeletion(int row, int col, std::size_t len);
// Get accumulated changes since last sync
std::vector<TextDocumentContentChangeEvent> getChanges() const;
// Clear changes after sending to LSP
void clearChanges();
// Get current document version for LSP
int getVersion() const
{
return version_;
}
private:
const Buffer *buffer_ = nullptr;
bool fullChangePending_ = false;
int version_ = 0;
};
} // namespace kte::lsp
#endif // KTE_BUFFER_CHANGE_TRACKER_H

37
lsp/Diagnostic.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* Diagnostic.h - LSP diagnostic data types
*/
#ifndef KTE_LSP_DIAGNOSTIC_H
#define KTE_LSP_DIAGNOSTIC_H
#include <optional>
#include <string>
#include <vector>
#include "LspTypes.h"
namespace kte::lsp {
enum class DiagnosticSeverity {
Error = 1,
Warning = 2,
Information = 3,
Hint = 4
};
struct DiagnosticRelatedInformation {
std::string uri; // related location URI
Range range; // related range
std::string message;
};
struct Diagnostic {
Range range{};
DiagnosticSeverity severity{DiagnosticSeverity::Information};
std::optional<std::string> code;
std::optional<std::string> source;
std::string message;
std::vector<DiagnosticRelatedInformation> relatedInfo;
};
} // namespace kte::lsp
#endif // KTE_LSP_DIAGNOSTIC_H

30
lsp/DiagnosticDisplay.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* DiagnosticDisplay.h - Abstract interface for showing diagnostics
*/
#ifndef KTE_LSP_DIAGNOSTIC_DISPLAY_H
#define KTE_LSP_DIAGNOSTIC_DISPLAY_H
#include <string>
#include <vector>
#include "Diagnostic.h"
namespace kte::lsp {
class DiagnosticDisplay {
public:
virtual ~DiagnosticDisplay() = default;
virtual void updateDiagnostics(const std::string &uri,
const std::vector<Diagnostic> &diagnostics) = 0;
virtual void showInlineDiagnostic(const Diagnostic &diagnostic) = 0;
virtual void showDiagnosticList(const std::vector<Diagnostic> &diagnostics) = 0;
virtual void hideDiagnosticList() = 0;
virtual void updateStatusBar(int errorCount, int warningCount) = 0;
};
} // namespace kte::lsp
#endif // KTE_LSP_DIAGNOSTIC_DISPLAY_H

123
lsp/DiagnosticStore.cc Normal file
View File

@@ -0,0 +1,123 @@
/*
* DiagnosticStore.cc - implementation
*/
#include "DiagnosticStore.h"
#include <algorithm>
namespace kte::lsp {
void
DiagnosticStore::setDiagnostics(const std::string &uri, std::vector<Diagnostic> diagnostics)
{
diagnostics_[uri] = std::move(diagnostics);
}
const std::vector<Diagnostic> &
DiagnosticStore::getDiagnostics(const std::string &uri) const
{
auto it = diagnostics_.find(uri);
static const std::vector<Diagnostic> kEmpty;
if (it == diagnostics_.end())
return kEmpty;
return it->second;
}
std::vector<Diagnostic>
DiagnosticStore::getDiagnosticsAtLine(const std::string &uri, int line) const
{
std::vector<Diagnostic> out;
auto it = diagnostics_.find(uri);
if (it == diagnostics_.end())
return out;
out.reserve(it->second.size());
for (const auto &d: it->second) {
if (containsLine(d.range, line))
out.push_back(d);
}
return out;
}
std::optional<Diagnostic>
DiagnosticStore::getDiagnosticAtPosition(const std::string &uri, Position pos) const
{
auto it = diagnostics_.find(uri);
if (it == diagnostics_.end())
return std::nullopt;
for (const auto &d: it->second) {
if (containsPosition(d.range, pos))
return d;
}
return std::nullopt;
}
void
DiagnosticStore::clear(const std::string &uri)
{
diagnostics_.erase(uri);
}
void
DiagnosticStore::clearAll()
{
diagnostics_.clear();
}
int
DiagnosticStore::getErrorCount(const std::string &uri) const
{
auto it = diagnostics_.find(uri);
if (it == diagnostics_.end())
return 0;
int count = 0;
for (const auto &d: it->second) {
if (d.severity == DiagnosticSeverity::Error)
++count;
}
return count;
}
int
DiagnosticStore::getWarningCount(const std::string &uri) const
{
auto it = diagnostics_.find(uri);
if (it == diagnostics_.end())
return 0;
int count = 0;
for (const auto &d: it->second) {
if (d.severity == DiagnosticSeverity::Warning)
++count;
}
return count;
}
bool
DiagnosticStore::containsLine(const Range &r, int line)
{
return (line > r.start.line || line == r.start.line) &&
(line < r.end.line || line == r.end.line);
}
bool
DiagnosticStore::containsPosition(const Range &r, const Position &p)
{
if (p.line < r.start.line || p.line > r.end.line)
return false;
if (r.start.line == r.end.line) {
return p.line == r.start.line && p.character >= r.start.character && p.character <= r.end.character;
}
if (p.line == r.start.line)
return p.character >= r.start.character;
if (p.line == r.end.line)
return p.character <= r.end.character;
return true; // between start and end lines
}
} // namespace kte::lsp

42
lsp/DiagnosticStore.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* DiagnosticStore.h - Central storage for diagnostics by document URI
*/
#ifndef KTE_LSP_DIAGNOSTIC_STORE_H
#define KTE_LSP_DIAGNOSTIC_STORE_H
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "Diagnostic.h"
namespace kte::lsp {
class DiagnosticStore {
public:
void setDiagnostics(const std::string &uri, std::vector<Diagnostic> diagnostics);
const std::vector<Diagnostic> &getDiagnostics(const std::string &uri) const;
std::vector<Diagnostic> getDiagnosticsAtLine(const std::string &uri, int line) const;
std::optional<Diagnostic> getDiagnosticAtPosition(const std::string &uri, Position pos) const;
void clear(const std::string &uri);
void clearAll();
int getErrorCount(const std::string &uri) const;
int getWarningCount(const std::string &uri) const;
private:
std::unordered_map<std::string, std::vector<Diagnostic> > diagnostics_;
static bool containsLine(const Range &r, int line);
static bool containsPosition(const Range &r, const Position &p);
};
} // namespace kte::lsp
#endif // KTE_LSP_DIAGNOSTIC_STORE_H

147
lsp/JsonRpcTransport.cc Normal file
View File

@@ -0,0 +1,147 @@
/*
* JsonRpcTransport.cc - minimal stdio JSON-RPC framing (Content-Length)
*/
#include "JsonRpcTransport.h"
#include <cerrno>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <string>
#include <optional>
#include <unistd.h>
namespace kte::lsp {
void
JsonRpcTransport::connect(int inFd, int outFd)
{
inFd_ = inFd;
outFd_ = outFd;
}
void
JsonRpcTransport::send(const std::string &/*method*/, const std::string &payload)
{
if (outFd_ < 0)
return;
const std::string header = "Content-Length: " + std::to_string(payload.size()) + "\r\n\r\n";
std::lock_guard<std::mutex> lk(writeMutex_);
// write header
const char *hbuf = header.data();
size_t hleft = header.size();
while (hleft > 0) {
ssize_t n = ::write(outFd_, hbuf, hleft);
if (n < 0) {
if (errno == EINTR)
continue;
return;
}
hbuf += static_cast<size_t>(n);
hleft -= static_cast<size_t>(n);
}
// write payload
const char *pbuf = payload.data();
size_t pleft = payload.size();
while (pleft > 0) {
ssize_t n = ::write(outFd_, pbuf, pleft);
if (n < 0) {
if (errno == EINTR)
continue;
return;
}
pbuf += static_cast<size_t>(n);
pleft -= static_cast<size_t>(n);
}
}
static bool
readLineCrlf(int fd, std::string &out, size_t maxLen)
{
out.clear();
char ch;
while (true) {
ssize_t n = ::read(fd, &ch, 1);
if (n == 0)
return false; // EOF
if (n < 0) {
if (errno == EINTR)
continue;
return false;
}
out.push_back(ch);
// Handle CRLF or bare LF as end-of-line
if ((out.size() >= 2 && out[out.size() - 2] == '\r' && out[out.size() - 1] == '\n') ||
(out.size() >= 1 && out[out.size() - 1] == '\n')) {
return true;
}
if (out.size() > maxLen) {
// sanity cap
return false;
}
}
}
std::optional<JsonRpcMessage>
JsonRpcTransport::read()
{
if (inFd_ < 0)
return std::nullopt;
// Parse headers (case-insensitive), accept/ignore extras
size_t contentLength = 0;
while (true) {
std::string line;
if (!readLineCrlf(inFd_, line, kMaxHeaderLine))
return std::nullopt;
// Normalize end-of-line handling: consider blank line as end of headers
if (line == "\r\n" || line == "\n" || line == "\r")
break;
// Trim trailing CRLF
if (!line.empty() && (line.back() == '\n' || line.back() == '\r')) {
while (!line.empty() && (line.back() == '\n' || line.back() == '\r'))
line.pop_back();
}
// Find colon
auto pos = line.find(':');
if (pos == std::string::npos)
continue;
std::string name = line.substr(0, pos);
std::string value = line.substr(pos + 1);
// trim leading spaces in value
size_t i = 0;
while (i < value.size() && (value[i] == ' ' || value[i] == '\t'))
++i;
value.erase(0, i);
// lower-case name for comparison
for (auto &c: name)
c = static_cast<char>(::tolower(static_cast<unsigned char>(c)));
if (name == "content-length") {
size_t len = static_cast<size_t>(std::strtoull(value.c_str(), nullptr, 10));
if (len > kMaxBody) {
return std::nullopt; // drop too-large message
}
contentLength = len;
}
// else: ignore other headers
}
if (contentLength == 0)
return std::nullopt;
std::string body;
body.resize(contentLength);
size_t readTotal = 0;
while (readTotal < contentLength) {
ssize_t n = ::read(inFd_, &body[readTotal], contentLength - readTotal);
if (n == 0)
return std::nullopt;
if (n < 0) {
if (errno == EINTR)
continue;
return std::nullopt;
}
readTotal += static_cast<size_t>(n);
}
return JsonRpcMessage{std::move(body)};
}
} // namespace kte::lsp

43
lsp/JsonRpcTransport.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* JsonRpcTransport.h - minimal JSON-RPC over stdio transport
*/
#ifndef KTE_JSON_RPC_TRANSPORT_H
#define KTE_JSON_RPC_TRANSPORT_H
#include <optional>
#include <string>
#include <mutex>
namespace kte::lsp {
struct JsonRpcMessage {
std::string raw; // raw JSON payload (stub)
};
class JsonRpcTransport {
public:
JsonRpcTransport() = default;
~JsonRpcTransport() = default;
// Connect this transport to file descriptors (read from inFd, write to outFd)
void connect(int inFd, int outFd);
// Send a method call (request or notification)
// 'payload' should be a complete JSON object string to send as the message body.
void send(const std::string &method, const std::string &payload);
// Blocking read next message; returns nullopt on EOF or error
std::optional<JsonRpcMessage> read();
private:
int inFd_ = -1;
int outFd_ = -1;
std::mutex writeMutex_;
// Limits to keep the transport resilient
static constexpr size_t kMaxHeaderLine = 16 * 1024; // 16 KiB per header line
static constexpr size_t kMaxBody = 64ull * 1024ull * 1024ull; // 64 MiB body cap
};
} // namespace kte::lsp
#endif // KTE_JSON_RPC_TRANSPORT_H

75
lsp/LspClient.h Normal file
View File

@@ -0,0 +1,75 @@
/*
* LspClient.h - Core LSP client abstraction (initial stub)
*/
#ifndef KTE_LSP_CLIENT_H
#define KTE_LSP_CLIENT_H
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "LspTypes.h"
#include "Diagnostic.h"
namespace kte::lsp {
// Callback types for initial language features
// If error is non-empty, the result may be default-constructed/empty
using CompletionCallback = std::function<void(const CompletionList & result, const std::string & error)>;
using HoverCallback = std::function<void(const HoverResult & result, const std::string & error)>;
using LocationCallback = std::function<void(const std::vector<Location> & result, const std::string & error)>;
class LspClient {
public:
virtual ~LspClient() = default;
// Lifecycle
virtual bool initialize(const std::string &rootPath) = 0;
virtual void shutdown() = 0;
// Document Synchronization
virtual void didOpen(const std::string &uri, const std::string &languageId,
int version, const std::string &text) = 0;
virtual void didChange(const std::string &uri, int version,
const std::vector<TextDocumentContentChangeEvent> &changes) = 0;
virtual void didClose(const std::string &uri) = 0;
virtual void didSave(const std::string &uri) = 0;
// Language Features (initial)
virtual void completion(const std::string &, Position,
CompletionCallback) {}
virtual void hover(const std::string &, Position,
HoverCallback) {}
virtual void definition(const std::string &, Position,
LocationCallback) {}
// Process Management
virtual bool isRunning() const = 0;
virtual std::string getServerName() const = 0;
// Handlers (optional; set by manager)
using DiagnosticsHandler = std::function<void(const std::string & uri,
const std::vector<Diagnostic> &diagnostics
)
>;
virtual void setDiagnosticsHandler(DiagnosticsHandler h)
{
(void) h;
}
};
} // namespace kte::lsp
#endif // KTE_LSP_CLIENT_H

736
lsp/LspManager.cc Normal file
View File

@@ -0,0 +1,736 @@
/*
* LspManager.cc - central coordination of LSP servers and diagnostics
*/
#include "LspManager.h"
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <utility>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cstdarg>
#include "../Buffer.h"
#include "../Editor.h"
#include "BufferChangeTracker.h"
#include "LspProcessClient.h"
#include "UtfCodec.h"
namespace fs = std::filesystem;
namespace kte::lsp {
static void
lsp_debug_file(const char *fmt, ...)
{
FILE *f = std::fopen("/tmp/kte-lsp.log", "a");
if (!f)
return;
// prepend timestamp
std::time_t t = std::time(nullptr);
char ts[32];
std::strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
std::fprintf(f, "[%s] ", ts);
va_list ap;
va_start(ap, fmt);
std::vfprintf(f, fmt, ap);
va_end(ap);
std::fputc('\n', f);
std::fclose(f);
}
LspManager::LspManager(Editor *editor, DiagnosticDisplay *display)
: editor_(editor), display_(display)
{
// Pre-populate with sensible default server configs
registerDefaultServers();
}
void
LspManager::registerServer(const std::string &languageId, const LspServerConfig &config)
{
serverConfigs_[languageId] = config;
}
bool
LspManager::startServerForBuffer(Buffer *buffer)
{
const auto lang = getLanguageId(buffer);
if (lang.empty())
return false;
if (servers_.find(lang) != servers_.end() && servers_[lang]->isRunning()) {
return true;
}
auto it = serverConfigs_.find(lang);
if (it == serverConfigs_.end()) {
return false;
}
const auto &cfg = it->second;
// Respect autostart for automatic starts on buffer open
if (!cfg.autostart) {
return false;
}
// Allow env override of server path
std::string command = cfg.command;
if (lang == "cpp") {
if (const char *p = std::getenv("KTE_LSP_CLANGD"); p && *p)
command = p;
} else if (lang == "go") {
if (const char *p = std::getenv("KTE_LSP_GOPLS"); p && *p)
command = p;
} else if (lang == "rust") {
if (const char *p = std::getenv("KTE_LSP_RUST_ANALYZER"); p && *p)
command = p;
}
if (debug_) {
std::fprintf(stderr, "[kte][lsp] startServerForBuffer: lang=%s cmd=%s args=%zu file=%s\n",
lang.c_str(), command.c_str(), cfg.args.size(), buffer->Filename().c_str());
lsp_debug_file("startServerForBuffer: lang=%s cmd=%s args=%zu file=%s",
lang.c_str(), command.c_str(), cfg.args.size(), buffer->Filename().c_str());
}
auto client = std::make_unique<LspProcessClient>(command, cfg.args);
// Wire diagnostics handler to manager
client->setDiagnosticsHandler([this](const std::string &uri, const std::vector<Diagnostic> &diags) {
this->handleDiagnostics(uri, diags);
});
// Determine workspace root using rootPatterns if set; fallback to file's parent
std::string rootPath;
if (!buffer->Filename().empty()) {
rootPath = detectWorkspaceRoot(buffer->Filename(), cfg);
if (rootPath.empty()) {
fs::path p(buffer->Filename());
rootPath = p.has_parent_path() ? p.parent_path().string() : std::string{};
}
}
if (debug_) {
const char *pathEnv = std::getenv("PATH");
std::fprintf(stderr, "[kte][lsp] initializing server: rootPath=%s PATH=%s\n",
rootPath.c_str(), pathEnv ? pathEnv : "<null>");
lsp_debug_file("initializing server: rootPath=%s PATH=%s",
rootPath.c_str(), pathEnv ? pathEnv : "<null>");
}
if (!client->initialize(rootPath)) {
if (debug_) {
std::fprintf(stderr, "[kte][lsp] initialize failed for lang=%s\n", lang.c_str());
lsp_debug_file("initialize failed for lang=%s", lang.c_str());
}
return false;
}
servers_[lang] = std::move(client);
return true;
}
void
LspManager::stopServer(const std::string &languageId)
{
auto it = servers_.find(languageId);
if (it != servers_.end()) {
it->second->shutdown();
servers_.erase(it);
}
}
void
LspManager::stopAllServers()
{
for (auto &kv: servers_) {
kv.second->shutdown();
}
servers_.clear();
}
bool
LspManager::startServerForLanguage(const std::string &languageId, const std::string &rootPath)
{
auto cfgIt = serverConfigs_.find(languageId);
if (cfgIt == serverConfigs_.end())
return false;
// If already running, nothing to do
auto it = servers_.find(languageId);
if (it != servers_.end() && it->second && it->second->isRunning()) {
return true;
}
const auto &cfg = cfgIt->second;
std::string command = cfg.command;
if (languageId == "cpp") {
if (const char *p = std::getenv("KTE_LSP_CLANGD"); p && *p)
command = p;
} else if (languageId == "go") {
if (const char *p = std::getenv("KTE_LSP_GOPLS"); p && *p)
command = p;
} else if (languageId == "rust") {
if (const char *p = std::getenv("KTE_LSP_RUST_ANALYZER"); p && *p)
command = p;
}
if (debug_) {
std::fprintf(stderr, "[kte][lsp] startServerForLanguage: lang=%s cmd=%s args=%zu root=%s\n",
languageId.c_str(), command.c_str(), cfg.args.size(), rootPath.c_str());
lsp_debug_file("startServerForLanguage: lang=%s cmd=%s args=%zu root=%s",
languageId.c_str(), command.c_str(), cfg.args.size(), rootPath.c_str());
}
auto client = std::make_unique<LspProcessClient>(command, cfg.args);
client->setDiagnosticsHandler([this](const std::string &uri, const std::vector<Diagnostic> &diags) {
this->handleDiagnostics(uri, diags);
});
std::string root = rootPath;
if (!root.empty()) {
// keep
} else {
// Try cwd if not provided
root = std::string();
}
if (!client->initialize(root)) {
if (debug_) {
std::fprintf(stderr, "[kte][lsp] initialize failed for lang=%s\n", languageId.c_str());
lsp_debug_file("initialize failed for lang=%s", languageId.c_str());
}
return false;
}
servers_[languageId] = std::move(client);
return true;
}
bool
LspManager::restartServer(const std::string &languageId, const std::string &rootPath)
{
stopServer(languageId);
return startServerForLanguage(languageId, rootPath);
}
void
LspManager::onBufferOpened(Buffer *buffer)
{
if (debug_) {
std::fprintf(stderr, "[kte][lsp] onBufferOpened: file=%s lang=%s\n",
buffer->Filename().c_str(), getLanguageId(buffer).c_str());
lsp_debug_file("onBufferOpened: file=%s lang=%s",
buffer->Filename().c_str(), getLanguageId(buffer).c_str());
}
if (!startServerForBuffer(buffer)) {
if (debug_) {
std::fprintf(stderr, "[kte][lsp] onBufferOpened: server did not start\n");
lsp_debug_file("onBufferOpened: server did not start");
}
return;
}
auto *client = ensureServerForLanguage(getLanguageId(buffer));
if (!client)
return;
const auto uri = getUri(buffer);
const auto lang = getLanguageId(buffer);
const int version = static_cast<int>(buffer->Version());
const std::string text = buffer->FullText();
if (debug_) {
std::fprintf(stderr, "[kte][lsp] didOpen: uri=%s lang=%s version=%d bytes=%zu\n",
uri.c_str(), lang.c_str(), version, text.size());
lsp_debug_file("didOpen: uri=%s lang=%s version=%d bytes=%zu",
uri.c_str(), lang.c_str(), version, text.size());
}
client->didOpen(uri, lang, version, text);
}
void
LspManager::onBufferChanged(Buffer *buffer)
{
auto *client = ensureServerForLanguage(getLanguageId(buffer));
if (!client)
return;
const auto uri = getUri(buffer);
int version = static_cast<int>(buffer->Version());
std::vector<TextDocumentContentChangeEvent> changes;
if (auto *tracker = buffer->GetChangeTracker()) {
changes = tracker->getChanges();
tracker->clearChanges();
version = tracker->getVersion();
} else {
// Fallback: full document change
TextDocumentContentChangeEvent ev;
ev.range.reset();
ev.text = buffer->FullText();
changes.push_back(std::move(ev));
}
// Option A: convert ranges from UTF-8 (editor coords) -> UTF-16 (LSP wire)
std::vector<TextDocumentContentChangeEvent> changes16;
changes16.reserve(changes.size());
// LineProvider that serves lines from this buffer by URI
Buffer *bufForUri = buffer; // changes are for this buffer
auto provider = [bufForUri](const std::string &/*u*/, int line) -> std::string_view {
if (!bufForUri)
return std::string_view();
const auto &rows = bufForUri->Rows();
if (line < 0 || static_cast<size_t>(line) >= rows.size())
return std::string_view();
// Materialize one line into a thread_local scratch; return view
thread_local std::string scratch;
scratch = static_cast<std::string>(rows[static_cast<size_t>(line)]);
return std::string_view(scratch);
};
for (const auto &ch: changes) {
TextDocumentContentChangeEvent out = ch;
if (ch.range.has_value()) {
Range r16 = toUtf16(uri, *ch.range, provider);
if (debug_) {
lsp_debug_file("didChange range convert: L%d C%d-%d -> L%d C%d-%d",
ch.range->start.line, ch.range->start.character,
ch.range->end.character,
r16.start.line, r16.start.character, r16.end.character);
}
out.range = r16;
}
changes16.push_back(std::move(out));
}
client->didChange(uri, version, changes16);
}
void
LspManager::onBufferClosed(Buffer *buffer)
{
auto *client = ensureServerForLanguage(getLanguageId(buffer));
if (!client)
return;
client->didClose(getUri(buffer));
// Clear diagnostics for this file
diagnosticStore_.clear(getUri(buffer));
}
void
LspManager::onBufferSaved(Buffer *buffer)
{
auto *client = ensureServerForLanguage(getLanguageId(buffer));
if (!client)
return;
client->didSave(getUri(buffer));
}
void
LspManager::requestCompletion(Buffer *buffer, Position pos, CompletionCallback callback)
{
if (auto *client = ensureServerForLanguage(getLanguageId(buffer))) {
const auto uri = getUri(buffer);
// Convert position to UTF-16 using Option A provider
auto provider = [buffer](const std::string &/*u*/, int line) -> std::string_view {
if (!buffer)
return std::string_view();
const auto &rows = buffer->Rows();
if (line < 0 || static_cast<size_t>(line) >= rows.size())
return std::string_view();
thread_local std::string scratch;
scratch = static_cast<std::string>(rows[static_cast<size_t>(line)]);
return std::string_view(scratch);
};
Position p16 = toUtf16(uri, pos, provider);
if (debug_) {
lsp_debug_file("completion pos convert: L%d C%d -> L%d C%d", pos.line, pos.character, p16.line,
p16.character);
}
client->completion(uri, p16, std::move(callback));
}
}
void
LspManager::requestHover(Buffer *buffer, Position pos, HoverCallback callback)
{
if (auto *client = ensureServerForLanguage(getLanguageId(buffer))) {
const auto uri = getUri(buffer);
auto provider = [buffer](const std::string &/*u*/, int line) -> std::string_view {
if (!buffer)
return std::string_view();
const auto &rows = buffer->Rows();
if (line < 0 || static_cast<size_t>(line) >= rows.size())
return std::string_view();
thread_local std::string scratch;
scratch = static_cast<std::string>(rows[static_cast<size_t>(line)]);
return std::string_view(scratch);
};
Position p16 = toUtf16(uri, pos, provider);
if (debug_) {
lsp_debug_file("hover pos convert: L%d C%d -> L%d C%d", pos.line, pos.character, p16.line,
p16.character);
}
// Wrap the callback to convert any returned range from UTF-16 (wire) -> UTF-8 (editor)
HoverCallback wrapped = [this, uri, provider, cb = std::move(callback)](const HoverResult &res16,
const std::string &err) {
if (!cb)
return;
if (!res16.range.has_value()) {
cb(res16, err);
return;
}
HoverResult res8 = res16;
res8.range = toUtf8(uri, *res16.range, provider);
if (debug_) {
const auto &r16 = *res16.range;
const auto &r8 = *res8.range;
lsp_debug_file("hover range convert: L%d %d-%d -> L%d %d-%d",
r16.start.line, r16.start.character, r16.end.character,
r8.start.line, r8.start.character, r8.end.character);
}
cb(res8, err);
};
client->hover(uri, p16, std::move(wrapped));
}
}
void
LspManager::requestDefinition(Buffer *buffer, Position pos, LocationCallback callback)
{
if (auto *client = ensureServerForLanguage(getLanguageId(buffer))) {
const auto uri = getUri(buffer);
auto provider = [buffer](const std::string &/*u*/, int line) -> std::string_view {
if (!buffer)
return std::string_view();
const auto &rows = buffer->Rows();
if (line < 0 || static_cast<size_t>(line) >= rows.size())
return std::string_view();
thread_local std::string scratch;
scratch = static_cast<std::string>(rows[static_cast<size_t>(line)]);
return std::string_view(scratch);
};
Position p16 = toUtf16(uri, pos, provider);
if (debug_) {
lsp_debug_file("definition pos convert: L%d C%d -> L%d C%d", pos.line, pos.character, p16.line,
p16.character);
}
// Wrap callback to convert Location ranges from UTF-16 (wire) -> UTF-8 (editor)
LocationCallback wrapped = [this, uri, provider, cb = std::move(callback)](
const std::vector<Location> &locs16,
const std::string &err) {
if (!cb)
return;
std::vector<Location> locs8;
locs8.reserve(locs16.size());
for (const auto &l: locs16) {
Location x = l;
x.range = toUtf8(uri, l.range, provider);
if (debug_) {
lsp_debug_file("definition range convert: L%d %d-%d -> L%d %d-%d",
l.range.start.line, l.range.start.character,
l.range.end.character,
x.range.start.line, x.range.start.character,
x.range.end.character);
}
locs8.push_back(std::move(x));
}
cb(locs8, err);
};
client->definition(uri, p16, std::move(wrapped));
}
}
void
LspManager::handleDiagnostics(const std::string &uri, const std::vector<Diagnostic> &diagnostics)
{
// Convert incoming ranges from UTF-16 (wire) -> UTF-8 (editor)
std::vector<Diagnostic> conv = diagnostics;
Buffer *buf = findBufferByUri(uri);
auto provider = [buf](const std::string &/*u*/, int line) -> std::string_view {
if (!buf)
return std::string_view();
const auto &rows = buf->Rows();
if (line < 0 || static_cast<size_t>(line) >= rows.size())
return std::string_view();
thread_local std::string scratch;
scratch = static_cast<std::string>(rows[static_cast<size_t>(line)]);
return std::string_view(scratch);
};
for (auto &d: conv) {
Range r8 = toUtf8(uri, d.range, provider);
if (debug_) {
lsp_debug_file("diagnostic range convert: L%d C%d-%d -> L%d C%d-%d",
d.range.start.line, d.range.start.character, d.range.end.character,
r8.start.line, r8.start.character, r8.end.character);
}
d.range = r8;
}
diagnosticStore_.setDiagnostics(uri, conv);
if (display_) {
display_->updateDiagnostics(uri, conv);
display_->updateStatusBar(diagnosticStore_.getErrorCount(uri), diagnosticStore_.getWarningCount(uri));
}
}
bool
LspManager::toggleAutostart(const std::string &languageId)
{
auto it = serverConfigs_.find(languageId);
if (it == serverConfigs_.end())
return false;
it->second.autostart = !it->second.autostart;
return it->second.autostart;
}
std::vector<std::string>
LspManager::configuredLanguages() const
{
std::vector<std::string> out;
out.reserve(serverConfigs_.size());
for (const auto &kv: serverConfigs_)
out.push_back(kv.first);
std::sort(out.begin(), out.end());
return out;
}
std::vector<std::string>
LspManager::runningLanguages() const
{
std::vector<std::string> out;
for (const auto &kv: servers_) {
if (kv.second && kv.second->isRunning())
out.push_back(kv.first);
}
std::sort(out.begin(), out.end());
return out;
}
std::string
LspManager::getLanguageId(Buffer *buffer)
{
// Prefer explicit filetype if set
const auto &ft = buffer->Filetype();
if (!ft.empty())
return ft;
// Otherwise map extension
fs::path p(buffer->Filename());
return extToLanguageId(p.extension().string());
}
std::string
LspManager::getUri(Buffer *buffer)
{
const auto &path = buffer->Filename();
if (path.empty()) {
// Untitled buffer: use a pseudo-URI
return std::string("untitled:") + std::to_string(reinterpret_cast<std::uintptr_t>(buffer));
}
fs::path p(path);
p = fs::weakly_canonical(p);
#ifdef _WIN32
// rudimentary file URI; future: robust encoding
return std::string("file:/") + p.string();
#else
return std::string("file://") + p.string();
#endif
}
// Resolve a Buffer* by matching constructed file URI
Buffer *
LspManager::findBufferByUri(const std::string &uri)
{
if (!editor_)
return nullptr;
// Compare against getUri for each buffer
auto &bufs = editor_->Buffers();
for (auto &b: bufs) {
if (getUri(&b) == uri)
return &b;
}
return nullptr;
}
std::string
LspManager::extToLanguageId(const std::string &ext)
{
std::string e = ext;
if (!e.empty() && e[0] == '.')
e.erase(0, 1);
std::string lower;
lower.resize(e.size());
std::transform(e.begin(), e.end(), lower.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
if (lower == "rs")
return "rust";
if (lower == "c" || lower == "cc" || lower == "cpp" || lower == "h" || lower == "hpp")
return "cpp";
if (lower == "go")
return "go";
if (lower == "py")
return "python";
if (lower == "js")
return "javascript";
if (lower == "ts")
return "typescript";
if (lower == "json")
return "json";
if (lower == "sh" || lower == "bash" || lower == "zsh")
return "shell";
if (lower == "md")
return "markdown";
return lower; // best-effort
}
LspClient *
LspManager::ensureServerForLanguage(const std::string &languageId)
{
auto it = servers_.find(languageId);
if (it != servers_.end() && it->second && it->second->isRunning()) {
return it->second.get();
}
// Attempt to start from config if present
auto cfg = serverConfigs_.find(languageId);
if (cfg == serverConfigs_.end())
return nullptr;
auto client = std::make_unique<LspProcessClient>(cfg->second.command, cfg->second.args);
client->setDiagnosticsHandler([this](const std::string &uri, const std::vector<Diagnostic> &diags) {
this->handleDiagnostics(uri, diags);
});
// No specific file context here; initialize with empty or current working dir
if (!client->initialize(""))
return nullptr;
auto *ret = client.get();
servers_[languageId] = std::move(client);
return ret;
}
void
LspManager::registerDefaultServers()
{
// Import defaults and register by inferred languageId from file patterns
for (const auto &cfg: GetDefaultServerConfigs()) {
if (cfg.filePatterns.empty()) {
// If no patterns, we can't infer; skip
continue;
}
for (const auto &pat: cfg.filePatterns) {
const auto lang = patternToLanguageId(pat);
if (lang.empty())
continue;
// Don't overwrite if user already registered a server for this lang
if (serverConfigs_.find(lang) == serverConfigs_.end()) {
serverConfigs_.emplace(lang, cfg);
}
}
}
}
std::string
LspManager::patternToLanguageId(const std::string &pattern)
{
// Expect patterns like "*.rs", "*.cpp" etc. Extract extension and reuse extToLanguageId
// Find last '.' in the pattern and take substring after it, stripping any trailing wildcards
std::string ext;
// Common case: starts with *.
auto pos = pattern.rfind('.');
if (pos != std::string::npos && pos + 1 < pattern.size()) {
ext = pattern.substr(pos + 1);
// Remove any trailing wildcard characters
while (!ext.empty() && (ext.back() == '*' || ext.back() == '?')) {
ext.pop_back();
}
} else {
// No dot; try to treat whole pattern as extension after trimming leading '*'
ext = pattern;
while (!ext.empty() && (ext.front() == '*' || ext.front() == '.')) {
ext.erase(ext.begin());
}
}
if (ext.empty())
return {};
return extToLanguageId(ext);
}
// Detect workspace root by walking up from filePath looking for any of the
// configured rootPatterns (simple filenames). Supports comma/semicolon-separated
// patterns in cfg.rootPatterns.
std::string
LspManager::detectWorkspaceRoot(const std::string &filePath, const LspServerConfig &cfg)
{
if (filePath.empty())
return {};
fs::path start(filePath);
fs::path dir = start.has_parent_path() ? start.parent_path() : start;
// Build cache key
const std::string cacheKey = (dir.string() + "|" + cfg.rootPatterns);
auto it = rootCache_.find(cacheKey);
if (it != rootCache_.end()) {
return it->second;
}
// Split patterns by ',', ';', or ':'
std::vector<std::string> pats;
{
std::string acc;
for (char c: cfg.rootPatterns) {
if (c == ',' || c == ';' || c == ':') {
if (!acc.empty()) {
pats.push_back(acc);
acc.clear();
}
} else if (!std::isspace(static_cast<unsigned char>(c))) {
acc.push_back(c);
}
}
if (!acc.empty())
pats.push_back(acc);
}
// If no patterns defined, cache empty and return {}
if (pats.empty()) {
rootCache_[cacheKey] = {};
return {};
}
fs::path cur = dir;
while (true) {
// Check each pattern in this directory
for (const auto &pat: pats) {
if (pat.empty())
continue;
fs::path candidate = cur / pat;
std::error_code ec;
bool exists = fs::exists(candidate, ec);
if (!ec && exists) {
rootCache_[cacheKey] = cur.string();
return rootCache_[cacheKey];
}
}
if (cur.has_parent_path()) {
fs::path parent = cur.parent_path();
if (parent == cur)
break; // reached root guard
cur = parent;
} else {
break;
}
}
rootCache_[cacheKey] = {};
return {};
}
} // namespace kte::lsp

108
lsp/LspManager.h Normal file
View File

@@ -0,0 +1,108 @@
/*
* LspManager.h - central coordination of LSP servers and diagnostics
*/
#ifndef KTE_LSP_MANAGER_H
#define KTE_LSP_MANAGER_H
#include <memory>
#include <string>
#include <unordered_map>
class Buffer; // fwd
class Editor; // fwd
#include "DiagnosticDisplay.h"
#include "DiagnosticStore.h"
#include "LspClient.h"
#include "LspServerConfig.h"
#include "UtfCodec.h"
namespace kte::lsp {
class LspManager {
public:
explicit LspManager(Editor *editor, DiagnosticDisplay *display);
// Server management
void registerServer(const std::string &languageId, const LspServerConfig &config);
bool startServerForBuffer(Buffer *buffer);
void stopServer(const std::string &languageId);
void stopAllServers();
// Manual lifecycle controls
bool startServerForLanguage(const std::string &languageId, const std::string &rootPath = std::string());
bool restartServer(const std::string &languageId, const std::string &rootPath = std::string());
// Document sync (to be called by editor/buffer events)
void onBufferOpened(Buffer *buffer);
void onBufferChanged(Buffer *buffer);
void onBufferClosed(Buffer *buffer);
void onBufferSaved(Buffer *buffer);
// Feature requests (stubs)
void requestCompletion(Buffer *buffer, Position pos, CompletionCallback callback);
void requestHover(Buffer *buffer, Position pos, HoverCallback callback);
void requestDefinition(Buffer *buffer, Position pos, LocationCallback callback);
// Diagnostics (public so LspClient impls can forward results here later)
void handleDiagnostics(const std::string &uri, const std::vector<Diagnostic> &diagnostics);
void setDebugLogging(bool enabled)
{
debug_ = enabled;
}
// Configuration utilities
bool toggleAutostart(const std::string &languageId);
std::vector<std::string> configuredLanguages() const;
std::vector<std::string> runningLanguages() const;
private:
[[maybe_unused]] Editor *editor_{}; // non-owning
DiagnosticDisplay *display_{}; // non-owning
DiagnosticStore diagnosticStore_{};
// Key: languageId → client
std::unordered_map<std::string, std::unique_ptr<LspClient> > servers_;
std::unordered_map<std::string, LspServerConfig> serverConfigs_;
// Helpers
static std::string getLanguageId(Buffer *buffer);
static std::string getUri(Buffer *buffer);
static std::string extToLanguageId(const std::string &ext);
LspClient *ensureServerForLanguage(const std::string &languageId);
bool debug_ = false;
// Configuration helpers
void registerDefaultServers();
static std::string patternToLanguageId(const std::string &pattern);
// Workspace root detection helpers/cache
std::string detectWorkspaceRoot(const std::string &filePath, const LspServerConfig &cfg);
// key = startDir + "|" + cfg.rootPatterns
std::unordered_map<std::string, std::string> rootCache_;
// Resolve a buffer by its file:// (or untitled:) URI
Buffer *findBufferByUri(const std::string &uri);
};
} // namespace kte::lsp
#endif // KTE_LSP_MANAGER_H

948
lsp/LspProcessClient.cc Normal file
View File

@@ -0,0 +1,948 @@
/*
* LspProcessClient.cc - process-based LSP client (Phase 1 minimal)
*/
#include "LspProcessClient.h"
#include <sstream>
#include <vector>
#include <string>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <thread>
#include "json.h"
namespace kte::lsp {
LspProcessClient::LspProcessClient(std::string serverCommand, std::vector<std::string> serverArgs)
: command_(std::move(serverCommand)), args_(std::move(serverArgs)), transport_(new JsonRpcTransport())
{
if (const char *dbg = std::getenv("KTE_LSP_DEBUG"); dbg && *dbg) {
debug_ = true;
}
if (const char *to = std::getenv("KTE_LSP_REQ_TIMEOUT_MS"); to && *to) {
char *end = nullptr;
long long v = std::strtoll(to, &end, 10);
if (end && *end == '\0' && v >= 0) {
requestTimeoutMs_ = v;
}
}
if (const char *mp = std::getenv("KTE_LSP_MAX_PENDING"); mp && *mp) {
char *end = nullptr;
long long v = std::strtoll(mp, &end, 10);
if (end && *end == '\0' && v >= 0) {
maxPending_ = static_cast<size_t>(v);
}
}
}
LspProcessClient::~LspProcessClient()
{
shutdown();
}
bool
LspProcessClient::spawnServerProcess()
{
int toChild[2]; // parent writes toChild[1] -> child's stdin
int fromChild[2]; // child writes fromChild[1] -> parent's stdout reader
if (pipe(toChild) != 0) {
if (debug_)
std::fprintf(stderr, "[kte][lsp] pipe(toChild) failed: %s\n", std::strerror(errno));
return false;
}
if (pipe(fromChild) != 0) {
::close(toChild[0]);
::close(toChild[1]);
if (debug_)
std::fprintf(stderr, "[kte][lsp] pipe(fromChild) failed: %s\n", std::strerror(errno));
return false;
}
pid_t pid = fork();
if (pid < 0) {
// fork failed
::close(toChild[0]);
::close(toChild[1]);
::close(fromChild[0]);
::close(fromChild[1]);
if (debug_)
std::fprintf(stderr, "[kte][lsp] fork failed: %s\n", std::strerror(errno));
return false;
}
if (pid == 0) {
// Child: set up stdio
::dup2(toChild[0], STDIN_FILENO);
::dup2(fromChild[1], STDOUT_FILENO);
// Close extra fds
::close(toChild[0]);
::close(toChild[1]);
::close(fromChild[0]);
::close(fromChild[1]);
// Build argv
std::vector<char *> argv;
argv.push_back(const_cast<char *>(command_.c_str()));
for (auto &s: args_)
argv.push_back(const_cast<char *>(s.c_str()));
argv.push_back(nullptr);
// Exec
execvp(command_.c_str(), argv.data());
// If exec fails
// Note: in child; cannot easily log to parent. Attempt to write to stderr.
std::fprintf(stderr, "[kte][lsp] execvp failed for '%s': %s\n", command_.c_str(), std::strerror(errno));
_exit(127);
}
// Parent: keep ends
childPid_ = pid;
outFd_ = toChild[1]; // write to child's stdin
inFd_ = fromChild[0]; // read from child's stdout
// Close the other ends we don't use
::close(toChild[0]);
::close(fromChild[1]);
// Set CLOEXEC on our fds
fcntl(outFd_, F_SETFD, FD_CLOEXEC);
fcntl(inFd_, F_SETFD, FD_CLOEXEC);
if (debug_) {
std::ostringstream oss;
oss << command_;
for (const auto &a: args_) {
oss << ' ' << a;
}
const char *pathEnv = std::getenv("PATH");
std::fprintf(stderr, "[kte][lsp] spawned pid=%d argv=[%s] inFd=%d outFd=%d PATH=%s\n",
static_cast<int>(childPid_), oss.str().c_str(), inFd_, outFd_,
pathEnv ? pathEnv : "<null>");
}
transport_->connect(inFd_, outFd_);
return true;
}
void
LspProcessClient::terminateProcess()
{
if (outFd_ >= 0) {
::close(outFd_);
outFd_ = -1;
}
if (inFd_ >= 0) {
::close(inFd_);
inFd_ = -1;
}
if (childPid_ > 0) {
// Try to wait non-blocking; if still running, send SIGTERM
int status = 0;
pid_t r = waitpid(childPid_, &status, WNOHANG);
if (r == 0) {
// still running
kill(childPid_, SIGTERM);
waitpid(childPid_, &status, 0);
}
childPid_ = -1;
}
}
void
LspProcessClient::sendInitialize(const std::string &rootPath)
{
int idNum = nextRequestIntId_++;
pendingInitializeId_ = std::to_string(idNum);
nlohmann::json j;
j["jsonrpc"] = "2.0";
j["id"] = idNum;
j["method"] = "initialize";
nlohmann::json params;
params["processId"] = static_cast<int>(getpid());
params["rootUri"] = toFileUri(rootPath);
// Minimal client capabilities for now
nlohmann::json caps;
caps["textDocument"]["synchronization"]["didSave"] = true;
params["capabilities"] = std::move(caps);
j["params"] = std::move(params);
transport_->send("initialize", j.dump());
}
bool
LspProcessClient::initialize(const std::string &rootPath)
{
if (running_)
return true;
if (debug_)
std::fprintf(stderr, "[kte][lsp] initialize: rootPath=%s\n", rootPath.c_str());
if (!spawnServerProcess())
return false;
running_ = true;
sendInitialize(rootPath);
startReader();
startTimeoutWatchdog();
return true;
}
void
LspProcessClient::shutdown()
{
if (!running_)
return;
if (debug_)
std::fprintf(stderr, "[kte][lsp] shutdown\n");
// Send shutdown request then exit notification (best-effort)
int id = nextRequestIntId_++;
{
nlohmann::json j;
j["jsonrpc"] = "2.0";
j["id"] = id;
j["method"] = "shutdown";
transport_->send("shutdown", j.dump());
}
{
nlohmann::json j;
j["jsonrpc"] = "2.0";
j["method"] = "exit";
transport_->send("exit", j.dump());
}
// Close pipes to unblock reader, then join thread, then ensure child is gone
terminateProcess();
stopReader();
stopTimeoutWatchdog();
// Clear any pending callbacks
{
std::lock_guard<std::mutex> lk(pendingMutex_);
pending_.clear();
pendingOrder_.clear();
}
running_ = false;
}
void
LspProcessClient::didOpen(const std::string &uri, const std::string &languageId,
int version, const std::string &text)
{
if (!running_)
return;
if (debug_)
std::fprintf(stderr, "[kte][lsp] -> didOpen uri=%s lang=%s version=%d bytes=%zu\n",
uri.c_str(), languageId.c_str(), version, text.size());
nlohmann::json j;
j["jsonrpc"] = "2.0";
j["method"] = "textDocument/didOpen";
j["params"]["textDocument"]["uri"] = uri;
j["params"]["textDocument"]["languageId"] = languageId;
j["params"]["textDocument"]["version"] = version;
j["params"]["textDocument"]["text"] = text;
transport_->send("textDocument/didOpen", j.dump());
}
void
LspProcessClient::didChange(const std::string &uri, int version,
const std::vector<TextDocumentContentChangeEvent> &changes)
{
if (!running_)
return;
if (debug_)
std::fprintf(stderr, "[kte][lsp] -> didChange uri=%s version=%d changes=%zu\n",
uri.c_str(), version, changes.size());
// Phase 1: send full or ranged changes using proper JSON construction
nlohmann::json j;
j["jsonrpc"] = "2.0";
j["method"] = "textDocument/didChange";
j["params"]["textDocument"]["uri"] = uri;
j["params"]["textDocument"]["version"] = version;
auto &arr = j["params"]["contentChanges"];
arr = nlohmann::json::array();
for (const auto &ch: changes) {
nlohmann::json c;
if (ch.range.has_value()) {
c["range"]["start"]["line"] = ch.range->start.line;
c["range"]["start"]["character"] = ch.range->start.character;
c["range"]["end"]["line"] = ch.range->end.line;
c["range"]["end"]["character"] = ch.range->end.character;
}
c["text"] = ch.text;
arr.push_back(std::move(c));
}
transport_->send("textDocument/didChange", j.dump());
}
void
LspProcessClient::didClose(const std::string &uri)
{
if (!running_)
return;
if (debug_)
std::fprintf(stderr, "[kte][lsp] -> didClose uri=%s\n", uri.c_str());
nlohmann::json j;
j["jsonrpc"] = "2.0";
j["method"] = "textDocument/didClose";
j["params"]["textDocument"]["uri"] = uri;
transport_->send("textDocument/didClose", j.dump());
}
void
LspProcessClient::didSave(const std::string &uri)
{
if (!running_)
return;
if (debug_)
std::fprintf(stderr, "[kte][lsp] -> didSave uri=%s\n", uri.c_str());
nlohmann::json j;
j["jsonrpc"] = "2.0";
j["method"] = "textDocument/didSave";
j["params"]["textDocument"]["uri"] = uri;
transport_->send("textDocument/didSave", j.dump());
}
void
LspProcessClient::startReader()
{
stopReader_ = false;
reader_ = std::thread([this] {
this->readerLoop();
});
}
void
LspProcessClient::stopReader()
{
stopReader_ = true;
if (reader_.joinable()) {
// Waking up read() by closing inFd_ is handled in terminateProcess(); ensure its closed first
// Here, best-effort join with small delay
reader_.join();
}
}
void
LspProcessClient::readerLoop()
{
if (debug_)
std::fprintf(stderr, "[kte][lsp] readerLoop start\n");
while (!stopReader_) {
auto msg = transport_->read();
if (!msg.has_value()) {
// EOF or error
break;
}
handleIncoming(msg->raw);
}
if (debug_)
std::fprintf(stderr, "[kte][lsp] readerLoop end\n");
}
void
LspProcessClient::handleIncoming(const std::string &json)
{
try {
auto j = nlohmann::json::parse(json, nullptr, false);
if (j.is_discarded())
return; // malformed JSON
// Validate jsonrpc if present
if (auto itRpc = j.find("jsonrpc"); itRpc != j.end()) {
if (!itRpc->is_string() || *itRpc != "2.0")
return;
}
auto normalizeId = [](const nlohmann::json &idVal) -> std::string {
if (idVal.is_string())
return idVal.get<std::string>();
if (idVal.is_number_integer())
return std::to_string(idVal.get<long long>());
return std::string();
};
// Handle responses (have id and no method) or server -> client requests (have id and method)
if (auto itId = j.find("id"); itId != j.end() && !itId->is_null()) {
const std::string respIdStr = normalizeId(*itId);
// If it's a request from server, it will also have a method
if (auto itMeth = j.find("method"); itMeth != j.end() && itMeth->is_string()) {
const std::string method = *itMeth;
if (method == "workspace/configuration") {
// Respond with default empty settings array matching requested items length
size_t n = 0;
if (auto itParams = j.find("params");
itParams != j.end() && itParams->is_object()) {
if (auto itItems = itParams->find("items");
itItems != itParams->end() && itItems->is_array()) {
n = itItems->size();
}
}
nlohmann::json resp;
resp["jsonrpc"] = "2.0";
// echo id type: if original was string, send string; else number
if (itId->is_string())
resp["id"] = *itId;
else if (itId->is_number_integer())
resp["id"] = *itId;
nlohmann::json arr = nlohmann::json::array();
for (size_t i = 0; i < n; ++i)
arr.push_back(nlohmann::json::object());
resp["result"] = std::move(arr);
transport_->send("response", resp.dump());
return;
}
if (method == "window/showMessageRequest") {
// Best-effort respond with null result (dismiss)
nlohmann::json resp;
resp["jsonrpc"] = "2.0";
if (itId->is_string())
resp["id"] = *itId;
else if (itId->is_number_integer())
resp["id"] = *itId;
resp["result"] = nullptr;
transport_->send("response", resp.dump());
return;
}
// Unknown server request: respond with MethodNotFound
nlohmann::json err;
err["code"] = -32601;
err["message"] = "Method not found";
nlohmann::json resp;
resp["jsonrpc"] = "2.0";
if (itId->is_string())
resp["id"] = *itId;
else if (itId->is_number_integer())
resp["id"] = *itId;
resp["error"] = std::move(err);
transport_->send("response", resp.dump());
return;
}
// Initialize handshake special-case
if (!pendingInitializeId_.empty() && respIdStr == pendingInitializeId_) {
nlohmann::json init;
init["jsonrpc"] = "2.0";
init["method"] = "initialized";
init["params"] = nlohmann::json::object();
transport_->send("initialized", init.dump());
pendingInitializeId_.clear();
}
// Dispatcher lookup
std::function < void(const nlohmann::json &, const nlohmann::json *) > cb;
{
std::lock_guard<std::mutex> lk(pendingMutex_);
auto it = pending_.find(respIdStr);
if (it != pending_.end()) {
cb = it->second.callback;
if (it->second.orderIt != pendingOrder_.end()) {
pendingOrder_.erase(it->second.orderIt);
}
pending_.erase(it);
}
}
if (cb) {
const nlohmann::json *errPtr = nullptr;
const auto itErr = j.find("error");
if (itErr != j.end() && itErr->is_object())
errPtr = &(*itErr);
nlohmann::json result;
const auto itRes = j.find("result");
if (itRes != j.end())
result = *itRes; // may be null
cb(result, errPtr);
}
return;
}
const auto itMethod = j.find("method");
if (itMethod == j.end() || !itMethod->is_string())
return;
const std::string method = *itMethod;
if (method == "window/logMessage") {
if (debug_) {
const auto itParams = j.find("params");
if (itParams != j.end()) {
const auto itMsg = itParams->find("message");
if (itMsg != itParams->end() && itMsg->is_string()) {
std::fprintf(stderr, "[kte][lsp] logMessage: %s\n",
itMsg->get_ref<const std::string &>().c_str());
}
}
}
return;
}
if (method == "window/showMessage") {
const auto itParams = j.find("params");
if (debug_ &&itParams
!=
j.end() && itParams->is_object()
)
{
int typ = 0;
std::string msg;
if (auto itm = itParams->find("message"); itm != itParams->end() && itm->is_string())
msg = *itm;
if (auto ity = itParams->find("type");
ity != itParams->end() && ity->is_number_integer())
typ = *ity;
std::fprintf(stderr, "[kte][lsp] showMessage(type=%d): %s\n", typ, msg.c_str());
}
return;
}
if (method != "textDocument/publishDiagnostics") {
return;
}
const auto itParams = j.find("params");
if (itParams == j.end() || !itParams->is_object())
return;
const auto itUri = itParams->find("uri");
if (itUri == itParams->end() || !itUri->is_string())
return;
const std::string uri = *itUri;
std::vector<Diagnostic> diags;
const auto itDiag = itParams->find("diagnostics");
if (itDiag != itParams->end() && itDiag->is_array()) {
for (const auto &djson: *itDiag) {
if (!djson.is_object())
continue;
Diagnostic d;
// severity
int sev = 3;
if (auto itS = djson.find("severity"); itS != djson.end() && itS->is_number_integer()) {
sev = *itS;
}
switch (sev) {
case 1:
d.severity = DiagnosticSeverity::Error;
break;
case 2:
d.severity = DiagnosticSeverity::Warning;
break;
case 3:
d.severity = DiagnosticSeverity::Information;
break;
case 4:
d.severity = DiagnosticSeverity::Hint;
break;
default:
d.severity = DiagnosticSeverity::Information;
break;
}
if (auto itM = djson.find("message"); itM != djson.end() && itM->is_string()) {
d.message = *itM;
}
if (auto itR = djson.find("range"); itR != djson.end() && itR->is_object()) {
if (auto itStart = itR->find("start");
itStart != itR->end() && itStart->is_object()) {
if (auto itL = itStart->find("line");
itL != itStart->end() && itL->is_number_integer()) {
d.range.start.line = *itL;
}
if (auto itC = itStart->find("character");
itC != itStart->end() && itC->is_number_integer()) {
d.range.start.character = *itC;
}
}
if (auto itEnd = itR->find("end"); itEnd != itR->end() && itEnd->is_object()) {
if (auto itL = itEnd->find("line");
itL != itEnd->end() && itL->is_number_integer()) {
d.range.end.line = *itL;
}
if (auto itC = itEnd->find("character");
itC != itEnd->end() && itC->is_number_integer()) {
d.range.end.character = *itC;
}
}
}
// optional code/source
if (auto itCode = djson.find("code"); itCode != djson.end()) {
if (itCode->is_string())
d.code = itCode->get<std::string>();
else if (itCode->is_number_integer())
d.code = std::to_string(itCode->get<int>());
}
if (auto itSrc = djson.find("source"); itSrc != djson.end() && itSrc->is_string()) {
d.source = itSrc->get<std::string>();
}
diags.push_back(std::move(d));
}
}
if (diagnosticsHandler_) {
diagnosticsHandler_(uri, diags);
}
} catch (...) {
// swallow parse errors
}
}
int
LspProcessClient::sendRequest(const std::string &method, const nlohmann::json &params,
std::function<void(const nlohmann::json & result, const nlohmann::json * errorJson)> cb)
{
if (!running_)
return 0;
int id = nextRequestIntId_++;
nlohmann::json j;
j["jsonrpc"] = "2.0";
j["id"] = id;
j["method"] = method;
if (!params.is_null())
j["params"] = params;
if (debug_)
std::fprintf(stderr, "[kte][lsp] -> request method=%s id=%d\n", method.c_str(), id);
transport_->send(method, j.dump());
if (cb) {
std::function < void() > callDropped;
{
std::lock_guard<std::mutex> lk(pendingMutex_);
if (maxPending_ > 0 && pending_.size() >= maxPending_) {
// Evict oldest
if (!pendingOrder_.empty()) {
std::string oldestId = pendingOrder_.front();
auto it = pending_.find(oldestId);
if (it != pending_.end()) {
auto cbOld = it->second.callback;
std::string methOld = it->second.method;
if (debug_) {
std::fprintf(
stderr,
"[kte][lsp] dropping oldest pending id=%s method=%s (cap=%zu)\n",
oldestId.c_str(), methOld.c_str(), maxPending_);
}
// Prepare drop callback to run outside lock
callDropped = [cbOld] {
if (cbOld) {
nlohmann::json err;
err["code"] = -32001;
err["message"] =
"Request dropped (max pending exceeded)";
cbOld(nlohmann::json(), &err);
}
};
pending_.erase(it);
}
pendingOrder_.pop_front();
}
}
pendingOrder_.push_back(std::to_string(id));
auto itOrder = pendingOrder_.end();
--itOrder;
PendingRequest pr;
pr.method = method;
pr.callback = std::move(cb);
if (requestTimeoutMs_ > 0) {
pr.deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(
requestTimeoutMs_);
}
pr.orderIt = itOrder;
pending_[std::to_string(id)] = std::move(pr);
}
if (callDropped)
callDropped();
}
return id;
}
void
LspProcessClient::completion(const std::string &uri, Position pos, CompletionCallback cb)
{
nlohmann::json params;
params["textDocument"]["uri"] = uri;
params["position"]["line"] = pos.line;
params["position"]["character"] = pos.character;
sendRequest("textDocument/completion", params,
[cb = std::move(cb)](const nlohmann::json &result, const nlohmann::json *error) {
CompletionList out{};
std::string err;
if (error) {
if (auto itMsg = error->find("message");
itMsg != error->end() && itMsg->is_string())
err = itMsg->get<std::string>();
else
err = "LSP error";
} else {
auto parseItem = [](const nlohmann::json &j) -> CompletionItem {
CompletionItem it{};
if (auto il = j.find("label"); il != j.end() && il->is_string())
it.label = il->get<std::string>();
if (auto idt = j.find("detail"); idt != j.end() && idt->is_string())
it.detail = idt->get<std::string>();
if (auto ins = j.find("insertText"); ins != j.end() && ins->is_string())
it.insertText = ins->get<std::string>();
return it;
};
if (result.is_array()) {
for (const auto &ji: result) {
if (ji.is_object())
out.items.push_back(parseItem(ji));
}
} else if (result.is_object()) {
if (auto ii = result.find("isIncomplete");
ii != result.end() && ii->is_boolean())
out.isIncomplete = ii->get<bool>();
if (auto itms = result.find("items");
itms != result.end() && itms->is_array()) {
for (const auto &ji: *itms) {
if (ji.is_object())
out.items.push_back(parseItem(ji));
}
}
}
}
if (cb)
cb(out, err);
});
}
void
LspProcessClient::hover(const std::string &uri, Position pos, HoverCallback cb)
{
nlohmann::json params;
params["textDocument"]["uri"] = uri;
params["position"]["line"] = pos.line;
params["position"]["character"] = pos.character;
sendRequest("textDocument/hover", params,
[cb = std::move(cb)](const nlohmann::json &result, const nlohmann::json *error) {
HoverResult out{};
std::string err;
if (error) {
if (auto itMsg = error->find("message");
itMsg != error->end() && itMsg->is_string())
err = itMsg->get<std::string>();
else
err = "LSP error";
} else if (!result.is_null()) {
auto appendText = [&](const std::string &s) {
if (!out.contents.empty())
out.contents.push_back('\n');
out.contents += s;
};
if (result.is_object()) {
if (auto itC = result.find("contents"); itC != result.end()) {
if (itC->is_string()) {
appendText(itC->get<std::string>());
} else if (itC->is_object()) {
if (auto itV = itC->find("value");
itV != itC->end() && itV->is_string())
appendText(itV->get<std::string>());
} else if (itC->is_array()) {
for (const auto &el: *itC) {
if (el.is_string())
appendText(el.get<std::string>());
else if (el.is_object()) {
if (auto itV = el.find("value");
itV != el.end() && itV->is_string())
appendText(itV->get<std::string>());
}
}
}
}
if (auto itR = result.find("range");
itR != result.end() && itR->is_object()) {
Range r{};
if (auto s = itR->find("start");
s != itR->end() && s->is_object()) {
if (auto il = s->find("line");
il != s->end() && il->is_number_integer())
r.start.line = *il;
if (auto ic = s->find("character");
ic != s->end() && ic->is_number_integer())
r.start.character = *ic;
}
if (auto e = itR->find("end"); e != itR->end() && e->is_object()) {
if (auto il = e->find("line");
il != e->end() && il->is_number_integer())
r.end.line = *il;
if (auto ic = e->find("character");
ic != e->end() && ic->is_number_integer())
r.end.character = *ic;
}
out.range = r;
}
}
}
if (cb)
cb(out, err);
});
}
void
LspProcessClient::definition(const std::string &uri, Position pos, LocationCallback cb)
{
nlohmann::json params;
params["textDocument"]["uri"] = uri;
params["position"]["line"] = pos.line;
params["position"]["character"] = pos.character;
sendRequest("textDocument/definition", params,
[cb = std::move(cb)](const nlohmann::json &result, const nlohmann::json *error) {
std::vector<Location> out;
std::string err;
auto parseRange = [](const nlohmann::json &jr) -> Range {
Range r{};
if (!jr.is_object())
return r;
if (auto s = jr.find("start"); s != jr.end() && s->is_object()) {
if (auto il = s->find("line"); il != s->end() && il->is_number_integer())
r.start.line = *il;
if (auto ic = s->find("character");
ic != s->end() && ic->is_number_integer())
r.start.character = *ic;
}
if (auto e = jr.find("end"); e != jr.end() && e->is_object()) {
if (auto il = e->find("line"); il != e->end() && il->is_number_integer())
r.end.line = *il;
if (auto ic = e->find("character");
ic != e->end() && e->is_number_integer())
r.end.character = *ic;
}
return r;
};
auto pushLocObj = [&](const nlohmann::json &jo) {
Location loc{};
if (auto iu = jo.find("uri"); iu != jo.end() && iu->is_string())
loc.uri = iu->get<std::string>();
if (auto ir = jo.find("range"); ir != jo.end())
loc.range = parseRange(*ir);
out.push_back(std::move(loc));
};
if (error) {
if (auto itMsg = error->find("message");
itMsg != error->end() && itMsg->is_string())
err = itMsg->get<std::string>();
else
err = "LSP error";
} else if (!result.is_null()) {
if (result.is_object()) {
if (result.contains("uri") && result.contains("range")) {
pushLocObj(result);
} else if (result.contains("targetUri")) {
Location loc{};
if (auto tu = result.find("targetUri");
tu != result.end() && tu->is_string())
loc.uri = tu->get<std::string>();
if (auto tr = result.find("targetRange"); tr != result.end())
loc.range = parseRange(*tr);
out.push_back(std::move(loc));
}
} else if (result.is_array()) {
for (const auto &el: result) {
if (el.is_object()) {
if (el.contains("uri")) {
pushLocObj(el);
} else if (el.contains("targetUri")) {
Location loc{};
if (auto tu = el.find("targetUri");
tu != el.end() && tu->is_string())
loc.uri = tu->get<std::string>();
if (auto tr = el.find("targetRange");
tr != el.end())
loc.range = parseRange(*tr);
out.push_back(std::move(loc));
}
}
}
}
}
if (cb)
cb(out, err);
});
}
bool
LspProcessClient::isRunning() const
{
return running_;
}
std::string
LspProcessClient::getServerName() const
{
return command_;
}
std::string
LspProcessClient::toFileUri(const std::string &path)
{
if (path.empty())
return std::string();
#ifdef _WIN32
return std::string("file:/") + path;
#else
return std::string("file://") + path;
#endif
}
void
LspProcessClient::startTimeoutWatchdog()
{
stopTimeout_ = false;
if (requestTimeoutMs_ <= 0)
return;
timeoutThread_ = std::thread([this] {
while (!stopTimeout_) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto now = std::chrono::steady_clock::now();
struct Expired {
std::string id;
std::string method;
std::function<void(const nlohmann::json &, const nlohmann::json *)> cb;
};
std::vector<Expired> expired;
{
std::lock_guard<std::mutex> lk(pendingMutex_);
for (auto it = pending_.begin(); it != pending_.end();) {
const auto &pr = it->second;
if (pr.deadline.time_since_epoch().count() != 0 && now >= pr.deadline) {
expired.push_back(Expired{it->first, pr.method, pr.callback});
if (pr.orderIt != pendingOrder_.end())
pendingOrder_.erase(pr.orderIt);
it = pending_.erase(it);
} else {
++it;
}
}
}
for (auto &kv: expired) {
if (debug_) {
std::fprintf(stderr, "[kte][lsp] request timeout id=%s method=%s\n",
kv.id.c_str(), kv.method.c_str());
}
if (kv.cb) {
nlohmann::json err;
err["code"] = -32000;
err["message"] = "Request timed out";
kv.cb(nlohmann::json(), &err);
}
}
}
});
}
void
LspProcessClient::stopTimeoutWatchdog()
{
stopTimeout_ = true;
if (timeoutThread_.joinable())
timeoutThread_.join();
}
} // namespace kte::lsp

189
lsp/LspProcessClient.h Normal file
View File

@@ -0,0 +1,189 @@
/*
* LspProcessClient.h - process-based LSP client (initial stub)
*/
#ifndef KTE_LSP_PROCESS_CLIENT_H
#define KTE_LSP_PROCESS_CLIENT_H
#include <memory>
#include <string>
#include <vector>
#include <thread>
#include <atomic>
#include <functional>
#include <unordered_map>
#include <mutex>
#include <chrono>
#include <list>
#include "json.h"
#include "LspClient.h"
#include "JsonRpcTransport.h"
namespace kte::lsp {
class LspProcessClient : public LspClient {
public:
LspProcessClient(std::string serverCommand, std::vector<std::string> serverArgs);
~LspProcessClient() override;
bool initialize(const std::string &rootPath) override;
void shutdown() override;
void didOpen(const std::string &uri, const std::string &languageId,
int version, const std::string &text) override;
void didChange(const std::string &uri, int version,
const std::vector<TextDocumentContentChangeEvent> &changes) override;
void didClose(const std::string &uri) override;
void didSave(const std::string &uri) override;
// Language Features (wire-up via dispatcher; minimal callbacks for now)
void completion(const std::string &uri, Position pos,
CompletionCallback cb) override;
void hover(const std::string &uri, Position pos,
HoverCallback cb) override;
void definition(const std::string &uri, Position pos,
LocationCallback cb) override;
bool isRunning() const override;
std::string getServerName() const override;
void setDiagnosticsHandler(DiagnosticsHandler h) override
{
diagnosticsHandler_ = std::move(h);
}
private:
std::string command_;
std::vector<std::string> args_;
std::unique_ptr<JsonRpcTransport> transport_;
bool running_ = false;
bool debug_ = false;
int inFd_ = -1; // read from server (server stdout)
int outFd_ = -1; // write to server (server stdin)
pid_t childPid_ = -1;
int nextRequestIntId_ = 1;
std::string pendingInitializeId_{}; // echo exactly as sent (string form)
// Incoming processing
std::thread reader_;
std::atomic<bool> stopReader_{false};
DiagnosticsHandler diagnosticsHandler_{};
// Simple request dispatcher: map request id -> callback
struct PendingRequest {
std::string method;
// If error is present, errorJson points to it; otherwise nullptr
std::function<void(const nlohmann::json & result, const nlohmann::json * errorJson)> callback;
// Optional timeout
std::chrono::steady_clock::time_point deadline{}; // epoch if no timeout
// Order tracking for LRU eviction
std::list<std::string>::iterator orderIt{};
};
std::unordered_map<std::string, PendingRequest> pending_;
// Maintain insertion order (oldest at front)
std::list<std::string> pendingOrder_;
std::mutex pendingMutex_;
// Timeout/watchdog for pending requests
std::thread timeoutThread_;
std::atomic<bool> stopTimeout_{false};
int64_t requestTimeoutMs_ = 0; // 0 = disabled
size_t maxPending_ = 0; // 0 = unlimited
bool spawnServerProcess();
void terminateProcess();
static std::string toFileUri(const std::string &path);
void sendInitialize(const std::string &rootPath);
void startReader();
void stopReader();
void readerLoop();
void handleIncoming(const std::string &json);
// Helper to send a request with params and register a response callback
int sendRequest(const std::string &method, const nlohmann::json &params,
std::function<void(const nlohmann::json & result, const nlohmann::json * errorJson)> cb);
// Start/stop timeout thread
void startTimeoutWatchdog();
void stopTimeoutWatchdog();
public:
// Test hook: inject a raw JSON message as if received from server
void debugInjectMessageForTest(const std::string &raw)
{
handleIncoming(raw);
}
// Test hook: add a pending request entry manually
void debugAddPendingForTest(const std::string &id, const std::string &method,
std::function<void(const nlohmann::json & result,
const nlohmann::json *errorJson)
>
cb
)
{
std::lock_guard<std::mutex> lk(pendingMutex_);
pendingOrder_.push_back(id);
auto it = pendingOrder_.end();
--it;
PendingRequest pr{method, std::move(cb), {}, it};
pending_[id] = std::move(pr);
}
// Test hook: override timeout
void setRequestTimeoutMsForTest(int64_t ms)
{
requestTimeoutMs_ = ms;
}
// Test hook: set max pending
void setMaxPendingForTest(size_t maxPending)
{
maxPending_ = maxPending;
}
// Test hook: set running flag (to allow sendRequest in tests without spawning)
void setRunningForTest(bool r)
{
running_ = r;
}
// Test hook: send a raw request using internal machinery
int debugSendRequestForTest(const std::string &method, const nlohmann::json &params,
std::function<void(const nlohmann::json & result,
const nlohmann::json *errorJson)
>
cb
)
{
return sendRequest(method, params, std::move(cb));
}
};
} // namespace kte::lsp
#endif // KTE_LSP_PROCESS_CLIENT_H

47
lsp/LspServerConfig.h Normal file
View File

@@ -0,0 +1,47 @@
/*
* LspServerConfig.h - per-language LSP server configuration
*/
#ifndef KTE_LSP_SERVER_CONFIG_H
#define KTE_LSP_SERVER_CONFIG_H
#include <string>
#include <unordered_map>
#include <vector>
namespace kte::lsp {
enum class LspSyncMode {
None = 0,
Full = 1,
Incremental = 2,
};
struct LspServerConfig {
std::string command; // executable name/path
std::vector<std::string> args; // CLI args
std::vector<std::string> filePatterns; // e.g. {"*.rs"}
std::string rootPatterns; // e.g. "Cargo.toml"
LspSyncMode preferredSyncMode = LspSyncMode::Incremental;
bool autostart = true;
std::unordered_map<std::string, std::string> initializationOptions; // placeholder
std::unordered_map<std::string, std::string> settings; // placeholder
};
// Provide a small set of defaults; callers may ignore
inline std::vector<LspServerConfig>
GetDefaultServerConfigs()
{
return std::vector<LspServerConfig>{
LspServerConfig{
.command = "rust-analyzer", .args = {}, .filePatterns = {"*.rs"}, .rootPatterns = "Cargo.toml"
},
LspServerConfig{
.command = "clangd", .args = {"--background-index"},
.filePatterns = {"*.c", "*.cc", "*.cpp", "*.h", "*.hpp"},
.rootPatterns = "compile_commands.json"
},
LspServerConfig{.command = "gopls", .args = {}, .filePatterns = {"*.go"}, .rootPatterns = "go.mod"},
};
}
} // namespace kte::lsp
#endif // KTE_LSP_SERVER_CONFIG_H

55
lsp/LspTypes.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* LspTypes.h - minimal LSP-related data types for initial integration
*/
#ifndef KTE_LSP_TYPES_H
#define KTE_LSP_TYPES_H
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace kte::lsp {
// NOTE on coordinates:
// - Internal editor coordinates use UTF-8 columns counted by Unicode scalars.
// - LSP wire protocol uses UTF-16 code units for the `character` field.
// Conversions are performed in higher layers via `lsp/UtfCodec.h` helpers.
struct Position {
int line = 0;
int character = 0;
};
struct Range {
Position start;
Position end;
};
struct TextDocumentContentChangeEvent {
std::optional<Range> range; // if not set, represents full document change
std::string text; // new text for the given range
};
// Minimal feature result types for phase 1
struct CompletionItem {
std::string label;
std::optional<std::string> detail; // optional extra info
std::optional<std::string> insertText; // if present, use instead of label
};
struct CompletionList {
bool isIncomplete = false;
std::vector<CompletionItem> items;
};
struct HoverResult {
std::string contents; // concatenated plaintext/markdown for now
std::optional<Range> range; // optional range
};
struct Location {
std::string uri;
Range range;
};
} // namespace kte::lsp
#endif // KTE_LSP_TYPES_H

View File

@@ -0,0 +1,53 @@
/*
* TerminalDiagnosticDisplay.cc - minimal stub implementation
*/
#include "TerminalDiagnosticDisplay.h"
#include "../TerminalRenderer.h"
namespace kte::lsp {
TerminalDiagnosticDisplay::TerminalDiagnosticDisplay(TerminalRenderer *renderer)
: renderer_(renderer) {}
void
TerminalDiagnosticDisplay::updateDiagnostics(const std::string &uri,
const std::vector<Diagnostic> &diagnostics)
{
(void) uri;
(void) diagnostics;
// Stub: no rendering yet. Future: gutter markers, underlines, virtual text.
}
void
TerminalDiagnosticDisplay::showInlineDiagnostic(const Diagnostic &diagnostic)
{
(void) diagnostic;
// Stub: show as message line in future.
}
void
TerminalDiagnosticDisplay::showDiagnosticList(const std::vector<Diagnostic> &diagnostics)
{
(void) diagnostics;
// Stub: open a panel/list in future.
}
void
TerminalDiagnosticDisplay::hideDiagnosticList()
{
// Stub
}
void
TerminalDiagnosticDisplay::updateStatusBar(int errorCount, int warningCount)
{
(void) errorCount;
(void) warningCount;
// Stub: integrate with status bar rendering later.
}
} // namespace kte::lsp

View File

@@ -0,0 +1,35 @@
/*
* TerminalDiagnosticDisplay.h - Terminal (ncurses) diagnostics visualization stub
*/
#ifndef KTE_LSP_TERMINAL_DIAGNOSTIC_DISPLAY_H
#define KTE_LSP_TERMINAL_DIAGNOSTIC_DISPLAY_H
#include <string>
#include <vector>
#include "DiagnosticDisplay.h"
class TerminalRenderer; // fwd
namespace kte::lsp {
class TerminalDiagnosticDisplay final : public DiagnosticDisplay {
public:
explicit TerminalDiagnosticDisplay(TerminalRenderer *renderer);
void updateDiagnostics(const std::string &uri,
const std::vector<Diagnostic> &diagnostics) override;
void showInlineDiagnostic(const Diagnostic &diagnostic) override;
void showDiagnosticList(const std::vector<Diagnostic> &diagnostics) override;
void hideDiagnosticList() override;
void updateStatusBar(int errorCount, int warningCount) override;
private:
[[maybe_unused]] TerminalRenderer *renderer_{}; // non-owning
};
} // namespace kte::lsp
#endif // KTE_LSP_TERMINAL_DIAGNOSTIC_DISPLAY_H

155
lsp/UtfCodec.cc Normal file
View File

@@ -0,0 +1,155 @@
/*
* UtfCodec.cc - UTF-8 <-> UTF-16 code unit position conversions
*/
#include "UtfCodec.h"
#include <cassert>
namespace kte::lsp {
// Decode next code point from a UTF-8 string.
// On invalid input, consumes 1 byte and returns U+FFFD.
// Returns: (codepoint, bytesConsumed)
static inline std::pair<uint32_t, size_t>
decodeUtf8(std::string_view s, size_t i)
{
if (i >= s.size())
return {0, 0};
unsigned char c0 = static_cast<unsigned char>(s[i]);
if (c0 < 0x80) {
return {c0, 1};
}
// Determine sequence length
if ((c0 & 0xE0) == 0xC0) {
if (i + 1 >= s.size())
return {0xFFFD, 1};
unsigned char c1 = static_cast<unsigned char>(s[i + 1]);
if ((c1 & 0xC0) != 0x80)
return {0xFFFD, 1};
uint32_t cp = ((c0 & 0x1F) << 6) | (c1 & 0x3F);
// Overlong check: must be >= 0x80
if (cp < 0x80)
return {0xFFFD, 1};
return {cp, 2};
}
if ((c0 & 0xF0) == 0xE0) {
if (i + 2 >= s.size())
return {0xFFFD, 1};
unsigned char c1 = static_cast<unsigned char>(s[i + 1]);
unsigned char c2 = static_cast<unsigned char>(s[i + 2]);
if ((c1 & 0xC0) != 0x80 || (c2 & 0xC0) != 0x80)
return {0xFFFD, 1};
uint32_t cp = ((c0 & 0x0F) << 12) | ((c1 & 0x3F) << 6) | (c2 & 0x3F);
// Overlong / surrogate range check
if (cp < 0x800 || (cp >= 0xD800 && cp <= 0xDFFF))
return {0xFFFD, 1};
return {cp, 3};
}
if ((c0 & 0xF8) == 0xF0) {
if (i + 3 >= s.size())
return {0xFFFD, 1};
unsigned char c1 = static_cast<unsigned char>(s[i + 1]);
unsigned char c2 = static_cast<unsigned char>(s[i + 2]);
unsigned char c3 = static_cast<unsigned char>(s[i + 3]);
if ((c1 & 0xC0) != 0x80 || (c2 & 0xC0) != 0x80 || (c3 & 0xC0) != 0x80)
return {0xFFFD, 1};
uint32_t cp = ((c0 & 0x07) << 18) | ((c1 & 0x3F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
// Overlong / max range check
if (cp < 0x10000 || cp > 0x10FFFF)
return {0xFFFD, 1};
return {cp, 4};
}
return {0xFFFD, 1};
}
static inline size_t
utf16UnitsForCodepoint(uint32_t cp)
{
return (cp <= 0xFFFF) ? 1 : 2;
}
size_t
utf8ColToUtf16Units(std::string_view lineUtf8, size_t utf8Col)
{
// Count by Unicode scalars up to utf8Col; clamp at EOL
size_t units = 0;
size_t col = 0;
size_t i = 0;
while (i < lineUtf8.size()) {
if (col >= utf8Col)
break;
auto [cp, n] = decodeUtf8(lineUtf8, i);
if (n == 0)
break;
units += utf16UnitsForCodepoint(cp);
i += n;
++col;
}
return units;
}
size_t
utf16UnitsToUtf8Col(std::string_view lineUtf8, size_t utf16Units)
{
// Traverse code points until consuming utf16Units (or reaching EOL)
size_t units = 0;
size_t col = 0;
size_t i = 0;
while (i < lineUtf8.size()) {
auto [cp, n] = decodeUtf8(lineUtf8, i);
if (n == 0)
break;
size_t add = utf16UnitsForCodepoint(cp);
if (units + add > utf16Units)
break;
units += add;
i += n;
++col;
if (units == utf16Units)
break;
}
return col;
}
Position
toUtf16(const std::string &uri, const Position &pUtf8, const LineProvider &provider)
{
Position out = pUtf8;
std::string_view line = provider ? provider(uri, pUtf8.line) : std::string_view();
out.character = static_cast<int>(utf8ColToUtf16Units(line, static_cast<size_t>(pUtf8.character)));
return out;
}
Position
toUtf8(const std::string &uri, const Position &pUtf16, const LineProvider &provider)
{
Position out = pUtf16;
std::string_view line = provider ? provider(uri, pUtf16.line) : std::string_view();
out.character = static_cast<int>(utf16UnitsToUtf8Col(line, static_cast<size_t>(pUtf16.character)));
return out;
}
Range
toUtf16(const std::string &uri, const Range &rUtf8, const LineProvider &provider)
{
Range r;
r.start = toUtf16(uri, rUtf8.start, provider);
r.end = toUtf16(uri, rUtf8.end, provider);
return r;
}
Range
toUtf8(const std::string &uri, const Range &rUtf16, const LineProvider &provider)
{
Range r;
r.start = toUtf8(uri, rUtf16.start, provider);
r.end = toUtf8(uri, rUtf16.end, provider);
return r;
}
} // namespace kte::lsp

37
lsp/UtfCodec.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* UtfCodec.h - Helpers for UTF-8 <-> UTF-16 code unit position conversions
*/
#ifndef KTE_LSP_UTF_CODEC_H
#define KTE_LSP_UTF_CODEC_H
#include <cstddef>
#include <functional>
#include <string>
#include <string_view>
#include "LspTypes.h"
namespace kte::lsp {
// Map between editor-internal UTF-8 columns (by Unicode scalar count)
// and LSP wire UTF-16 code units (per LSP spec).
// Convert a UTF-8 column index (in Unicode scalars) to UTF-16 code units for a given line.
size_t utf8ColToUtf16Units(std::string_view lineUtf8, size_t utf8Col);
// Convert a UTF-16 code unit count to a UTF-8 column index (in Unicode scalars) for a given line.
size_t utf16UnitsToUtf8Col(std::string_view lineUtf8, size_t utf16Units);
// Line text provider to allow conversions without giving the codec direct buffer access.
using LineProvider = std::function<std::string_view(const std::string & uri, int line)>;
// Convenience helpers for positions and ranges using a line provider.
Position toUtf16(const std::string &uri, const Position &pUtf8, const LineProvider &provider);
Position toUtf8(const std::string &uri, const Position &pUtf16, const LineProvider &provider);
Range toUtf16(const std::string &uri, const Range &rUtf8, const LineProvider &provider);
Range toUtf8(const std::string &uri, const Range &rUtf16, const LineProvider &provider);
} // namespace kte::lsp
#endif // KTE_LSP_UTF_CODEC_H

49
main.cc
View File

@@ -12,6 +12,7 @@
#include "Editor.h"
#include "Frontend.h"
#include "TerminalFrontend.h"
#include "lsp/LspManager.h"
#if defined(KTE_BUILD_GUI)
#include "GUIFrontend.h"
@@ -28,6 +29,8 @@ PrintUsage(const char *prog)
{
std::cerr << "Usage: " << prog << " [OPTIONS] [files]\n"
<< "Options:\n"
<< " -c, --chdir DIR Change working directory before opening files\n"
<< " -d, --debug Enable LSP debug logging\n"
<< " -g, --gui Use GUI frontend (if built)\n"
<< " -t, --term Use terminal (ncurses) frontend [default]\n"
<< " -h, --help Show this help and exit\n"
@@ -36,17 +39,25 @@ PrintUsage(const char *prog)
int
main(int argc, const char *argv[])
main(const int argc, const char *argv[])
{
Editor editor;
// Wire up LSP manager (no diagnostic UI yet; frontends may provide later)
kte::lsp::LspManager lspMgr(&editor, nullptr);
editor.SetLspManager(&lspMgr);
// CLI parsing using getopt_long
bool req_gui = false;
bool req_term = false;
bool show_help = false;
bool show_version = false;
bool lsp_debug = false;
std::string nwd;
static struct option long_opts[] = {
{"chdir", required_argument, nullptr, 'c'},
{"debug", no_argument, nullptr, 'd'},
{"gui", no_argument, nullptr, 'g'},
{"term", no_argument, nullptr, 't'},
{"help", no_argument, nullptr, 'h'},
@@ -56,8 +67,14 @@ main(int argc, const char *argv[])
int opt;
int long_index = 0;
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "gthV", long_opts, &long_index)) != -1) {
while ((opt = getopt_long(argc, const_cast<char *const *>(argv), "c:dgthV", long_opts, &long_index)) != -1) {
switch (opt) {
case 'c':
nwd = optarg;
break;
case 'd':
lsp_debug = true;
break;
case 'g':
req_gui = true;
break;
@@ -90,6 +107,16 @@ main(int argc, const char *argv[])
(void) req_term; // suppress unused warning when GUI is not compiled in
#endif
// Apply LSP debug setting strictly based on -d flag
lspMgr.setDebugLogging(lsp_debug);
if (lsp_debug) {
// Ensure LSP subprocess client picks up debug via environment
::setenv("KTE_LSP_DEBUG", "1", 1);
} else {
// Prevent environment from enabling debug implicitly
::unsetenv("KTE_LSP_DEBUG");
}
// Determine frontend
#if !defined(KTE_BUILD_GUI)
if (req_gui) {
@@ -104,11 +131,14 @@ main(int argc, const char *argv[])
} else if (req_term) {
use_gui = false;
} else {
// Default depends on build target: kge defaults to GUI, kte to terminal
// Default depends on build target: kge defaults to GUI, kte to terminal
#if defined(KTE_DEFAULT_GUI)
use_gui = true;
use_gui = true;
#else
use_gui = false;
use_gui = false;
#endif
}
#endif
@@ -199,6 +229,13 @@ main(int argc, const char *argv[])
}
#endif
#if defined(KTE_BUILD_GUI)
if (!nwd.empty()) {
if (chdir(nwd.c_str()) != 0) {
std::cerr << "kge: failed to chdir to " << nwd << std::endl;
}
}
#endif
if (!fe->Init(editor)) {
std::cerr << "kte: failed to initialize frontend" << std::endl;
return 1;
@@ -212,4 +249,4 @@ main(int argc, const char *argv[])
fe->Shutdown();
return 0;
}
}

View File

@@ -1,4 +1,4 @@
#include "TreeSitterHighlighter.h"
#include "../TreeSitterHighlighter.h"
#ifdef KTE_ENABLE_TREESITTER

119
test_lsp_decode.cc Normal file
View File

@@ -0,0 +1,119 @@
// test_lsp_decode.cc - tests for LspProcessClient JSON decoding/dispatch
#include <cassert>
#include <atomic>
#include <string>
#include <vector>
#include "lsp/LspProcessClient.h"
#include "lsp/Diagnostic.h"
using namespace kte::lsp;
int
main()
{
// Create client (won't start a process for these tests)
LspProcessClient client("/bin/echo", {});
// 1) Numeric id response should match string key "42"
{
std::atomic<bool> called{false};
client.debugAddPendingForTest("42", "dummy",
[&](const nlohmann::json &result, const nlohmann::json *err) {
(void) result;
(void) err;
called = true;
});
std::string resp = "{\"jsonrpc\":\"2.0\",\"id\":42,\"result\":null}";
client.debugInjectMessageForTest(resp);
assert(called.load());
}
// 2) String id response should resolve
{
std::atomic<bool> called{false};
client.debugAddPendingForTest("abc123", "dummy",
[&](const nlohmann::json &result, const nlohmann::json *err) {
(void) result;
(void) err;
called = true;
});
std::string resp = "{\"jsonrpc\":\"2.0\",\"id\":\"abc123\",\"result\":{}}";
client.debugInjectMessageForTest(resp);
assert(called.load());
}
// 3) Diagnostics notification decoding
{
std::atomic<bool> diagCalled{false};
client.setDiagnosticsHandler([&](const std::string &uri, const std::vector<Diagnostic> &d) {
assert(uri == "file:///tmp/x.rs");
assert(!d.empty());
diagCalled = true;
});
std::string notif = R"({
"jsonrpc":"2.0",
"method":"textDocument/publishDiagnostics",
"params":{
"uri":"file:///tmp/x.rs",
"diagnostics":[{
"range": {"start": {"line":0, "character":1}, "end": {"line":0, "character":2}},
"severity": 1,
"message": "oops"
}]
}
})";
client.debugInjectMessageForTest(notif);
assert(diagCalled.load());
}
// 4) ShowMessage notification should be safely handled (no crash)
{
std::string msg =
"{\"jsonrpc\":\"2.0\",\"method\":\"window/showMessage\",\"params\":{\"type\":2,\"message\":\"hi\"}}";
client.debugInjectMessageForTest(msg);
}
// 5) workspace/configuration request should be responded to (no crash)
{
std::string req = R"({
"jsonrpc":"2.0",
"id": 7,
"method":"workspace/configuration",
"params": {"items": [{"section":"x"},{"section":"y"}]}
})";
client.debugInjectMessageForTest(req);
}
// 6) Pending cap eviction: oldest request is dropped with -32001
{
LspProcessClient c2("/bin/echo", {});
c2.setRunningForTest(true);
c2.setMaxPendingForTest(2);
std::atomic<int> drops{0};
auto make_cb = [&](const char *tag) {
return [&, tag](const nlohmann::json &res, const nlohmann::json *err) {
(void) res;
if (err && err->is_object()) {
auto it = err->find("code");
if (it != err->end() && it->is_number_integer() && *it == -32001) {
// Only the oldest (first) should be dropped
if (std::string(tag) == "first")
drops.fetch_add(1);
}
}
};
};
// Enqueue 3 requests; cap=2 -> first should be dropped immediately when third is added
c2.debugSendRequestForTest("a", nlohmann::json::object(), make_cb("first"));
c2.debugSendRequestForTest("b", nlohmann::json::object(), make_cb("second"));
c2.debugSendRequestForTest("c", nlohmann::json::object(), make_cb("third"));
// Allow callbacks (none are async here, drop is invoked inline after send)
assert(drops.load() == 1);
}
std::puts("test_lsp_decode: OK");
return 0;
}

76
test_transport.cc Normal file
View File

@@ -0,0 +1,76 @@
// test_transport.cc - transport framing tests
#include <cassert>
#include <cstdio>
#include <cstring>
#include <string>
#include <optional>
#include <unistd.h>
#include "lsp/JsonRpcTransport.h"
using namespace kte::lsp;
static void
write_all(int fd, const void *bufv, size_t len)
{
const char *buf = static_cast<const char *>(bufv);
size_t left = len;
while (left > 0) {
ssize_t n = ::write(fd, buf, left);
if (n < 0) {
if (errno == EINTR)
continue;
std::perror("write");
std::abort();
}
buf += static_cast<size_t>(n);
left -= static_cast<size_t>(n);
}
}
int
main()
{
int p[2];
assert(pipe(p) == 0);
int readFd = p[0];
int writeFd = p[1];
JsonRpcTransport t;
// We only need inFd for read tests; pass writeFd for completeness
t.connect(readFd, writeFd);
auto sendMsg = [&](const std::string &payload, bool lowerHeader) {
std::string header = (lowerHeader ? std::string("content-length: ") : std::string("Content-Length: ")) +
std::to_string(payload.size()) + "\r\n\r\n";
write_all(writeFd, header.data(), header.size());
// Send body in two parts to exercise partial reads
size_t mid = payload.size() / 2;
write_all(writeFd, payload.data(), mid);
write_all(writeFd, payload.data() + mid, payload.size() - mid);
};
std::string p1 = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}";
std::string p2 = "{\"jsonrpc\":\"2.0\",\"method\":\"ping\"}";
sendMsg(p1, false);
sendMsg(p2, true); // case-insensitive header
auto m1 = t.read();
assert(m1.has_value());
assert(m1->raw == p1);
auto m2 = t.read();
assert(m2.has_value());
assert(m2->raw == p2);
// Close write end to signal EOF; next read should return nullopt
::close(writeFd);
auto m3 = t.read();
assert(!m3.has_value());
::close(readFd);
std::puts("test_transport: OK");
return 0;
}

101
test_utfcodec.cc Normal file
View File

@@ -0,0 +1,101 @@
// test_utfcodec.cc - simple tests for UtfCodec helpers
#include <cassert>
#include <cstdio>
#include <string>
#include <string_view>
#include "lsp/UtfCodec.h"
using namespace kte::lsp;
static std::string_view
lp(const std::string &, int)
{
return std::string_view();
}
int
main()
{
// ASCII: each scalar = 1 UTF-16 unit
{
std::string s = "hello"; // 5 ASCII
assert(utf8ColToUtf16Units(s, 0) == 0);
assert(utf8ColToUtf16Units(s, 3) == 3);
assert(utf16UnitsToUtf8Col(s, 3) == 3);
assert(utf16UnitsToUtf8Col(s, 10) == 5); // clamp to EOL
}
// BMP multibyte (e.g., ü U+00FC, α U+03B1) -> still 1 UTF-16 unit
{
std::string s = u8"αb"; // bytes: a [C3 BC] [CE B1] b
// columns by codepoints: a(0), ü(1), α(2), b(3)
assert(utf8ColToUtf16Units(s, 0) == 0);
assert(utf8ColToUtf16Units(s, 1) == 1);
assert(utf8ColToUtf16Units(s, 2) == 2);
assert(utf8ColToUtf16Units(s, 4) == 4); // past EOL clamps to 4 units
assert(utf16UnitsToUtf8Col(s, 0) == 0);
assert(utf16UnitsToUtf8Col(s, 2) == 2);
assert(utf16UnitsToUtf8Col(s, 4) == 4);
}
// Non-BMP (emoji) -> 2 UTF-16 units per code point
{
std::string s = u8"A😀B"; // U+1F600 between A and B
// codepoints: A, 😀, B => utf8 columns 0..3
// utf16 units: A(1), 😀(2), B(1) cumulative: 0,1,3,4
assert(utf8ColToUtf16Units(s, 0) == 0);
assert(utf8ColToUtf16Units(s, 1) == 1); // after A
assert(utf8ColToUtf16Units(s, 2) == 3); // after 😀 (2 units)
assert(utf8ColToUtf16Units(s, 3) == 4); // after B
assert(utf16UnitsToUtf8Col(s, 0) == 0);
assert(utf16UnitsToUtf8Col(s, 1) == 1); // A
assert(utf16UnitsToUtf8Col(s, 2) == 1); // mid-surrogate -> stays before 😀
assert(utf16UnitsToUtf8Col(s, 3) == 2); // end of 😀
assert(utf16UnitsToUtf8Col(s, 4) == 3); // after B
assert(utf16UnitsToUtf8Col(s, 10) == 3); // clamp
}
// Invalid UTF-8: treat invalid byte as U+FFFD (1 UTF-16 unit), consume 1 byte
{
std::string s;
s.push_back('X');
s.push_back(char(0xFF)); // invalid single byte
s.push_back('Y');
// Columns by codepoints as we decode: 'X', U+FFFD, 'Y'
assert(utf8ColToUtf16Units(s, 0) == 0);
assert(utf8ColToUtf16Units(s, 1) == 1);
assert(utf8ColToUtf16Units(s, 2) == 2);
assert(utf8ColToUtf16Units(s, 3) == 3);
assert(utf16UnitsToUtf8Col(s, 0) == 0);
assert(utf16UnitsToUtf8Col(s, 1) == 1);
assert(utf16UnitsToUtf8Col(s, 2) == 2);
assert(utf16UnitsToUtf8Col(s, 3) == 3);
}
// Position/Range helpers with a simple provider
{
std::string lines[] = {u8"A😀B"};
LineProvider provider = [&](const std::string &, int line) -> std::string_view {
return (line == 0) ? std::string_view(lines[0]) : std::string_view();
};
Position p8{0, 2}; // after 😀 in utf8 columns
Position p16 = toUtf16("file:///x", p8, provider);
assert(p16.line == 0 && p16.character == 3);
Position back = toUtf8("file:///x", p16, provider);
assert(back.line == 0 && back.character == 2);
Range r8{{0, 1}, {0, 3}}; // A|😀|B end
Range r16 = toUtf16("file:///x", r8, provider);
assert(r16.start.character == 1 && r16.end.character == 4);
}
std::puts("test_utfcodec: OK");
return 0;
}

View File

@@ -1,107 +0,0 @@
// themes/Amber.h — Vim Amber inspired ImGui theme (header-only)
#pragma once
#include "ThemeHelpers.h"
// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper
static inline void
ApplyAmberTheme()
{
// Amber: dark background with amber/orange highlights and warm text
const ImVec4 bg0 = RGBA(0x141414);
const ImVec4 bg1 = RGBA(0x1E1E1E);
const ImVec4 bg2 = RGBA(0x2A2A2A);
const ImVec4 bg3 = RGBA(0x343434);
const ImVec4 text = RGBA(0xE6D5A3); // warm parchment-like text
const ImVec4 text2 = RGBA(0xFFF2C2);
const ImVec4 amber = RGBA(0xFFB000);
const ImVec4 orange = RGBA(0xFF8C32);
const ImVec4 yellow = RGBA(0xFFD166);
const ImVec4 teal = RGBA(0x3AAFA9);
const ImVec4 cyan = RGBA(0x4CC9F0);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(6.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *colors = style.Colors;
colors[ImGuiCol_Text] = text;
colors[ImGuiCol_TextDisabled] = ImVec4(text.x, text.y, text.z, 0.55f);
colors[ImGuiCol_WindowBg] = bg0;
colors[ImGuiCol_ChildBg] = bg0;
colors[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
colors[ImGuiCol_Border] = bg3;
colors[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
colors[ImGuiCol_FrameBg] = bg2;
colors[ImGuiCol_FrameBgHovered] = ImVec4(amber.x, amber.y, amber.z, 0.20f);
colors[ImGuiCol_FrameBgActive] = ImVec4(orange.x, orange.y, orange.z, 0.25f);
colors[ImGuiCol_TitleBg] = bg1;
colors[ImGuiCol_TitleBgActive] = bg2;
colors[ImGuiCol_TitleBgCollapsed] = bg1;
colors[ImGuiCol_MenuBarBg] = bg1;
colors[ImGuiCol_ScrollbarBg] = bg0;
colors[ImGuiCol_ScrollbarGrab] = bg3;
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(amber.x, amber.y, amber.z, 0.50f);
colors[ImGuiCol_ScrollbarGrabActive] = amber;
colors[ImGuiCol_CheckMark] = amber;
colors[ImGuiCol_SliderGrab] = amber;
colors[ImGuiCol_SliderGrabActive] = orange;
colors[ImGuiCol_Button] = bg3;
colors[ImGuiCol_ButtonHovered] = ImVec4(amber.x, amber.y, amber.z, 0.25f);
colors[ImGuiCol_ButtonActive] = ImVec4(amber.x, amber.y, amber.z, 0.40f);
colors[ImGuiCol_Header] = ImVec4(amber.x, amber.y, amber.z, 0.25f);
colors[ImGuiCol_HeaderHovered] = ImVec4(yellow.x, yellow.y, yellow.z, 0.30f);
colors[ImGuiCol_HeaderActive] = ImVec4(orange.x, orange.y, orange.z, 0.30f);
colors[ImGuiCol_Separator] = bg3;
colors[ImGuiCol_SeparatorHovered] = amber;
colors[ImGuiCol_SeparatorActive] = orange;
colors[ImGuiCol_ResizeGrip] = ImVec4(text2.x, text2.y, text2.z, 0.12f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(amber.x, amber.y, amber.z, 0.67f);
colors[ImGuiCol_ResizeGripActive] = orange;
colors[ImGuiCol_Tab] = bg2;
colors[ImGuiCol_TabHovered] = ImVec4(amber.x, amber.y, amber.z, 0.25f);
colors[ImGuiCol_TabActive] = ImVec4(yellow.x, yellow.y, yellow.z, 0.30f);
colors[ImGuiCol_TabUnfocused] = bg2;
colors[ImGuiCol_TabUnfocusedActive] = bg3;
colors[ImGuiCol_TableHeaderBg] = bg2;
colors[ImGuiCol_TableBorderStrong] = bg1;
colors[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
colors[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.2f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.35f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(amber.x, amber.y, amber.z, 0.28f);
colors[ImGuiCol_DragDropTarget] = orange;
colors[ImGuiCol_NavHighlight] = cyan;
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(text2.x, text2.y, text2.z, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_PlotLines] = teal;
colors[ImGuiCol_PlotLinesHovered] = cyan;
colors[ImGuiCol_PlotHistogram] = amber;
colors[ImGuiCol_PlotHistogramHovered] = orange;
}

View File

@@ -1,106 +0,0 @@
// themes/Everforest.h — Everforest (hard, dark) inspired ImGui theme (header-only)
#pragma once
#include "ThemeHelpers.h"
// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper
static void
ApplyEverforestTheme()
{
// Everforest hard dark palette (approximate)
const ImVec4 bg0 = RGBA(0x2B3339); // base background
const ImVec4 bg1 = RGBA(0x323C41);
const ImVec4 bg2 = RGBA(0x3A454A);
const ImVec4 bg3 = RGBA(0x4C555B);
const ImVec4 fg0 = RGBA(0xD3C6AA); // foreground
const ImVec4 fg1 = RGBA(0xEDE0C8);
// Accents
const ImVec4 green = RGBA(0xA7C080);
const ImVec4 aqua = RGBA(0x83C092);
const ImVec4 blue = RGBA(0x7FBBB3);
const ImVec4 yellow = RGBA(0xDBBC7F);
const ImVec4 orange = RGBA(0xE69875);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(6.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *colors = style.Colors;
colors[ImGuiCol_Text] = fg0;
colors[ImGuiCol_TextDisabled] = ImVec4(fg0.x, fg0.y, fg0.z, 0.55f);
colors[ImGuiCol_WindowBg] = bg0;
colors[ImGuiCol_ChildBg] = bg0;
colors[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
colors[ImGuiCol_Border] = bg2;
colors[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
colors[ImGuiCol_FrameBg] = bg2;
colors[ImGuiCol_FrameBgHovered] = bg3;
colors[ImGuiCol_FrameBgActive] = bg1;
colors[ImGuiCol_TitleBg] = bg1;
colors[ImGuiCol_TitleBgActive] = bg2;
colors[ImGuiCol_TitleBgCollapsed] = bg1;
colors[ImGuiCol_MenuBarBg] = bg1;
colors[ImGuiCol_ScrollbarBg] = bg0;
colors[ImGuiCol_ScrollbarGrab] = bg3;
colors[ImGuiCol_ScrollbarGrabHovered] = bg2;
colors[ImGuiCol_ScrollbarGrabActive] = bg1;
colors[ImGuiCol_CheckMark] = green;
colors[ImGuiCol_SliderGrab] = green;
colors[ImGuiCol_SliderGrabActive] = aqua;
colors[ImGuiCol_Button] = bg3;
colors[ImGuiCol_ButtonHovered] = bg2;
colors[ImGuiCol_ButtonActive] = bg1;
colors[ImGuiCol_Header] = bg3;
colors[ImGuiCol_HeaderHovered] = bg2;
colors[ImGuiCol_HeaderActive] = bg2;
colors[ImGuiCol_Separator] = bg2;
colors[ImGuiCol_SeparatorHovered] = bg1;
colors[ImGuiCol_SeparatorActive] = blue;
colors[ImGuiCol_ResizeGrip] = ImVec4(fg1.x, fg1.y, fg1.z, 0.12f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(green.x, green.y, green.z, 0.67f);
colors[ImGuiCol_ResizeGripActive] = aqua;
colors[ImGuiCol_Tab] = bg2;
colors[ImGuiCol_TabHovered] = bg1;
colors[ImGuiCol_TabActive] = bg3;
colors[ImGuiCol_TabUnfocused] = bg2;
colors[ImGuiCol_TabUnfocusedActive] = bg3;
colors[ImGuiCol_TableHeaderBg] = bg2;
colors[ImGuiCol_TableBorderStrong] = bg1;
colors[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
colors[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.2f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.35f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(orange.x, orange.y, orange.z, 0.30f);
colors[ImGuiCol_DragDropTarget] = orange;
colors[ImGuiCol_NavHighlight] = orange;
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(fg1.x, fg1.y, fg1.z, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_PlotLines] = aqua;
colors[ImGuiCol_PlotLinesHovered] = blue;
colors[ImGuiCol_PlotHistogram] = yellow;
colors[ImGuiCol_PlotHistogramHovered] = orange;
}

View File

@@ -1,206 +0,0 @@
// themes/KanagawaPaper.h — Kanagawa Paper (light) inspired ImGui theme (header-only)
#pragma once
#include "ThemeHelpers.h"
// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper
static void
ApplyKanagawaPaperLightTheme()
{
// Approximate Kanagawa Paper (light) palette
const ImVec4 bg0 = RGBA(0xF2EAD3); // paper
const ImVec4 bg1 = RGBA(0xE6DBC3);
const ImVec4 bg2 = RGBA(0xD9CEB5);
const ImVec4 bg3 = RGBA(0xCBBE9E);
const ImVec4 fg0 = RGBA(0x3A3A3A); // ink
const ImVec4 fg1 = RGBA(0x1F1F1F);
// Accents (muted teal/orange/blue)
const ImVec4 teal = RGBA(0x3A7D7C);
const ImVec4 orange = RGBA(0xC4746E);
const ImVec4 blue = RGBA(0x487AA1);
const ImVec4 yellow = RGBA(0xB58900);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(6.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *colors = style.Colors;
colors[ImGuiCol_Text] = fg0;
colors[ImGuiCol_TextDisabled] = ImVec4(fg0.x, fg0.y, fg0.z, 0.55f);
colors[ImGuiCol_WindowBg] = bg0;
colors[ImGuiCol_ChildBg] = bg0;
colors[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
colors[ImGuiCol_Border] = bg2;
colors[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
colors[ImGuiCol_FrameBg] = bg2;
colors[ImGuiCol_FrameBgHovered] = bg3;
colors[ImGuiCol_FrameBgActive] = bg1;
colors[ImGuiCol_TitleBg] = bg1;
colors[ImGuiCol_TitleBgActive] = bg2;
colors[ImGuiCol_TitleBgCollapsed] = bg1;
colors[ImGuiCol_MenuBarBg] = bg1;
colors[ImGuiCol_ScrollbarBg] = bg0;
colors[ImGuiCol_ScrollbarGrab] = bg3;
colors[ImGuiCol_ScrollbarGrabHovered] = bg2;
colors[ImGuiCol_ScrollbarGrabActive] = bg1;
colors[ImGuiCol_CheckMark] = teal;
colors[ImGuiCol_SliderGrab] = teal;
colors[ImGuiCol_SliderGrabActive] = blue;
colors[ImGuiCol_Button] = bg3;
colors[ImGuiCol_ButtonHovered] = bg2;
colors[ImGuiCol_ButtonActive] = bg1;
colors[ImGuiCol_Header] = bg3;
colors[ImGuiCol_HeaderHovered] = bg2;
colors[ImGuiCol_HeaderActive] = bg2;
colors[ImGuiCol_Separator] = bg2;
colors[ImGuiCol_SeparatorHovered] = bg1;
colors[ImGuiCol_SeparatorActive] = teal;
colors[ImGuiCol_ResizeGrip] = ImVec4(fg1.x, fg1.y, fg1.z, 0.12f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(teal.x, teal.y, teal.z, 0.67f);
colors[ImGuiCol_ResizeGripActive] = blue;
colors[ImGuiCol_Tab] = bg2;
colors[ImGuiCol_TabHovered] = bg1;
colors[ImGuiCol_TabActive] = bg3;
colors[ImGuiCol_TabUnfocused] = bg2;
colors[ImGuiCol_TabUnfocusedActive] = bg3;
colors[ImGuiCol_TableHeaderBg] = bg2;
colors[ImGuiCol_TableBorderStrong] = bg1;
colors[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
colors[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.2f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.35f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(orange.x, orange.y, orange.z, 0.30f);
colors[ImGuiCol_DragDropTarget] = orange;
colors[ImGuiCol_NavHighlight] = orange;
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(fg1.x, fg1.y, fg1.z, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.25f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.25f);
colors[ImGuiCol_PlotLines] = teal;
colors[ImGuiCol_PlotLinesHovered] = blue;
colors[ImGuiCol_PlotHistogram] = yellow;
colors[ImGuiCol_PlotHistogramHovered] = orange;
}
static void
ApplyKanagawaPaperDarkTheme()
{
// Approximate Kanagawa (dark) with paper-inspired warmth
const ImVec4 bg0 = RGBA(0x1F1E1A); // deep warm black
const ImVec4 bg1 = RGBA(0x25241F);
const ImVec4 bg2 = RGBA(0x2E2C26);
const ImVec4 bg3 = RGBA(0x3A372F);
const ImVec4 fg0 = RGBA(0xDCD7BA); // warm ivory ink (kanagawa fg)
const ImVec4 fg1 = RGBA(0xC8C093);
// Accents similar to kanagawa-wave
const ImVec4 teal = RGBA(0x7AA89F);
const ImVec4 orange = RGBA(0xFFA066);
const ImVec4 blue = RGBA(0x7E9CD8);
const ImVec4 yellow = RGBA(0xE6C384);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(6.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *c = style.Colors;
c[ImGuiCol_Text] = fg0;
c[ImGuiCol_TextDisabled] = ImVec4(fg1.x, fg1.y, fg1.z, 0.6f);
c[ImGuiCol_WindowBg] = bg0;
c[ImGuiCol_ChildBg] = bg0;
c[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
c[ImGuiCol_Border] = bg2;
c[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
c[ImGuiCol_FrameBg] = bg2;
c[ImGuiCol_FrameBgHovered] = bg3;
c[ImGuiCol_FrameBgActive] = bg1;
c[ImGuiCol_TitleBg] = bg1;
c[ImGuiCol_TitleBgActive] = bg2;
c[ImGuiCol_TitleBgCollapsed] = bg1;
c[ImGuiCol_MenuBarBg] = bg1;
c[ImGuiCol_ScrollbarBg] = bg0;
c[ImGuiCol_ScrollbarGrab] = bg3;
c[ImGuiCol_ScrollbarGrabHovered] = bg2;
c[ImGuiCol_ScrollbarGrabActive] = bg1;
c[ImGuiCol_CheckMark] = teal;
c[ImGuiCol_SliderGrab] = teal;
c[ImGuiCol_SliderGrabActive] = blue;
c[ImGuiCol_Button] = bg3;
c[ImGuiCol_ButtonHovered] = bg2;
c[ImGuiCol_ButtonActive] = bg1;
c[ImGuiCol_Header] = bg3;
c[ImGuiCol_HeaderHovered] = bg2;
c[ImGuiCol_HeaderActive] = bg2;
c[ImGuiCol_Separator] = bg2;
c[ImGuiCol_SeparatorHovered] = bg1;
c[ImGuiCol_SeparatorActive] = teal;
c[ImGuiCol_ResizeGrip] = ImVec4(fg1.x, fg1.y, fg1.z, 0.15f);
c[ImGuiCol_ResizeGripHovered] = ImVec4(teal.x, teal.y, teal.z, 0.67f);
c[ImGuiCol_ResizeGripActive] = blue;
c[ImGuiCol_Tab] = bg2;
c[ImGuiCol_TabHovered] = bg1;
c[ImGuiCol_TabActive] = bg3;
c[ImGuiCol_TabUnfocused] = bg2;
c[ImGuiCol_TabUnfocusedActive] = bg3;
c[ImGuiCol_TableHeaderBg] = bg2;
c[ImGuiCol_TableBorderStrong] = bg1;
c[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
c[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.12f);
c[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.22f);
c[ImGuiCol_TextSelectedBg] = ImVec4(orange.x, orange.y, orange.z, 0.28f);
c[ImGuiCol_DragDropTarget] = orange;
c[ImGuiCol_NavHighlight] = orange;
c[ImGuiCol_NavWindowingHighlight] = ImVec4(fg1.x, fg1.y, fg1.z, 0.70f);
c[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.35f);
c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.35f);
c[ImGuiCol_PlotLines] = teal;
c[ImGuiCol_PlotLinesHovered] = blue;
c[ImGuiCol_PlotHistogram] = yellow;
c[ImGuiCol_PlotHistogramHovered] = orange;
}
// Back-compat name kept; select by global background
static inline void
ApplyKanagawaPaperTheme()
{
if (GetBackgroundMode() == BackgroundMode::Dark)
ApplyKanagawaPaperDarkTheme();
else
ApplyKanagawaPaperLightTheme();
}

View File

@@ -1,108 +0,0 @@
// themes/LCARS.h — LCARS-inspired ImGui theme (header-only)
#pragma once
#include "ThemeHelpers.h"
// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper
static void
ApplyLcarsTheme()
{
// LCARS-inspired palette: dark background with vibrant pastel accents
const ImVec4 bg0 = RGBA(0x0E0E10); // near-black background
const ImVec4 bg1 = RGBA(0x1A1A1F);
const ImVec4 bg2 = RGBA(0x26262C);
const ImVec4 bg3 = RGBA(0x33333A);
const ImVec4 fg0 = RGBA(0xECECEC); // primary text
const ImVec4 fg1 = RGBA(0xBFBFC4);
// LCARS accent colors (approximations)
const ImVec4 lcars_orange = RGBA(0xFF9966);
const ImVec4 lcars_yellow = RGBA(0xFFCC66);
const ImVec4 lcars_pink = RGBA(0xFF99CC);
const ImVec4 lcars_purple = RGBA(0xCC99FF);
const ImVec4 lcars_blue = RGBA(0x66CCFF);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(10.0f, 10.0f);
style.FramePadding = ImVec2(8.0f, 6.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(8.0f, 8.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 16.0f;
style.GrabMinSize = 12.0f;
style.WindowRounding = 6.0f;
style.FrameRounding = 6.0f;
style.PopupRounding = 6.0f;
style.GrabRounding = 6.0f;
style.TabRounding = 6.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *colors = style.Colors;
colors[ImGuiCol_Text] = fg0;
colors[ImGuiCol_TextDisabled] = ImVec4(fg0.x, fg0.y, fg0.z, 0.55f);
colors[ImGuiCol_WindowBg] = bg0;
colors[ImGuiCol_ChildBg] = bg0;
colors[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
colors[ImGuiCol_Border] = bg3;
colors[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
colors[ImGuiCol_FrameBg] = bg2;
colors[ImGuiCol_FrameBgHovered] = ImVec4(lcars_purple.x, lcars_purple.y, lcars_purple.z, 0.35f);
colors[ImGuiCol_FrameBgActive] = ImVec4(lcars_orange.x, lcars_orange.y, lcars_orange.z, 0.35f);
colors[ImGuiCol_TitleBg] = bg1;
colors[ImGuiCol_TitleBgActive] = ImVec4(bg2.x, bg2.y, bg2.z, 1.0f);
colors[ImGuiCol_TitleBgCollapsed] = bg1;
colors[ImGuiCol_MenuBarBg] = bg1;
colors[ImGuiCol_ScrollbarBg] = bg0;
colors[ImGuiCol_ScrollbarGrab] = bg3;
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(lcars_blue.x, lcars_blue.y, lcars_blue.z, 0.6f);
colors[ImGuiCol_ScrollbarGrabActive] = lcars_blue;
colors[ImGuiCol_CheckMark] = lcars_orange;
colors[ImGuiCol_SliderGrab] = lcars_orange;
colors[ImGuiCol_SliderGrabActive] = lcars_pink;
colors[ImGuiCol_Button] = ImVec4(bg3.x, bg3.y, bg3.z, 1.0f);
colors[ImGuiCol_ButtonHovered] = ImVec4(lcars_orange.x, lcars_orange.y, lcars_orange.z, 0.35f);
colors[ImGuiCol_ButtonActive] = ImVec4(lcars_orange.x, lcars_orange.y, lcars_orange.z, 0.55f);
colors[ImGuiCol_Header] = ImVec4(lcars_purple.x, lcars_purple.y, lcars_purple.z, 0.30f);
colors[ImGuiCol_HeaderHovered] = ImVec4(lcars_blue.x, lcars_blue.y, lcars_blue.z, 0.35f);
colors[ImGuiCol_HeaderActive] = ImVec4(lcars_orange.x, lcars_orange.y, lcars_orange.z, 0.35f);
colors[ImGuiCol_Separator] = bg3;
colors[ImGuiCol_SeparatorHovered] = lcars_blue;
colors[ImGuiCol_SeparatorActive] = lcars_pink;
colors[ImGuiCol_ResizeGrip] = ImVec4(fg1.x, fg1.y, fg1.z, 0.12f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(lcars_yellow.x, lcars_yellow.y, lcars_yellow.z, 0.67f);
colors[ImGuiCol_ResizeGripActive] = lcars_orange;
colors[ImGuiCol_Tab] = bg2;
colors[ImGuiCol_TabHovered] = ImVec4(lcars_blue.x, lcars_blue.y, lcars_blue.z, 0.35f);
colors[ImGuiCol_TabActive] = ImVec4(lcars_purple.x, lcars_purple.y, lcars_purple.z, 0.35f);
colors[ImGuiCol_TabUnfocused] = bg2;
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(bg3.x, bg3.y, bg3.z, 1.0f);
colors[ImGuiCol_TableHeaderBg] = bg2;
colors[ImGuiCol_TableBorderStrong] = bg1;
colors[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
colors[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.2f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.35f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(lcars_yellow.x, lcars_yellow.y, lcars_yellow.z, 0.30f);
colors[ImGuiCol_DragDropTarget] = lcars_orange;
colors[ImGuiCol_NavHighlight] = lcars_blue;
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(fg0.x, fg0.y, fg0.z, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_PlotLines] = lcars_blue;
colors[ImGuiCol_PlotLinesHovered] = lcars_purple;
colors[ImGuiCol_PlotHistogram] = lcars_yellow;
colors[ImGuiCol_PlotHistogramHovered] = lcars_orange;
}

View File

@@ -108,4 +108,4 @@ ApplyNordImGuiTheme()
colors[ImGuiCol_PlotLinesHovered] = nord9;
colors[ImGuiCol_PlotHistogram] = nord13;
colors[ImGuiCol_PlotHistogramHovered] = nord12;
}
}

View File

@@ -1,220 +0,0 @@
// themes/OldBook.h — Old Book (sepia) inspired ImGui theme (header-only)
#pragma once
#include "ThemeHelpers.h"
// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper
// Light variant (existing)
static inline void
ApplyOldBookLightTheme()
{
// Sepia old paper aesthetic (light)
const ImVec4 bg0 = RGBA(0xF5E6C8); // paper
const ImVec4 bg1 = RGBA(0xEAD9B8);
const ImVec4 bg2 = RGBA(0xDECBA6);
const ImVec4 bg3 = RGBA(0xCDBA96);
const ImVec4 ink = RGBA(0x3B2F2F); // dark brown ink
const ImVec4 inkSoft = RGBA(0x5A4540);
// Accents: muted teal/red/blue like aged inks
const ImVec4 teal = RGBA(0x3F7F7A);
const ImVec4 red = RGBA(0xA65858);
const ImVec4 blue = RGBA(0x4F6D8C);
const ImVec4 yellow = RGBA(0xB58B00);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(6.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *colors = style.Colors;
colors[ImGuiCol_Text] = ink;
colors[ImGuiCol_TextDisabled] = ImVec4(ink.x, ink.y, ink.z, 0.55f);
colors[ImGuiCol_WindowBg] = bg0;
colors[ImGuiCol_ChildBg] = bg0;
colors[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
colors[ImGuiCol_Border] = bg2;
colors[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
colors[ImGuiCol_FrameBg] = bg2;
colors[ImGuiCol_FrameBgHovered] = bg3;
colors[ImGuiCol_FrameBgActive] = bg1;
colors[ImGuiCol_TitleBg] = bg1;
colors[ImGuiCol_TitleBgActive] = bg2;
colors[ImGuiCol_TitleBgCollapsed] = bg1;
colors[ImGuiCol_MenuBarBg] = bg1;
colors[ImGuiCol_ScrollbarBg] = bg0;
colors[ImGuiCol_ScrollbarGrab] = bg3;
colors[ImGuiCol_ScrollbarGrabHovered] = bg2;
colors[ImGuiCol_ScrollbarGrabActive] = bg1;
colors[ImGuiCol_CheckMark] = teal;
colors[ImGuiCol_SliderGrab] = teal;
colors[ImGuiCol_SliderGrabActive] = blue;
colors[ImGuiCol_Button] = bg3;
colors[ImGuiCol_ButtonHovered] = bg2;
colors[ImGuiCol_ButtonActive] = bg1;
colors[ImGuiCol_Header] = bg3;
colors[ImGuiCol_HeaderHovered] = bg2;
colors[ImGuiCol_HeaderActive] = bg2;
colors[ImGuiCol_Separator] = bg2;
colors[ImGuiCol_SeparatorHovered] = bg1;
colors[ImGuiCol_SeparatorActive] = teal;
colors[ImGuiCol_ResizeGrip] = ImVec4(inkSoft.x, inkSoft.y, inkSoft.z, 0.12f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(teal.x, teal.y, teal.z, 0.67f);
colors[ImGuiCol_ResizeGripActive] = blue;
colors[ImGuiCol_Tab] = bg2;
colors[ImGuiCol_TabHovered] = bg1;
colors[ImGuiCol_TabActive] = bg3;
colors[ImGuiCol_TabUnfocused] = bg2;
colors[ImGuiCol_TabUnfocusedActive] = bg3;
colors[ImGuiCol_TableHeaderBg] = bg2;
colors[ImGuiCol_TableBorderStrong] = bg1;
colors[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
colors[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.2f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.35f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(red.x, red.y, red.z, 0.25f);
colors[ImGuiCol_DragDropTarget] = red;
colors[ImGuiCol_NavHighlight] = red;
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(inkSoft.x, inkSoft.y, inkSoft.z, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.15f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.15f);
colors[ImGuiCol_PlotLines] = teal;
colors[ImGuiCol_PlotLinesHovered] = blue;
colors[ImGuiCol_PlotHistogram] = yellow;
colors[ImGuiCol_PlotHistogramHovered] = red;
}
// Dark variant (new)
static inline void
ApplyOldBookDarkTheme()
{
// Old Book 8 (vim-oldbook8) — exact dark palette mapping
// Source: oldbook8.vim (background=dark)
const ImVec4 bg0 = RGBA(0x3C4855); // Normal guibg
const ImVec4 bg1 = RGBA(0x445160); // Fold/Pmenu bg
const ImVec4 bg2 = RGBA(0x626C77); // StatusLine/MatchParen bg
const ImVec4 bg3 = RGBA(0x85939B); // Delimiter (used as a lighter hover tier)
const ImVec4 fg0 = RGBA(0xD5D4D2); // Normal guifg
const ImVec4 fg1 = RGBA(0x626C77); // Comment/secondary text
const ImVec4 fg2 = RGBA(0xA5A6A4); // Type/Function (used for active controls)
// Accents from scheme
const ImVec4 yellow = RGBA(0xDDD668); // Search
const ImVec4 yellow2 = RGBA(0xD5BC02); // IncSearch
const ImVec4 cyan = RGBA(0x87D7FF); // Visual selection
const ImVec4 green = RGBA(0x5BB899); // DiffAdd
const ImVec4 red = RGBA(0xDB6C6C); // Error/DiffDelete
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(6.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *colors = style.Colors;
colors[ImGuiCol_Text] = fg0;
colors[ImGuiCol_TextDisabled] = fg1;
colors[ImGuiCol_WindowBg] = bg0;
colors[ImGuiCol_ChildBg] = bg0;
colors[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
colors[ImGuiCol_Border] = bg2;
colors[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
colors[ImGuiCol_FrameBg] = bg1;
colors[ImGuiCol_FrameBgHovered] = bg2;
colors[ImGuiCol_FrameBgActive] = fg2;
colors[ImGuiCol_TitleBg] = bg1;
colors[ImGuiCol_TitleBgActive] = bg2;
colors[ImGuiCol_TitleBgCollapsed] = bg1;
colors[ImGuiCol_MenuBarBg] = bg1;
colors[ImGuiCol_ScrollbarBg] = bg0;
colors[ImGuiCol_ScrollbarGrab] = bg2;
colors[ImGuiCol_ScrollbarGrabHovered] = bg3;
colors[ImGuiCol_ScrollbarGrabActive] = fg2;
colors[ImGuiCol_CheckMark] = yellow;
colors[ImGuiCol_SliderGrab] = yellow;
colors[ImGuiCol_SliderGrabActive] = cyan;
colors[ImGuiCol_Button] = bg2;
colors[ImGuiCol_ButtonHovered] = bg3;
colors[ImGuiCol_ButtonActive] = fg2;
colors[ImGuiCol_Header] = bg2;
colors[ImGuiCol_HeaderHovered] = bg3;
colors[ImGuiCol_HeaderActive] = bg3;
colors[ImGuiCol_Separator] = bg2;
colors[ImGuiCol_SeparatorHovered] = yellow;
colors[ImGuiCol_SeparatorActive] = yellow2;
colors[ImGuiCol_ResizeGrip] = ImVec4(fg1.x, fg1.y, fg1.z, 0.12f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(cyan.x, cyan.y, cyan.z, 0.67f);
colors[ImGuiCol_ResizeGripActive] = cyan;
colors[ImGuiCol_Tab] = bg1;
colors[ImGuiCol_TabHovered] = bg2;
colors[ImGuiCol_TabActive] = bg2;
colors[ImGuiCol_TabUnfocused] = bg1;
colors[ImGuiCol_TabUnfocusedActive] = bg2;
colors[ImGuiCol_TableHeaderBg] = bg2;
colors[ImGuiCol_TableBorderStrong] = bg1;
colors[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
colors[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.2f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.35f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(cyan.x, cyan.y, cyan.z, 0.28f);
colors[ImGuiCol_DragDropTarget] = yellow;
colors[ImGuiCol_NavHighlight] = yellow;
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(fg1.x, fg1.y, fg1.z, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.35f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.35f);
colors[ImGuiCol_PlotLines] = green;
colors[ImGuiCol_PlotLinesHovered] = cyan;
colors[ImGuiCol_PlotHistogram] = yellow;
colors[ImGuiCol_PlotHistogramHovered] = red;
}
static void
ApplyOldBookTheme()
{
// Back-compat: keep calling light by default; GUITheme dispatches variants
ApplyOldBookLightTheme();
}

View File

@@ -1,105 +0,0 @@
// themes/Orbital.h — Orbital (dark) inspired ImGui theme (header-only)
#pragma once
#include "ThemeHelpers.h"
// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper
static inline void
ApplyOrbitalTheme()
{
// Orbital: deep-space dark with cool blues and magentas
const ImVec4 bg0 = RGBA(0x0C0F12); // background
const ImVec4 bg1 = RGBA(0x12161B);
const ImVec4 bg2 = RGBA(0x192029);
const ImVec4 bg3 = RGBA(0x212A36);
const ImVec4 fg0 = RGBA(0xC8D3E5); // primary text
const ImVec4 fg1 = RGBA(0x9FB2CC); // secondary text
const ImVec4 blue = RGBA(0x6AA2FF); // primary accent
const ImVec4 cyan = RGBA(0x5AD1E6);
const ImVec4 magenta = RGBA(0xD681F8);
const ImVec4 yellow = RGBA(0xE6C17D);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(6.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *c = style.Colors;
c[ImGuiCol_Text] = fg0;
c[ImGuiCol_TextDisabled] = ImVec4(fg1.x, fg1.y, fg1.z, 0.7f);
c[ImGuiCol_WindowBg] = bg0;
c[ImGuiCol_ChildBg] = bg0;
c[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
c[ImGuiCol_Border] = bg2;
c[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
c[ImGuiCol_FrameBg] = bg2;
c[ImGuiCol_FrameBgHovered] = bg3;
c[ImGuiCol_FrameBgActive] = bg1;
c[ImGuiCol_TitleBg] = bg1;
c[ImGuiCol_TitleBgActive] = bg2;
c[ImGuiCol_TitleBgCollapsed] = bg1;
c[ImGuiCol_MenuBarBg] = bg1;
c[ImGuiCol_ScrollbarBg] = bg0;
c[ImGuiCol_ScrollbarGrab] = bg3;
c[ImGuiCol_ScrollbarGrabHovered] = bg2;
c[ImGuiCol_ScrollbarGrabActive] = bg1;
c[ImGuiCol_CheckMark] = cyan;
c[ImGuiCol_SliderGrab] = cyan;
c[ImGuiCol_SliderGrabActive] = blue;
c[ImGuiCol_Button] = ImVec4(blue.x, blue.y, blue.z, 0.18f);
c[ImGuiCol_ButtonHovered] = ImVec4(blue.x, blue.y, blue.z, 0.28f);
c[ImGuiCol_ButtonActive] = ImVec4(blue.x, blue.y, blue.z, 0.40f);
c[ImGuiCol_Header] = ImVec4(magenta.x, magenta.y, magenta.z, 0.18f);
c[ImGuiCol_HeaderHovered] = ImVec4(magenta.x, magenta.y, magenta.z, 0.28f);
c[ImGuiCol_HeaderActive] = ImVec4(magenta.x, magenta.y, magenta.z, 0.40f);
c[ImGuiCol_Separator] = bg2;
c[ImGuiCol_SeparatorHovered] = cyan;
c[ImGuiCol_SeparatorActive] = blue;
c[ImGuiCol_ResizeGrip] = ImVec4(fg1.x, fg1.y, fg1.z, 0.10f);
c[ImGuiCol_ResizeGripHovered] = ImVec4(cyan.x, cyan.y, cyan.z, 0.67f);
c[ImGuiCol_ResizeGripActive] = blue;
c[ImGuiCol_Tab] = bg2;
c[ImGuiCol_TabHovered] = bg1;
c[ImGuiCol_TabActive] = bg3;
c[ImGuiCol_TabUnfocused] = bg2;
c[ImGuiCol_TabUnfocusedActive] = bg3;
c[ImGuiCol_TableHeaderBg] = bg2;
c[ImGuiCol_TableBorderStrong] = bg1;
c[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
c[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.12f);
c[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.22f);
c[ImGuiCol_TextSelectedBg] = ImVec4(blue.x, blue.y, blue.z, 0.28f);
c[ImGuiCol_DragDropTarget] = yellow;
c[ImGuiCol_NavHighlight] = blue;
c[ImGuiCol_NavWindowingHighlight] = ImVec4(fg1.x, fg1.y, fg1.z, 0.70f);
c[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.35f);
c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.35f);
c[ImGuiCol_PlotLines] = cyan;
c[ImGuiCol_PlotLinesHovered] = blue;
c[ImGuiCol_PlotHistogram] = yellow;
c[ImGuiCol_PlotHistogramHovered] = magenta;
}

View File

@@ -1,105 +0,0 @@
// themes/WeylandYutani.h — Weyland-Yutani inspired ImGui theme (header-only)
#pragma once
#include "ThemeHelpers.h"
// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper
static inline void
ApplyWeylandYutaniTheme()
{
// Corporate sci-fi aesthetic: near-black with hazard yellow accents
const ImVec4 bg0 = RGBA(0x0A0A0A);
const ImVec4 bg1 = RGBA(0x151515);
const ImVec4 bg2 = RGBA(0x202020);
const ImVec4 bg3 = RGBA(0x2B2B2B);
const ImVec4 fg0 = RGBA(0xE6E6E6);
const ImVec4 fg1 = RGBA(0xBDBDBD);
const ImVec4 hazard = RGBA(0xFFC300); // Weyland-Yutani yellow
const ImVec4 orange = RGBA(0xFF8C00);
const ImVec4 cyan = RGBA(0x4CC9F0);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(10.0f, 8.0f);
style.FramePadding = ImVec2(8.0f, 5.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(8.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 2.0f;
style.FrameRounding = 2.0f;
style.PopupRounding = 2.0f;
style.GrabRounding = 2.0f;
style.TabRounding = 2.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *colors = style.Colors;
colors[ImGuiCol_Text] = fg0;
colors[ImGuiCol_TextDisabled] = ImVec4(fg0.x, fg0.y, fg0.z, 0.55f);
colors[ImGuiCol_WindowBg] = bg0;
colors[ImGuiCol_ChildBg] = bg0;
colors[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
colors[ImGuiCol_Border] = bg3;
colors[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
colors[ImGuiCol_FrameBg] = bg2;
colors[ImGuiCol_FrameBgHovered] = ImVec4(hazard.x, hazard.y, hazard.z, 0.20f);
colors[ImGuiCol_FrameBgActive] = ImVec4(orange.x, orange.y, orange.z, 0.25f);
colors[ImGuiCol_TitleBg] = bg1;
colors[ImGuiCol_TitleBgActive] = bg2;
colors[ImGuiCol_TitleBgCollapsed] = bg1;
colors[ImGuiCol_MenuBarBg] = bg1;
colors[ImGuiCol_ScrollbarBg] = bg0;
colors[ImGuiCol_ScrollbarGrab] = bg3;
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(hazard.x, hazard.y, hazard.z, 0.50f);
colors[ImGuiCol_ScrollbarGrabActive] = hazard;
colors[ImGuiCol_CheckMark] = hazard;
colors[ImGuiCol_SliderGrab] = hazard;
colors[ImGuiCol_SliderGrabActive] = orange;
colors[ImGuiCol_Button] = bg3;
colors[ImGuiCol_ButtonHovered] = ImVec4(hazard.x, hazard.y, hazard.z, 0.25f);
colors[ImGuiCol_ButtonActive] = ImVec4(hazard.x, hazard.y, hazard.z, 0.40f);
colors[ImGuiCol_Header] = ImVec4(hazard.x, hazard.y, hazard.z, 0.25f);
colors[ImGuiCol_HeaderHovered] = ImVec4(cyan.x, cyan.y, cyan.z, 0.30f);
colors[ImGuiCol_HeaderActive] = ImVec4(orange.x, orange.y, orange.z, 0.30f);
colors[ImGuiCol_Separator] = bg3;
colors[ImGuiCol_SeparatorHovered] = hazard;
colors[ImGuiCol_SeparatorActive] = orange;
colors[ImGuiCol_ResizeGrip] = ImVec4(fg1.x, fg1.y, fg1.z, 0.12f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(hazard.x, hazard.y, hazard.z, 0.67f);
colors[ImGuiCol_ResizeGripActive] = orange;
colors[ImGuiCol_Tab] = bg2;
colors[ImGuiCol_TabHovered] = ImVec4(hazard.x, hazard.y, hazard.z, 0.25f);
colors[ImGuiCol_TabActive] = ImVec4(cyan.x, cyan.y, cyan.z, 0.30f);
colors[ImGuiCol_TabUnfocused] = bg2;
colors[ImGuiCol_TabUnfocusedActive] = bg3;
colors[ImGuiCol_TableHeaderBg] = bg2;
colors[ImGuiCol_TableBorderStrong] = bg1;
colors[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
colors[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.2f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.35f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(hazard.x, hazard.y, hazard.z, 0.28f);
colors[ImGuiCol_DragDropTarget] = orange;
colors[ImGuiCol_NavHighlight] = cyan;
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(fg0.x, fg0.y, fg0.z, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_PlotLines] = cyan;
colors[ImGuiCol_PlotLinesHovered] = hazard;
colors[ImGuiCol_PlotHistogram] = hazard;
colors[ImGuiCol_PlotHistogramHovered] = orange;
}

View File

@@ -1,104 +0,0 @@
// themes/Zenburn.h — Zenburn (low-contrast dark) inspired ImGui theme (header-only)
#pragma once
#include "ThemeHelpers.h"
// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper
static void
ApplyZenburnTheme()
{
// Zenburn palette (approximate)
const ImVec4 bg0 = RGBA(0x3F3F3F);
const ImVec4 bg1 = RGBA(0x4F4F4F);
const ImVec4 bg2 = RGBA(0x5F5F5F);
const ImVec4 bg3 = RGBA(0x6F6F6F);
const ImVec4 fg0 = RGBA(0xDCDCCC);
const ImVec4 fg1 = RGBA(0xFFFFEF);
const ImVec4 green = RGBA(0x7F9F7F);
const ImVec4 blue = RGBA(0x8CD0D3);
const ImVec4 yellow = RGBA(0xF0DFAF);
const ImVec4 orange = RGBA(0xDCA3A3);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.CellPadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(6.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.ScrollbarSize = 14.0f;
style.GrabMinSize = 10.0f;
style.WindowRounding = 4.0f;
style.FrameRounding = 3.0f;
style.PopupRounding = 4.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 4.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 1.0f;
ImVec4 *colors = style.Colors;
colors[ImGuiCol_Text] = fg0;
colors[ImGuiCol_TextDisabled] = ImVec4(fg0.x, fg0.y, fg0.z, 0.55f);
colors[ImGuiCol_WindowBg] = bg0;
colors[ImGuiCol_ChildBg] = bg0;
colors[ImGuiCol_PopupBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.98f);
colors[ImGuiCol_Border] = bg2;
colors[ImGuiCol_BorderShadow] = RGBA(0x000000, 0.0f);
colors[ImGuiCol_FrameBg] = bg2;
colors[ImGuiCol_FrameBgHovered] = bg3;
colors[ImGuiCol_FrameBgActive] = bg1;
colors[ImGuiCol_TitleBg] = bg1;
colors[ImGuiCol_TitleBgActive] = bg2;
colors[ImGuiCol_TitleBgCollapsed] = bg1;
colors[ImGuiCol_MenuBarBg] = bg1;
colors[ImGuiCol_ScrollbarBg] = bg0;
colors[ImGuiCol_ScrollbarGrab] = bg3;
colors[ImGuiCol_ScrollbarGrabHovered] = bg2;
colors[ImGuiCol_ScrollbarGrabActive] = bg1;
colors[ImGuiCol_CheckMark] = green;
colors[ImGuiCol_SliderGrab] = green;
colors[ImGuiCol_SliderGrabActive] = blue;
colors[ImGuiCol_Button] = bg3;
colors[ImGuiCol_ButtonHovered] = bg2;
colors[ImGuiCol_ButtonActive] = bg1;
colors[ImGuiCol_Header] = bg3;
colors[ImGuiCol_HeaderHovered] = bg2;
colors[ImGuiCol_HeaderActive] = bg2;
colors[ImGuiCol_Separator] = bg2;
colors[ImGuiCol_SeparatorHovered] = bg1;
colors[ImGuiCol_SeparatorActive] = blue;
colors[ImGuiCol_ResizeGrip] = ImVec4(fg1.x, fg1.y, fg1.z, 0.12f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(green.x, green.y, green.z, 0.67f);
colors[ImGuiCol_ResizeGripActive] = blue;
colors[ImGuiCol_Tab] = bg2;
colors[ImGuiCol_TabHovered] = bg1;
colors[ImGuiCol_TabActive] = bg3;
colors[ImGuiCol_TabUnfocused] = bg2;
colors[ImGuiCol_TabUnfocusedActive] = bg3;
colors[ImGuiCol_TableHeaderBg] = bg2;
colors[ImGuiCol_TableBorderStrong] = bg1;
colors[ImGuiCol_TableBorderLight] = ImVec4(bg1.x, bg1.y, bg1.z, 0.6f);
colors[ImGuiCol_TableRowBg] = ImVec4(bg1.x, bg1.y, bg1.z, 0.2f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(bg1.x, bg1.y, bg1.z, 0.35f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(orange.x, orange.y, orange.z, 0.30f);
colors[ImGuiCol_DragDropTarget] = orange;
colors[ImGuiCol_NavHighlight] = orange;
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(fg1.x, fg1.y, fg1.z, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.45f);
colors[ImGuiCol_PlotLines] = green;
colors[ImGuiCol_PlotLinesHovered] = blue;
colors[ImGuiCol_PlotHistogram] = yellow;
colors[ImGuiCol_PlotHistogramHovered] = orange;
}