Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / 5fb2865

sdl_runtime_compiler git · main

SDL3 game for running and compiling code at runtime

sdl3 c++ compiler dlopen cmake · first commit 2026-04-19 · last commit 2026-07-03 (3 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-runtime-compiler

C++ 72.3% C 26.2%
git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gz

Add tree-sitter to create propper cpp-token tree

Christian Immanuel · 2026-06-06 21:51 · 5fb2865f6ae8ae5aaf2457ccf3bfb6035648f789

 .gitignore                           |   1 +
 CMakeLists.txt                       |  54 ++++++
 README.md                            |  11 ++
 src/c_token/token_color.hpp          |  39 ----
 src/c_token/token_db.cpp             | 362 -----------------------------------
 src/c_token/token_db.hpp             |  15 --
 src/cpp_token/cpp_highlight.cpp      | 113 +++++++++++
 src/cpp_token/cpp_highlight.hpp      |  11 ++
 src/{c_token => cpp_token}/token.hpp |   6 +
 src/cpp_token/token_color.hpp        |  31 +++
 src/cpp_token/token_db.cpp           | 158 +++++++++++++++
 src/cpp_token/token_db.hpp           |  17 ++
 src/cpp_token/ts_test.cpp            |  21 ++
 src/states/game_state.cpp            | 141 ++++++++++++--
 src/states/game_state.hpp            |  16 +-
 src/systems/cpp_highlight_system.hpp |  74 +++++++
 src/systems/wall_connection.cpp      | 224 +++++++++++++---------
 src/systems/wall_connection.hpp      |   3 +-
 src/systems/word_ai_system.cpp       |  11 +-
 src/text_3d/text_3d.cpp              |  12 +-
 src/text_3d/text_3d.hpp              |   4 +-
 src/text_3d/text_file.hpp            |  84 ++++++++
 src/wall/editor_wall.cpp             |  30 +++
 src/wall/editor_wall.hpp             |   3 +
 src/wall/text_wall.cpp               |  36 ++--
 src/wall/text_wall.hpp               |   2 +-
 26 files changed, 913 insertions(+), 566 deletions(-)

diff --git a/.gitignore b/.gitignore
index 1db59fe..b1102d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ compile_commands.json
 
 build/
 build-prof/
+external/treesitter*
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fa73236..2b62ecc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -59,6 +59,8 @@ function(apply_common_settings target)
         OpenGL::GL
         m
         ${CMAKE_DL_LIBS}
+        tree_sitter_runtime
+        tree_sitter_cpp
     )
 
     # Profiler flag.
@@ -98,6 +100,58 @@ function(apply_common_settings target)
     )
 endfunction()
 
+# ─────────────────────────────────────────────────────────────────────────────
+# tree-sitter (C++ syntax highlighting) — vendored at configure time.
+# Runtime (MIT) + C++ grammar (MIT). We compile the committed generated C
+# sources directly; no tree-sitter CLI / generation step is involved.
+# ─────────────────────────────────────────────────────────────────────────────
+set(FETCHCONTENT_BASE_DIR "${CMAKE_SOURCE_DIR}/external")
+
+include(FetchContent)
+
+FetchContent_Declare(treesitter
+    GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter.git
+    GIT_TAG        v0.26.9
+    GIT_SHALLOW    TRUE
+)
+FetchContent_Declare(treesitter_cpp
+    GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-cpp.git
+    GIT_TAG        v0.23.4
+    GIT_SHALLOW    TRUE
+)
+
+message(STATUS "Fetching tree-sitter... <<-- Do i need that ?")
+FetchContent_MakeAvailable(treesitter)
+message(STATUS "tree-sitter ready")
+
+message(STATUS "Fetching tree-sitter-cpp...")
+FetchContent_MakeAvailable(treesitter_cpp)
+message(STATUS "tree-sitter-cpp ready")
+
+# The tree-sitter runtime: compile the amalgamated lib.c.
+add_library(tree_sitter_runtime STATIC
+    ${treesitter_SOURCE_DIR}/lib/src/lib.c
+)
+target_include_directories(tree_sitter_runtime PUBLIC
+    ${treesitter_SOURCE_DIR}/lib/include
+)
+target_include_directories(tree_sitter_runtime PRIVATE
+    ${treesitter_SOURCE_DIR}/lib/src
+)
+
+# The C++ grammar: generated parser + external scanner.
+add_library(tree_sitter_cpp STATIC
+    ${treesitter_cpp_SOURCE_DIR}/src/parser.c
+    ${treesitter_cpp_SOURCE_DIR}/src/scanner.c
+)
+target_include_directories(tree_sitter_cpp PRIVATE
+    ${treesitter_cpp_SOURCE_DIR}/src
+)
+# Expose the function declaration tree_sitter_cpp() to our C++ code.
+target_include_directories(tree_sitter_cpp PUBLIC
+    ${treesitter_cpp_SOURCE_DIR}/bindings/c
+)
+
 # ─────────────────────────────────────────────────────────────────────────────
 # Main executable
 # ─────────────────────────────────────────────────────────────────────────────
diff --git a/README.md b/README.md
index 433f24c..1102f10 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,10 @@ git submodule update --remote --recursive
 
 > Better replace my own smartpointer with std ones^^
 
+When compiling we fetch [tree-sitter](https://github.com/tree-sitter/tree-sitter) and [tree-sitter-pp](https://github.com/tree-sitter/tree-sitter-cpp) to lex CPP
+
+
+
 ---
 
 The following packages need to be findable by cmake's find\_package:
@@ -34,6 +38,13 @@ The following packages need to be findable by cmake's find\_package:
 
 ## Compilation
 
+Check that the font we use is existent at your system or fix the font path to an existing font which is actually getting set in [src/text\_3d/text\_3d.hpp](src/text_3d/text_3d.hpp)
+
+```
+sdl_runtime_compiler > grep -rI "/usr/sh" src/
+src/text_3d/text_3d.hpp:        "/usr/share/fonts/dejavu/DejaVuSansMono.ttf";
+```
+
 Smash a 
 
 ```bash
diff --git a/src/c_token/token_color.hpp b/src/c_token/token_color.hpp
deleted file mode 100644
index 70594f6..0000000
--- a/src/c_token/token_color.hpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#pragma once
-#include <SDL3/SDL.h>
-#include "token.hpp"
-#include "token_db.hpp"
-#include "text_3d/text_3d.hpp"
-
-namespace TokenColor
-{
-inline SDL_FColor
-get(TokenType t)
-{
-    switch (t) {
-        case TokenType::Keyword:     return ConfigColor::orange;   // if, for, return
-        case TokenType::Type:        return ConfigColor::green;    // int, void
-        case TokenType::Identifier:  return ConfigColor::light_gray;    // main, printf, i, x …
-        case TokenType::Literal:     return ConfigColor::red;      // 0, 1, "\n", %s
-        case TokenType::Operator:    return ConfigColor::light_gray;    // = == + - * / ++ 
-        case TokenType::Delimiter:   return ConfigColor::light_gray;    // ( ) { } ; , [ ]
-        case TokenType::Preprocessor:return ConfigColor::pink;    // #define #if
-    }
-    return ConfigColor::white;
-}
-
-inline void
-apply(Text3d* word)
-{
-    if (!word) return;
-    const Token* tok = TokenDB::find_OrSynthesize(word->text);
-    if (tok) word->set_Color(get(tok->type));
-}
-
-inline SDL_FColor
-get_ByString(std::string token_text)
-{
-    const Token* tok = TokenDB::find_OrSynthesize(token_text);
-    return tok ? get(tok->type) : ConfigColor::white;
-}
-
-}
diff --git a/src/c_token/token_db.cpp b/src/c_token/token_db.cpp
deleted file mode 100644
index 1a0b403..0000000
--- a/src/c_token/token_db.cpp
+++ /dev/null
@@ -1,362 +0,0 @@
-#include "token_db.hpp"
-#include <unordered_map>
-
-
-static const DynArray<Token>
-all_tokens = {
-    { "int",       TokenType::Type, "Integer type",           "Declares a 32-bit integer variable"  },
-    { "float",     TokenType::Type, "Float type",             "Declares a floating point variable"  },
-    { "char",      TokenType::Type, "Character type",         "Declares a single character"         },
-    { "void",      TokenType::Type, "No value type",          "Function returns nothing"            },
-    { "bool",      TokenType::Type, "Boolean type",           "Stores true or false values"                  },
-    { "short",     TokenType::Type, "Short integer type",     "Declares a short integer variable"            },
-    { "long",      TokenType::Type, "Long integer type",      "Declares a long integer variable"             },
-    { "unsigned",  TokenType::Type, "Unsigned modifier",      "Declares an unsigned integer type"            },
-    { "signed",    TokenType::Type, "Signed modifier",        "Declares a signed integer type"               },
-    { "double",    TokenType::Type, "Double type",            "Declares a double-precision floating point variable" },
-    { "wchar_t",   TokenType::Type, "Wide character type",    "Stores wide character values"                 },
-    { "char8_t",   TokenType::Type, "UTF-8 character type",   "Stores a UTF-8 code unit"                     },
-    { "char16_t",  TokenType::Type, "UTF-16 character type",  "Stores a UTF-16 code unit"                    },
-    { "char32_t",  TokenType::Type, "UTF-32 character type",  "Stores a UTF-32 code unit"                    },
-    { "size_t",    TokenType::Type, "Size type",              "Stores object sizes and array indices"        },
-    { "struct",    TokenType::Type, "Structure type",         "Defines a structure type"                     },
-    { "union",     TokenType::Type, "Union type",             "Defines a union type"                         },
-    { "enum",      TokenType::Type, "Enumeration type",       "Defines an enumeration type"                  },
-    { "typedef",   TokenType::Type, "Type alias",             "Creates an alias for a type"                  },
-    { "extern",    TokenType::Type, "External linkage",       "Declares a symbol defined elsewhere"          },
-    { "static",    TokenType::Type, "Static storage",         "Declares static storage duration"             },
-    { "register",  TokenType::Type, "Register storage",       "Hints that a variable is frequently used"     },
-    { "volatile",  TokenType::Type, "Volatile qualifier",     "Prevents certain compiler optimizations"      },
-    { "const",     TokenType::Type, "Constant qualifier",     "Prevents modification of an object"           },
-
-    { "return",    TokenType::Keyword, "Return statement",    "Ends function and returns a value"   },
-    { "if",        TokenType::Keyword, "Conditional branch",  "Executes code if condition is true"  },
-    { "else",      TokenType::Keyword, "Alternative branch",  "Executes if 'if' condition is false" },
-    { "while",     TokenType::Keyword, "While loop",          "Repeats while condition is true"     },
-    { "for",       TokenType::Keyword, "For loop",            "Loop with init, condition, increment"},
-    { "switch",    TokenType::Keyword, "Switch statement",    "Selects execution path based on a value"      },
-    { "case",      TokenType::Keyword, "Case label",          "Marks a branch in a switch statement"         },
-    { "default",   TokenType::Keyword, "Default label",       "Fallback branch in a switch statement"        },
-    { "break",     TokenType::Keyword, "Break statement",     "Exits a loop or switch statement"             },
-    { "continue",  TokenType::Keyword, "Continue statement",  "Skips to the next loop iteration"             },
-    { "goto",      TokenType::Keyword, "Goto statement",      "Jumps to a labeled statement"                 },
-    { "do",        TokenType::Keyword, "Do loop",             "Begins a do-while loop"                        },
-    { "sizeof",    TokenType::Keyword, "Size operator",       "Returns the size of a type or object"         },
-
-    { "main",   TokenType::Identifier, "Entry point",         "First function called by the OS"     },
-    { "x",      TokenType::Identifier, "Variable name",       "Stores a value"                      },
-    { "y",      TokenType::Identifier, "Variable name",       "Stores a value"                      },
-    { "z",      TokenType::Identifier, "Variable name",       "Stores a value"                      },
-    { "func",   TokenType::Identifier, "Function name",       "A callable block of code"            },
-    { "printf", TokenType::Identifier, "Print formatted",     "Outputs formatted text to stdout"    },
-    { "strlen", TokenType::Identifier, "String length",       "Returns length of a string"          },
-    { "src",    TokenType::Identifier, "Source variable",     "Input data"                          },
-    { "dst",    TokenType::Identifier, "Destination variable","Output data"                         },
-    { "i",      TokenType::Identifier, "Loop counter",        "Common loop index variable"          },
-    { "j",      TokenType::Identifier, "Loop counter",        "Common loop index variable"          },
-    { "k",      TokenType::Identifier, "Loop counter",        "Common loop index variable"          },
-
-    { "NULL",   TokenType::Literal, "Null pointer constant",  "Macro representing a null pointer" },
-    { "0",      TokenType::Literal, "Integer zero",        "The integer constant 0"              },
-    { "1",      TokenType::Literal, "Integer one",         "The integer constant 1"              },
-    { "\"\\n\"",TokenType::Literal, "Newline string",      "String containing a newline character"},
-    { "%s",     TokenType::Literal, "String format",       "Printf format specifier for strings" },
-    { "\"",     TokenType::Literal, "Double quote character","Quotation mark character (\")" },
-    { "'",      TokenType::Literal, "Single quote character","Apostrophe or character literal delimiter" },
-    { "true",   TokenType::Literal, "Boolean true",        "Boolean constant true"                   },
-    { "false",  TokenType::Literal, "Boolean false",       "Boolean constant false"                  },
-    { "%d",     TokenType::Literal, "Integer format",      "Printf format specifier for integers"    },
-    { "%u",     TokenType::Literal, "Unsigned format",     "Printf format specifier for unsigned integers" },
-    { "%f",     TokenType::Literal, "Float format",        "Printf format specifier for floating-point values" },
-    { "%c",     TokenType::Literal, "Character format",    "Printf format specifier for characters"  },
-    { "%p",     TokenType::Literal, "Pointer format",      "Printf format specifier for pointers"    },
-    { "%%",     TokenType::Literal, "Percent format",      "Printf escape sequence for percent sign" },
-    { "%ld",    TokenType::Literal, "Long integer format",        "Printf format specifier for long integers" },
-    { "%lld",   TokenType::Literal, "Long long format",           "Printf format specifier for long long integers" },
-    { "%lu",    TokenType::Literal, "Unsigned long format",       "Printf format specifier for unsigned long integers" },
-    { "%llu",   TokenType::Literal, "Unsigned long long format",  "Printf format specifier for unsigned long long integers" },
-    { "%x",     TokenType::Literal, "Hex lowercase",              "Printf format specifier for hexadecimal (lowercase)" },
-    { "%X",     TokenType::Literal, "Hex uppercase",              "Printf format specifier for hexadecimal (uppercase)" },
-    { "%o",     TokenType::Literal, "Octal format",               "Printf format specifier for octal integers" },
-    { "%e",     TokenType::Literal, "Scientific format",          "Printf format specifier for scientific notation (lowercase e)" },
-    { "%E",     TokenType::Literal, "Scientific format",          "Printf format specifier for scientific notation (uppercase E)" },
-    { "%g",     TokenType::Literal, "Shortest float format",      "Printf format specifier for shortest representation (float/double)" },
-    { "%G",     TokenType::Literal, "Shortest float format",      "Printf format specifier for shortest representation (uppercase)" },
-    { "%s",     TokenType::Literal, "String format",              "Printf format specifier for null-terminated strings" },
-    { "%n",     TokenType::Literal, "Write-back count",           "Printf specifier that writes number of characters printed so far" },
-
-    { "+",      TokenType::Operator, "Addition",        "Adds two operands" },
-    { "-",      TokenType::Operator, "Subtraction",     "Subtracts right operand from left" },
-    //{ "*",      TokenType::Operator, "Multiplication",  "Multiplies two operands" },
-    { "/",      TokenType::Operator, "Division",        "Divides left operand by right" },
-
-    { "++",     TokenType::Operator, "Increment",       "Increases value by 1" },
-    { "--",     TokenType::Operator, "Decrement",       "Decreases value by 1" },
-    { "!=",     TokenType::Operator, "Not equal",             "Checks whether two values differ"           },
-    { "<",      TokenType::Operator, "Less than",             "Checks whether left is less than right"  },
-    { ">",      TokenType::Operator, "Greater than",          "Checks whether left is greater than right"  },
-    { "<=",     TokenType::Operator, "Less or equal",         "Checks whether left is less than or equal to right" },
-    { ">=",     TokenType::Operator, "Greater or equal",      "Checks whether left is greater than or equal to right" },
-    { "+=",     TokenType::Operator, "Addition assignment",   "Adds and assigns the result"                },
-    { "-=",     TokenType::Operator, "Subtraction assignment","Subtracts and assigns the result"           },
-    { "*=",     TokenType::Operator, "Multiplication assignment", "Multiplies and assigns the result"      },
-    { "/=",     TokenType::Operator, "Division assignment",   "Divides and assigns the result"             },
-    { "%=",     TokenType::Operator, "Modulo assignment",     "Calculates remainder and assigns it"        },
-    { "%",      TokenType::Operator, "Modulo",                "Returns the division remainder"             },
-    { "&&",     TokenType::Operator, "Logical AND",           "True if both operands are true"             },
-    { "||",     TokenType::Operator, "Logical OR",            "True if either operand is true"             },
-    { "!",      TokenType::Operator, "Logical NOT",           "Inverts a boolean value"                    },
-    { "&",      TokenType::Operator, "Bitwise AND",           "Performs bitwise conjunction"               },
-    { "|",      TokenType::Operator, "Bitwise OR",            "Performs bitwise disjunction"               },
-    { "^",      TokenType::Operator, "Bitwise XOR",           "Performs bitwise exclusive OR"              },
-    { "~",      TokenType::Operator, "Bitwise NOT",           "Inverts all bits"                           },
-    { "<<",     TokenType::Operator, "Left shift",            "Shifts bits to the left"                    },
-    { ">>",     TokenType::Operator, "Right shift",           "Shifts bits to the right"                   },
-    { "<<=",    TokenType::Operator, "Left shift assignment", "Left shifts and assigns the result"         },
-    { ">>=",    TokenType::Operator, "Right shift assignment","Right shifts and assigns the result"        },
-    { "&=",     TokenType::Operator, "AND assignment",        "Performs bitwise AND and assigns"           },
-    { "|=",     TokenType::Operator, "OR assignment",         "Performs bitwise OR and assigns"            },
-    { "^=",     TokenType::Operator, "XOR assignment",        "Performs bitwise XOR and assigns"           },
-    { "->",     TokenType::Operator, "Member access",         "Accesses a member through a pointer"        },
-    { ".",      TokenType::Operator, "Member access",         "Accesses a member of an object"             },
-    { "?",      TokenType::Operator, "Conditional operator",  "Begins a ternary conditional expression"    },
-    { ":",      TokenType::Operator, "Conditional separator", "Separates true and false branches"          },
-    { "*",      TokenType::Operator, "Dereference",           "Accesses the object pointed to by a pointer" },
-
-    { "(",      TokenType::Delimiter,  "Open parenthesis",    "Starts a function call or condition" },
-    { ")",      TokenType::Delimiter,  "Close parenthesis",   "Ends a function call or condition"   },
-    { "{",      TokenType::Delimiter,  "Open brace",          "Starts a code block"                 },
-    { "}",      TokenType::Delimiter,  "Close brace",         "Ends a code block"                   },
-    { ";",      TokenType::Delimiter,  "Statement terminator","Ends a statement"                    },
-    { ",",      TokenType::Delimiter,  "Separator",           "Separates function arguments"        },
-    { "[",      TokenType::Delimiter,  "Open bracket",        "Start array subscript"               },
-    { "]",      TokenType::Delimiter,  "Close bracket",       "End array subscript"                 },
-
-    { "#include", TokenType::Preprocessor, "Include directive", "Includes a header file" },
-    { "#define",  TokenType::Preprocessor, "Macro definition",  "Defines a macro" },
-    { "#undef",   TokenType::Preprocessor, "Macro undefinition","Removes a macro definition" },
-
-    { "#ifdef",   TokenType::Preprocessor, "If defined",        "Compiles code if macro is defined" },
-    { "#ifndef",  TokenType::Preprocessor, "If not defined",    "Compiles code if macro is not defined" },
-    { "#if",      TokenType::Preprocessor, "If condition",      "Conditional compilation block" },
-    { "#elif",    TokenType::Preprocessor, "Else if",           "Alternative conditional branch" },
-    { "#else",    TokenType::Preprocessor, "Else branch",       "Fallback conditional branch" },
-    { "#endif",   TokenType::Preprocessor, "End conditional",   "Ends conditional compilation block" },
-};
-
-
-static const std::unordered_map<std::string, const Token*>
-build_index()
-{
-    std::unordered_map<std::string, const Token*> m;
-    for (const Token& t : all_tokens)
-        m[t.text] = &t;
-    return m;
-}
-
-
-static const std::unordered_map<std::string, const Token*>
-token_index = build_index();
-
-
-DynArray<std::string>
-TokenDB::get_TestArrayAllKeywords()
-{
-    return { "float", "return", "main", "0", "++", "(", ",", ";", "\"", "\"", "#define" };
-}
-
-
-const Token*
-TokenDB::find(const std::string& text)
-{
-    auto it = token_index.find(text);
-    return (it != token_index.end()) ? it->second : nullptr;
-}
-
-
-DynArray<const Token*>
-TokenDB::get_AllOfType(TokenType type)
-{
-    DynArray<const Token*> result;
-    for (const auto& t : all_tokens)
-        if (t.type == type)
-            result.push_back(&t);
-    return result;
-}
-
-
-const char*
-TokenDB::get_TypeName(TokenType t)
-{
-    switch (t) {
-        case TokenType::Keyword:     return "Keyword";
-        case TokenType::Type:        return "Type";
-        case TokenType::Identifier:  return "Identifier";
-        case TokenType::Literal:     return "Literal";
-        case TokenType::Operator:    return "Operator";
-        case TokenType::Delimiter:   return "Delimiter";
-        case TokenType::Preprocessor:return "Preprocessor";
-    }
-    return "Unknown";
-}
-
-
-bool
-TokenDB::known(const std::string& text)
-{
-    return token_index.find(text) != token_index.end();
-}
-
-
-// ---------------------------------------------------------------------------
-// Pattern helpers
-// ---------------------------------------------------------------------------
-
-static bool
-is_number(const std::string& s)
-{
-    if (s.empty()) return false;
-    size_t i = (s[0] == '-' || s[0] == '+') ? 1 : 0;
-    if (i == s.size()) return false;
-
-    // hex: 0x / 0X
-    if (i + 1 < s.size() && s[i] == '0' && (s[i+1] == 'x' || s[i+1] == 'X')) {
-        for (size_t j = i + 2; j < s.size(); ++j) {
-            char c = s[j];
-            if (std::isxdigit(c)) continue;
-            if (std::string("uUlL").find(c) != std::string::npos) continue;
-            return false;
-        }
-        return i + 2 < s.size();
-    }
-
-    bool has_dot = false, has_exp = false;
-    for (; i < s.size(); ++i) {
-        char c = s[i];
-        if (std::isdigit(c)) continue;
-        if (c == '.' && !has_dot && !has_exp)       { has_dot = true;  continue; }
-        if ((c == 'e' || c == 'E') && !has_exp && i > 0) {
-            has_exp = true;
-            if (i + 1 < s.size() && (s[i+1] == '+' || s[i+1] == '-')) ++i;
-            continue;
-        }
-        if (std::string("fFuUlL").find(c) != std::string::npos && i == s.size() - 1) continue;
-        return false;
-    }
-    return true;
-}
-
-static bool
-is_string_literal(const std::string& s)
-{
-    if (s.size() < 2) return false;
-    if (s.front() != '"' || s.back() != '"') return false;
-    // ensure closing " is not escaped
-    size_t backslashes = 0;
-    for (size_t i = s.size() - 2; i > 0 && s[i] == '\\'; --i)
-        ++backslashes;
-    return (backslashes % 2) == 0;
-}
-
-static bool
-is_char_literal(const std::string& s)
-{
-    // 'a'  '\n'  '\0'  '\x41'
-    if (s.size() < 3) return false;
-    if (s.front() != '\'' || s.back() != '\'') return false;
-    std::string inner = s.substr(1, s.size() - 2);
-    if (inner.empty() || inner.size() > 6) return false;
-    if (inner[0] != '\\') return inner.size() == 1;  // plain char
-    return inner.size() >= 2;                         // escape sequence
-}
-
-
-// ---------------------------------------------------------------------------
-// Token synthesizers
-// ---------------------------------------------------------------------------
-
-static const Token*
-synth_number(const std::string& s)
-{
-    bool is_hex = s.size() > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X');
-
-    // collect suffix characters (trailing u U l L f F)
-    size_t suffix_start = s.size();
-    while (suffix_start > 0 && std::string("uUlLfF").find(s[suffix_start - 1]) != std::string::npos)
-        --suffix_start;
-    std::string suffix = s.substr(suffix_start);
-
-    // lowercase the suffix for easier comparison
-    std::string suf_low;
-    for (char c : suffix) suf_low += static_cast<char>(std::tolower(c));
-
-    bool has_f_suffix   = suf_low == "f";
-    bool has_fp_content = s.find('.') != std::string::npos ||
-                          s.find('e') != std::string::npos ||
-                          s.find('E') != std::string::npos;
-    bool is_fp          = has_fp_content || has_f_suffix;
-
-    bool is_unsigned = suf_low.find('u') != std::string::npos;
-    bool is_long     = suf_low.find('l') != std::string::npos;
-
-    thread_local Token t;
-    if (is_hex) {
-        if (is_unsigned && is_long)
-            t = { s, TokenType::Literal, "Hex literal",          "Unsigned long hexadecimal constant"   };
-        else if (is_unsigned)
-            t = { s, TokenType::Literal, "Hex literal",          "Unsigned hexadecimal constant"        };
-        else
-            t = { s, TokenType::Literal, "Hex literal",          "Hexadecimal integer constant"         };
-    }
-    else if (is_fp) {
-        if (has_f_suffix)
-            t = { s, TokenType::Literal, "Float literal",        "Single-precision floating-point (float)"  };
-        else if (is_long)
-            t = { s, TokenType::Literal, "Long double literal",  "Extended-precision floating-point (long double)" };
-        else
-            t = { s, TokenType::Literal, "Double literal",       "Double-precision floating-point (double)" };
-    }
-    else {
-        if (is_unsigned && is_long)
-            t = { s, TokenType::Literal, "Unsigned long literal","Unsigned long integer constant"       };
-        else if (is_unsigned)
-            t = { s, TokenType::Literal, "Unsigned literal",     "Unsigned integer constant"            };
-        else if (is_long)
-            t = { s, TokenType::Literal, "Long literal",         "Long integer constant"                };
-        else
-            t = { s, TokenType::Literal, "Integer literal",      "Integer constant"                     };
-    }
-    return &t;
-}
-
-static const Token*
-synth_string(const std::string& s)
-{
-    thread_local Token t;
-    t = { s, TokenType::Literal, "String literal", "Null-terminated character string" };
-    return &t;
-}
-
-static const Token*
-synth_char(const std::string& s)
-{
-    thread_local Token t;
-    t = { s, TokenType::Literal, "Char literal", "Single character constant" };
-    return &t;
-}
-
-
-// ---------------------------------------------------------------------------
-// Public API
-// ---------------------------------------------------------------------------
-
-const Token*
-TokenDB::find_OrSynthesize(const std::string& text)
-{
-    const Token* t = find(text);
-    if (t)                        return t;
-    if (is_string_literal(text))  return synth_string(text);
-    if (is_char_literal(text))    return synth_char(text);
-    if (is_number(text))          return synth_number(text);
-    return nullptr;
-}
diff --git a/src/c_token/token_db.hpp b/src/c_token/token_db.hpp
deleted file mode 100644
index e0a0355..0000000
--- a/src/c_token/token_db.hpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma once
-
-#include "token.hpp"
-#include "utils/globals.hpp"
-
-namespace TokenDB
-{
-    bool         known(const std::string& text);
-    const Token* find (const std::string& text);
-    const Token* find_OrSynthesize(const std::string& text);
-
-    const char*            get_TypeName (TokenType type);
-    DynArray<const Token*> get_AllOfType(TokenType type);
-    DynArray<std::string>  get_TestArrayAllKeywords();
-}
diff --git a/src/cpp_token/cpp_highlight.cpp b/src/cpp_token/cpp_highlight.cpp
new file mode 100644
index 0000000..07c3645
--- /dev/null
+++ b/src/cpp_token/cpp_highlight.cpp
@@ -0,0 +1,113 @@
+#include <cstring>
+#include <mutex>
+#include <tree_sitter/api.h>
+#include <unordered_map>
+
+#include "cpp_token/cpp_highlight.hpp"
+
+extern "C" const TSLanguage* tree_sitter_cpp(void);
+
+namespace {
+
+// Map a node's type string (+ its parent's) to our TokenType.
+// Returns false if this node should not emit a span (recurse into children).
+bool
+classify_Node(TSNode node, TokenType& out)
+{
+    const char* t = ts_node_type(node);
+
+    // Leaf categories that color directly.
+    if (std::strcmp(t, "comment") == 0)            { out = TokenType::Comment;    return true; }
+    if (std::strcmp(t, "string_literal") == 0 ||
+        std::strcmp(t, "raw_string_literal") == 0 ||
+        std::strcmp(t, "char_literal") == 0 ||
+        std::strcmp(t, "system_lib_string") == 0)  { out = TokenType::StringLit;  return true; }
+    if (std::strcmp(t, "number_literal") == 0)     { out = TokenType::NumberLit;  return true; }
+    if (std::strcmp(t, "primitive_type") == 0 ||
+        std::strcmp(t, "type_identifier") == 0 ||
+        std::strcmp(t, "sized_type_specifier") == 0) { out = TokenType::Type;     return true; }
+    if (std::strcmp(t, "namespace_identifier") == 0) { out = TokenType::Namespace; return true; }
+    if (std::strcmp(t, "field_identifier") == 0)   { out = TokenType::Variable;   return true; }
+
+    // Identifiers: depend on parent.
+    if (std::strcmp(t, "identifier") == 0) {
+        TSNode parent = ts_node_parent(node);
+        const char* p = ts_node_is_null(parent) ? "" : ts_node_type(parent);
+        if (std::strcmp(p, "function_declarator") == 0 ||
+            std::strcmp(p, "call_expression") == 0)
+            { out = TokenType::Function; return true; }
+        out = TokenType::Variable; return true;
+    }
+
+    // Preprocessor: the #include/#define lines.
+    if (std::strncmp(t, "preproc", 7) == 0)        { out = TokenType::Preprocessor; return true; }
+
+    return false;   // not a terminal we color — recurse into children
+}
+
+void
+walk(TSNode node, DynArray<HiSpan>& out)
+{
+    TokenType type;
+    if (ts_node_child_count(node) == 0) {
+        // leaf — classify and emit if recognized
+        if (classify_Node(node, type))
+            out.push_back({ ts_node_start_byte(node), ts_node_end_byte(node), type });
+        return;
+    }
+    // named-but-classifiable interior nodes (comments, strings can be interior)
+    if (classify_Node(node, type)) {
+        out.push_back({ ts_node_start_byte(node), ts_node_end_byte(node), type });
+        return;   // don't descend into a classified span
+    }
+    const uint32_t n = ts_node_child_count(node);
+    for (uint32_t i = 0; i < n; ++i)
+        walk(ts_node_child(node, i), out);
+}
+
+} // namespace
+
+DynArray<HiSpan>
+CppHighlight::classify(const std::string& source)
+{
+    DynArray<HiSpan> spans;
+    TSParser* p = ts_parser_new();
+    ts_parser_set_language(p, tree_sitter_cpp());
+    TSTree* tree = ts_parser_parse_string(p, nullptr, source.c_str(),
+                                          (uint32_t)source.size());
+    walk(ts_tree_root_node(tree), spans);
+    ts_tree_delete(tree);
+    ts_parser_delete(p);
+    return spans;
+}
+
+
+TokenType
+CppHighlight::classify_Word(const std::string& word)
+{
+    static std::unordered_map<std::string, TokenType> cache;
+    static std::mutex mtx;
+
+    {
+        std::lock_guard<std::mutex> lk(mtx);
+        auto it = cache.find(word);
+        if (it != cache.end()) return it->second;
+    }
+
+    // First sight: classify this lone token with tree-sitter.
+    DynArray<HiSpan> spans = classify(word);
+
+    // Pick the span covering the most of the word (usually the only one).
+    TokenType type = TokenType::Identifier;
+    size_t best_len = 0;
+    for (const HiSpan& s : spans) {
+        size_t len = s.byte_end - s.byte_begin;
+        if (len > best_len) { best_len = len; type = s.type; }
+    }
+
+    {
+        std::lock_guard<std::mutex> lk(mtx);
+        cache[word] = type;
+    }
+    return type;
+}
diff --git a/src/cpp_token/cpp_highlight.hpp b/src/cpp_token/cpp_highlight.hpp
new file mode 100644
index 0000000..13e65e1
--- /dev/null
+++ b/src/cpp_token/cpp_highlight.hpp
@@ -0,0 +1,11 @@
+#pragma once
+#include <string>
+#include "cpp_token/token.hpp"
+#include "utils/globals.hpp"
+
+struct HiSpan { size_t byte_begin; size_t byte_end; TokenType type; };
+
+namespace CppHighlight {
+    DynArray<HiSpan> classify(const std::string& source);
+    TokenType        classify_Word(const std::string& word);
+}
diff --git a/src/c_token/token.hpp b/src/cpp_token/token.hpp
similarity index 78%
rename from src/c_token/token.hpp
rename to src/cpp_token/token.hpp
index 88ea12b..5a1aa3a 100644
--- a/src/c_token/token.hpp
+++ b/src/cpp_token/token.hpp
@@ -11,6 +11,12 @@ enum class TokenType : uint8_t {
     Operator,
     Preprocessor,
     Type,
+    Function,
+    Variable,
+    Comment,
+    StringLit,
+    NumberLit,
+    Namespace,
 };
 
 struct Token {
diff --git a/src/cpp_token/token_color.hpp b/src/cpp_token/token_color.hpp
new file mode 100644
index 0000000..1127b25
--- /dev/null
+++ b/src/cpp_token/token_color.hpp
@@ -0,0 +1,31 @@
+#pragma once
+#include <SDL3/SDL.h>
+#include "token.hpp"
+#include "token_db.hpp"
+#include "text_3d/text_3d.hpp"
+
+namespace TokenColor
+{
+
+inline SDL_FColor
+get(TokenType t)
+{
+    switch (t) {
+        case TokenType::Keyword:     return ConfigColor::orange;
+        case TokenType::Type:        return ConfigColor::green;
+        case TokenType::Identifier:  return ConfigColor::light_gray;
+        case TokenType::Literal:     return ConfigColor::red;
+        case TokenType::Operator:    return ConfigColor::soft_blue;
+        case TokenType::Delimiter:   return ConfigColor::slate_blue;
+        case TokenType::Preprocessor:return ConfigColor::pink;
+        case TokenType::Function:    return ConfigColor::blue;
+        case TokenType::Variable:    return ConfigColor::pale_sky;
+        case TokenType::Comment:     return ConfigColor::gray;
+        case TokenType::StringLit:   return ConfigColor::lime;
+        case TokenType::NumberLit:   return ConfigColor::amber;
+        case TokenType::Namespace:   return ConfigColor::teal;
+    }
+    return ConfigColor::white;
+}
+
+}
diff --git a/src/cpp_token/token_db.cpp b/src/cpp_token/token_db.cpp
new file mode 100644
index 0000000..457993b
--- /dev/null
+++ b/src/cpp_token/token_db.cpp
@@ -0,0 +1,158 @@
+#include "token_db.hpp"
+#include <unordered_map>
+
+namespace {
+
+struct Entry { const char* short_desc; const char* long_desc; };
+
+// Curated, hand-written help text. Keyed by exact token text.
+const std::unordered_map<std::string, Entry>&
+table()
+{
+    static const std::unordered_map<std::string, Entry> m = {
+        { "int",      { "Integer type",          "Declares a 32-bit integer variable" } },
+        { "float",    { "Float type",            "Declares a floating point variable" } },
+        { "char",     { "Character type",        "Declares a single character" } },
+        { "void",     { "No value type",         "Function returns nothing" } },
+        { "bool",     { "Boolean type",          "Stores true or false values" } },
+        { "short",    { "Short integer type",    "Declares a short integer variable" } },
+        { "long",     { "Long integer type",     "Declares a long integer variable" } },
+        { "unsigned", { "Unsigned modifier",     "Declares an unsigned integer type" } },
+        { "signed",   { "Signed modifier",       "Declares a signed integer type" } },
+        { "double",   { "Double type",           "Declares a double-precision floating point variable" } },
+        { "size_t",   { "Size type",             "Stores object sizes and array indices" } },
+        { "struct",   { "Structure type",        "Defines a structure type" } },
+        { "union",    { "Union type",            "Defines a union type" } },
+        { "enum",     { "Enumeration type",      "Defines an enumeration type" } },
+        { "typedef",  { "Type alias",            "Creates an alias for a type" } },
+        { "const",    { "Constant qualifier",    "Prevents modification of an object" } },
+        { "static",   { "Static storage",        "Declares static storage duration" } },
+        { "extern",   { "External linkage",      "Declares a symbol defined elsewhere" } },
+        { "volatile", { "Volatile qualifier",    "Prevents certain compiler optimizations" } },
+
+        { "return",   { "Return statement",      "Ends function and returns a value" } },
+        { "if",       { "Conditional branch",    "Executes code if condition is true" } },
+        { "else",     { "Alternative branch",    "Executes if 'if' condition is false" } },
+        { "while",    { "While loop",            "Repeats while condition is true" } },
+        { "for",      { "For loop",              "Loop with init, condition, increment" } },
+        { "switch",   { "Switch statement",      "Selects execution path based on a value" } },
+        { "case",     { "Case label",            "Marks a branch in a switch statement" } },
+        { "default",  { "Default label",         "Fallback branch in a switch statement" } },
+        { "break",    { "Break statement",       "Exits a loop or switch statement" } },
+        { "continue", { "Continue statement",    "Skips to the next loop iteration" } },
+        { "goto",     { "Goto statement",        "Jumps to a labeled statement" } },
+        { "do",       { "Do loop",               "Begins a do-while loop" } },
+        { "sizeof",   { "Size operator",         "Returns the size of a type or object" } },
+
+        { "main",     { "Entry point",           "First function called by the OS" } },
+        { "printf",   { "Print formatted",       "Outputs formatted text to stdout" } },
+        { "strlen",   { "String length",         "Returns length of a string" } },
+        { "i",        { "Loop counter",          "Common loop index variable" } },
+        { "j",        { "Loop counter",          "Common loop index variable" } },
+        { "k",        { "Loop counter",          "Common loop index variable" } },
+
+        { "NULL",     { "Null pointer constant", "Macro representing a null pointer" } },
+        { "true",     { "Boolean true",          "Boolean constant true" } },
+        { "false",    { "Boolean false",         "Boolean constant false" } },
+        { "%d",       { "Integer format",        "Printf format specifier for integers" } },
+        { "%s",       { "String format",         "Printf format specifier for strings" } },
+        { "%f",       { "Float format",          "Printf format specifier for floating-point values" } },
+        { "%c",       { "Character format",      "Printf format specifier for characters" } },
+        { "%p",       { "Pointer format",        "Printf format specifier for pointers" } },
+        { "%x",       { "Hex format",            "Printf format specifier for hexadecimal" } },
+        { "\\n",      { "Newline",               "Newline escape sequence" } },
+
+        { "#include", { "Include directive",     "Includes a header file" } },
+        { "#define",  { "Macro definition",      "Defines a macro" } },
+        { "#ifndef",  { "If not defined",        "Compiles code if macro is not defined" } },
+        { "#endif",   { "End conditional",       "Ends conditional compilation block" } },
+
+        { "+",        { "Addition",              "Adds two operands" } },
+        { "-",        { "Subtraction",           "Subtracts right operand from left" } },
+        { "*",        { "Dereference / multiply","Dereferences a pointer or multiplies operands" } },
+        { "/",        { "Division",              "Divides left operand by right" } },
+        { "=",        { "Assignment",            "Assigns the right value to the left operand" } },
+        { "==",       { "Equality",              "Checks whether two values are equal" } },
+        { "!=",       { "Not equal",             "Checks whether two values differ" } },
+        { "++",       { "Increment",             "Increases value by 1" } },
+        { "--",       { "Decrement",             "Decreases value by 1" } },
+        { "&&",       { "Logical AND",           "True if both operands are true" } },
+        { "||",       { "Logical OR",            "True if either operand is true" } },
+        { "->",       { "Member access",         "Accesses a member through a pointer" } },
+
+        { "(", { "Open parenthesis",  "Starts a function call or condition" } },
+        { ")", { "Close parenthesis", "Ends a function call or condition" } },
+        { "{", { "Open brace",        "Starts a code block" } },
+        { "}", { "Close brace",        "Ends a code block" } },
+        { ";", { "Statement terminator","Ends a statement" } },
+        { ",", { "Separator",          "Separates function arguments" } },
+        { "[", { "Open bracket",       "Start array subscript" } },
+        { "]", { "Close bracket",      "End array subscript" } },
+    };
+    return m;
+}
+
+// Generic fallback description per token type, for tokens not in the table
+// (e.g. user-named variables, arbitrary literals).
+TokenDB::Desc
+generic_For(TokenType type)
+{
+    switch (type) {
+        case TokenType::Keyword:      return { "Keyword",      "A C/C++ language keyword" };
+        case TokenType::Type:         return { "Type",         "A data type" };
+        case TokenType::Identifier:   return { "Identifier",   "A name in the program" };
+        case TokenType::Literal:      return { "Literal",      "A literal value" };
+        case TokenType::Operator:     return { "Operator",     "An operator" };
+        case TokenType::Delimiter:    return { "Delimiter",    "A delimiter" };
+        case TokenType::Preprocessor: return { "Preprocessor", "A preprocessor directive" };
+        case TokenType::Function:     return { "Function",     "A function" };
+        case TokenType::Variable:     return { "Variable",     "A variable" };
+        case TokenType::Comment:      return { "Comment",      "A source comment" };
+        case TokenType::StringLit:    return { "String",       "A string literal" };
+        case TokenType::NumberLit:    return { "Number",       "A numeric literal" };
+        case TokenType::Namespace:    return { "Namespace",    "A namespace" };
+    }
+    return { "", "" };
+}
+
+} // namespace
+
+
+TokenDB::Desc
+TokenDB::describe(const std::string& text, TokenType type)
+{
+    const auto& t = table();
+    auto it = t.find(text);
+    if (it != t.end())
+        return { it->second.short_desc, it->second.long_desc };
+    return generic_For(type);
+}
+
+
+const char*
+TokenDB::type_Name(TokenType t)
+{
+    switch (t) {
+        case TokenType::Keyword:      return "Keyword";
+        case TokenType::Type:         return "Type";
+        case TokenType::Identifier:   return "Identifier";
+        case TokenType::Literal:      return "Literal";
+        case TokenType::Operator:     return "Operator";
+        case TokenType::Delimiter:    return "Delimiter";
+        case TokenType::Preprocessor: return "Preprocessor";
+        case TokenType::Function:     return "Function";
+        case TokenType::Variable:     return "Variable";
+        case TokenType::Comment:      return "Comment";
+        case TokenType::StringLit:    return "String";
+        case TokenType::NumberLit:    return "Number";
+        case TokenType::Namespace:    return "Namespace";
+    }
+    return "Unknown";
+}
+
+
+DynArray<std::string>
+TokenDB::get_TestArrayAllKeywords()
+{
+    return { "float", "return", "main", "0", "++", "(", ",", ";", "\"", "\"", "#define" };
+}
diff --git a/src/cpp_token/token_db.hpp b/src/cpp_token/token_db.hpp
new file mode 100644
index 0000000..8e2d59e
--- /dev/null
+++ b/src/cpp_token/token_db.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <string>
+
+#include "token.hpp"
+#include "utils/globals.hpp"
+
+namespace TokenDB
+{
+    struct Desc { std::string short_desc; std::string long_desc; };
+
+    Desc describe(const std::string& text, TokenType type);
+
+    const char* type_Name(TokenType type);
+
+    DynArray<std::string> get_TestArrayAllKeywords();
+}
diff --git a/src/cpp_token/ts_test.cpp b/src/cpp_token/ts_test.cpp
new file mode 100644
index 0000000..c78c0a6
--- /dev/null
+++ b/src/cpp_token/ts_test.cpp
@@ -0,0 +1,21 @@
+#include <tree_sitter/api.h>
+#include <SDL3/SDL.h>
+#include <string>
+
+extern "C" const TSLanguage* tree_sitter_cpp(void);
+
+void ts_LinkTest()
+{
+    TSParser* p = ts_parser_new();
+    ts_parser_set_language(p, tree_sitter_cpp());
+    const std::string src = "int main(){ return 0; }";
+    TSTree* tree = ts_parser_parse_string(p, nullptr, src.c_str(),
+                                          (uint32_t)src.size());
+    TSNode root = ts_tree_root_node(tree);
+    SDL_Log("tree-sitter root node type: %s", ts_node_type(root));
+    char* s = ts_node_string(root);
+    SDL_Log("tree-sitter tree: %s", s);
+    free(s);
+    ts_tree_delete(tree);
+    ts_parser_delete(p);
+}
diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index f0d6240..8612639 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -3,7 +3,6 @@
 #include <unordered_set>
 
 
-#include "c_token/token_color.hpp"
 #include "core/game.hpp"
 #include "entities/solid_box.hpp"
 #include "entities/world.hpp"
@@ -25,6 +24,23 @@
 #include "wall/text_wall_scroll.hpp"
 
 
+
+static DynArray<std::string>
+normalize_EditorLines(const DynArray<std::string>& lines)
+{
+    DynArray<std::string> out;
+    for (const std::string& line : lines) {
+        if (line.find_first_not_of(' ') == std::string::npos) continue;
+        std::istringstream in(line);
+        std::string joined, tk;
+        while (in >> tk) { if (!joined.empty()) joined += ' '; joined += tk; }
+        out.push_back(joined);
+    }
+    return out;
+}
+
+
+
 GameState::GameState(size_t starting_level)
 
 {
@@ -482,6 +498,9 @@ GameState::projectile_LandOnWall(ProjectileResult& result)
 
     for (TextWallPtr& wt : placed)
         _ctx.wall_texts.push_back(std::move(wt));
+
+    if (!editor_group.empty())
+        editor_SyncFile();
 }
 
 
@@ -512,9 +531,11 @@ void
 GameState::walltext_HandleShootDown(ProjectileResult& result,
                                   DynArray<size_t>& to_erase)
 {
+    bool removed_editor = false;
     if (result.wall_text_index < _ctx.wall_texts.size()) {
         TextWallPtr& wt = _ctx.wall_texts[result.wall_text_index];
         if (wt && wt->text3d) {
+            removed_editor = (wt->role == Wall::Role::Editor);
             _editor_wall.on_WordRemoved(wt->text3d.get());
 
             Text3dPtr orphan = std::move(wt->text3d);
@@ -529,6 +550,9 @@ GameState::walltext_HandleShootDown(ProjectileResult& result,
         if (!w) continue;
         _ctx.drop_WordToFloor(std::move(w), result.vel);
     }
+
+    if (removed_editor)
+        editor_SyncFile();
 }
 
 
@@ -567,21 +591,21 @@ GameState::handle_Input(FrameInput& input, double dt) {
         input.vim_keys.clear();
         switch (req) {
             case WallConnection::Request::Write:
-                editor_CommitToWall(_wall_connection.lines());
+                editor_CommitToWall(_wall_connection.lines(), false);
                 _wall_connection.mark_Saved();
                 break;
             case WallConnection::Request::WriteQuit:
-                editor_CommitToWall(_wall_connection.lines());
+                editor_CommitToWall(_wall_connection.lines(), true);
                 _wall_connection.end();
                 _frame_input.capture_vim = false;
                 break;
             case WallConnection::Request::Exit:
-                editor_CommitToWall(_wall_connection.lines());
+                editor_CommitToWall(_wall_connection.lines(), true);
                 _wall_connection.end();
                 _frame_input.capture_vim = false;
                 break;
             case WallConnection::Request::QuitDiscard:
-                editor_CommitToWall(_wall_connection.lines());
+                editor_CommitToWall(_wall_connection.lines(), true);
                 _wall_connection.end();
                 _frame_input.capture_vim = false;
                 break;
@@ -688,6 +712,13 @@ GameState::update(double dt, const GlRenderer* gl)
         level_TryAdvance(dt);
     }
 
+    { FP_ZONE("update.highlight");
+        DynArray<HiSpan> spans;
+        uint64_t gen;
+        if (_highlight_sys.poll(spans, gen) && gen == _editor_gen)
+            _editor_file.apply_Spans(spans);
+    }
+
     input.look_x = 0;
     input.look_y = 0;
     input.commands.clear();
@@ -713,7 +744,11 @@ GameState::render(SDL_Window& window, GlRenderer& renderer)
     { FP_ZONE("render.set_Light");   renderer.set_Light(*_ctx.light); }
 
     { FP_ZONE("render.world");        _ctx.world ->draw(renderer, *_ctx.camera, *_ctx.light); }
-    { FP_ZONE("render._editor_wall"); _editor_wall.draw(renderer, *_ctx.camera, *_ctx.light); }
+
+    { FP_ZONE("render._editor_wall");
+        if (!_editor_hidden)
+            _editor_wall.draw(renderer, *_ctx.camera, *_ctx.light);
+    }
 
     { FP_ZONE("render.scenery");
         for (const GameObjectPtr& s : _ctx.scenery)
@@ -726,8 +761,10 @@ GameState::render(SDL_Window& window, GlRenderer& renderer)
     }
 
     { FP_ZONE("render.wall_texts");
-        for (const TextWallPtr& wt : _ctx.wall_texts)
+        for (const TextWallPtr& wt : _ctx.wall_texts) {
+            if (_editor_hidden && wt && wt->role == Wall::Role::Editor) continue;
             wt->draw(renderer, *_ctx.camera, *_ctx.light);
+        }
     }
 
     { FP_ZONE("render.wall_labels");
@@ -1120,7 +1157,11 @@ GameState::wallconnect_BeginProgram()
 void
 GameState::wallconnect_BeginEditor()
 {
-    DynArray<std::string> lines = editor_AbsorbLines();
+    DynArray<std::string> lines = editor_ReadLines();
+    _editor_absorbed = normalize_EditorLines(lines);
+
+    _editor_hidden = true;
+
     _frame_input.capture_vim = true;
     _frame_input.vim_keys.clear();
 
@@ -1185,6 +1226,21 @@ GameState::editor_AbsorbLines()
 }
 
 
+DynArray<std::string>
+GameState::editor_ReadLines() const
+{
+    std::string src = TextWall::extract_Text(_ctx.wall_texts, Wall::Role::Editor);
+    DynArray<std::string> lines;
+    std::string cur;
+    for (char ch : src) {
+        if (ch == '\n') { lines.push_back(cur); cur.clear(); }
+        else            { cur.push_back(ch); }
+    }
+    if (!cur.empty()) lines.push_back(cur);
+    return lines;
+}
+
+
 void
 GameState::editor_PlaceLines(const DynArray<std::string>& lines)
 {
@@ -1204,12 +1260,17 @@ GameState::editor_PlaceLines(const DynArray<std::string>& lines)
     const double bottom_y = floor_y + 1.2;
     double y = bottom_y + static_cast<double>(n_lines - 1) * lh;
 
+    struct Tok { Text3dPtr t; double half; };
+    struct Row { DynArray<Tok> toks; double width; double y; };
+    DynArray<Row> rows;
+
+    double max_width = 0.0;
     for (const std::string& line : lines) {
         if (line.find_first_not_of(' ') == std::string::npos)
             continue;
 
-        struct Tok { Text3dPtr t; double half; };
-        DynArray<Tok> toks;
+        Row row;
+        row.y = y;
         double total_w = 0.0;
 
         std::istringstream in(line);
@@ -1218,31 +1279,69 @@ GameState::editor_PlaceLines(const DynArray<std::string>& lines)
             Text3dPtr t = make_Text3d(tk, Text3d::depth_wall, 0.003f,
                                       Vec3{ x_wall, y, 0.0 }, ConfigColor::wall);
             t->yaw = tf.yaw;
-            TokenColor::apply(t.get());
             const double half = static_cast<double>(t->half_extent);
             total_w += 2.0 * half;
-            toks.push_back({ std::move(t), half });
+            row.toks.push_back({ std::move(t), half });
         }
-        if (toks.empty()) { y -= lh; continue; }
-        total_w += gap * static_cast<double>(toks.size() - 1);
+        if (row.toks.empty()) { y -= lh; continue; }
+        total_w += gap * static_cast<double>(row.toks.size() - 1);
+        row.width = total_w;
+        max_width = SDL_max(max_width, total_w);
 
+        rows.push_back(std::move(row));
+        y -= lh;
+    }
+    if (rows.empty()) return;
+
+    const double left_z = max_width * 0.5;
+
+    for (Row& row : rows) {
         DynArray<Text3d*> placed;
-        double cursor = total_w * 0.5;
-        for (Tok& tok : toks) {
+        double cursor = left_z;
+        for (Tok& tok : row.toks) {
             const double center_z = cursor - tok.half;
-            tok.t->pos = Vec3{ x_wall, y, center_z };
+            tok.t->pos = Vec3{ x_wall, row.y, center_z };
             placed.push_back(tok.t.get());
             _ctx.add_WallText(Wall::Position::NegX, Wall::Role::Editor, std::move(tok.t));
             cursor = center_z - tok.half - gap;
         }
-        _editor_wall.add_PlacedLine(placed, y);
-
-        y -= lh;
+        _editor_wall.add_PlacedLine(placed, row.y);
     }
+
+    editor_SyncFile();
 }
 
+
 void
-GameState::editor_CommitToWall(const DynArray<std::string>& lines)
+GameState::editor_CommitToWall(const DynArray<std::string>& lines, bool ending)
 {
+    DynArray<std::string> incoming = normalize_EditorLines(lines);
+
+    bool same = (incoming.size() == _editor_absorbed.size());
+    if (same)
+        for (size_t i = 0; i < incoming.size(); ++i)
+            if (incoming[i] != _editor_absorbed[i]) { same = false; break; }
+
+    if (ending) _editor_hidden = false;
+
+    if (same) return;
+
+    editor_Clear();
     editor_PlaceLines(lines);
+    _editor_absorbed = incoming;
+}
+
+void
+GameState::editor_SyncFile()
+{
+    _editor_file.clear();
+    for (const DynArray<Text3d*>& line : _editor_wall.lines_InOrder()) {
+        TextFile::Line fl;
+        for (Text3d* w : line)
+            if (w) fl.tokens.push_back(TextFile::Token{ w->text, 0, 0, w });
+        _editor_file.lines.push_back(std::move(fl));
+    }
+
+    ++_editor_gen;
+    _highlight_sys.request(_editor_file.render_Source(), _editor_gen);
 }
diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp
index fa84f45..8d6a19e 100644
--- a/src/states/game_state.hpp
+++ b/src/states/game_state.hpp
@@ -12,6 +12,7 @@
 #include "systems/carry_system.hpp"
 #include "systems/celebration_system.hpp"
 #include "systems/compiler_system.hpp"
+#include "systems/cpp_highlight_system.hpp"
 #include "systems/dyn_loader_system.hpp"
 #include "systems/freelook_system.hpp"
 #include "systems/hint_system.hpp"
@@ -26,6 +27,7 @@
 #include "systems/wall_connection.hpp"
 #include "systems/word_ai_system.hpp"
 #include "systems/word_anim.hpp"
+#include "text_3d/text_file.hpp"
 #include "text_3d/vim_buffer.hpp"
 #include "utils/carry_math.hpp"
 #include "utils/globals.hpp"
@@ -54,8 +56,10 @@ class GameState : public IState
     cpputils::unique_ptr<WordAiSystem>     _word_ai_sys;
     cpputils::unique_ptr<WordAnimSystem>   _word_anim_sys;
 
-    double _pick_cooldown  { 0.0 };
-    double _shoot_cooldown { 0.0 };
+    bool     _editor_hidden  { false };
+    uint64_t _editor_gen     { 0 };
+    double   _pick_cooldown  { 0.0 };
+    double   _shoot_cooldown { 0.0 };
 
     std::string    _typing_last;
 
@@ -64,11 +68,14 @@ class GameState : public IState
     FrameInput     _frame_input;
     GameContext    _ctx;
     WallConnection _wall_connection;
+    TextFile       _editor_file;
+    CppHighlightSystem _highlight_sys;
 
 
     DynArray<GameObject*>   _dyn_scenery;
     DynArray<Text3d*>       _typing_words;
     DynArray<Vec3>          _freelook_return_from;
+    DynArray<std::string>   _editor_absorbed;
     DynArray<Projectile>    _projectiles;
     DynArray<WallLabel>     _wall_labels;
     DynArray<WordAnim_Add>  _anims_add;
@@ -81,9 +88,12 @@ class GameState : public IState
 
     DynArray<std::string>
          editor_AbsorbLines  ();
+    DynArray<std::string>
+         editor_ReadLines    () const;
     void editor_Clear        ();
-    void editor_CommitToWall (const DynArray<std::string>& lines);
+    void editor_CommitToWall (const DynArray<std::string>& lines, bool ending);
     void editor_PlaceLines   (const DynArray<std::string>& lines);
+    void editor_SyncFile     ();
 
 
     bool on_Pick         ();
diff --git a/src/systems/cpp_highlight_system.hpp b/src/systems/cpp_highlight_system.hpp
new file mode 100644
index 0000000..baf3268
--- /dev/null
+++ b/src/systems/cpp_highlight_system.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <atomic>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include "cpp_token/cpp_highlight.hpp"
+#include "utils/globals.hpp"
+
+class CppHighlightSystem {
+public:
+    ~CppHighlightSystem() { if (_worker.joinable()) _worker.join(); }
+
+    // Request a lex of `source` tagged with `gen`. No-op if source unchanged
+    // since last request. Single-flight: coalesces while a lex is in flight.
+    void request(const std::string& source, uint64_t gen)
+    {
+        if (source == _last_requested) return;     // content unchanged → skip
+        _last_requested = source;
+
+        if (_running.load()) { _redo = true; return; }   // coalesce
+        spawn(source, gen);
+    }
+
+    // Drain finished results. Returns true + fills out if a lex completed this
+    // call; caller checks gen against current content before applying.
+    bool poll(DynArray<HiSpan>& out, uint64_t& out_gen)
+    {
+        if (!_done.load()) return false;
+        {
+            std::lock_guard<std::mutex> lk(_mtx);
+            out     = _pending;
+            out_gen = _result_gen;
+        }
+        _done.store(false);
+        if (_worker.joinable()) _worker.join();
+        _running.store(false);
+
+        if (_redo) {                       // content changed mid-lex → relex
+            _redo = false;
+            spawn(_last_requested, _last_gen_pending);
+        }
+        return true;
+    }
+
+private:
+    void spawn(const std::string& source, uint64_t gen)
+    {
+        if (_worker.joinable()) _worker.join();
+        _running.store(true);
+        _done.store(false);
+        _last_gen_pending = gen;
+        _worker = std::thread([this, source, gen]() {
+            DynArray<HiSpan> spans = CppHighlight::classify(source);
+            {
+                std::lock_guard<std::mutex> lk(_mtx);
+                _pending    = std::move(spans);
+                _result_gen = gen;
+            }
+            _done.store(true);
+        });
+    }
+
+    std::thread       _worker;
+    std::mutex        _mtx;
+    std::atomic<bool> _running { false };
+    std::atomic<bool> _done    { false };
+    DynArray<HiSpan>  _pending;
+    uint64_t          _result_gen { 0 };
+    uint64_t          _last_gen_pending { 0 };
+    std::string       _last_requested;
+    bool              _redo { false };
+};
diff --git a/src/systems/wall_connection.cpp b/src/systems/wall_connection.cpp
index 23b2e63..c44a937 100644
--- a/src/systems/wall_connection.cpp
+++ b/src/systems/wall_connection.cpp
@@ -2,7 +2,8 @@
 #include <cstdlib>
 #include <sstream>
 
-#include "c_token/token_color.hpp"
+#include "cpp_token/cpp_highlight.hpp"
+#include "cpp_token/token_color.hpp"
 #include "entities/camera.hpp"
 #include "entities/world.hpp"
 #include "rendering/gl_renderer.hpp"
@@ -13,6 +14,31 @@
 #include "wall/wall.hpp"
 
 
+static double
+mono_CharWidth()
+{
+    static double w = 0.0;
+    if (w == 0.0) {
+        Text3dPtr m1 = make_Text3d("M",  Text3d::depth_wall, 0.003f, Vec3{0,0,0}, ConfigColor::white);
+        Text3dPtr m2 = make_Text3d("MM", Text3d::depth_wall, 0.003f, Vec3{0,0,0}, ConfigColor::white);
+        const double w1 = 2.0 * static_cast<double>(m1->half_extent);
+        const double w2 = 2.0 * static_cast<double>(m2->half_extent);
+        w = w2 - w1;
+    }
+    return w;
+}
+static double
+mono_HalfExtentY()
+{
+    static double h = 0.0;
+    if (h == 0.0) {
+        Text3dPtr m = make_Text3d("M", Text3d::depth_wall, 0.003f, Vec3{0,0,0}, ConfigColor::white);
+        h = static_cast<double>(m->half_extent_y);
+    }
+    return h;
+}
+
+
 void
 WallConnection::begin_Program(const Vec3& pult_pos)
 {
@@ -126,9 +152,6 @@ WallConnection::lines() const
 void
 WallConnection::rebuild_Editor()
 {
-    _rows.clear();
-    _linenums.clear();
-
     const float ws = World::get_BoxScale();
     Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws);
     const double lh    = ConfigWall::line_height;
@@ -142,124 +165,153 @@ WallConnection::rebuild_Editor()
 
     const auto& lines = _vim.lines();
 
-    // ── Pass 1: build line-number labels (orange), measure gutter width. ──
-    struct NumRow { Text3dPtr num; };
-    DynArray<NumRow> nums;
-    nums.reserve(lines.size());
+    const double char_w = mono_CharWidth();
+    const double he_y   = mono_HalfExtentY();
+
+    // Can we reuse cached meshes? Only when the line COUNT is unchanged — then
+    // a line whose text is identical keeps its existing token meshes (we only
+    // reposition). Lines whose text changed are rebuilt. Count change (enter,
+    // dd) → full rebuild (rare vs. per-char typing).
+    const bool can_reuse = (_cached_line_text.size() == lines.size());
+
+    // Move old state aside so we can salvage rows from it.
+    DynArray<EditorRow>  old_rows  = std::move(_rows);
+    DynArray<Text3dPtr>  old_nums  = std::move(_linenums);
+    _rows.clear();
+    _linenums.clear();
 
+    // ── gutter width: build/reuse line-number meshes ──
+    // Numbers only change when the line count changes, so on reuse we keep them.
+    DynArray<Text3dPtr> nums;
+    nums.reserve(lines.size());
     double gutter_w = 0.0;
-    for (size_t i = 0; i < lines.size(); ++i) {
-        Text3dPtr num = make_Text3d(std::to_string(i + 1), Text3d::depth_wall, 0.0022f,
-                                    tf.position, ConfigColor::orange);
-        num->yaw = tf.yaw;
-        num->set_Color(ConfigColor::orange);   // digit would token-lookup to red; force orange
-        gutter_w = SDL_max(gutter_w, 2.0 * static_cast<double>(num->half_extent));
-        nums.push_back({ std::move(num) });
+    if (can_reuse && old_nums.size() == lines.size()) {
+        for (size_t i = 0; i < lines.size(); ++i) {
+            gutter_w = SDL_max(gutter_w, 2.0 * static_cast<double>(old_nums[i]->half_extent));
+            nums.push_back(std::move(old_nums[i]));
+        }
+    } else {
+        for (size_t i = 0; i < lines.size(); ++i) {
+            Text3dPtr num = make_Text3d(std::to_string(i + 1), Text3d::depth_wall, 0.0022f,
+                                        tf.position, ConfigColor::orange);
+            num->yaw = tf.yaw;
+            num->set_Color(ConfigColor::orange);
+            gutter_w = SDL_max(gutter_w, 2.0 * static_cast<double>(num->half_extent));
+            nums.push_back(std::move(num));
+        }
     }
     const double gutter_gap = 0.6;
     const double code_u0    = gutter_w + gutter_gap;
 
-    // ── Pass 2: build per-token meshes for each line; measure block width. ──
-    // We need a uniform char width per row so the block-relative cursor lands
-    // correctly. Measure each line by rendering one hidden full-line mesh just
-    // to get its total width; tokens are then laid out within that width.
-    struct BuiltRow {
-        DynArray<Text3dPtr> toks;     // colored token meshes
-        DynArray<double>    tok_half; // half-extent per token
-        double line_w   { 0.0 };      // full line render width (incl. spaces)
-        double char_w   { 0.18 };
-        double he_y     { 0.2 };
-        size_t n_chars  { 0 };
-    };
+    // ── Per-line token meshes + canonical source for lexing. ──
+    struct BuiltTok { Text3dPtr t; size_t col; size_t len; size_t byte_begin; size_t byte_end; };
+    struct BuiltRow { DynArray<BuiltTok> toks; };
     DynArray<BuiltRow> built;
     built.reserve(lines.size());
 
+    std::string source;   // canonical buffer for tree-sitter (token-joined)
+
     double block_code_w = 0.0;
     for (size_t i = 0; i < lines.size(); ++i) {
-        BuiltRow br;
         const std::string& s = lines[i];
-        br.n_chars = s.size();
-
-       // Measure the full line (spaces included) for a faithful char width.
-        const bool drawable = s.find_first_not_of(' ') != std::string::npos;
-        if (drawable) {
-            Text3dPtr meas = make_Text3d(s, Text3d::depth_wall, 0.003f, tf.position, ConfigColor::white);
-            br.line_w = 2.0 * static_cast<double>(meas->half_extent);
-            br.he_y   = static_cast<double>(meas->half_extent_y);
-            br.char_w = (s.empty() ? 0.18 : br.line_w / static_cast<double>(s.size()));
-            // meas is discarded — measurement only.
-
-            // Build per-token colored meshes.
-            std::istringstream in(s);
-            std::string tk;
-            while (in >> tk) {
-                Text3dPtr t = make_Text3d(tk, Text3d::depth_wall, 0.003f, tf.position, ConfigColor::white);
+        block_code_w = SDL_max(block_code_w, char_w * static_cast<double>(s.size()));
+
+        BuiltRow br;
+        const bool reuse_line = can_reuse
+                             && i < _cached_line_text.size()
+                             && _cached_line_text[i] == s
+                             && i < old_rows.size();
+
+        // Walk tokens once; build meshes (reuse or fresh) AND record byte ranges
+        // in the canonical source (space-joined tokens, newline-joined lines).
+        size_t pos = 0, ti = 0;
+        bool   first_tok = true;
+        while (pos < s.size()) {
+            while (pos < s.size() && s[pos] == ' ') ++pos;
+            if (pos >= s.size()) break;
+            size_t start = pos; std::string tk;
+            while (pos < s.size() && s[pos] != ' ') { tk += s[pos]; ++pos; }
+
+            if (!first_tok) source += ' ';
+            first_tok = false;
+            const size_t b0 = source.size();
+            source += tk;
+            const size_t b1 = source.size();
+
+            Text3dPtr t;
+            if (reuse_line && ti < old_rows[i].toks.size()) {
+                t = std::move(old_rows[i].toks[ti]);
+            } else {
+                t = make_Text3d(tk, Text3d::depth_wall, 0.003f, tf.position, ConfigColor::white);
                 t->yaw = tf.yaw;
-                TokenColor::apply(t.get());
-                br.tok_half.push_back(static_cast<double>(t->half_extent));
-                br.toks.push_back(std::move(t));
+                // NO TokenColor::apply — coloring comes solely from tree-sitter below.
             }
+            br.toks.push_back({ std::move(t), start, tk.size(), b0, b1 });
+            ++ti;
         }
-        block_code_w = SDL_max(block_code_w, br.line_w);
+        source += '\n';
+
         built.push_back(std::move(br));
     }
 
     const double block_w      = code_u0 + block_code_w;
     const double z_block_left = block_w * 0.5;
-    _code_left_z = z_block_left - code_u0;   // left edge of the CODE area (char 0 starts here)
+    _code_left_z = z_block_left - code_u0;
 
-    // ── Pass 3: place numbers + tokens, fill _rows for the cursor. ──
+    // ── Place numbers + tokens (always reposition — cheap). ──
     for (size_t i = 0; i < lines.size(); ++i) {
         const double row_y = _row0_y - static_cast<double>(i) * lh;
 
-        // line number in the gutter (right-edge of number at z_block_left)
-        if (nums[i].num) {
-            const double he = static_cast<double>(nums[i].num->half_extent);
-            nums[i].num->pos   = tf.position;
-            nums[i].num->pos.y = row_y;
-            nums[i].num->pos.z = z_block_left - he;
-            _linenums.push_back(std::move(nums[i].num));
+        if (nums[i]) {
+            const double he = static_cast<double>(nums[i]->half_extent);
+            nums[i]->pos   = tf.position;
+            nums[i]->pos.y = row_y;
+            nums[i]->pos.z = z_block_left - he;
+            _linenums.push_back(std::move(nums[i]));
         }
 
         EditorRow er;
         er.left_z   = _code_left_z;
-        er.char_w   = built[i].char_w;
-        er.he_y     = built[i].he_y;
+        er.char_w   = char_w;
+        er.he_y     = he_y;
         er.line_x   = tf.position.x;
         er.has_text = !built[i].toks.empty();
 
-        // Lay tokens left→right from the code-left edge, advancing by token
-        // width + one space-width so columns stay aligned with char_w.
-        const std::string& s = lines[i];
-        double cursor_z = _code_left_z;   // left edge of next token
-        size_t char_pos = 0;              // running character column
-        std::istringstream in(s);
-        std::string tk;
-        size_t ti = 0;
-        // Walk the string to honor leading/internal spaces between tokens.
-        {
-            size_t pos = 0;
-            while (ti < built[i].toks.size()) {
-                // advance over spaces
-                while (pos < s.size() && s[pos] == ' ') { cursor_z -= built[i].char_w; ++pos; ++char_pos; }
-                // place token ti at cursor_z (left edge), center it
-                const double half = built[i].tok_half[ti];
-                Text3dPtr& t = built[i].toks[ti];
-                t->pos   = tf.position;
-                t->pos.y = row_y;
-                t->pos.z = cursor_z - half;          // center = left edge − half
-                // advance cursor by the token's character count
-                size_t tok_len = 0;
-                while (pos < s.size() && s[pos] != ' ') { ++pos; ++tok_len; ++char_pos; }
-                cursor_z -= built[i].char_w * static_cast<double>(tok_len);
-                er.toks.push_back(std::move(t));
-                ++ti;
-            }
+        for (BuiltTok& bt : built[i].toks) {
+            const double left_z = _code_left_z - static_cast<double>(bt.col) * char_w;
+            const double tok_w  = char_w * static_cast<double>(bt.len);
+            bt.t->pos   = tf.position;
+            bt.t->pos.y = row_y;
+            bt.t->pos.z = left_z - tok_w * 0.5;
+            er.toks.push_back(std::move(bt.t));
         }
 
         _rows.push_back(std::move(er));
     }
 
+    // Snapshot line text for next reuse check.
+    _cached_line_text.clear();
+    for (const std::string& s : lines) _cached_line_text.push_back(s);
+
+    // ── Color every token via tree-sitter (same classifier as the wall). ──
+    {
+        DynArray<HiSpan> spans = CppHighlight::classify(source);
+        // For each span, color the tokens whose byte range overlaps it.
+        // built[] holds byte ranges; _rows[] holds the meshes in the same order.
+        for (const HiSpan& span : spans) {
+            for (size_t i = 0; i < built.size() && i < _rows.size(); ++i) {
+                for (size_t j = 0; j < built[i].toks.size() && j < _rows[i].toks.size(); ++j) {
+                    const BuiltTok& bt = built[i].toks[j];
+                    // overlap test: [bt.byte_begin,bt.byte_end) ∩ [span.begin,span.end)
+                    if (bt.byte_begin < span.byte_end && span.byte_begin < bt.byte_end) {
+                        if (_rows[i].toks[j])
+                            _rows[i].toks[j]->set_Color(TokenColor::get(span.type));
+                    }
+                }
+            }
+        }
+    }
+
     rebuild_Cmdline();
 }
 
diff --git a/src/systems/wall_connection.hpp b/src/systems/wall_connection.hpp
index 48cbc00..1c4487c 100644
--- a/src/systems/wall_connection.hpp
+++ b/src/systems/wall_connection.hpp
@@ -69,7 +69,8 @@ private:
 
     VimBuffer _vim;
 
-    DynArray<EditorRow> _rows;
+    DynArray<std::string> _cached_line_text;
+    DynArray<EditorRow>   _rows;
     DynArray<Text3dPtr>   _linenums;
     DynArray<std::string> _status;
 
diff --git a/src/systems/word_ai_system.cpp b/src/systems/word_ai_system.cpp
index 6ceec1f..5cbc296 100644
--- a/src/systems/word_ai_system.cpp
+++ b/src/systems/word_ai_system.cpp
@@ -1,11 +1,12 @@
 #include <SDL3/SDL.h>
 #include <cstdlib>
 
-#include "c_token/token_color.hpp"
+#include "cpp_token/token_color.hpp"
 #include "entities/player.hpp"
 #include "entities/world.hpp"
 #include "systems/carry_system.hpp"
 #include "systems/collision.hpp"
+#include "systems/cpp_highlight_system.hpp"
 #include "utils/config.hpp"
 #include "word_ai_system.hpp"
 
@@ -46,12 +47,12 @@ WordAiSystem::update(DynArray<Text3dPtr>& words,
                 if (!s) continue;
                 s->body.mode = PhysicsMode::Dynamic;
                 s->body.vel  = w->body.vel * 0.5;
-                s->set_Color(s->token_info ? TokenColor::get(s->token_info->type) : ConfigColor::floor);
+                s->set_Color(TokenColor::get(CppHighlight::classify_Word(s->text)));
                 released.push_back(std::move(s));
             }
             w->ai.stolen.clear();
             w->ai.state = Text3dAI::State::Idle;
-            w->set_Color(w->token_info ? TokenColor::get(w->token_info->type) : ConfigColor::floor);
+            w->set_Color(TokenColor::get(CppHighlight::classify_Word(w->text)));
             continue;
         }
 
@@ -270,7 +271,7 @@ WordAiSystem::release_StolenWords(Text3d& w, DynArray<Text3dPtr>& out)
         if (!s) continue;
         s->body.mode = PhysicsMode::Dynamic;
         s->body.vel  = { w.body.vel.x * 0.5, 2.0, w.body.vel.z * 0.5 };
-        s->set_Color(s->token_info ? TokenColor::get(s->token_info->type) : ConfigColor::floor);
+        s->set_Color(TokenColor::get(CppHighlight::classify_Word(s->text)));
         out.push_back(std::move(s));
     }
     w.ai.stolen.clear();
@@ -324,7 +325,7 @@ WordAiSystem::enter_Idle(Text3d& w)
     w.ai.state_timer = 0.0;
     w.body.mode      = PhysicsMode::Hover;
     w.body.hover_height = w.ai.hover_height_saved;
-    w.set_Color(w.token_info ? TokenColor::get(w.token_info->type) : ConfigColor::floor);
+    w.set_Color(TokenColor::get(CppHighlight::classify_Word(w.text)));
 }
 
 
diff --git a/src/text_3d/text_3d.cpp b/src/text_3d/text_3d.cpp
index 2b26986..b89b565 100644
--- a/src/text_3d/text_3d.cpp
+++ b/src/text_3d/text_3d.cpp
@@ -1,10 +1,10 @@
 #include <SDL3/SDL.h>
 
-#include "c_token/token_color.hpp"
-#include "c_token/token_db.hpp"
+#include "cpp_token/token_color.hpp"
 #include "entities/camera.hpp"
 #include "entities/world.hpp"
 #include "rendering/gl_renderer.hpp"
+#include "systems/cpp_highlight_system.hpp"
 #include "text_3d.hpp"
 #include "utils/frame_profiler.hpp"
 #include "utils/globals.hpp"
@@ -81,10 +81,10 @@ Text3d::Text3d(const std::string& txt,
        : GameObject(position, ObjectType::Text,
                     CollisionShape{}, RigidBody{PhysicsMode::Hover},
                     true, true, true),
-         text(txt), color(colr), depth(dpth), scale(scle), token_info(TokenDB::find_OrSynthesize(text))
+         text(txt), color(colr), depth(dpth), scale(scle)
 {
-    if (token_info)
-        set_Color(TokenColor::get(token_info->type));
+    set_Color(TokenColor::get(CppHighlight::classify_Word(text)));
+
 
     rebuild_Mesh();
     body.hover_phase = Random::unit() * SDL_PI_D * 2.0;
@@ -149,7 +149,7 @@ void
 Text3d::set_Look(SDL_FColor c, float new_depth)
 {
     if (c.r == -1.0f)
-        color = TokenColor::get_ByString(text);
+        color = TokenColor::get(CppHighlight::classify_Word(text));
     else if (c.g == -1.0f) { }
     else color = c;
 
diff --git a/src/text_3d/text_3d.hpp b/src/text_3d/text_3d.hpp
index d6492a1..9d85683 100644
--- a/src/text_3d/text_3d.hpp
+++ b/src/text_3d/text_3d.hpp
@@ -3,7 +3,7 @@
 #include <SDL3/SDL.h>
 #include <string>
 
-#include "c_token/token_db.hpp"
+#include "cpp_token/token_db.hpp"
 #include "cpputils/unique_ptr.hpp"
 #include "entities/game_object.hpp"
 #include "text_3d/text_mesh.hpp"
@@ -89,8 +89,6 @@ struct Text3d final : public GameObject {
     float      half_extent{0.0f};
     float      pitch{0.0f};
     Vec3       ribbon_local{};
-    const Token* token_info;
-
 
     Text3d(const std::string& txt,
            float depth, float scale,
diff --git a/src/text_3d/text_file.hpp b/src/text_3d/text_file.hpp
new file mode 100644
index 0000000..21d2b93
--- /dev/null
+++ b/src/text_3d/text_file.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <string>
+#include "cpp_token/cpp_highlight.hpp"   // HiSpan
+#include "cpp_token/token_color.hpp"     // TokenColor::get
+#include "text_3d/text_3d.hpp"
+#include "utils/globals.hpp"
+
+struct TextFile {
+    struct Token {
+        std::string text;
+        size_t      byte_begin { 0 };
+        size_t      byte_end   { 0 };
+        Text3d*     view       { nullptr };
+    };
+    struct Line {
+        DynArray<Token> tokens;
+    };
+
+    DynArray<Line> lines;
+
+    void clear() { lines.clear(); }
+
+    std::string render_Source()
+    {
+        std::string out;
+        for (Line& line : lines) {
+            for (size_t ti = 0; ti < line.tokens.size(); ++ti) {
+                if (ti) out += ' ';
+                Token& tk = line.tokens[ti];
+                tk.byte_begin = out.size();
+                out += tk.text;
+                tk.byte_end = out.size();
+            }
+            out += '\n';
+        }
+        return out;
+    }
+
+    Token* token_At(size_t off)
+    {
+        for (Line& line : lines)
+            for (Token& tk : line.tokens)
+                if (off >= tk.byte_begin && off < tk.byte_end)
+                    return &tk;
+        return nullptr;
+    }
+
+    void apply_Spans(const DynArray<HiSpan>& spans)
+    {
+        for (Line& line : lines) {
+            for (Token& tk : line.tokens) {
+                if (!tk.view) continue;
+
+                size_t best_overlap = 0;
+                TokenType best_type = TokenType::Identifier;
+                bool found = false;
+                for (const HiSpan& s : spans) {
+                    size_t lo = (tk.byte_begin > s.byte_begin) ? tk.byte_begin : s.byte_begin;
+                    size_t hi = (tk.byte_end   < s.byte_end)   ? tk.byte_end   : s.byte_end;
+                    if (lo < hi) {
+                        size_t ov = hi - lo;
+                        if (ov > best_overlap) { best_overlap = ov; best_type = s.type; found = true; }
+                    }
+                }
+                if (found) tk.view->set_Color(TokenColor::get(best_type));
+            }
+        }
+    }
+
+    DynArray<std::string> as_Lines() const
+    {
+        DynArray<std::string> out;
+        for (const Line& line : lines) {
+            std::string s;
+            for (size_t ti = 0; ti < line.tokens.size(); ++ti) {
+                if (ti) s += ' ';
+                s += line.tokens[ti].text;
+            }
+            out.push_back(s);
+        }
+        return out;
+    }
+};
diff --git a/src/wall/editor_wall.cpp b/src/wall/editor_wall.cpp
index a6905ea..9017def 100644
--- a/src/wall/editor_wall.cpp
+++ b/src/wall/editor_wall.cpp
@@ -435,6 +435,34 @@ EditorWall::update(double dt, const Vec3& eye_pos)
 }
 
 
+DynArray<DynArray<Text3d*>>
+EditorWall::lines_InOrder() const
+{
+    // Order rows top→bottom (largest y first), matching update()'s numbering.
+    DynArray<size_t> order;
+    order.reserve(_lines.size());
+    for (size_t i = 0; i < _lines.size(); ++i)
+        if (!_lines[i].words.empty()) order.push_back(i);
+
+    std::sort(order.begin(), order.end(),
+        [&](size_t a, size_t b){ return _lines[a].y > _lines[b].y; });
+
+    DynArray<DynArray<Text3d*>> out;
+    out.reserve(order.size());
+    for (size_t oi : order) {
+        // Within a row, visual left→right is decreasing z (left = larger z).
+        DynArray<Text3d*> ws;
+        ws.reserve(_lines[oi].words.size());
+        for (Text3d* w : _lines[oi].words)
+            if (w) ws.push_back(w);
+        std::sort(ws.begin(), ws.end(),
+            [](const Text3d* a, const Text3d* b){ return a->pos.z > b->pos.z; });
+        out.push_back(std::move(ws));
+    }
+    return out;
+}
+
+
 DynArray<EditorWall::NumberHit>
 EditorWall::number_Hitboxes() const
 {
@@ -491,6 +519,7 @@ EditorWall::draw(GlRenderer& gl, const Camera& cam, const Light& light) const
     verts  .reserve(_lines.size() * 4 * 9);
     indices.reserve(_lines.size() * 6);
 
+    /*
     const float ws = World::get_BoxScale();
     const float x_front = -ws + 0.02f;
 
@@ -548,6 +577,7 @@ EditorWall::draw(GlRenderer& gl, const Camera& cam, const Light& light) const
 
         gl.set_ColorUseVertex(false);
     }
+    */
 
     // Orange line numbers (own meshes; positioned/numbered in update()).
     for (const Line& l : _lines) {
diff --git a/src/wall/editor_wall.hpp b/src/wall/editor_wall.hpp
index e64baa1..feb666c 100644
--- a/src/wall/editor_wall.hpp
+++ b/src/wall/editor_wall.hpp
@@ -49,6 +49,7 @@ public:
     static bool   is_LineNumberHit(size_t idx) { return idx >= HIT_BASE; }
     static size_t line_Of         (size_t idx) { return idx - HIT_BASE; }
 
+
     struct NumberHit {
         size_t index;
         Vec3   aabb_min;
@@ -70,6 +71,8 @@ public:
     double snap_RowY  (double y) const;
     double scroll_DeltaToEye(size_t line_id, double eye_y) const;
     size_t line_Count       () const { return _lines.size(); }
+    DynArray<DynArray<Text3d*>>
+           lines_InOrder() const;
 
     DynArray<NumberHit>
            number_Hitboxes() const;
diff --git a/src/wall/text_wall.cpp b/src/wall/text_wall.cpp
index afbf9f7..7abb641 100644
--- a/src/wall/text_wall.cpp
+++ b/src/wall/text_wall.cpp
@@ -1,4 +1,5 @@
 #include "entities/world.hpp"
+#include "systems/cpp_highlight_system.hpp"
 #include "text_wall.hpp"
 #include "utils/config.hpp"
 
@@ -128,43 +129,30 @@ TextWall::draw(GlRenderer& g, const Camera& c, const Light& l) const
 
 
 void
-TextWall::show_TokenInfo(TextWallList& texts,
-                           const std::string& token_text)
+TextWall::show_TokenInfo(TextWallList& texts, const std::string& token_text)
 {
-    const Token* tok = TokenDB::find_OrSynthesize(token_text);
-
-    // Clear previous info wall content
     texts.erase(
         std::remove_if(texts.begin(), texts.end(),
             [](const TextWallPtr& wt){ return wt->role == Wall::Role::Info; }),
         texts.end());
 
-    if (!tok) return;
-
     Wall::Transform tf = Wall::get_Transform(Wall::Role::Info, World::get_BoxScale());
 
-    // Line 1: token text + type
-    std::string line1 = tok->text + "  [" + TokenDB::get_TypeName(tok->type) + "]";
-    // Line 2: short description
-    std::string line2 = tok->short_desc;
-    // Line 3: long description
-    std::string line3 = tok->long_desc;
+    TokenType        tt = CppHighlight::classify_Word(token_text);
+    TokenDB::Desc    d  = TokenDB::describe(token_text, tt);
+
+    std::string line1 = token_text + "  [" + TokenDB::type_Name(tt) + "]";
+    std::string line2 = d.short_desc;
+    std::string line3 = d.long_desc;
 
     float y = -14.0f;
     for (const std::string& line : { line1, line2, line3 }) {
-        Vec3 pos = tf.position;
-        pos.y = y;
-
-        Text3dPtr t = make_Text3d(line, Text3d::depth_wall, 0.003f,
-                             pos, ConfigColor::white);
+        if (line.empty()) { y -= ConfigWall::text_line_spacing; continue; }
+        Vec3 pos = tf.position; pos.y = y;
+        Text3dPtr t = make_Text3d(line, Text3d::depth_wall, 0.003f, pos, ConfigColor::white);
         t->yaw = tf.yaw;
-
         texts.push_back(cpputils::make_unique<TextWall>(TextWall{
-            tf.wall_position,
-            Wall::Role::Info,
-            std::move(t)
-        }));
-
+            tf.wall_position, Wall::Role::Info, std::move(t) }));
         y -= ConfigWall::text_line_spacing;
     }
 }
diff --git a/src/wall/text_wall.hpp b/src/wall/text_wall.hpp
index a359ca6..636c425 100644
--- a/src/wall/text_wall.hpp
+++ b/src/wall/text_wall.hpp
@@ -3,7 +3,7 @@
 #include "wall/wall.hpp"
 #include "text_3d/text_3d.hpp"
 #include "utils/globals.hpp"
-#include "c_token/token_db.hpp"
+#include "cpp_token/token_db.hpp"
 
 class GlRenderer;