diff --git a/PROGRESS.md b/PROGRESS.md index 54dd170..03c8bf0 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -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 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 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 81:** Compile and pass (2/2) **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 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 83: Added tree-sitter grammars for JS/TS/Java/Rust/Go and editor modes; syntax highlighting extended to 8 languages. 2/2 tests pass. | diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 5fc5951..675dd9e 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -34,6 +34,43 @@ FetchContent_Declare( 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) FetchContent_GetProperties(tree-sitter-python) if(NOT tree-sitter-python_POPULATED) @@ -50,6 +87,31 @@ if(NOT tree-sitter-elisp_POPULATED) FetchContent_Populate(tree-sitter-elisp) 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 add_library(tree_sitter_python STATIC ${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) +# 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) 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_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(OpenGL REQUIRED) find_package(glad CONFIG REQUIRED) @@ -401,7 +520,12 @@ target_link_libraries(whetstone_editor PRIVATE unofficial::tree-sitter::tree-sitter tree_sitter_python 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) target_include_directories(orchestrator PRIVATE src) diff --git a/editor/src/EditorMode.h b/editor/src/EditorMode.h index d60dcdd..75f469b 100644 --- a/editor/src/EditorMode.h +++ b/editor/src/EditorMode.h @@ -42,6 +42,11 @@ enum class EditorModeType { Python, Cpp, Elisp, + JavaScript, + TypeScript, + Java, + Rust, + Go, PlainText }; @@ -56,6 +61,11 @@ public: if (language == "python") loadPython(); else if (language == "cpp") loadCpp(); 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(); } @@ -146,6 +156,11 @@ public: case EditorModeType::Python: return "Python"; case EditorModeType::Cpp: return "C++"; 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"; } return "Unknown"; @@ -155,6 +170,11 @@ public: if (lang == "python") return EditorModeType::Python; if (lang == "cpp") return EditorModeType::Cpp; 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; } @@ -231,6 +251,62 @@ private: 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_; EditorModeType type_; IndentRule indent_; diff --git a/editor/src/SyntaxHighlighter.h b/editor/src/SyntaxHighlighter.h index b09b18e..d12d233 100644 --- a/editor/src/SyntaxHighlighter.h +++ b/editor/src/SyntaxHighlighter.h @@ -19,6 +19,11 @@ extern "C" { const TSLanguage* tree_sitter_python(); const TSLanguage* tree_sitter_cpp(); 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 { @@ -55,6 +60,11 @@ public: if (language == "python") lang = tree_sitter_python(); else if (language == "cpp") lang = tree_sitter_cpp(); 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) { ts_parser_delete(parser); @@ -70,6 +80,11 @@ public: if (language == "python") walkPython(root, source, spans); else if (language == "cpp") walkCpp(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_parser_delete(parser); @@ -265,6 +280,281 @@ private: 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& 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& 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& 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& 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& 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, std::vector& spans) { if (ts_node_is_null(node)) return; diff --git a/editor/src/main.cpp b/editor/src/main.cpp index 70634bc..3d6a672 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -188,6 +188,16 @@ struct EditorState { language = "cpp"; else if (path.size() > 3 && path.substr(path.size() - 3) == ".el") 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); sync.setText(editBuf, language); sync.syncNow(); @@ -574,6 +584,16 @@ int main(int, char**) { state.setLanguage("cpp"); if (ImGui::MenuItem("Elisp", nullptr, state.language == "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(); } if (ImGui::BeginMenu("Keybindings")) { diff --git a/editor/tests/step83_test.cpp b/editor/tests/step83_test.cpp new file mode 100644 index 0000000..668df7c --- /dev/null +++ b/editor/tests/step83_test.cpp @@ -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 +#include +#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; +}