Step 83: add new tree-sitter grammars

This commit is contained in:
Bill
2026-02-09 09:22:03 -07:00
parent c040f6f936
commit c9553590e7
6 changed files with 566 additions and 1 deletions

View File

@@ -178,6 +178,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1
- [x] Step 80: **IMPLEMENTED** — Scroll/selection/clipboard: shift-extend selection, double/triple click selection, Ctrl+C/X/V clipboard, drag select (3/3 tests pass) - [x] Step 80: **IMPLEMENTED** — Scroll/selection/clipboard: shift-extend selection, double/triple click selection, Ctrl+C/X/V clipboard, drag select (3/3 tests pass)
- [x] Step 81: **IMPLEMENTED** — Code folding: tree-sitter fold regions, gutter fold toggles, hidden ranges with placeholders (2/2 tests pass) - [x] Step 81: **IMPLEMENTED** — Code folding: tree-sitter fold regions, gutter fold toggles, hidden ranges with placeholders (2/2 tests pass)
- [x] Step 82: **IMPLEMENTED** — Minimap: right-side overview with viewport indicator and click-to-scroll (1/1 tests pass) - [x] Step 82: **IMPLEMENTED** — Minimap: right-side overview with viewport indicator and click-to-scroll (1/1 tests pass)
- [x] Step 83: **IMPLEMENTED** — Additional tree-sitter grammars (JS/TS/Java/Rust/Go), new EditorMode configs, syntax highlighting for 8 languages (2/2 tests pass)
--- ---
@@ -239,6 +240,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
**Step 80:** Compile and pass (3/3) **Step 80:** Compile and pass (3/3)
**Step 81:** Compile and pass (2/2) **Step 81:** Compile and pass (2/2)
**Step 82:** Compile and pass (1/1) **Step 82:** Compile and pass (1/1)
**Step 83:** Compile and pass (2/2)
--- ---
@@ -329,3 +331,4 @@ Sprint 4 in progress. Step 76 (LayoutManager) done. Next: Step 77 (custom code e
| 2026-02-09 | Codex | Step 80: Selection and clipboard operations (Ctrl+C/X/V, shift-extend, double/triple click). 3/3 tests pass. | | 2026-02-09 | Codex | Step 80: Selection and clipboard operations (Ctrl+C/X/V, shift-extend, double/triple click). 3/3 tests pass. |
| 2026-02-09 | Codex | Step 81: Code folding with tree-sitter fold regions and gutter toggles. 2/2 tests pass. | | 2026-02-09 | Codex | Step 81: Code folding with tree-sitter fold regions and gutter toggles. 2/2 tests pass. |
| 2026-02-09 | Codex | Step 82: Minimap overview with viewport indicator and click-to-scroll. 1/1 tests pass. | | 2026-02-09 | Codex | Step 82: Minimap overview with viewport indicator and click-to-scroll. 1/1 tests pass. |
| 2026-02-09 | Codex | Step 83: Added tree-sitter grammars for JS/TS/Java/Rust/Go and editor modes; syntax highlighting extended to 8 languages. 2/2 tests pass. |

View File

@@ -34,6 +34,43 @@ FetchContent_Declare(
GIT_SHALLOW TRUE GIT_SHALLOW TRUE
) )
FetchContent_Declare(
tree-sitter-javascript
GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-javascript.git
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_Declare(
tree-sitter-typescript
GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-typescript.git
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_Declare(
tree-sitter-java
GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-java.git
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_Declare(
tree-sitter-rust
GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-rust.git
GIT_TAG master
GIT_SHALLOW TRUE
SOURCE_DIR ${CMAKE_BINARY_DIR}/_deps/tree-sitter-rust-src2
BINARY_DIR ${CMAKE_BINARY_DIR}/_deps/tree-sitter-rust-build2
)
FetchContent_Declare(
tree-sitter-go
GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-go.git
GIT_TAG master
GIT_SHALLOW TRUE
)
# Populate without add_subdirectory (grammars have their own CMakeLists with conflicting targets) # Populate without add_subdirectory (grammars have their own CMakeLists with conflicting targets)
FetchContent_GetProperties(tree-sitter-python) FetchContent_GetProperties(tree-sitter-python)
if(NOT tree-sitter-python_POPULATED) if(NOT tree-sitter-python_POPULATED)
@@ -50,6 +87,31 @@ if(NOT tree-sitter-elisp_POPULATED)
FetchContent_Populate(tree-sitter-elisp) FetchContent_Populate(tree-sitter-elisp)
endif() endif()
FetchContent_GetProperties(tree-sitter-javascript)
if(NOT tree-sitter-javascript_POPULATED)
FetchContent_Populate(tree-sitter-javascript)
endif()
FetchContent_GetProperties(tree-sitter-typescript)
if(NOT tree-sitter-typescript_POPULATED)
FetchContent_Populate(tree-sitter-typescript)
endif()
FetchContent_GetProperties(tree-sitter-java)
if(NOT tree-sitter-java_POPULATED)
FetchContent_Populate(tree-sitter-java)
endif()
FetchContent_GetProperties(tree-sitter-rust)
if(NOT tree-sitter-rust_POPULATED)
FetchContent_Populate(tree-sitter-rust)
endif()
FetchContent_GetProperties(tree-sitter-go)
if(NOT tree-sitter-go_POPULATED)
FetchContent_Populate(tree-sitter-go)
endif()
# Build tree-sitter-python grammar as a static C library # Build tree-sitter-python grammar as a static C library
add_library(tree_sitter_python STATIC add_library(tree_sitter_python STATIC
${tree-sitter-python_SOURCE_DIR}/src/parser.c ${tree-sitter-python_SOURCE_DIR}/src/parser.c
@@ -80,6 +142,51 @@ target_include_directories(tree_sitter_elisp PUBLIC
) )
target_link_libraries(tree_sitter_elisp PUBLIC unofficial::tree-sitter::tree-sitter) target_link_libraries(tree_sitter_elisp PUBLIC unofficial::tree-sitter::tree-sitter)
# Build tree-sitter-javascript grammar as a static C library
set(_ts_js_src ${tree-sitter-javascript_SOURCE_DIR}/src/parser.c)
if(EXISTS "${tree-sitter-javascript_SOURCE_DIR}/src/scanner.c")
list(APPEND _ts_js_src "${tree-sitter-javascript_SOURCE_DIR}/src/scanner.c")
endif()
add_library(tree_sitter_javascript STATIC ${_ts_js_src})
target_include_directories(tree_sitter_javascript PUBLIC ${tree-sitter-javascript_SOURCE_DIR}/src)
target_link_libraries(tree_sitter_javascript PUBLIC unofficial::tree-sitter::tree-sitter)
# Build tree-sitter-typescript grammar as a static C library (typescript only)
set(_ts_ts_src ${tree-sitter-typescript_SOURCE_DIR}/typescript/src/parser.c)
if(EXISTS "${tree-sitter-typescript_SOURCE_DIR}/typescript/src/scanner.c")
list(APPEND _ts_ts_src "${tree-sitter-typescript_SOURCE_DIR}/typescript/src/scanner.c")
endif()
add_library(tree_sitter_typescript STATIC ${_ts_ts_src})
target_include_directories(tree_sitter_typescript PUBLIC ${tree-sitter-typescript_SOURCE_DIR}/typescript/src)
target_link_libraries(tree_sitter_typescript PUBLIC unofficial::tree-sitter::tree-sitter)
# Build tree-sitter-java grammar as a static C library
set(_ts_java_src ${tree-sitter-java_SOURCE_DIR}/src/parser.c)
if(EXISTS "${tree-sitter-java_SOURCE_DIR}/src/scanner.c")
list(APPEND _ts_java_src "${tree-sitter-java_SOURCE_DIR}/src/scanner.c")
endif()
add_library(tree_sitter_java STATIC ${_ts_java_src})
target_include_directories(tree_sitter_java PUBLIC ${tree-sitter-java_SOURCE_DIR}/src)
target_link_libraries(tree_sitter_java PUBLIC unofficial::tree-sitter::tree-sitter)
# Build tree-sitter-rust grammar as a static C library
set(_ts_rust_src ${tree-sitter-rust_SOURCE_DIR}/src/parser.c)
if(EXISTS "${tree-sitter-rust_SOURCE_DIR}/src/scanner.c")
list(APPEND _ts_rust_src "${tree-sitter-rust_SOURCE_DIR}/src/scanner.c")
endif()
add_library(tree_sitter_rust STATIC ${_ts_rust_src})
target_include_directories(tree_sitter_rust PUBLIC ${tree-sitter-rust_SOURCE_DIR}/src)
target_link_libraries(tree_sitter_rust PUBLIC unofficial::tree-sitter::tree-sitter)
# Build tree-sitter-go grammar as a static C library
set(_ts_go_src ${tree-sitter-go_SOURCE_DIR}/src/parser.c)
if(EXISTS "${tree-sitter-go_SOURCE_DIR}/src/scanner.c")
list(APPEND _ts_go_src "${tree-sitter-go_SOURCE_DIR}/src/scanner.c")
endif()
add_library(tree_sitter_go STATIC ${_ts_go_src})
target_include_directories(tree_sitter_go PUBLIC ${tree-sitter-go_SOURCE_DIR}/src)
target_link_libraries(tree_sitter_go PUBLIC unofficial::tree-sitter::tree-sitter)
add_executable(step1_test tests/step1_test.cpp) add_executable(step1_test tests/step1_test.cpp)
target_include_directories(step1_test PRIVATE src) target_include_directories(step1_test PRIVATE src)
@@ -347,6 +454,18 @@ add_executable(step82_test tests/step82_test.cpp)
target_include_directories(step82_test PRIVATE src) target_include_directories(step82_test PRIVATE src)
target_link_libraries(step82_test PRIVATE imgui::imgui unofficial::tree-sitter::tree-sitter tree_sitter_python tree_sitter_cpp tree_sitter_elisp) target_link_libraries(step82_test PRIVATE imgui::imgui unofficial::tree-sitter::tree-sitter tree_sitter_python tree_sitter_cpp tree_sitter_elisp)
add_executable(step83_test tests/step83_test.cpp)
target_include_directories(step83_test PRIVATE src)
target_link_libraries(step83_test PRIVATE unofficial::tree-sitter::tree-sitter
tree_sitter_python
tree_sitter_cpp
tree_sitter_elisp
tree_sitter_javascript
tree_sitter_typescript
tree_sitter_java
tree_sitter_rust
tree_sitter_go)
find_package(SDL2 CONFIG REQUIRED) find_package(SDL2 CONFIG REQUIRED)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
find_package(glad CONFIG REQUIRED) find_package(glad CONFIG REQUIRED)
@@ -401,7 +520,12 @@ target_link_libraries(whetstone_editor PRIVATE
unofficial::tree-sitter::tree-sitter unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_python
tree_sitter_cpp tree_sitter_cpp
tree_sitter_elisp) tree_sitter_elisp
tree_sitter_javascript
tree_sitter_typescript
tree_sitter_java
tree_sitter_rust
tree_sitter_go)
add_executable(orchestrator src/orchestrator_main.cpp) add_executable(orchestrator src/orchestrator_main.cpp)
target_include_directories(orchestrator PRIVATE src) target_include_directories(orchestrator PRIVATE src)

View File

@@ -42,6 +42,11 @@ enum class EditorModeType {
Python, Python,
Cpp, Cpp,
Elisp, Elisp,
JavaScript,
TypeScript,
Java,
Rust,
Go,
PlainText PlainText
}; };
@@ -56,6 +61,11 @@ public:
if (language == "python") loadPython(); if (language == "python") loadPython();
else if (language == "cpp") loadCpp(); else if (language == "cpp") loadCpp();
else if (language == "elisp") loadElisp(); else if (language == "elisp") loadElisp();
else if (language == "javascript") loadJavaScript();
else if (language == "typescript") loadTypeScript();
else if (language == "java") loadJava();
else if (language == "rust") loadRust();
else if (language == "go") loadGo();
else loadPlainText(); else loadPlainText();
} }
@@ -146,6 +156,11 @@ public:
case EditorModeType::Python: return "Python"; case EditorModeType::Python: return "Python";
case EditorModeType::Cpp: return "C++"; case EditorModeType::Cpp: return "C++";
case EditorModeType::Elisp: return "Elisp"; case EditorModeType::Elisp: return "Elisp";
case EditorModeType::JavaScript:return "JavaScript";
case EditorModeType::TypeScript:return "TypeScript";
case EditorModeType::Java: return "Java";
case EditorModeType::Rust: return "Rust";
case EditorModeType::Go: return "Go";
case EditorModeType::PlainText: return "Plain Text"; case EditorModeType::PlainText: return "Plain Text";
} }
return "Unknown"; return "Unknown";
@@ -155,6 +170,11 @@ public:
if (lang == "python") return EditorModeType::Python; if (lang == "python") return EditorModeType::Python;
if (lang == "cpp") return EditorModeType::Cpp; if (lang == "cpp") return EditorModeType::Cpp;
if (lang == "elisp") return EditorModeType::Elisp; if (lang == "elisp") return EditorModeType::Elisp;
if (lang == "javascript") return EditorModeType::JavaScript;
if (lang == "typescript") return EditorModeType::TypeScript;
if (lang == "java") return EditorModeType::Java;
if (lang == "rust") return EditorModeType::Rust;
if (lang == "go") return EditorModeType::Go;
return EditorModeType::PlainText; return EditorModeType::PlainText;
} }
@@ -231,6 +251,62 @@ private:
snippets_ = {}; snippets_ = {};
} }
void loadJavaScript() {
type_ = EditorModeType::JavaScript;
indent_ = {"{", "}", 2, false};
comment_ = {"//", "/*", "*/"};
brackets_ = {{'(', ')'}, {'[', ']'}, {'{', '}'}, {'\'', '\''}, {'"', '"'}};
snippets_ = {
{"fn", "function $1($2) {\\n $0\\n}", "Function"},
{"if", "if ($1) {\\n $0\\n}", "If statement"},
{"for", "for (let $1 = 0; $1 < $2; $1++) {\\n $0\\n}", "For loop"},
};
}
void loadTypeScript() {
type_ = EditorModeType::TypeScript;
indent_ = {"{", "}", 2, false};
comment_ = {"//", "/*", "*/"};
brackets_ = {{'(', ')'}, {'[', ']'}, {'{', '}'}, {'\'', '\''}, {'"', '"'}, {'<', '>'}};
snippets_ = {
{"fn", "function $1($2): $3 {\\n $0\\n}", "Function"},
{"if", "if ($1) {\\n $0\\n}", "If statement"},
};
}
void loadJava() {
type_ = EditorModeType::Java;
indent_ = {"{", "}", 4, false};
comment_ = {"//", "/*", "*/"};
brackets_ = {{'(', ')'}, {'[', ']'}, {'{', '}'}, {'\'', '\''}, {'"', '"'}};
snippets_ = {
{"class", "class $1 {\\n $0\\n}", "Class"},
{"if", "if ($1) {\\n $0\\n}", "If statement"},
};
}
void loadRust() {
type_ = EditorModeType::Rust;
indent_ = {"{", "}", 4, false};
comment_ = {"//", "/*", "*/"};
brackets_ = {{'(', ')'}, {'[', ']'}, {'{', '}'}, {'\'', '\''}, {'\"', '\"'}};
snippets_ = {
{"fn", "fn $1($2) -> $3 {\\n $0\\n}", "Function"},
{"if", "if $1 {\\n $0\\n}", "If statement"},
};
}
void loadGo() {
type_ = EditorModeType::Go;
indent_ = {"{", "}", 4, false};
comment_ = {"//", "/*", "*/"};
brackets_ = {{'(', ')'}, {'[', ']'}, {'{', '}'}, {'\'', '\''}, {'\"', '\"'}};
snippets_ = {
{"fn", "func $1($2) $3 {\\n $0\\n}", "Function"},
{"if", "if $1 {\\n $0\\n}", "If statement"},
};
}
std::string language_; std::string language_;
EditorModeType type_; EditorModeType type_;
IndentRule indent_; IndentRule indent_;

View File

@@ -19,6 +19,11 @@ extern "C" {
const TSLanguage* tree_sitter_python(); const TSLanguage* tree_sitter_python();
const TSLanguage* tree_sitter_cpp(); const TSLanguage* tree_sitter_cpp();
const TSLanguage* tree_sitter_elisp(); const TSLanguage* tree_sitter_elisp();
const TSLanguage* tree_sitter_javascript();
const TSLanguage* tree_sitter_typescript();
const TSLanguage* tree_sitter_java();
const TSLanguage* tree_sitter_rust();
const TSLanguage* tree_sitter_go();
} }
enum class TokenCategory { enum class TokenCategory {
@@ -55,6 +60,11 @@ public:
if (language == "python") lang = tree_sitter_python(); if (language == "python") lang = tree_sitter_python();
else if (language == "cpp") lang = tree_sitter_cpp(); else if (language == "cpp") lang = tree_sitter_cpp();
else if (language == "elisp") lang = tree_sitter_elisp(); else if (language == "elisp") lang = tree_sitter_elisp();
else if (language == "javascript") lang = tree_sitter_javascript();
else if (language == "typescript") lang = tree_sitter_typescript();
else if (language == "java") lang = tree_sitter_java();
else if (language == "rust") lang = tree_sitter_rust();
else if (language == "go") lang = tree_sitter_go();
if (!lang) { if (!lang) {
ts_parser_delete(parser); ts_parser_delete(parser);
@@ -70,6 +80,11 @@ public:
if (language == "python") walkPython(root, source, spans); if (language == "python") walkPython(root, source, spans);
else if (language == "cpp") walkCpp(root, source, spans); else if (language == "cpp") walkCpp(root, source, spans);
else if (language == "elisp") walkElisp(root, source, spans); else if (language == "elisp") walkElisp(root, source, spans);
else if (language == "javascript") walkJavaScript(root, source, spans);
else if (language == "typescript") walkTypeScript(root, source, spans);
else if (language == "java") walkJava(root, source, spans);
else if (language == "rust") walkRust(root, source, spans);
else if (language == "go") walkGo(root, source, spans);
ts_tree_delete(tree); ts_tree_delete(tree);
ts_parser_delete(parser); ts_parser_delete(parser);
@@ -265,6 +280,281 @@ private:
return false; return false;
} }
// --- JavaScript / TypeScript --------------------------------------
static bool isJsKeyword(const std::string& text) {
static const char* keywords[] = {
"function", "return", "if", "else", "for", "while", "class",
"const", "let", "var", "import", "export", "new", "try", "catch",
"finally", "switch", "case", "break", "continue", "throw",
"async", "await", "yield", nullptr
};
for (const char** k = keywords; *k; ++k) {
if (text == *k) return true;
}
return false;
}
static void walkJavaScript(TSNode node, const std::string& source,
std::vector<HighlightSpan>& spans) {
if (ts_node_is_null(node)) return;
std::string type = nodeType(node);
std::string text = nodeText(node, source);
bool descend = true;
if (type.find("comment") != std::string::npos) {
addSpan(spans, node, TokenCategory::Comment);
descend = false;
} else if (type == "string" || type == "string_fragment" || type == "template_string" ||
type == "template_substitution") {
addSpan(spans, node, TokenCategory::String);
descend = false;
} else if (type == "number" || type == "number_literal") {
addSpan(spans, node, TokenCategory::Number);
descend = false;
} else if (type == "identifier" || type == "property_identifier") {
TSNode parent = ts_node_parent(node);
std::string parentType = ts_node_is_null(parent) ? "" : nodeType(parent);
if (parentType == "function_declaration" || parentType == "method_definition") {
addSpan(spans, node, TokenCategory::Function);
} else {
addSpan(spans, node, TokenCategory::Identifier);
}
descend = false;
} else if (type == "true" || type == "false" || type == "null" || type == "undefined") {
addSpan(spans, node, TokenCategory::Builtin);
descend = false;
} else if (!ts_node_is_named(node)) {
if (isJsKeyword(text)) {
addSpan(spans, node, TokenCategory::Keyword);
} else if (text == "(" || text == ")" || text == "[" || text == "]" ||
text == "{" || text == "}" || text == ";" || text == "," ||
text == "." || text == ":" ) {
addSpan(spans, node, TokenCategory::Punctuation);
} else {
addSpan(spans, node, TokenCategory::Operator);
}
descend = false;
}
if (descend) {
uint32_t count = ts_node_child_count(node);
for (uint32_t i = 0; i < count; ++i) {
walkJavaScript(ts_node_child(node, i), source, spans);
}
}
}
static void walkTypeScript(TSNode node, const std::string& source,
std::vector<HighlightSpan>& spans) {
walkJavaScript(node, source, spans);
}
// --- Java -----------------------------------------------------------
static bool isJavaKeyword(const std::string& text) {
static const char* keywords[] = {
"class", "interface", "enum", "public", "private", "protected",
"static", "final", "void", "int", "float", "double", "boolean",
"return", "if", "else", "for", "while", "switch", "case",
"break", "continue", "new", "try", "catch", "finally", "throw",
"extends", "implements", "import", "package", "this", "super",
nullptr
};
for (const char** k = keywords; *k; ++k) {
if (text == *k) return true;
}
return false;
}
static void walkJava(TSNode node, const std::string& source,
std::vector<HighlightSpan>& spans) {
if (ts_node_is_null(node)) return;
std::string type = nodeType(node);
std::string text = nodeText(node, source);
bool descend = true;
if (type.find("comment") != std::string::npos) {
addSpan(spans, node, TokenCategory::Comment);
descend = false;
} else if (type.find("string") != std::string::npos || type == "character_literal") {
addSpan(spans, node, TokenCategory::String);
descend = false;
} else if (type.find("integer") != std::string::npos || type.find("floating") != std::string::npos ||
type == "decimal_integer_literal" || type == "decimal_floating_point_literal") {
addSpan(spans, node, TokenCategory::Number);
descend = false;
} else if (type == "identifier") {
TSNode parent = ts_node_parent(node);
std::string parentType = ts_node_is_null(parent) ? "" : nodeType(parent);
if (parentType == "method_declaration" || parentType == "constructor_declaration") {
addSpan(spans, node, TokenCategory::Function);
} else {
addSpan(spans, node, TokenCategory::Identifier);
}
descend = false;
} else if (type == "type_identifier" || type == "integral_type" || type == "floating_point_type") {
addSpan(spans, node, TokenCategory::Type);
descend = false;
} else if (type == "true" || type == "false" || type == "null_literal") {
addSpan(spans, node, TokenCategory::Builtin);
descend = false;
} else if (!ts_node_is_named(node)) {
if (isJavaKeyword(text)) {
addSpan(spans, node, TokenCategory::Keyword);
} else if (text == "(" || text == ")" || text == "[" || text == "]" ||
text == "{" || text == "}" || text == ";" || text == "," ||
text == "." || text == ":" ) {
addSpan(spans, node, TokenCategory::Punctuation);
} else {
addSpan(spans, node, TokenCategory::Operator);
}
descend = false;
}
if (descend) {
uint32_t count = ts_node_child_count(node);
for (uint32_t i = 0; i < count; ++i) {
walkJava(ts_node_child(node, i), source, spans);
}
}
}
// --- Rust -----------------------------------------------------------
static bool isRustKeyword(const std::string& text) {
static const char* keywords[] = {
"fn", "let", "mut", "pub", "impl", "trait", "struct", "enum",
"use", "mod", "crate", "self", "super", "return", "if", "else",
"for", "while", "loop", "match", "break", "continue", "const",
"static", "async", "await", "move", nullptr
};
for (const char** k = keywords; *k; ++k) {
if (text == *k) return true;
}
return false;
}
static void walkRust(TSNode node, const std::string& source,
std::vector<HighlightSpan>& spans) {
if (ts_node_is_null(node)) return;
std::string type = nodeType(node);
std::string text = nodeText(node, source);
bool descend = true;
if (type.find("comment") != std::string::npos) {
addSpan(spans, node, TokenCategory::Comment);
descend = false;
} else if (type == "string_literal" || type == "char_literal" ||
type == "raw_string_literal") {
addSpan(spans, node, TokenCategory::String);
descend = false;
} else if (type.find("integer") != std::string::npos || type.find("float") != std::string::npos ||
type == "number") {
addSpan(spans, node, TokenCategory::Number);
descend = false;
} else if (type == "identifier") {
TSNode parent = ts_node_parent(node);
std::string parentType = ts_node_is_null(parent) ? "" : nodeType(parent);
if (parentType == "function_item") {
addSpan(spans, node, TokenCategory::Function);
} else {
addSpan(spans, node, TokenCategory::Identifier);
}
descend = false;
} else if (type == "type_identifier" || type == "primitive_type") {
addSpan(spans, node, TokenCategory::Type);
descend = false;
} else if (!ts_node_is_named(node)) {
if (isRustKeyword(text)) {
addSpan(spans, node, TokenCategory::Keyword);
} else if (text == "(" || text == ")" || text == "[" || text == "]" ||
text == "{" || text == "}" || text == ";" || text == "," ||
text == ":" ) {
addSpan(spans, node, TokenCategory::Punctuation);
} else {
addSpan(spans, node, TokenCategory::Operator);
}
descend = false;
}
if (descend) {
uint32_t count = ts_node_child_count(node);
for (uint32_t i = 0; i < count; ++i) {
walkRust(ts_node_child(node, i), source, spans);
}
}
}
// --- Go -------------------------------------------------------------
static bool isGoKeyword(const std::string& text) {
static const char* keywords[] = {
"func", "package", "import", "return", "if", "else", "for",
"switch", "case", "break", "continue", "struct", "interface",
"type", "var", "const", "go", "defer", "range", nullptr
};
for (const char** k = keywords; *k; ++k) {
if (text == *k) return true;
}
return false;
}
static void walkGo(TSNode node, const std::string& source,
std::vector<HighlightSpan>& spans) {
if (ts_node_is_null(node)) return;
std::string type = nodeType(node);
std::string text = nodeText(node, source);
bool descend = true;
if (type.find("comment") != std::string::npos) {
addSpan(spans, node, TokenCategory::Comment);
descend = false;
} else if (type == "interpreted_string_literal" || type == "raw_string_literal" ||
type == "rune_literal") {
addSpan(spans, node, TokenCategory::String);
descend = false;
} else if (type.find("int") != std::string::npos || type.find("float") != std::string::npos ||
type == "number") {
addSpan(spans, node, TokenCategory::Number);
descend = false;
} else if (type == "identifier") {
TSNode parent = ts_node_parent(node);
std::string parentType = ts_node_is_null(parent) ? "" : nodeType(parent);
if (parentType == "function_declaration" || parentType == "method_declaration") {
addSpan(spans, node, TokenCategory::Function);
} else {
addSpan(spans, node, TokenCategory::Identifier);
}
descend = false;
} else if (type == "type_identifier" || type == "primitive_type") {
addSpan(spans, node, TokenCategory::Type);
descend = false;
} else if (!ts_node_is_named(node)) {
if (isGoKeyword(text)) {
addSpan(spans, node, TokenCategory::Keyword);
} else if (text == "(" || text == ")" || text == "[" || text == "]" ||
text == "{" || text == "}" || text == ";" || text == "," ||
text == ":" ) {
addSpan(spans, node, TokenCategory::Punctuation);
} else {
addSpan(spans, node, TokenCategory::Operator);
}
descend = false;
}
if (descend) {
uint32_t count = ts_node_child_count(node);
for (uint32_t i = 0; i < count; ++i) {
walkGo(ts_node_child(node, i), source, spans);
}
}
}
static void walkCpp(TSNode node, const std::string& source, static void walkCpp(TSNode node, const std::string& source,
std::vector<HighlightSpan>& spans) { std::vector<HighlightSpan>& spans) {
if (ts_node_is_null(node)) return; if (ts_node_is_null(node)) return;

View File

@@ -188,6 +188,16 @@ struct EditorState {
language = "cpp"; language = "cpp";
else if (path.size() > 3 && path.substr(path.size() - 3) == ".el") else if (path.size() > 3 && path.substr(path.size() - 3) == ".el")
language = "elisp"; language = "elisp";
else if (path.size() > 3 && path.substr(path.size() - 3) == ".js")
language = "javascript";
else if (path.size() > 3 && path.substr(path.size() - 3) == ".ts")
language = "typescript";
else if (path.size() > 5 && path.substr(path.size() - 5) == ".java")
language = "java";
else if (path.size() > 3 && path.substr(path.size() - 3) == ".rs")
language = "rust";
else if (path.size() > 3 && path.substr(path.size() - 3) == ".go")
language = "go";
editor.setContent(editBuf, language); editor.setContent(editBuf, language);
sync.setText(editBuf, language); sync.setText(editBuf, language);
sync.syncNow(); sync.syncNow();
@@ -574,6 +584,16 @@ int main(int, char**) {
state.setLanguage("cpp"); state.setLanguage("cpp");
if (ImGui::MenuItem("Elisp", nullptr, state.language == "elisp")) if (ImGui::MenuItem("Elisp", nullptr, state.language == "elisp"))
state.setLanguage("elisp"); state.setLanguage("elisp");
if (ImGui::MenuItem("JavaScript", nullptr, state.language == "javascript"))
state.setLanguage("javascript");
if (ImGui::MenuItem("TypeScript", nullptr, state.language == "typescript"))
state.setLanguage("typescript");
if (ImGui::MenuItem("Java", nullptr, state.language == "java"))
state.setLanguage("java");
if (ImGui::MenuItem("Rust", nullptr, state.language == "rust"))
state.setLanguage("rust");
if (ImGui::MenuItem("Go", nullptr, state.language == "go"))
state.setLanguage("go");
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Keybindings")) { if (ImGui::BeginMenu("Keybindings")) {

View File

@@ -0,0 +1,52 @@
// Step 83 TDD Test: Additional tree-sitter grammars
//
// Tests:
// 1. SyntaxHighlighter returns spans for new languages
// 2. EditorMode can switch to new languages
#include <cassert>
#include <iostream>
#include "SyntaxHighlighter.h"
#include "EditorMode.h"
int main() {
int passed = 0;
int failed = 0;
// --- Test 1: SyntaxHighlighter spans ---
{
const char* js = "function add(a,b){ return a+b; }";
const char* ts = "function add(a: number, b: number): number { return a + b; }";
const char* java = "class A { int add(int a, int b){ return a+b; } }";
const char* rust = "fn add(a:i32,b:i32)->i32{ a+b }";
const char* go = "func add(a int, b int) int { return a+b }";
auto jsSpans = SyntaxHighlighter::highlight(js, "javascript");
auto tsSpans = SyntaxHighlighter::highlight(ts, "typescript");
auto javaSpans = SyntaxHighlighter::highlight(java, "java");
auto rustSpans = SyntaxHighlighter::highlight(rust, "rust");
auto goSpans = SyntaxHighlighter::highlight(go, "go");
assert(!jsSpans.empty());
assert(!tsSpans.empty());
assert(!javaSpans.empty());
assert(!rustSpans.empty());
assert(!goSpans.empty());
std::cout << "Test 1 PASS: Highlight spans for new languages" << std::endl;
++passed;
}
// --- Test 2: EditorMode switches ---
{
EditorMode mode("javascript");
assert(mode.getLanguage() == "javascript");
mode.setLanguage("rust");
assert(mode.getLanguage() == "rust");
std::cout << "Test 2 PASS: EditorMode supports new languages" << std::endl;
++passed;
}
std::cout << "\n=== Step 83 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
return failed > 0 ? 1 : 0;
}