From 3262e8451414b62a03e81bd2677dd299488db6f2 Mon Sep 17 00:00:00 2001 From: Patrick Dewey Date: Wed, 4 Feb 2026 16:50:45 -0500 Subject: [PATCH] feat: tree-sitter grammar, highlights and injection queries --- CLAUDE.md | 2 +- __snapshots__/combined_ignore_and_scrub.snap | 13 + __snapshots__/complex_nested_structure.snap | 78 +++ __snapshots__/custom_ignore_function.snap | 10 + __snapshots__/custom_regex_scrubber.snap | 11 + __snapshots__/custom_scrubber.snap | 7 + __snapshots__/custom_type_test.snap | 10 + __snapshots__/exact_match_scrubber.snap | 7 + __snapshots__/ignore_empty_values.snap | 10 + __snapshots__/ignore_in_arrays.snap | 20 + __snapshots__/ignore_key_pattern.snap | 10 + .../ignore_keys_matching_pattern.snap | 10 + __snapshots__/ignore_multiple_keys.snap | 11 + __snapshots__/ignore_null_values.snap | 11 + __snapshots__/ignore_password_field.snap | 10 + __snapshots__/ignore_sensitive_keys.snap | 11 + __snapshots__/ignore_specific_values.snap | 9 + .../json_with_special_characters.snap | 16 + __snapshots__/large_json_structure.snap | 54 ++ .../multiple_complex_structures.snap | 56 ++ __snapshots__/multiple_scrubbers.snap | 27 + __snapshots__/multiple_values_test.snap | 16 + __snapshots__/nested_ignore_patterns.snap | 18 + __snapshots__/nested_maps_and_slices.snap | 47 ++ __snapshots__/real_world_api_response.snap | 28 + __snapshots__/scrub_with_snap.snap | 12 + __snapshots__/scrubbed_api_keys.snap | 12 + __snapshots__/scrubbed_credit_cards.snap | 12 + __snapshots__/scrubbed_dates.snap | 12 + __snapshots__/scrubbed_emails.snap | 11 + __snapshots__/scrubbed_ips.snap | 11 + __snapshots__/scrubbed_jwts.snap | 10 + __snapshots__/scrubbed_timestamps.snap | 12 + __snapshots__/scrubbed_unix_timestamps.snap | 12 + __snapshots__/scrubbed_uuids.snap | 11 + .../snapjson_complex_api_response.snap | 42 ++ __snapshots__/snapjson_mixed_types.snap | 41 ++ __snapshots__/snapjson_nested_objects.snap | 26 + .../snapjson_real_world_example.snap | 84 +++ .../structure_with_empty_values.snap | 34 ++ .../structure_with_interface_fields.snap | 76 +++ __snapshots__/structure_with_pointers.snap | 27 + .../tree-sitter-snapshot}/.editorconfig | 0 .../tree-sitter-snapshot}/.gitattributes | 0 .../tree-sitter-snapshot}/.gitignore | 0 .../tree-sitter-snapshot}/CMakeLists.txt | 0 .../tree-sitter-snapshot}/Cargo.toml | 0 .../tree-sitter-snapshot}/Makefile | 0 .../tree-sitter-snapshot}/Package.swift | 0 .../tree-sitter-snapshot}/binding.gyp | 0 .../bindings/c/tree-sitter-snapshot.pc.in | 0 .../c/tree_sitter/tree-sitter-snapshot.h | 0 .../bindings/go/binding.go | 0 .../bindings/go/binding_test.go | 0 .../bindings/node/binding.cc | 0 .../bindings/node/binding_test.js | 0 .../bindings/node/index.d.ts | 0 .../bindings/node/index.js | 0 .../bindings/python/tests/test_binding.py | 0 .../python/tree_sitter_snapshot/__init__.py | 0 .../python/tree_sitter_snapshot/__init__.pyi | 0 .../python/tree_sitter_snapshot/binding.c | 0 .../python/tree_sitter_snapshot/py.typed | 0 .../bindings/rust/build.rs | 0 .../bindings/rust/lib.rs | 0 .../swift/TreeSitterSnapshot/snapshot.h | 0 .../TreeSitterSnapshotTests.swift | 0 .../tree-sitter-snapshot}/go.mod | 0 editor/tree-sitter-snapshot/grammar.js | 45 ++ .../tree-sitter-snapshot}/package.json | 0 .../tree-sitter-snapshot}/pyproject.toml | 0 .../queries/highlights.scm | 10 + .../queries/injections.scm | 23 + .../tree-sitter-snapshot}/setup.py | 0 editor/tree-sitter-snapshot/src/grammar.json | 129 +++++ .../tree-sitter-snapshot/src/node-types.json | 95 ++++ editor/tree-sitter-snapshot/src/parser.c | 502 ++++++++++++++++++ .../src/tree_sitter/alloc.h | 54 ++ .../src/tree_sitter/array.h | 291 ++++++++++ .../src/tree_sitter/parser.h | 286 ++++++++++ .../test/corpus/basic.txt | 92 ++++ .../tree-sitter-snapshot}/tree-sitter.json | 2 +- editors/grammar.js | 17 - internal/files/files.go | 4 + 84 files changed, 2478 insertions(+), 19 deletions(-) create mode 100644 __snapshots__/combined_ignore_and_scrub.snap create mode 100644 __snapshots__/complex_nested_structure.snap create mode 100644 __snapshots__/custom_ignore_function.snap create mode 100644 __snapshots__/custom_regex_scrubber.snap create mode 100644 __snapshots__/custom_scrubber.snap create mode 100644 __snapshots__/custom_type_test.snap create mode 100644 __snapshots__/exact_match_scrubber.snap create mode 100644 __snapshots__/ignore_empty_values.snap create mode 100644 __snapshots__/ignore_in_arrays.snap create mode 100644 __snapshots__/ignore_key_pattern.snap create mode 100644 __snapshots__/ignore_keys_matching_pattern.snap create mode 100644 __snapshots__/ignore_multiple_keys.snap create mode 100644 __snapshots__/ignore_null_values.snap create mode 100644 __snapshots__/ignore_password_field.snap create mode 100644 __snapshots__/ignore_sensitive_keys.snap create mode 100644 __snapshots__/ignore_specific_values.snap create mode 100644 __snapshots__/json_with_special_characters.snap create mode 100644 __snapshots__/large_json_structure.snap create mode 100644 __snapshots__/multiple_complex_structures.snap create mode 100644 __snapshots__/multiple_scrubbers.snap create mode 100644 __snapshots__/multiple_values_test.snap create mode 100644 __snapshots__/nested_ignore_patterns.snap create mode 100644 __snapshots__/nested_maps_and_slices.snap create mode 100644 __snapshots__/real_world_api_response.snap create mode 100644 __snapshots__/scrub_with_snap.snap create mode 100644 __snapshots__/scrubbed_api_keys.snap create mode 100644 __snapshots__/scrubbed_credit_cards.snap create mode 100644 __snapshots__/scrubbed_dates.snap create mode 100644 __snapshots__/scrubbed_emails.snap create mode 100644 __snapshots__/scrubbed_ips.snap create mode 100644 __snapshots__/scrubbed_jwts.snap create mode 100644 __snapshots__/scrubbed_timestamps.snap create mode 100644 __snapshots__/scrubbed_unix_timestamps.snap create mode 100644 __snapshots__/scrubbed_uuids.snap create mode 100644 __snapshots__/snapjson_complex_api_response.snap create mode 100644 __snapshots__/snapjson_mixed_types.snap create mode 100644 __snapshots__/snapjson_nested_objects.snap create mode 100644 __snapshots__/snapjson_real_world_example.snap create mode 100644 __snapshots__/structure_with_empty_values.snap create mode 100644 __snapshots__/structure_with_interface_fields.snap create mode 100644 __snapshots__/structure_with_pointers.snap rename {editors => editor/tree-sitter-snapshot}/.editorconfig (100%) rename {editors => editor/tree-sitter-snapshot}/.gitattributes (100%) rename {editors => editor/tree-sitter-snapshot}/.gitignore (100%) rename {editors => editor/tree-sitter-snapshot}/CMakeLists.txt (100%) rename {editors => editor/tree-sitter-snapshot}/Cargo.toml (100%) rename {editors => editor/tree-sitter-snapshot}/Makefile (100%) rename {editors => editor/tree-sitter-snapshot}/Package.swift (100%) rename {editors => editor/tree-sitter-snapshot}/binding.gyp (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/c/tree-sitter-snapshot.pc.in (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/c/tree_sitter/tree-sitter-snapshot.h (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/go/binding.go (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/go/binding_test.go (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/node/binding.cc (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/node/binding_test.js (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/node/index.d.ts (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/node/index.js (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/python/tests/test_binding.py (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/python/tree_sitter_snapshot/__init__.py (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/python/tree_sitter_snapshot/__init__.pyi (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/python/tree_sitter_snapshot/binding.c (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/python/tree_sitter_snapshot/py.typed (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/rust/build.rs (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/rust/lib.rs (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/swift/TreeSitterSnapshot/snapshot.h (100%) rename {editors => editor/tree-sitter-snapshot}/bindings/swift/TreeSitterSnapshotTests/TreeSitterSnapshotTests.swift (100%) rename {editors => editor/tree-sitter-snapshot}/go.mod (100%) create mode 100644 editor/tree-sitter-snapshot/grammar.js rename {editors => editor/tree-sitter-snapshot}/package.json (100%) rename {editors => editor/tree-sitter-snapshot}/pyproject.toml (100%) create mode 100644 editor/tree-sitter-snapshot/queries/highlights.scm create mode 100644 editor/tree-sitter-snapshot/queries/injections.scm rename {editors => editor/tree-sitter-snapshot}/setup.py (100%) create mode 100644 editor/tree-sitter-snapshot/src/grammar.json create mode 100644 editor/tree-sitter-snapshot/src/node-types.json create mode 100644 editor/tree-sitter-snapshot/src/parser.c create mode 100644 editor/tree-sitter-snapshot/src/tree_sitter/alloc.h create mode 100644 editor/tree-sitter-snapshot/src/tree_sitter/array.h create mode 100644 editor/tree-sitter-snapshot/src/tree_sitter/parser.h create mode 100644 editor/tree-sitter-snapshot/test/corpus/basic.txt rename {editors => editor/tree-sitter-snapshot}/tree-sitter.json (97%) delete mode 100644 editors/grammar.js diff --git a/CLAUDE.md b/CLAUDE.md index 69aa6ef..d918b7c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,4 +73,4 @@ cd ./cmd/shutter && go build -o shutter ./main.go # Build TUI - Root module (`go.mod`): Main library - Go 1.23.12+ - TUI module (`cmd/shutter/go.mod`): Separate module with Bubbletea dependencies - Go 1.25.2 -- `/editors/`: Tree-sitter grammar for snapshot format (Node.js/Rust/Python/Swift bindings) +- `/editor/tree-sitter-snapshot/`: Tree-sitter grammar for snapshot format (Node.js/Rust/Python/Swift bindings) diff --git a/__snapshots__/combined_ignore_and_scrub.snap b/__snapshots__/combined_ignore_and_scrub.snap new file mode 100644 index 0000000..0c69f41 --- /dev/null +++ b/__snapshots__/combined_ignore_and_scrub.snap @@ -0,0 +1,13 @@ +--- +title: Combined Ignore and Scrub +test_name: TestCombinedIgnoreAndScrub +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "created_at": "", + "email": "", + "ip_address": "", + "name": "John Doe", + "user_id": "" +} \ No newline at end of file diff --git a/__snapshots__/complex_nested_structure.snap b/__snapshots__/complex_nested_structure.snap new file mode 100644 index 0000000..9e987b6 --- /dev/null +++ b/__snapshots__/complex_nested_structure.snap @@ -0,0 +1,78 @@ +--- +title: Complex Nested Structure +test_name: TestComplexNestedStructure +file_name: shutter_test.go +version: 0.1.0 +--- +shutter_test.Post{ + ID: 100, + Title: "Introduction to Go Snapshot Testing", + Content: "This is a comprehensive guide to snapshot testing in Go...", + Author: shutter_test.User{ + ID: 1, + Username: "john_doe", + Email: "john@example.com", + Active: true, + CreatedAt: time.Time{ + wall: 0x0, + ext: 63809375400, + loc: (*time.Location)(nil), + }, + Roles: []string{"admin", "moderator", "user"}, + Metadata: map[string]interface{}{ + "language": "en", + "notifications": true, + "preferences": map[string]interface{}{ + "email_frequency": "weekly", + "notifications": true, + }, + "theme": "dark", + }, + }, + Tags: []string{"go", "testing", "snapshots", "best-practices"}, + Comments: []shutter_test.Comment{ + { + ID: 1, + Author: "alice", + Content: "Great post!", + CreatedAt: time.Time{ + wall: 0x0, + ext: 63810858120, + loc: (*time.Location)(nil), + }, + Replies: []shutter_test.Comment{ + { + ID: 2, + Author: "bob", + Content: "I agree!", + CreatedAt: time.Time{ + wall: 0x0, + ext: 63810863100, + loc: (*time.Location)(nil), + }, + Replies: []shutter_test.Comment{ + }, + }, + }, + }, + { + ID: 3, + Author: "charlie", + Content: "Thanks for sharing!", + CreatedAt: time.Time{ + wall: 0x0, + ext: 63810927000, + loc: (*time.Location)(nil), + }, + Replies: []shutter_test.Comment{ + }, + }, + }, + Likes: 42, + Published: true, + CreatedAt: time.Time{ + wall: 0x0, + ext: 63809802000, + loc: (*time.Location)(nil), + }, +} diff --git a/__snapshots__/custom_ignore_function.snap b/__snapshots__/custom_ignore_function.snap new file mode 100644 index 0000000..6b58384 --- /dev/null +++ b/__snapshots__/custom_ignore_function.snap @@ -0,0 +1,10 @@ +--- +title: Custom Ignore Function +test_name: TestCustomIgnore +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "grade": "A", + "name": "John Doe" +} \ No newline at end of file diff --git a/__snapshots__/custom_regex_scrubber.snap b/__snapshots__/custom_regex_scrubber.snap new file mode 100644 index 0000000..e1cde1f --- /dev/null +++ b/__snapshots__/custom_regex_scrubber.snap @@ -0,0 +1,11 @@ +--- +title: Custom Regex Scrubber +test_name: TestCustomScrubbers/regex_scrubber +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "api_key": "", + "name": "Test User", + "secret_key": "" +} \ No newline at end of file diff --git a/__snapshots__/custom_scrubber.snap b/__snapshots__/custom_scrubber.snap new file mode 100644 index 0000000..0cb2c55 --- /dev/null +++ b/__snapshots__/custom_scrubber.snap @@ -0,0 +1,7 @@ +--- +title: Custom Scrubber +test_name: TestCustomScrubbers/custom_function_scrubber +file_name: scrubbers_test.go +version: 0.1.0 +--- +hello world! this is a test. \ No newline at end of file diff --git a/__snapshots__/custom_type_test.snap b/__snapshots__/custom_type_test.snap new file mode 100644 index 0000000..74b1774 --- /dev/null +++ b/__snapshots__/custom_type_test.snap @@ -0,0 +1,10 @@ +--- +title: Custom Type Test +test_name: TestSnapCustomType +file_name: shutter_test.go +version: 0.1.0 +--- +shutter_test.CustomStruct{ + Name: "Alice", + Age: 30, +} diff --git a/__snapshots__/exact_match_scrubber.snap b/__snapshots__/exact_match_scrubber.snap new file mode 100644 index 0000000..288c262 --- /dev/null +++ b/__snapshots__/exact_match_scrubber.snap @@ -0,0 +1,7 @@ +--- +title: Exact Match Scrubber +test_name: TestCustomScrubbers/exact_match_scrubber +file_name: scrubbers_test.go +version: 0.1.0 +--- +The secret password is '' and should be hidden. \ No newline at end of file diff --git a/__snapshots__/ignore_empty_values.snap b/__snapshots__/ignore_empty_values.snap new file mode 100644 index 0000000..ec59716 --- /dev/null +++ b/__snapshots__/ignore_empty_values.snap @@ -0,0 +1,10 @@ +--- +title: Ignore Empty Values +test_name: TestIgnoreValues/empty_values +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "email": "john@example.com", + "name": "John Doe" +} \ No newline at end of file diff --git a/__snapshots__/ignore_in_arrays.snap b/__snapshots__/ignore_in_arrays.snap new file mode 100644 index 0000000..963399e --- /dev/null +++ b/__snapshots__/ignore_in_arrays.snap @@ -0,0 +1,20 @@ +--- +title: Ignore in Arrays +test_name: TestIgnoreKeys/arrays +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "users": [ + { + "email": "alice@example.com", + "id": 1, + "name": "Alice" + }, + { + "email": "bob@example.com", + "id": 2, + "name": "Bob" + } + ] +} \ No newline at end of file diff --git a/__snapshots__/ignore_key_pattern.snap b/__snapshots__/ignore_key_pattern.snap new file mode 100644 index 0000000..dd7235e --- /dev/null +++ b/__snapshots__/ignore_key_pattern.snap @@ -0,0 +1,10 @@ +--- +title: Ignore Key Pattern +test_name: TestIgnoreKeyPatterns/contains_pattern +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "email": "john@example.com", + "username": "john_doe" +} \ No newline at end of file diff --git a/__snapshots__/ignore_keys_matching_pattern.snap b/__snapshots__/ignore_keys_matching_pattern.snap new file mode 100644 index 0000000..9cfcff5 --- /dev/null +++ b/__snapshots__/ignore_keys_matching_pattern.snap @@ -0,0 +1,10 @@ +--- +title: Ignore Keys Matching Pattern +test_name: TestIgnoreKeyPatterns/prefix_pattern +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "product_id": 100, + "product_name": "Widget" +} \ No newline at end of file diff --git a/__snapshots__/ignore_multiple_keys.snap b/__snapshots__/ignore_multiple_keys.snap new file mode 100644 index 0000000..65b7e94 --- /dev/null +++ b/__snapshots__/ignore_multiple_keys.snap @@ -0,0 +1,11 @@ +--- +title: Ignore Multiple Keys +test_name: TestIgnoreKeys/multiple_keys +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "email": "john@example.com", + "id": 1, + "name": "John Doe" +} \ No newline at end of file diff --git a/__snapshots__/ignore_null_values.snap b/__snapshots__/ignore_null_values.snap new file mode 100644 index 0000000..f1b4356 --- /dev/null +++ b/__snapshots__/ignore_null_values.snap @@ -0,0 +1,11 @@ +--- +title: Ignore Null Values +test_name: TestIgnoreValues/null_values +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "age": 30, + "email": "john@example.com", + "name": "John Doe" +} \ No newline at end of file diff --git a/__snapshots__/ignore_password_field.snap b/__snapshots__/ignore_password_field.snap new file mode 100644 index 0000000..dcf47ce --- /dev/null +++ b/__snapshots__/ignore_password_field.snap @@ -0,0 +1,10 @@ +--- +title: Ignore Password Field +test_name: TestIgnoreKeys/key_value_pairs +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "email": "john@example.com", + "username": "john_doe" +} \ No newline at end of file diff --git a/__snapshots__/ignore_sensitive_keys.snap b/__snapshots__/ignore_sensitive_keys.snap new file mode 100644 index 0000000..e8d1e3e --- /dev/null +++ b/__snapshots__/ignore_sensitive_keys.snap @@ -0,0 +1,11 @@ +--- +title: Ignore Sensitive Keys +test_name: TestIgnoreSensitiveKeys +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "email": "john@example.com", + "name": "John Doe", + "username": "john_doe" +} \ No newline at end of file diff --git a/__snapshots__/ignore_specific_values.snap b/__snapshots__/ignore_specific_values.snap new file mode 100644 index 0000000..595b8f2 --- /dev/null +++ b/__snapshots__/ignore_specific_values.snap @@ -0,0 +1,9 @@ +--- +title: Ignore Specific Values +test_name: TestIgnoreValues/specific_values +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "message": "Processing" +} \ No newline at end of file diff --git a/__snapshots__/json_with_special_characters.snap b/__snapshots__/json_with_special_characters.snap new file mode 100644 index 0000000..81b2d05 --- /dev/null +++ b/__snapshots__/json_with_special_characters.snap @@ -0,0 +1,16 @@ +--- +title: JSON with Special Characters +test_name: TestJsonWithSpecialCharacters +file_name: shutter_test.go +version: 0.1.0 +--- +map[string]interface{}{ + "backslash": "path\\to\\file", + "emoji": "πŸ˜€ πŸ˜ƒ πŸ˜„ 😁 πŸ˜†", + "english": "Hello, World!", + "escaped": "quotes: \"double\" and 'single'", + "newlines": "line1\nline2\rline3\r\nline4", + "special_chars": "!@#$%^&*()_+-=[]{}|;:,.<>?", + "tabs": "col1\tcol2\tcol3", + "unicode": "こんにけは δΈ–η•Œ 🌍", +} diff --git a/__snapshots__/large_json_structure.snap b/__snapshots__/large_json_structure.snap new file mode 100644 index 0000000..1792ff4 --- /dev/null +++ b/__snapshots__/large_json_structure.snap @@ -0,0 +1,54 @@ +--- +title: Large JSON Structure +test_name: TestLargeJson +file_name: shutter_test.go +version: 0.1.0 +--- +map[string]interface{}{ + "created_at": "2023-01-28T14:30:00Z", + "customer_id": 42.0, + "delivered_at": nil, + "id": 1001.0, + "products": []interface{}{ + map[string]interface{}{ + "description": "High-performance laptop", + "id": 1.0, + "in_stock": true, + "name": "Laptop", + "price": 999.99, + "stock": 5.0, + "tags": []interface{}{ + "electronics", + "computers", + "laptops", + }, + }, + map[string]interface{}{ + "description": "Wireless mouse", + "id": 2.0, + "in_stock": true, + "name": "Mouse", + "price": 29.99, + "stock": 50.0, + "tags": []interface{}{ + "electronics", + "accessories", + }, + }, + map[string]interface{}{ + "description": "Mechanical keyboard", + "id": 3.0, + "in_stock": false, + "name": "Keyboard", + "price": 149.99, + "stock": 0.0, + "tags": []interface{}{ + "electronics", + "accessories", + }, + }, + }, + "shipped_at": "2023-02-01T10:00:00Z", + "status": "shipped", + "total": 1179.97, +} diff --git a/__snapshots__/multiple_complex_structures.snap b/__snapshots__/multiple_complex_structures.snap new file mode 100644 index 0000000..78bb61b --- /dev/null +++ b/__snapshots__/multiple_complex_structures.snap @@ -0,0 +1,56 @@ +--- +title: Multiple Complex Structures +test_name: TestMultipleComplexStructures +file_name: shutter_test.go +version: 0.1.0 +--- +[]shutter_test.User{ + { + ID: 1, + Username: "alice", + Email: "alice@example.com", + Active: true, + CreatedAt: time.Time{ + wall: 0x0, + ext: 0, + loc: (*time.Location)(nil), + }, + Roles: []string{"user", "moderator"}, + Metadata: map[string]interface{}{ + "badge": "verified", + "verified": true, + }, + }, + { + ID: 2, + Username: "bob", + Email: "bob@example.com", + Active: false, + CreatedAt: time.Time{ + wall: 0x0, + ext: 0, + loc: (*time.Location)(nil), + }, + Roles: []string{"user"}, + Metadata: map[string]interface{}{ + "avatar": "https://example.com/bob.jpg", + "verified": false, + }, + }, + { + ID: 3, + Username: "charlie", + Email: "charlie@example.com", + Active: true, + CreatedAt: time.Time{ + wall: 0x0, + ext: 0, + loc: (*time.Location)(nil), + }, + Roles: []string{"user", "admin"}, + Metadata: map[string]interface{}{ + "account_age_days": 365, + "verified": true, + }, + }, +} diff --git a/__snapshots__/multiple_scrubbers.snap b/__snapshots__/multiple_scrubbers.snap new file mode 100644 index 0000000..fb10ace --- /dev/null +++ b/__snapshots__/multiple_scrubbers.snap @@ -0,0 +1,27 @@ +--- +title: Multiple Scrubbers +test_name: TestBuiltInScrubbers +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "api_key": "", + "backup_card": "", + "backup_email": "", + "birth_date": "", + "card_number": "", + "client_ip": "", + "created_at": "", + "email": "", + "jwt_token": "", + "message": "Connection from ", + "name": "John Doe", + "server_ip": "", + "session_id": "", + "stripe_key": "", + "unix_created": , + "unix_updated": , + "updated_at": "", + "us_format_date": "", + "user_id": "" +} \ No newline at end of file diff --git a/__snapshots__/multiple_values_test.snap b/__snapshots__/multiple_values_test.snap new file mode 100644 index 0000000..3dcdde8 --- /dev/null +++ b/__snapshots__/multiple_values_test.snap @@ -0,0 +1,16 @@ +--- +title: Multiple Values Test +test_name: TestSnapMultiple +file_name: shutter_test.go +version: 0.1.0 +--- +"value1" +"value2" +42 +"foo" +"bar" +"baz" +"wibble" +"wobble" +"tock" +interface{}(nil) diff --git a/__snapshots__/nested_ignore_patterns.snap b/__snapshots__/nested_ignore_patterns.snap new file mode 100644 index 0000000..ab257ec --- /dev/null +++ b/__snapshots__/nested_ignore_patterns.snap @@ -0,0 +1,18 @@ +--- +title: Nested Ignore Patterns +test_name: TestNestedIgnorePatterns +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "admin": {}, + "user": { + "email": "john@example.com", + "id": 1, + "name": "John Doe", + "profile": { + "bio": "Developer", + "website": "https://example.com" + } + } +} \ No newline at end of file diff --git a/__snapshots__/nested_maps_and_slices.snap b/__snapshots__/nested_maps_and_slices.snap new file mode 100644 index 0000000..8a36c69 --- /dev/null +++ b/__snapshots__/nested_maps_and_slices.snap @@ -0,0 +1,47 @@ +--- +title: Nested Maps and Slices +test_name: TestNestedMapsAndSlices +file_name: shutter_test.go +version: 0.1.0 +--- +map[string]interface{}{ + "posts": map[string]interface{}{ + "categories": []string{"tech", "lifestyle", "news"}, + "drafts": 5, + "published": 42, + }, + "stats": map[string]interface{}{ + "daily": map[string]interface{}{ + "clicks": 320, + "conversions": map[string]interface{}{ + "by_source": map[string]int{ + "organic": 25, + "paid": 15, + "referral": 5, + }, + "total": 45, + }, + "views": 1500, + }, + }, + "users": map[string]interface{}{ + "active": []map[string]interface{}{ + { + "id": 1, + "name": "Alice", + "verified": true, + }, + { + "id": 2, + "name": "Bob", + "verified": false, + }, + }, + "inactive": []map[string]interface{}{ + { + "id": 3, + "name": "Charlie", + }, + }, + }, +} diff --git a/__snapshots__/real_world_api_response.snap b/__snapshots__/real_world_api_response.snap new file mode 100644 index 0000000..b0a9a86 --- /dev/null +++ b/__snapshots__/real_world_api_response.snap @@ -0,0 +1,28 @@ +--- +title: Real World API Response +test_name: TestComplexRealWorldExample +file_name: ignore_test.go +version: 0.1.0 +--- +{ + "metadata": { + "server_ip": "", + "session_token": "", + "user_agent": "Mozilla/5.0" + }, + "request_id": "", + "timestamp": "", + "transaction": { + "amount": 99.99, + "currency": "USD", + "id": "txn_abc123", + "timestamp": "" + }, + "user": { + "created_at": "", + "email": "", + "id": "", + "ip_address": "", + "name": "John Doe" + } +} \ No newline at end of file diff --git a/__snapshots__/scrub_with_snap.snap b/__snapshots__/scrub_with_snap.snap new file mode 100644 index 0000000..12c2baf --- /dev/null +++ b/__snapshots__/scrub_with_snap.snap @@ -0,0 +1,12 @@ +--- +title: Scrub With Snap +test_name: TestScrubWithSnapFunction +file_name: scrubbers_test.go +version: 0.1.0 +--- +map[string]interface{}{ + "created_at": "", + "email": "", + "name": "John Doe", + "user_id": "", +} diff --git a/__snapshots__/scrubbed_api_keys.snap b/__snapshots__/scrubbed_api_keys.snap new file mode 100644 index 0000000..2f782f7 --- /dev/null +++ b/__snapshots__/scrubbed_api_keys.snap @@ -0,0 +1,12 @@ +--- +title: Scrubbed API Keys +test_name: TestIndividualScrubbers/api_keys +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "api_key_prod": "", + "name": "Test Config", + "stripe_key": "", + "test_key": "" +} \ No newline at end of file diff --git a/__snapshots__/scrubbed_credit_cards.snap b/__snapshots__/scrubbed_credit_cards.snap new file mode 100644 index 0000000..1af7960 --- /dev/null +++ b/__snapshots__/scrubbed_credit_cards.snap @@ -0,0 +1,12 @@ +--- +title: Scrubbed Credit Cards +test_name: TestIndividualScrubbers/credit_cards +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "another_card": "", + "backup_card": "", + "card_number": "", + "name": "John Doe" +} \ No newline at end of file diff --git a/__snapshots__/scrubbed_dates.snap b/__snapshots__/scrubbed_dates.snap new file mode 100644 index 0000000..65309a8 --- /dev/null +++ b/__snapshots__/scrubbed_dates.snap @@ -0,0 +1,12 @@ +--- +title: Scrubbed Dates +test_name: TestIndividualScrubbers/dates +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "birth_date": "", + "hire_date": "", + "name": "John Doe", + "us_format": "" +} \ No newline at end of file diff --git a/__snapshots__/scrubbed_emails.snap b/__snapshots__/scrubbed_emails.snap new file mode 100644 index 0000000..615f455 --- /dev/null +++ b/__snapshots__/scrubbed_emails.snap @@ -0,0 +1,11 @@ +--- +title: Scrubbed Emails +test_name: TestIndividualScrubbers/emails +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "backup_email": "", + "email": "", + "name": "John Doe" +} \ No newline at end of file diff --git a/__snapshots__/scrubbed_ips.snap b/__snapshots__/scrubbed_ips.snap new file mode 100644 index 0000000..07201ea --- /dev/null +++ b/__snapshots__/scrubbed_ips.snap @@ -0,0 +1,11 @@ +--- +title: Scrubbed IPs +test_name: TestIndividualScrubbers/ip_addresses +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "client_ip": "", + "message": "Connection from ", + "server_ip": "" +} \ No newline at end of file diff --git a/__snapshots__/scrubbed_jwts.snap b/__snapshots__/scrubbed_jwts.snap new file mode 100644 index 0000000..9f1005a --- /dev/null +++ b/__snapshots__/scrubbed_jwts.snap @@ -0,0 +1,10 @@ +--- +title: Scrubbed JWTs +test_name: TestIndividualScrubbers/jwts +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "refresh_token": "", + "token": "" +} \ No newline at end of file diff --git a/__snapshots__/scrubbed_timestamps.snap b/__snapshots__/scrubbed_timestamps.snap new file mode 100644 index 0000000..89e5ec8 --- /dev/null +++ b/__snapshots__/scrubbed_timestamps.snap @@ -0,0 +1,12 @@ +--- +title: Scrubbed Timestamps +test_name: TestIndividualScrubbers/timestamps +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "created_at": "", + "deleted_at": "", + "name": "Test Event", + "updated_at": "" +} \ No newline at end of file diff --git a/__snapshots__/scrubbed_unix_timestamps.snap b/__snapshots__/scrubbed_unix_timestamps.snap new file mode 100644 index 0000000..9d09d27 --- /dev/null +++ b/__snapshots__/scrubbed_unix_timestamps.snap @@ -0,0 +1,12 @@ +--- +title: Scrubbed Unix Timestamps +test_name: TestIndividualScrubbers/unix_timestamps +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "created": , + "deleted": , + "name": "Test Event", + "updated": +} \ No newline at end of file diff --git a/__snapshots__/scrubbed_uuids.snap b/__snapshots__/scrubbed_uuids.snap new file mode 100644 index 0000000..dd57c64 --- /dev/null +++ b/__snapshots__/scrubbed_uuids.snap @@ -0,0 +1,11 @@ +--- +title: Scrubbed UUIDs +test_name: TestIndividualScrubbers/uuid +file_name: scrubbers_test.go +version: 0.1.0 +--- +{ + "name": "John Doe", + "session_id": "", + "user_id": "" +} \ No newline at end of file diff --git a/__snapshots__/snapjson_complex_api_response.snap b/__snapshots__/snapjson_complex_api_response.snap new file mode 100644 index 0000000..f032d71 --- /dev/null +++ b/__snapshots__/snapjson_complex_api_response.snap @@ -0,0 +1,42 @@ +--- +title: SnapJSON Complex API Response +test_name: TestSnapJsonComplexAPI +file_name: shutter_test.go +version: 0.1.0 +--- +{ + "code": 200, + "data": { + "pagination": { + "page": 1, + "per_page": 10, + "total": 3, + "total_pages": 1 + }, + "users": [ + { + "active": true, + "department": "Engineering", + "id": 1, + "name": "Alice", + "role": "admin" + }, + { + "active": true, + "department": "Sales", + "id": 2, + "name": "Bob", + "role": "user" + }, + { + "active": false, + "department": "Marketing", + "id": 3, + "name": "Charlie", + "role": "user" + } + ] + }, + "status": "success", + "timestamp": "2023-11-18T21:45:30Z" +} \ No newline at end of file diff --git a/__snapshots__/snapjson_mixed_types.snap b/__snapshots__/snapjson_mixed_types.snap new file mode 100644 index 0000000..522cc49 --- /dev/null +++ b/__snapshots__/snapjson_mixed_types.snap @@ -0,0 +1,41 @@ +--- +title: SnapJSON Mixed Types +test_name: TestSnapJsonMixedTypes +file_name: shutter_test.go +version: 0.1.0 +--- +{ + "complex": [ + { + "id": 1, + "type": "user" + }, + { + "id": 100, + "type": "post" + }, + [ + 1, + 2, + 3 + ], + "string", + null + ], + "mixed_array": [ + "string", + 123, + 45.67, + true, + false, + null, + { + "nested": "object" + }, + [ + 1, + 2, + 3 + ] + ] +} \ No newline at end of file diff --git a/__snapshots__/snapjson_nested_objects.snap b/__snapshots__/snapjson_nested_objects.snap new file mode 100644 index 0000000..5460b3b --- /dev/null +++ b/__snapshots__/snapjson_nested_objects.snap @@ -0,0 +1,26 @@ +--- +title: SnapJSON Nested Objects +test_name: TestSnapJsonWithNestedObjects +file_name: shutter_test.go +version: 0.1.0 +--- +{ + "created_at": "2023-06-15T10:30:00Z", + "user": { + "id": 42, + "permissions": [ + "read", + "write", + "admin" + ], + "profile": { + "avatar": "https://example.com/avatar.jpg", + "settings": { + "language": "en", + "notifications": true, + "theme": "dark" + }, + "username": "jane_smith" + } + } +} \ No newline at end of file diff --git a/__snapshots__/snapjson_real_world_example.snap b/__snapshots__/snapjson_real_world_example.snap new file mode 100644 index 0000000..27d3ea6 --- /dev/null +++ b/__snapshots__/snapjson_real_world_example.snap @@ -0,0 +1,84 @@ +--- +title: SnapJSON Real World Example +test_name: TestSnapJsonRealWorldExample +file_name: shutter_test.go +version: 0.1.0 +--- +{ + "data": { + "product": { + "description": "High-quality wireless headphones with noise cancellation", + "id": "prod_12345", + "inventory": { + "available": 425, + "damaged": 25, + "reserved": 50, + "total": 500 + }, + "name": "Premium Wireless Headphones", + "price": { + "amount": 199.99, + "currency": "USD", + "discount": 10, + "final_price": 179.99 + }, + "ratings": { + "average": 4.5, + "breakdown": { + "1": 20, + "2": 30, + "3": 100, + "4": 350, + "5": 750 + }, + "count": 1250 + }, + "reviews": [ + { + "content": "Great sound quality and comfortable to wear.", + "created_at": "2023-11-15T10:30:00Z", + "helpful": 25, + "id": "rev_001", + "rating": 5, + "title": "Excellent product!", + "user": "john_doe" + }, + { + "content": "Works well, could be cheaper.", + "created_at": "2023-11-10T14:20:00Z", + "helpful": 12, + "id": "rev_002", + "rating": 4, + "title": "Good but pricey", + "user": "jane_smith" + } + ], + "sku": "PWH-001", + "specifications": { + "battery_life": "30 hours", + "colors": [ + "black", + "white", + "blue" + ], + "warranty_months": 24, + "weight": "250g" + } + }, + "related_products": [ + { + "id": "prod_12346", + "name": "Headphone Case", + "price": 29.99 + }, + { + "id": "prod_12347", + "name": "Audio Cable", + "price": 14.99 + } + ] + }, + "request_id": "req_abc123def456", + "success": true, + "timestamp": "2023-11-18T22:00:00Z" +} \ No newline at end of file diff --git a/__snapshots__/structure_with_empty_values.snap b/__snapshots__/structure_with_empty_values.snap new file mode 100644 index 0000000..073af35 --- /dev/null +++ b/__snapshots__/structure_with_empty_values.snap @@ -0,0 +1,34 @@ +--- +title: Structure with Empty Values +test_name: TestStructureWithEmptyValues +file_name: shutter_test.go +version: 0.1.0 +--- +[]shutter_test.Container{ + { + Items: []string{ + }, + Tags: map[string]string{ + }, + OptionalID: (*int)(nil), + Count: 0, + Active: false, + }, + { + Items: []string(nil), + Tags: map[string]string(nil), + OptionalID: (*int)(nil), + Count: 0, + Active: true, + }, + { + Items: []string{"a", "b", "c"}, + Tags: map[string]string{ + "env": "dev", + "type": "test", + }, + OptionalID: &int(42), + Count: 3, + Active: true, + }, +} diff --git a/__snapshots__/structure_with_interface_fields.snap b/__snapshots__/structure_with_interface_fields.snap new file mode 100644 index 0000000..16127b3 --- /dev/null +++ b/__snapshots__/structure_with_interface_fields.snap @@ -0,0 +1,76 @@ +--- +title: Structure with Interface Fields +test_name: TestStructureWithInterface +file_name: shutter_test.go +version: 0.1.0 +--- +[]shutter_test.Response{ + { + Status: "success", + Message: "User retrieved", + Data: shutter_test.User{ + ID: 1, + Username: "john", + Email: "john@example.com", + Active: true, + CreatedAt: time.Time{ + wall: 0x0, + ext: 0, + loc: (*time.Location)(nil), + }, + Roles: []string(nil), + Metadata: map[string]interface{}(nil), + }, + Meta: map[string]interface{}{ + "request_id": "req-123", + "timestamp": "2023-01-20T10:30:00Z", + }, + }, + { + Status: "error", + Message: "User not found", + Data: nil, + Meta: map[string]interface{}{ + "error_code": 404, + "error_type": "NOT_FOUND", + }, + }, + { + Status: "success", + Message: "Posts retrieved", + Data: []shutter_test.Post{ + { + ID: 1, + Title: "First Post", + Content: "", + Author: shutter_test.User{ + ID: 0, + Username: "", + Email: "", + Active: false, + CreatedAt: time.Time{ + wall: 0x0, + ext: 0, + loc: (*time.Location)(nil), + }, + Roles: []string(nil), + Metadata: map[string]interface{}(nil), + }, + Tags: []string(nil), + Comments: []shutter_test.Comment(nil), + Likes: 0, + Published: true, + CreatedAt: time.Time{ + wall: 0x0, + ext: 0, + loc: (*time.Location)(nil), + }, + }, + }, + Meta: map[string]interface{}{ + "page": 1, + "per_page": 20, + "total_count": 10, + }, + }, +} diff --git a/__snapshots__/structure_with_pointers.snap b/__snapshots__/structure_with_pointers.snap new file mode 100644 index 0000000..1c015de --- /dev/null +++ b/__snapshots__/structure_with_pointers.snap @@ -0,0 +1,27 @@ +--- +title: Structure with Pointers +test_name: TestStructureWithPointers +file_name: shutter_test.go +version: 0.1.0 +--- +shutter_test.Person{ + Name: "John", + Age: 35, + Address: &shutter_test.Address{ + Street: "123 Main St", + City: "Boston", + Zip: "02101", + }, + Manager: &shutter_test.Person{ + Name: "Jane", + Age: 30, + Address: (*shutter_test.Address)(), + Manager: (*shutter_test.Person)(nil), + Friends: []*shutter_test.Person(nil), + Email: &string("jane@example.com"), + }, + Friends: []*shutter_test.Person{ + (*shutter_test.Person)(), + }, + Email: (*string)(nil), +} diff --git a/editors/.editorconfig b/editor/tree-sitter-snapshot/.editorconfig similarity index 100% rename from editors/.editorconfig rename to editor/tree-sitter-snapshot/.editorconfig diff --git a/editors/.gitattributes b/editor/tree-sitter-snapshot/.gitattributes similarity index 100% rename from editors/.gitattributes rename to editor/tree-sitter-snapshot/.gitattributes diff --git a/editors/.gitignore b/editor/tree-sitter-snapshot/.gitignore similarity index 100% rename from editors/.gitignore rename to editor/tree-sitter-snapshot/.gitignore diff --git a/editors/CMakeLists.txt b/editor/tree-sitter-snapshot/CMakeLists.txt similarity index 100% rename from editors/CMakeLists.txt rename to editor/tree-sitter-snapshot/CMakeLists.txt diff --git a/editors/Cargo.toml b/editor/tree-sitter-snapshot/Cargo.toml similarity index 100% rename from editors/Cargo.toml rename to editor/tree-sitter-snapshot/Cargo.toml diff --git a/editors/Makefile b/editor/tree-sitter-snapshot/Makefile similarity index 100% rename from editors/Makefile rename to editor/tree-sitter-snapshot/Makefile diff --git a/editors/Package.swift b/editor/tree-sitter-snapshot/Package.swift similarity index 100% rename from editors/Package.swift rename to editor/tree-sitter-snapshot/Package.swift diff --git a/editors/binding.gyp b/editor/tree-sitter-snapshot/binding.gyp similarity index 100% rename from editors/binding.gyp rename to editor/tree-sitter-snapshot/binding.gyp diff --git a/editors/bindings/c/tree-sitter-snapshot.pc.in b/editor/tree-sitter-snapshot/bindings/c/tree-sitter-snapshot.pc.in similarity index 100% rename from editors/bindings/c/tree-sitter-snapshot.pc.in rename to editor/tree-sitter-snapshot/bindings/c/tree-sitter-snapshot.pc.in diff --git a/editors/bindings/c/tree_sitter/tree-sitter-snapshot.h b/editor/tree-sitter-snapshot/bindings/c/tree_sitter/tree-sitter-snapshot.h similarity index 100% rename from editors/bindings/c/tree_sitter/tree-sitter-snapshot.h rename to editor/tree-sitter-snapshot/bindings/c/tree_sitter/tree-sitter-snapshot.h diff --git a/editors/bindings/go/binding.go b/editor/tree-sitter-snapshot/bindings/go/binding.go similarity index 100% rename from editors/bindings/go/binding.go rename to editor/tree-sitter-snapshot/bindings/go/binding.go diff --git a/editors/bindings/go/binding_test.go b/editor/tree-sitter-snapshot/bindings/go/binding_test.go similarity index 100% rename from editors/bindings/go/binding_test.go rename to editor/tree-sitter-snapshot/bindings/go/binding_test.go diff --git a/editors/bindings/node/binding.cc b/editor/tree-sitter-snapshot/bindings/node/binding.cc similarity index 100% rename from editors/bindings/node/binding.cc rename to editor/tree-sitter-snapshot/bindings/node/binding.cc diff --git a/editors/bindings/node/binding_test.js b/editor/tree-sitter-snapshot/bindings/node/binding_test.js similarity index 100% rename from editors/bindings/node/binding_test.js rename to editor/tree-sitter-snapshot/bindings/node/binding_test.js diff --git a/editors/bindings/node/index.d.ts b/editor/tree-sitter-snapshot/bindings/node/index.d.ts similarity index 100% rename from editors/bindings/node/index.d.ts rename to editor/tree-sitter-snapshot/bindings/node/index.d.ts diff --git a/editors/bindings/node/index.js b/editor/tree-sitter-snapshot/bindings/node/index.js similarity index 100% rename from editors/bindings/node/index.js rename to editor/tree-sitter-snapshot/bindings/node/index.js diff --git a/editors/bindings/python/tests/test_binding.py b/editor/tree-sitter-snapshot/bindings/python/tests/test_binding.py similarity index 100% rename from editors/bindings/python/tests/test_binding.py rename to editor/tree-sitter-snapshot/bindings/python/tests/test_binding.py diff --git a/editors/bindings/python/tree_sitter_snapshot/__init__.py b/editor/tree-sitter-snapshot/bindings/python/tree_sitter_snapshot/__init__.py similarity index 100% rename from editors/bindings/python/tree_sitter_snapshot/__init__.py rename to editor/tree-sitter-snapshot/bindings/python/tree_sitter_snapshot/__init__.py diff --git a/editors/bindings/python/tree_sitter_snapshot/__init__.pyi b/editor/tree-sitter-snapshot/bindings/python/tree_sitter_snapshot/__init__.pyi similarity index 100% rename from editors/bindings/python/tree_sitter_snapshot/__init__.pyi rename to editor/tree-sitter-snapshot/bindings/python/tree_sitter_snapshot/__init__.pyi diff --git a/editors/bindings/python/tree_sitter_snapshot/binding.c b/editor/tree-sitter-snapshot/bindings/python/tree_sitter_snapshot/binding.c similarity index 100% rename from editors/bindings/python/tree_sitter_snapshot/binding.c rename to editor/tree-sitter-snapshot/bindings/python/tree_sitter_snapshot/binding.c diff --git a/editors/bindings/python/tree_sitter_snapshot/py.typed b/editor/tree-sitter-snapshot/bindings/python/tree_sitter_snapshot/py.typed similarity index 100% rename from editors/bindings/python/tree_sitter_snapshot/py.typed rename to editor/tree-sitter-snapshot/bindings/python/tree_sitter_snapshot/py.typed diff --git a/editors/bindings/rust/build.rs b/editor/tree-sitter-snapshot/bindings/rust/build.rs similarity index 100% rename from editors/bindings/rust/build.rs rename to editor/tree-sitter-snapshot/bindings/rust/build.rs diff --git a/editors/bindings/rust/lib.rs b/editor/tree-sitter-snapshot/bindings/rust/lib.rs similarity index 100% rename from editors/bindings/rust/lib.rs rename to editor/tree-sitter-snapshot/bindings/rust/lib.rs diff --git a/editors/bindings/swift/TreeSitterSnapshot/snapshot.h b/editor/tree-sitter-snapshot/bindings/swift/TreeSitterSnapshot/snapshot.h similarity index 100% rename from editors/bindings/swift/TreeSitterSnapshot/snapshot.h rename to editor/tree-sitter-snapshot/bindings/swift/TreeSitterSnapshot/snapshot.h diff --git a/editors/bindings/swift/TreeSitterSnapshotTests/TreeSitterSnapshotTests.swift b/editor/tree-sitter-snapshot/bindings/swift/TreeSitterSnapshotTests/TreeSitterSnapshotTests.swift similarity index 100% rename from editors/bindings/swift/TreeSitterSnapshotTests/TreeSitterSnapshotTests.swift rename to editor/tree-sitter-snapshot/bindings/swift/TreeSitterSnapshotTests/TreeSitterSnapshotTests.swift diff --git a/editors/go.mod b/editor/tree-sitter-snapshot/go.mod similarity index 100% rename from editors/go.mod rename to editor/tree-sitter-snapshot/go.mod diff --git a/editor/tree-sitter-snapshot/grammar.js b/editor/tree-sitter-snapshot/grammar.js new file mode 100644 index 0000000..25cfd7a --- /dev/null +++ b/editor/tree-sitter-snapshot/grammar.js @@ -0,0 +1,45 @@ +/** + * @file Snapshot test files generated by Shutter + * @author ptdewey + * @license MIT + */ + +/// +// @ts-check + +module.exports = grammar({ + name: "snapshot", + + extras: $ => [], + + rules: { + source_file: $ => seq( + $.front_matter, + optional($.body) + ), + + front_matter: $ => seq( + $.delimiter, + repeat($.field), + $.delimiter + ), + + delimiter: $ => seq('---', '\n'), + + field: $ => seq( + $.field_name, + ':', + optional(seq(' ', $.field_value)), + '\n' + ), + + field_name: $ => /[a-z_]+/, + + field_value: $ => /[^\n]+/, + + body: $ => repeat1(choice( + /[^\n]+/, + /\n/ + )), + } +}); diff --git a/editors/package.json b/editor/tree-sitter-snapshot/package.json similarity index 100% rename from editors/package.json rename to editor/tree-sitter-snapshot/package.json diff --git a/editors/pyproject.toml b/editor/tree-sitter-snapshot/pyproject.toml similarity index 100% rename from editors/pyproject.toml rename to editor/tree-sitter-snapshot/pyproject.toml diff --git a/editor/tree-sitter-snapshot/queries/highlights.scm b/editor/tree-sitter-snapshot/queries/highlights.scm new file mode 100644 index 0000000..07d22d8 --- /dev/null +++ b/editor/tree-sitter-snapshot/queries/highlights.scm @@ -0,0 +1,10 @@ +; Snapshot file syntax highlighting + +; Front matter delimiters +(delimiter) @punctuation.delimiter + +; Field names (keys) +(field_name) @property + +; Field values +(field_value) @string diff --git a/editor/tree-sitter-snapshot/queries/injections.scm b/editor/tree-sitter-snapshot/queries/injections.scm new file mode 100644 index 0000000..a7282fc --- /dev/null +++ b/editor/tree-sitter-snapshot/queries/injections.scm @@ -0,0 +1,23 @@ +; Language injections for snapshot body content +; +; Explicit content_type field in header takes precedence if present. +; Otherwise, JSON objects and arrays are detected by heuristic. + +; Explicit content_type field takes precedence +((front_matter + (field + (field_name) @_name + (field_value) @injection.language)) + (body) @injection.content + (#eq? @_name "content_type")) + +; JSON object: starts with { +((body) @injection.content + (#match? @injection.content "^\\s*\\{") + (#set! injection.language "json")) + +; JSON array: starts with [ +((body) @injection.content + (#match? @injection.content "^\\s*\\[") + (#set! injection.language "json")) + diff --git a/editors/setup.py b/editor/tree-sitter-snapshot/setup.py similarity index 100% rename from editors/setup.py rename to editor/tree-sitter-snapshot/setup.py diff --git a/editor/tree-sitter-snapshot/src/grammar.json b/editor/tree-sitter-snapshot/src/grammar.json new file mode 100644 index 0000000..5433e6e --- /dev/null +++ b/editor/tree-sitter-snapshot/src/grammar.json @@ -0,0 +1,129 @@ +{ + "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/grammar.schema.json", + "name": "snapshot", + "rules": { + "source_file": { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "front_matter" + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "body" + }, + { + "type": "BLANK" + } + ] + } + ] + }, + "front_matter": { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "delimiter" + }, + { + "type": "REPEAT", + "content": { + "type": "SYMBOL", + "name": "field" + } + }, + { + "type": "SYMBOL", + "name": "delimiter" + } + ] + }, + "delimiter": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "---" + }, + { + "type": "STRING", + "value": "\n" + } + ] + }, + "field": { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "field_name" + }, + { + "type": "STRING", + "value": ":" + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": " " + }, + { + "type": "SYMBOL", + "name": "field_value" + } + ] + }, + { + "type": "BLANK" + } + ] + }, + { + "type": "STRING", + "value": "\n" + } + ] + }, + "field_name": { + "type": "PATTERN", + "value": "[a-z_]+" + }, + "field_value": { + "type": "PATTERN", + "value": "[^\\n]+" + }, + "body": { + "type": "REPEAT1", + "content": { + "type": "CHOICE", + "members": [ + { + "type": "PATTERN", + "value": "[^\\n]+" + }, + { + "type": "PATTERN", + "value": "\\n" + } + ] + } + } + }, + "extras": [], + "conflicts": [], + "precedences": [], + "externals": [], + "inline": [], + "supertypes": [], + "reserved": {} +} \ No newline at end of file diff --git a/editor/tree-sitter-snapshot/src/node-types.json b/editor/tree-sitter-snapshot/src/node-types.json new file mode 100644 index 0000000..94096e0 --- /dev/null +++ b/editor/tree-sitter-snapshot/src/node-types.json @@ -0,0 +1,95 @@ +[ + { + "type": "body", + "named": true, + "fields": {} + }, + { + "type": "delimiter", + "named": true, + "fields": {} + }, + { + "type": "field", + "named": true, + "fields": {}, + "children": { + "multiple": true, + "required": true, + "types": [ + { + "type": "field_name", + "named": true + }, + { + "type": "field_value", + "named": true + } + ] + } + }, + { + "type": "field_value", + "named": true, + "fields": {} + }, + { + "type": "front_matter", + "named": true, + "fields": {}, + "children": { + "multiple": true, + "required": true, + "types": [ + { + "type": "delimiter", + "named": true + }, + { + "type": "field", + "named": true + } + ] + } + }, + { + "type": "source_file", + "named": true, + "root": true, + "fields": {}, + "children": { + "multiple": true, + "required": true, + "types": [ + { + "type": "body", + "named": true + }, + { + "type": "front_matter", + "named": true + } + ] + } + }, + { + "type": "\n", + "named": false + }, + { + "type": " ", + "named": false + }, + { + "type": "---", + "named": false + }, + { + "type": ":", + "named": false + }, + { + "type": "field_name", + "named": true + } +] \ No newline at end of file diff --git a/editor/tree-sitter-snapshot/src/parser.c b/editor/tree-sitter-snapshot/src/parser.c new file mode 100644 index 0000000..49cc1e6 --- /dev/null +++ b/editor/tree-sitter-snapshot/src/parser.c @@ -0,0 +1,502 @@ +/* Automatically @generated by tree-sitter v0.25.10 */ + +#include "tree_sitter/parser.h" + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#define LANGUAGE_VERSION 15 +#define STATE_COUNT 23 +#define LARGE_STATE_COUNT 2 +#define SYMBOL_COUNT 16 +#define ALIAS_COUNT 0 +#define TOKEN_COUNT 8 +#define EXTERNAL_TOKEN_COUNT 0 +#define FIELD_COUNT 0 +#define MAX_ALIAS_SEQUENCE_LENGTH 5 +#define MAX_RESERVED_WORD_SET_SIZE 0 +#define PRODUCTION_ID_COUNT 1 +#define SUPERTYPE_COUNT 0 + +enum ts_symbol_identifiers { + anon_sym_DASH_DASH_DASH = 1, + anon_sym_LF = 2, + anon_sym_COLON = 3, + anon_sym_SPACE = 4, + sym_field_name = 5, + aux_sym_field_value_token1 = 6, + aux_sym_body_token1 = 7, + sym_source_file = 8, + sym_front_matter = 9, + sym_delimiter = 10, + sym_field = 11, + sym_field_value = 12, + sym_body = 13, + aux_sym_front_matter_repeat1 = 14, + aux_sym_body_repeat1 = 15, +}; + +static const char * const ts_symbol_names[] = { + [ts_builtin_sym_end] = "end", + [anon_sym_DASH_DASH_DASH] = "---", + [anon_sym_LF] = "\n", + [anon_sym_COLON] = ":", + [anon_sym_SPACE] = " ", + [sym_field_name] = "field_name", + [aux_sym_field_value_token1] = "field_value_token1", + [aux_sym_body_token1] = "body_token1", + [sym_source_file] = "source_file", + [sym_front_matter] = "front_matter", + [sym_delimiter] = "delimiter", + [sym_field] = "field", + [sym_field_value] = "field_value", + [sym_body] = "body", + [aux_sym_front_matter_repeat1] = "front_matter_repeat1", + [aux_sym_body_repeat1] = "body_repeat1", +}; + +static const TSSymbol ts_symbol_map[] = { + [ts_builtin_sym_end] = ts_builtin_sym_end, + [anon_sym_DASH_DASH_DASH] = anon_sym_DASH_DASH_DASH, + [anon_sym_LF] = anon_sym_LF, + [anon_sym_COLON] = anon_sym_COLON, + [anon_sym_SPACE] = anon_sym_SPACE, + [sym_field_name] = sym_field_name, + [aux_sym_field_value_token1] = aux_sym_field_value_token1, + [aux_sym_body_token1] = aux_sym_body_token1, + [sym_source_file] = sym_source_file, + [sym_front_matter] = sym_front_matter, + [sym_delimiter] = sym_delimiter, + [sym_field] = sym_field, + [sym_field_value] = sym_field_value, + [sym_body] = sym_body, + [aux_sym_front_matter_repeat1] = aux_sym_front_matter_repeat1, + [aux_sym_body_repeat1] = aux_sym_body_repeat1, +}; + +static const TSSymbolMetadata ts_symbol_metadata[] = { + [ts_builtin_sym_end] = { + .visible = false, + .named = true, + }, + [anon_sym_DASH_DASH_DASH] = { + .visible = true, + .named = false, + }, + [anon_sym_LF] = { + .visible = true, + .named = false, + }, + [anon_sym_COLON] = { + .visible = true, + .named = false, + }, + [anon_sym_SPACE] = { + .visible = true, + .named = false, + }, + [sym_field_name] = { + .visible = true, + .named = true, + }, + [aux_sym_field_value_token1] = { + .visible = false, + .named = false, + }, + [aux_sym_body_token1] = { + .visible = false, + .named = false, + }, + [sym_source_file] = { + .visible = true, + .named = true, + }, + [sym_front_matter] = { + .visible = true, + .named = true, + }, + [sym_delimiter] = { + .visible = true, + .named = true, + }, + [sym_field] = { + .visible = true, + .named = true, + }, + [sym_field_value] = { + .visible = true, + .named = true, + }, + [sym_body] = { + .visible = true, + .named = true, + }, + [aux_sym_front_matter_repeat1] = { + .visible = false, + .named = false, + }, + [aux_sym_body_repeat1] = { + .visible = false, + .named = false, + }, +}; + +static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = { + [0] = {0}, +}; + +static const uint16_t ts_non_terminal_alias_map[] = { + 0, +}; + +static const TSStateId ts_primary_state_ids[STATE_COUNT] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, + [4] = 4, + [5] = 5, + [6] = 6, + [7] = 7, + [8] = 8, + [9] = 9, + [10] = 10, + [11] = 10, + [12] = 12, + [13] = 13, + [14] = 14, + [15] = 15, + [16] = 16, + [17] = 17, + [18] = 18, + [19] = 19, + [20] = 20, + [21] = 21, + [22] = 16, +}; + +static bool ts_lex(TSLexer *lexer, TSStateId state) { + START_LEXER(); + eof = lexer->eof(lexer); + switch (state) { + case 0: + if (eof) ADVANCE(5); + if (lookahead == '\n') ADVANCE(7); + if (lookahead == ' ') ADVANCE(9); + if (lookahead == '-') ADVANCE(3); + if (lookahead == ':') ADVANCE(8); + if (lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(10); + END_STATE(); + case 1: + if (lookahead == '\n') ADVANCE(7); + if (lookahead == ' ') ADVANCE(9); + END_STATE(); + case 2: + if (lookahead == '-') ADVANCE(6); + END_STATE(); + case 3: + if (lookahead == '-') ADVANCE(2); + END_STATE(); + case 4: + if (eof) ADVANCE(5); + if (lookahead == '\n') ADVANCE(12); + if (lookahead != 0) ADVANCE(11); + END_STATE(); + case 5: + ACCEPT_TOKEN(ts_builtin_sym_end); + END_STATE(); + case 6: + ACCEPT_TOKEN(anon_sym_DASH_DASH_DASH); + END_STATE(); + case 7: + ACCEPT_TOKEN(anon_sym_LF); + END_STATE(); + case 8: + ACCEPT_TOKEN(anon_sym_COLON); + END_STATE(); + case 9: + ACCEPT_TOKEN(anon_sym_SPACE); + END_STATE(); + case 10: + ACCEPT_TOKEN(sym_field_name); + if (lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(10); + END_STATE(); + case 11: + ACCEPT_TOKEN(aux_sym_field_value_token1); + if (lookahead != 0 && + lookahead != '\n') ADVANCE(11); + END_STATE(); + case 12: + ACCEPT_TOKEN(aux_sym_body_token1); + END_STATE(); + default: + return false; + } +} + +static const TSLexerMode ts_lex_modes[STATE_COUNT] = { + [0] = {.lex_state = 0}, + [1] = {.lex_state = 0}, + [2] = {.lex_state = 0}, + [3] = {.lex_state = 4}, + [4] = {.lex_state = 0}, + [5] = {.lex_state = 4}, + [6] = {.lex_state = 4}, + [7] = {.lex_state = 0}, + [8] = {.lex_state = 4}, + [9] = {.lex_state = 4}, + [10] = {.lex_state = 4}, + [11] = {.lex_state = 0}, + [12] = {.lex_state = 1}, + [13] = {.lex_state = 0}, + [14] = {.lex_state = 4}, + [15] = {.lex_state = 0}, + [16] = {.lex_state = 1}, + [17] = {.lex_state = 0}, + [18] = {.lex_state = 0}, + [19] = {.lex_state = 0}, + [20] = {.lex_state = 1}, + [21] = {.lex_state = 1}, + [22] = {.lex_state = 1}, +}; + +static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { + [STATE(0)] = { + [ts_builtin_sym_end] = ACTIONS(1), + [anon_sym_DASH_DASH_DASH] = ACTIONS(1), + [anon_sym_LF] = ACTIONS(1), + [anon_sym_COLON] = ACTIONS(1), + [anon_sym_SPACE] = ACTIONS(1), + [sym_field_name] = ACTIONS(1), + [aux_sym_body_token1] = ACTIONS(1), + }, + [STATE(1)] = { + [sym_source_file] = STATE(18), + [sym_front_matter] = STATE(3), + [sym_delimiter] = STATE(2), + [anon_sym_DASH_DASH_DASH] = ACTIONS(3), + }, +}; + +static const uint16_t ts_small_parse_table[] = { + [0] = 4, + ACTIONS(5), 1, + anon_sym_DASH_DASH_DASH, + ACTIONS(7), 1, + sym_field_name, + STATE(8), 1, + sym_delimiter, + STATE(4), 2, + sym_field, + aux_sym_front_matter_repeat1, + [14] = 4, + ACTIONS(9), 1, + ts_builtin_sym_end, + STATE(5), 1, + aux_sym_body_repeat1, + STATE(17), 1, + sym_body, + ACTIONS(11), 2, + aux_sym_field_value_token1, + aux_sym_body_token1, + [28] = 4, + ACTIONS(5), 1, + anon_sym_DASH_DASH_DASH, + ACTIONS(7), 1, + sym_field_name, + STATE(9), 1, + sym_delimiter, + STATE(7), 2, + sym_field, + aux_sym_front_matter_repeat1, + [42] = 3, + ACTIONS(13), 1, + ts_builtin_sym_end, + STATE(6), 1, + aux_sym_body_repeat1, + ACTIONS(15), 2, + aux_sym_field_value_token1, + aux_sym_body_token1, + [53] = 3, + ACTIONS(17), 1, + ts_builtin_sym_end, + STATE(6), 1, + aux_sym_body_repeat1, + ACTIONS(19), 2, + aux_sym_field_value_token1, + aux_sym_body_token1, + [64] = 3, + ACTIONS(22), 1, + anon_sym_DASH_DASH_DASH, + ACTIONS(24), 1, + sym_field_name, + STATE(7), 2, + sym_field, + aux_sym_front_matter_repeat1, + [75] = 1, + ACTIONS(27), 3, + ts_builtin_sym_end, + aux_sym_field_value_token1, + aux_sym_body_token1, + [81] = 1, + ACTIONS(29), 3, + ts_builtin_sym_end, + aux_sym_field_value_token1, + aux_sym_body_token1, + [87] = 1, + ACTIONS(31), 3, + ts_builtin_sym_end, + aux_sym_field_value_token1, + aux_sym_body_token1, + [93] = 1, + ACTIONS(31), 2, + anon_sym_DASH_DASH_DASH, + sym_field_name, + [98] = 2, + ACTIONS(33), 1, + anon_sym_LF, + ACTIONS(35), 1, + anon_sym_SPACE, + [105] = 1, + ACTIONS(37), 2, + anon_sym_DASH_DASH_DASH, + sym_field_name, + [110] = 2, + ACTIONS(39), 1, + aux_sym_field_value_token1, + STATE(21), 1, + sym_field_value, + [117] = 1, + ACTIONS(41), 2, + anon_sym_DASH_DASH_DASH, + sym_field_name, + [122] = 1, + ACTIONS(43), 1, + anon_sym_LF, + [126] = 1, + ACTIONS(45), 1, + ts_builtin_sym_end, + [130] = 1, + ACTIONS(47), 1, + ts_builtin_sym_end, + [134] = 1, + ACTIONS(49), 1, + anon_sym_COLON, + [138] = 1, + ACTIONS(51), 1, + anon_sym_LF, + [142] = 1, + ACTIONS(53), 1, + anon_sym_LF, + [146] = 1, + ACTIONS(55), 1, + anon_sym_LF, +}; + +static const uint32_t ts_small_parse_table_map[] = { + [SMALL_STATE(2)] = 0, + [SMALL_STATE(3)] = 14, + [SMALL_STATE(4)] = 28, + [SMALL_STATE(5)] = 42, + [SMALL_STATE(6)] = 53, + [SMALL_STATE(7)] = 64, + [SMALL_STATE(8)] = 75, + [SMALL_STATE(9)] = 81, + [SMALL_STATE(10)] = 87, + [SMALL_STATE(11)] = 93, + [SMALL_STATE(12)] = 98, + [SMALL_STATE(13)] = 105, + [SMALL_STATE(14)] = 110, + [SMALL_STATE(15)] = 117, + [SMALL_STATE(16)] = 122, + [SMALL_STATE(17)] = 126, + [SMALL_STATE(18)] = 130, + [SMALL_STATE(19)] = 134, + [SMALL_STATE(20)] = 138, + [SMALL_STATE(21)] = 142, + [SMALL_STATE(22)] = 146, +}; + +static const TSParseActionEntry ts_parse_actions[] = { + [0] = {.entry = {.count = 0, .reusable = false}}, + [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(), + [3] = {.entry = {.count = 1, .reusable = true}}, SHIFT(16), + [5] = {.entry = {.count = 1, .reusable = true}}, SHIFT(22), + [7] = {.entry = {.count = 1, .reusable = true}}, SHIFT(19), + [9] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 1, 0, 0), + [11] = {.entry = {.count = 1, .reusable = true}}, SHIFT(5), + [13] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_body, 1, 0, 0), + [15] = {.entry = {.count = 1, .reusable = true}}, SHIFT(6), + [17] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_body_repeat1, 2, 0, 0), + [19] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_body_repeat1, 2, 0, 0), SHIFT_REPEAT(6), + [22] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_front_matter_repeat1, 2, 0, 0), + [24] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_front_matter_repeat1, 2, 0, 0), SHIFT_REPEAT(19), + [27] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_front_matter, 2, 0, 0), + [29] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_front_matter, 3, 0, 0), + [31] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_delimiter, 2, 0, 0), + [33] = {.entry = {.count = 1, .reusable = true}}, SHIFT(13), + [35] = {.entry = {.count = 1, .reusable = true}}, SHIFT(14), + [37] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_field, 3, 0, 0), + [39] = {.entry = {.count = 1, .reusable = true}}, SHIFT(20), + [41] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_field, 5, 0, 0), + [43] = {.entry = {.count = 1, .reusable = true}}, SHIFT(11), + [45] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 2, 0, 0), + [47] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(), + [49] = {.entry = {.count = 1, .reusable = true}}, SHIFT(12), + [51] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_field_value, 1, 0, 0), + [53] = {.entry = {.count = 1, .reusable = true}}, SHIFT(15), + [55] = {.entry = {.count = 1, .reusable = true}}, SHIFT(10), +}; + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef TREE_SITTER_HIDE_SYMBOLS +#define TS_PUBLIC +#elif defined(_WIN32) +#define TS_PUBLIC __declspec(dllexport) +#else +#define TS_PUBLIC __attribute__((visibility("default"))) +#endif + +TS_PUBLIC const TSLanguage *tree_sitter_snapshot(void) { + static const TSLanguage language = { + .abi_version = LANGUAGE_VERSION, + .symbol_count = SYMBOL_COUNT, + .alias_count = ALIAS_COUNT, + .token_count = TOKEN_COUNT, + .external_token_count = EXTERNAL_TOKEN_COUNT, + .state_count = STATE_COUNT, + .large_state_count = LARGE_STATE_COUNT, + .production_id_count = PRODUCTION_ID_COUNT, + .supertype_count = SUPERTYPE_COUNT, + .field_count = FIELD_COUNT, + .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, + .parse_table = &ts_parse_table[0][0], + .small_parse_table = ts_small_parse_table, + .small_parse_table_map = ts_small_parse_table_map, + .parse_actions = ts_parse_actions, + .symbol_names = ts_symbol_names, + .symbol_metadata = ts_symbol_metadata, + .public_symbol_map = ts_symbol_map, + .alias_map = ts_non_terminal_alias_map, + .alias_sequences = &ts_alias_sequences[0][0], + .lex_modes = (const void*)ts_lex_modes, + .lex_fn = ts_lex, + .primary_state_ids = ts_primary_state_ids, + .name = "snapshot", + .max_reserved_word_set_size = 0, + .metadata = { + .major_version = 0, + .minor_version = 1, + .patch_version = 0, + }, + }; + return &language; +} +#ifdef __cplusplus +} +#endif diff --git a/editor/tree-sitter-snapshot/src/tree_sitter/alloc.h b/editor/tree-sitter-snapshot/src/tree_sitter/alloc.h new file mode 100644 index 0000000..1abdd12 --- /dev/null +++ b/editor/tree-sitter-snapshot/src/tree_sitter/alloc.h @@ -0,0 +1,54 @@ +#ifndef TREE_SITTER_ALLOC_H_ +#define TREE_SITTER_ALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +// Allow clients to override allocation functions +#ifdef TREE_SITTER_REUSE_ALLOCATOR + +extern void *(*ts_current_malloc)(size_t size); +extern void *(*ts_current_calloc)(size_t count, size_t size); +extern void *(*ts_current_realloc)(void *ptr, size_t size); +extern void (*ts_current_free)(void *ptr); + +#ifndef ts_malloc +#define ts_malloc ts_current_malloc +#endif +#ifndef ts_calloc +#define ts_calloc ts_current_calloc +#endif +#ifndef ts_realloc +#define ts_realloc ts_current_realloc +#endif +#ifndef ts_free +#define ts_free ts_current_free +#endif + +#else + +#ifndef ts_malloc +#define ts_malloc malloc +#endif +#ifndef ts_calloc +#define ts_calloc calloc +#endif +#ifndef ts_realloc +#define ts_realloc realloc +#endif +#ifndef ts_free +#define ts_free free +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ALLOC_H_ diff --git a/editor/tree-sitter-snapshot/src/tree_sitter/array.h b/editor/tree-sitter-snapshot/src/tree_sitter/array.h new file mode 100644 index 0000000..a17a574 --- /dev/null +++ b/editor/tree-sitter-snapshot/src/tree_sitter/array.h @@ -0,0 +1,291 @@ +#ifndef TREE_SITTER_ARRAY_H_ +#define TREE_SITTER_ARRAY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./alloc.h" + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4101) +#elif defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#define Array(T) \ + struct { \ + T *contents; \ + uint32_t size; \ + uint32_t capacity; \ + } + +/// Initialize an array. +#define array_init(self) \ + ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) + +/// Create an empty array. +#define array_new() \ + { NULL, 0, 0 } + +/// Get a pointer to the element at a given `index` in the array. +#define array_get(self, _index) \ + (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index]) + +/// Get a pointer to the first element in the array. +#define array_front(self) array_get(self, 0) + +/// Get a pointer to the last element in the array. +#define array_back(self) array_get(self, (self)->size - 1) + +/// Clear the array, setting its size to zero. Note that this does not free any +/// memory allocated for the array's contents. +#define array_clear(self) ((self)->size = 0) + +/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is +/// less than the array's current capacity, this function has no effect. +#define array_reserve(self, new_capacity) \ + _array__reserve((Array *)(self), array_elem_size(self), new_capacity) + +/// Free any memory allocated for this array. Note that this does not free any +/// memory allocated for the array's contents. +#define array_delete(self) _array__delete((Array *)(self)) + +/// Push a new `element` onto the end of the array. +#define array_push(self, element) \ + (_array__grow((Array *)(self), 1, array_elem_size(self)), \ + (self)->contents[(self)->size++] = (element)) + +/// Increase the array's size by `count` elements. +/// New elements are zero-initialized. +#define array_grow_by(self, count) \ + do { \ + if ((count) == 0) break; \ + _array__grow((Array *)(self), count, array_elem_size(self)); \ + memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \ + (self)->size += (count); \ + } while (0) + +/// Append all elements from one array to the end of another. +#define array_push_all(self, other) \ + array_extend((self), (other)->size, (other)->contents) + +/// Append `count` elements to the end of the array, reading their values from the +/// `contents` pointer. +#define array_extend(self, count, contents) \ + _array__splice( \ + (Array *)(self), array_elem_size(self), (self)->size, \ + 0, count, contents \ + ) + +/// Remove `old_count` elements from the array starting at the given `index`. At +/// the same index, insert `new_count` new elements, reading their values from the +/// `new_contents` pointer. +#define array_splice(self, _index, old_count, new_count, new_contents) \ + _array__splice( \ + (Array *)(self), array_elem_size(self), _index, \ + old_count, new_count, new_contents \ + ) + +/// Insert one `element` into the array at the given `index`. +#define array_insert(self, _index, element) \ + _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element)) + +/// Remove one element from the array at the given `index`. +#define array_erase(self, _index) \ + _array__erase((Array *)(self), array_elem_size(self), _index) + +/// Pop the last element off the array, returning the element by value. +#define array_pop(self) ((self)->contents[--(self)->size]) + +/// Assign the contents of one array to another, reallocating if necessary. +#define array_assign(self, other) \ + _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self)) + +/// Swap one array with another +#define array_swap(self, other) \ + _array__swap((Array *)(self), (Array *)(other)) + +/// Get the size of the array contents +#define array_elem_size(self) (sizeof *(self)->contents) + +/// Search a sorted array for a given `needle` value, using the given `compare` +/// callback to determine the order. +/// +/// If an existing element is found to be equal to `needle`, then the `index` +/// out-parameter is set to the existing value's index, and the `exists` +/// out-parameter is set to true. Otherwise, `index` is set to an index where +/// `needle` should be inserted in order to preserve the sorting, and `exists` +/// is set to false. +#define array_search_sorted_with(self, compare, needle, _index, _exists) \ + _array__search_sorted(self, 0, compare, , needle, _index, _exists) + +/// Search a sorted array for a given `needle` value, using integer comparisons +/// of a given struct field (specified with a leading dot) to determine the order. +/// +/// See also `array_search_sorted_with`. +#define array_search_sorted_by(self, field, needle, _index, _exists) \ + _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists) + +/// Insert a given `value` into a sorted array, using the given `compare` +/// callback to determine the order. +#define array_insert_sorted_with(self, compare, value) \ + do { \ + unsigned _index, _exists; \ + array_search_sorted_with(self, compare, &(value), &_index, &_exists); \ + if (!_exists) array_insert(self, _index, value); \ + } while (0) + +/// Insert a given `value` into a sorted array, using integer comparisons of +/// a given struct field (specified with a leading dot) to determine the order. +/// +/// See also `array_search_sorted_by`. +#define array_insert_sorted_by(self, field, value) \ + do { \ + unsigned _index, _exists; \ + array_search_sorted_by(self, field, (value) field, &_index, &_exists); \ + if (!_exists) array_insert(self, _index, value); \ + } while (0) + +// Private + +typedef Array(void) Array; + +/// This is not what you're looking for, see `array_delete`. +static inline void _array__delete(Array *self) { + if (self->contents) { + ts_free(self->contents); + self->contents = NULL; + self->size = 0; + self->capacity = 0; + } +} + +/// This is not what you're looking for, see `array_erase`. +static inline void _array__erase(Array *self, size_t element_size, + uint32_t index) { + assert(index < self->size); + char *contents = (char *)self->contents; + memmove(contents + index * element_size, contents + (index + 1) * element_size, + (self->size - index - 1) * element_size); + self->size--; +} + +/// This is not what you're looking for, see `array_reserve`. +static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) { + if (new_capacity > self->capacity) { + if (self->contents) { + self->contents = ts_realloc(self->contents, new_capacity * element_size); + } else { + self->contents = ts_malloc(new_capacity * element_size); + } + self->capacity = new_capacity; + } +} + +/// This is not what you're looking for, see `array_assign`. +static inline void _array__assign(Array *self, const Array *other, size_t element_size) { + _array__reserve(self, element_size, other->size); + self->size = other->size; + memcpy(self->contents, other->contents, self->size * element_size); +} + +/// This is not what you're looking for, see `array_swap`. +static inline void _array__swap(Array *self, Array *other) { + Array swap = *other; + *other = *self; + *self = swap; +} + +/// This is not what you're looking for, see `array_push` or `array_grow_by`. +static inline void _array__grow(Array *self, uint32_t count, size_t element_size) { + uint32_t new_size = self->size + count; + if (new_size > self->capacity) { + uint32_t new_capacity = self->capacity * 2; + if (new_capacity < 8) new_capacity = 8; + if (new_capacity < new_size) new_capacity = new_size; + _array__reserve(self, element_size, new_capacity); + } +} + +/// This is not what you're looking for, see `array_splice`. +static inline void _array__splice(Array *self, size_t element_size, + uint32_t index, uint32_t old_count, + uint32_t new_count, const void *elements) { + uint32_t new_size = self->size + new_count - old_count; + uint32_t old_end = index + old_count; + uint32_t new_end = index + new_count; + assert(old_end <= self->size); + + _array__reserve(self, element_size, new_size); + + char *contents = (char *)self->contents; + if (self->size > old_end) { + memmove( + contents + new_end * element_size, + contents + old_end * element_size, + (self->size - old_end) * element_size + ); + } + if (new_count > 0) { + if (elements) { + memcpy( + (contents + index * element_size), + elements, + new_count * element_size + ); + } else { + memset( + (contents + index * element_size), + 0, + new_count * element_size + ); + } + } + self->size += new_count - old_count; +} + +/// A binary search routine, based on Rust's `std::slice::binary_search_by`. +/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`. +#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \ + do { \ + *(_index) = start; \ + *(_exists) = false; \ + uint32_t size = (self)->size - *(_index); \ + if (size == 0) break; \ + int comparison; \ + while (size > 1) { \ + uint32_t half_size = size / 2; \ + uint32_t mid_index = *(_index) + half_size; \ + comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \ + if (comparison <= 0) *(_index) = mid_index; \ + size -= half_size; \ + } \ + comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \ + if (comparison == 0) *(_exists) = true; \ + else if (comparison < 0) *(_index) += 1; \ + } while (0) + +/// Helper macro for the `_sorted_by` routines below. This takes the left (existing) +/// parameter by reference in order to work with the generic sorting function above. +#define _compare_int(a, b) ((int)*(a) - (int)(b)) + +#ifdef _MSC_VER +#pragma warning(pop) +#elif defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ARRAY_H_ diff --git a/editor/tree-sitter-snapshot/src/tree_sitter/parser.h b/editor/tree-sitter-snapshot/src/tree_sitter/parser.h new file mode 100644 index 0000000..858107d --- /dev/null +++ b/editor/tree-sitter-snapshot/src/tree_sitter/parser.h @@ -0,0 +1,286 @@ +#ifndef TREE_SITTER_PARSER_H_ +#define TREE_SITTER_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define ts_builtin_sym_error ((TSSymbol)-1) +#define ts_builtin_sym_end 0 +#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 + +#ifndef TREE_SITTER_API_H_ +typedef uint16_t TSStateId; +typedef uint16_t TSSymbol; +typedef uint16_t TSFieldId; +typedef struct TSLanguage TSLanguage; +typedef struct TSLanguageMetadata { + uint8_t major_version; + uint8_t minor_version; + uint8_t patch_version; +} TSLanguageMetadata; +#endif + +typedef struct { + TSFieldId field_id; + uint8_t child_index; + bool inherited; +} TSFieldMapEntry; + +// Used to index the field and supertype maps. +typedef struct { + uint16_t index; + uint16_t length; +} TSMapSlice; + +typedef struct { + bool visible; + bool named; + bool supertype; +} TSSymbolMetadata; + +typedef struct TSLexer TSLexer; + +struct TSLexer { + int32_t lookahead; + TSSymbol result_symbol; + void (*advance)(TSLexer *, bool); + void (*mark_end)(TSLexer *); + uint32_t (*get_column)(TSLexer *); + bool (*is_at_included_range_start)(const TSLexer *); + bool (*eof)(const TSLexer *); + void (*log)(const TSLexer *, const char *, ...); +}; + +typedef enum { + TSParseActionTypeShift, + TSParseActionTypeReduce, + TSParseActionTypeAccept, + TSParseActionTypeRecover, +} TSParseActionType; + +typedef union { + struct { + uint8_t type; + TSStateId state; + bool extra; + bool repetition; + } shift; + struct { + uint8_t type; + uint8_t child_count; + TSSymbol symbol; + int16_t dynamic_precedence; + uint16_t production_id; + } reduce; + uint8_t type; +} TSParseAction; + +typedef struct { + uint16_t lex_state; + uint16_t external_lex_state; +} TSLexMode; + +typedef struct { + uint16_t lex_state; + uint16_t external_lex_state; + uint16_t reserved_word_set_id; +} TSLexerMode; + +typedef union { + TSParseAction action; + struct { + uint8_t count; + bool reusable; + } entry; +} TSParseActionEntry; + +typedef struct { + int32_t start; + int32_t end; +} TSCharacterRange; + +struct TSLanguage { + uint32_t abi_version; + uint32_t symbol_count; + uint32_t alias_count; + uint32_t token_count; + uint32_t external_token_count; + uint32_t state_count; + uint32_t large_state_count; + uint32_t production_id_count; + uint32_t field_count; + uint16_t max_alias_sequence_length; + const uint16_t *parse_table; + const uint16_t *small_parse_table; + const uint32_t *small_parse_table_map; + const TSParseActionEntry *parse_actions; + const char * const *symbol_names; + const char * const *field_names; + const TSMapSlice *field_map_slices; + const TSFieldMapEntry *field_map_entries; + const TSSymbolMetadata *symbol_metadata; + const TSSymbol *public_symbol_map; + const uint16_t *alias_map; + const TSSymbol *alias_sequences; + const TSLexerMode *lex_modes; + bool (*lex_fn)(TSLexer *, TSStateId); + bool (*keyword_lex_fn)(TSLexer *, TSStateId); + TSSymbol keyword_capture_token; + struct { + const bool *states; + const TSSymbol *symbol_map; + void *(*create)(void); + void (*destroy)(void *); + bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); + unsigned (*serialize)(void *, char *); + void (*deserialize)(void *, const char *, unsigned); + } external_scanner; + const TSStateId *primary_state_ids; + const char *name; + const TSSymbol *reserved_words; + uint16_t max_reserved_word_set_size; + uint32_t supertype_count; + const TSSymbol *supertype_symbols; + const TSMapSlice *supertype_map_slices; + const TSSymbol *supertype_map_entries; + TSLanguageMetadata metadata; +}; + +static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) { + uint32_t index = 0; + uint32_t size = len - index; + while (size > 1) { + uint32_t half_size = size / 2; + uint32_t mid_index = index + half_size; + const TSCharacterRange *range = &ranges[mid_index]; + if (lookahead >= range->start && lookahead <= range->end) { + return true; + } else if (lookahead > range->end) { + index = mid_index; + } + size -= half_size; + } + const TSCharacterRange *range = &ranges[index]; + return (lookahead >= range->start && lookahead <= range->end); +} + +/* + * Lexer Macros + */ + +#ifdef _MSC_VER +#define UNUSED __pragma(warning(suppress : 4101)) +#else +#define UNUSED __attribute__((unused)) +#endif + +#define START_LEXER() \ + bool result = false; \ + bool skip = false; \ + UNUSED \ + bool eof = false; \ + int32_t lookahead; \ + goto start; \ + next_state: \ + lexer->advance(lexer, skip); \ + start: \ + skip = false; \ + lookahead = lexer->lookahead; + +#define ADVANCE(state_value) \ + { \ + state = state_value; \ + goto next_state; \ + } + +#define ADVANCE_MAP(...) \ + { \ + static const uint16_t map[] = { __VA_ARGS__ }; \ + for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \ + if (map[i] == lookahead) { \ + state = map[i + 1]; \ + goto next_state; \ + } \ + } \ + } + +#define SKIP(state_value) \ + { \ + skip = true; \ + state = state_value; \ + goto next_state; \ + } + +#define ACCEPT_TOKEN(symbol_value) \ + result = true; \ + lexer->result_symbol = symbol_value; \ + lexer->mark_end(lexer); + +#define END_STATE() return result; + +/* + * Parse Table Macros + */ + +#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT) + +#define STATE(id) id + +#define ACTIONS(id) id + +#define SHIFT(state_value) \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .state = (state_value) \ + } \ + }} + +#define SHIFT_REPEAT(state_value) \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .state = (state_value), \ + .repetition = true \ + } \ + }} + +#define SHIFT_EXTRA() \ + {{ \ + .shift = { \ + .type = TSParseActionTypeShift, \ + .extra = true \ + } \ + }} + +#define REDUCE(symbol_name, children, precedence, prod_id) \ + {{ \ + .reduce = { \ + .type = TSParseActionTypeReduce, \ + .symbol = symbol_name, \ + .child_count = children, \ + .dynamic_precedence = precedence, \ + .production_id = prod_id \ + }, \ + }} + +#define RECOVER() \ + {{ \ + .type = TSParseActionTypeRecover \ + }} + +#define ACCEPT_INPUT() \ + {{ \ + .type = TSParseActionTypeAccept \ + }} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_PARSER_H_ diff --git a/editor/tree-sitter-snapshot/test/corpus/basic.txt b/editor/tree-sitter-snapshot/test/corpus/basic.txt new file mode 100644 index 0000000..453148c --- /dev/null +++ b/editor/tree-sitter-snapshot/test/corpus/basic.txt @@ -0,0 +1,92 @@ +================== +Snapshot with single field +================== +--- +title: Test +--- +body content + +--- + +(source_file + (front_matter + (delimiter) + (field + (field_name) + (field_value)) + (delimiter)) + (body)) + +================== +Snapshot with multiple fields +================== +--- +title: Test Snapshot +test_name: TestExample +file_name: example_test.go +version: 0.1.0 +--- +{"key": "value"} + +--- + +(source_file + (front_matter + (delimiter) + (field + (field_name) + (field_value)) + (field + (field_name) + (field_value)) + (field + (field_name) + (field_value)) + (field + (field_name) + (field_value)) + (delimiter)) + (body)) + +================== +Empty field value +================== +--- +title: +test_name: TestEmpty +--- +content + +--- + +(source_file + (front_matter + (delimiter) + (field + (field_name)) + (field + (field_name) + (field_value)) + (delimiter)) + (body)) + +================== +Multiline body +================== +--- +title: Multiline +--- +line one +line two +line three + +--- + +(source_file + (front_matter + (delimiter) + (field + (field_name) + (field_value)) + (delimiter)) + (body)) diff --git a/editors/tree-sitter.json b/editor/tree-sitter-snapshot/tree-sitter.json similarity index 97% rename from editors/tree-sitter.json rename to editor/tree-sitter-snapshot/tree-sitter.json index 7ddeee7..e183bec 100644 --- a/editors/tree-sitter.json +++ b/editor/tree-sitter-snapshot/tree-sitter.json @@ -7,7 +7,7 @@ "title": "Snapshot", "scope": "source.snapshot", "file-types": [ - "snapshot" + "snap" ], "injection-regex": "^snapshot$", "class-name": "TreeSitterSnapshot" diff --git a/editors/grammar.js b/editors/grammar.js deleted file mode 100644 index 1de8b67..0000000 --- a/editors/grammar.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @file Snapshot test files generated by Shutter - * @author ptdewey - * @license MIT - */ - -/// -// @ts-check - -module.exports = grammar({ - name: "snapshot", - - rules: { - // TODO: add the actual grammar rules - source_file: $ => "hello" - } -}); diff --git a/internal/files/files.go b/internal/files/files.go index b880ab7..3c74d44 100644 --- a/internal/files/files.go +++ b/internal/files/files.go @@ -164,6 +164,10 @@ func SaveSnapshot(snap *Snapshot, state string) error { fileName := getSnapshotFileName(snap.Title, state) filePath := filepath.Join(snapshotDir, fileName) + if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { + return err + } + return os.WriteFile(filePath, []byte(snap.Serialize()), 0644) } -- 2.51.2