Step 83: add new tree-sitter grammars
This commit is contained in:
@@ -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. |
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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")) {
|
||||||
|
|||||||
52
editor/tests/step83_test.cpp
Normal file
52
editor/tests/step83_test.cpp
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user