A human-friendly DSL for ATProto Lexicons

Syntax highlighting and autocomplete for annotations

+782 -3
+52
mlf-lsp/src/context.rs
··· 10 10 TypePosition, 11 11 /// Inside constrained { } block 12 12 ConstraintBlock, 13 + /// Typing an annotation (after @) 14 + Annotation, 15 + /// Typing annotation selectors (after @rust,) 16 + AnnotationSelector, 13 17 /// Unknown context 14 18 Unknown, 15 19 } 16 20 17 21 /// Detect the completion context based on the text before the cursor 18 22 pub fn detect_context(text_before_cursor: &str) -> CompletionContext { 23 + // Check if we're typing an annotation 24 + if let Some(last_line) = text_before_cursor.lines().last() { 25 + let last_line_trimmed = last_line.trim_start(); 26 + 27 + // Check for annotation selector context: @rust, or @rust,typescript, 28 + if let Some(at_pos) = last_line_trimmed.rfind('@') { 29 + let after_at = &last_line_trimmed[at_pos + 1..]; 30 + 31 + // If there's a colon, we're past the selectors 32 + if after_at.contains(':') { 33 + // Check if we're completing the annotation name 34 + let parts: Vec<&str> = after_at.split(':').collect(); 35 + if parts.len() >= 2 && !parts[1].contains('(') { 36 + return CompletionContext::Annotation; 37 + } 38 + } else if after_at.ends_with(',') || (after_at.contains(',') && !after_at.contains(':')) { 39 + // We're completing another selector: @rust, or @rust,typescript, 40 + return CompletionContext::AnnotationSelector; 41 + } else if !after_at.is_empty() && !after_at.contains(':') && !after_at.contains('(') { 42 + // We're completing the first part (could be selector or annotation name) 43 + // Default to annotation for simplicity 44 + return CompletionContext::Annotation; 45 + } else if after_at.is_empty() { 46 + // Just typed @ - suggest annotations 47 + return CompletionContext::Annotation; 48 + } 49 + } 50 + } 51 + 19 52 // Check if we're in a use statement (check before trimming!) 20 53 if let Some(last_line) = text_before_cursor.lines().last() { 21 54 let last_line = last_line.trim_start(); // Only trim left side ··· 126 159 fn test_top_level() { 127 160 assert_eq!(detect_context(""), CompletionContext::TopLevel); 128 161 assert_eq!(detect_context("rec"), CompletionContext::TopLevel); 162 + } 163 + 164 + #[test] 165 + fn test_annotation() { 166 + assert_eq!(detect_context("@"), CompletionContext::Annotation); 167 + assert_eq!(detect_context("@dep"), CompletionContext::Annotation); 168 + assert_eq!(detect_context("@deprecated"), CompletionContext::Annotation); 169 + } 170 + 171 + #[test] 172 + fn test_annotation_selector() { 173 + assert_eq!(detect_context("@rust,"), CompletionContext::AnnotationSelector); 174 + assert_eq!(detect_context("@rust,typescript,"), CompletionContext::AnnotationSelector); 175 + } 176 + 177 + #[test] 178 + fn test_annotation_after_selector() { 179 + assert_eq!(detect_context("@rust:"), CompletionContext::Annotation); 180 + assert_eq!(detect_context("@rust,typescript:"), CompletionContext::Annotation); 129 181 } 130 182 }
+154
mlf-lsp/src/server.rs
··· 792 792 ".".to_string(), 793 793 ":".to_string(), 794 794 " ".to_string(), 795 + "@".to_string(), 796 + ",".to_string(), 795 797 ]), 796 798 ..Default::default() 797 799 }), ··· 896 898 if let Some(lexicon) = &doc_state.lexicon { 897 899 // Convert position to offset 898 900 if let Some(offset) = position_to_offset(&doc_state.text, position) { 901 + // Check if we're hovering over an annotation 902 + for item in &lexicon.items { 903 + let annotations_to_check = match item { 904 + Item::Record(r) => Some(&r.annotations), 905 + Item::InlineType(i) => Some(&i.annotations), 906 + Item::DefType(d) => Some(&d.annotations), 907 + Item::Token(t) => Some(&t.annotations), 908 + Item::Query(q) => Some(&q.annotations), 909 + Item::Procedure(p) => Some(&p.annotations), 910 + Item::Subscription(s) => Some(&s.annotations), 911 + _ => None, 912 + }; 913 + 914 + if let Some(annotations) = annotations_to_check { 915 + for annotation in annotations { 916 + if annotation.span.start <= offset && offset <= annotation.span.end { 917 + // Build hover content for the annotation 918 + let mut contents = vec![]; 919 + 920 + // Format annotation name with selectors if present 921 + let annotation_display = if annotation.selectors.is_empty() { 922 + format!("@{}", annotation.name.name) 923 + } else { 924 + let selector_names: Vec<_> = annotation.selectors.iter() 925 + .map(|s| s.name.as_str()) 926 + .collect(); 927 + format!("@{}:{}", selector_names.join(","), annotation.name.name) 928 + }; 929 + 930 + contents.push(MarkedString::LanguageString(LanguageString { 931 + language: "mlf".to_string(), 932 + value: annotation_display.clone(), 933 + })); 934 + 935 + // Add description based on annotation name 936 + let description = match annotation.name.name.as_str() { 937 + "deprecated" => "Marks this definition as deprecated", 938 + "main" => "Designates this as the main definition for conflict resolution", 939 + "key" => "Specifies the record key type (e.g., 'tid', 'literal:self')", 940 + "encoding" => "Specifies MIME type encoding for XRPC (e.g., 'application/json', 'application/cbor')", 941 + "since" => "Indicates the version when this was added", 942 + "doc" => "Provides a documentation URL", 943 + "validate" => "Specifies validation rules", 944 + "cache" => "Defines caching strategy", 945 + "indexed" => "Marks this field as indexed", 946 + "sensitive" => "Marks this field as containing sensitive data (e.g., PII)", 947 + _ => "Custom annotation", 948 + }; 949 + 950 + contents.push(MarkedString::String(description.to_string())); 951 + 952 + // Add information about selectors if present 953 + if !annotation.selectors.is_empty() { 954 + let selector_info = format!( 955 + "This annotation applies to: {}", 956 + annotation.selectors.iter() 957 + .map(|s| s.name.as_str()) 958 + .collect::<Vec<_>>() 959 + .join(", ") 960 + ); 961 + contents.push(MarkedString::String(selector_info)); 962 + } else { 963 + contents.push(MarkedString::String( 964 + "This annotation is visible to all generators".to_string() 965 + )); 966 + } 967 + 968 + return Ok(Some(Hover { 969 + contents: HoverContents::Array(contents), 970 + range: None, 971 + })); 972 + } 973 + } 974 + } 975 + 976 + // Also check field annotations 977 + match item { 978 + Item::Record(r) => { 979 + for field in &r.fields { 980 + for annotation in &field.annotations { 981 + if annotation.span.start <= offset && offset <= annotation.span.end { 982 + let annotation_display = if annotation.selectors.is_empty() { 983 + format!("@{}", annotation.name.name) 984 + } else { 985 + let selector_names: Vec<_> = annotation.selectors.iter() 986 + .map(|s| s.name.as_str()) 987 + .collect(); 988 + format!("@{}:{}", selector_names.join(","), annotation.name.name) 989 + }; 990 + 991 + return Ok(Some(Hover { 992 + contents: HoverContents::Scalar( 993 + MarkedString::LanguageString(LanguageString { 994 + language: "mlf".to_string(), 995 + value: format!("Field annotation: {}", annotation_display), 996 + }) 997 + ), 998 + range: None, 999 + })); 1000 + } 1001 + } 1002 + } 1003 + } 1004 + _ => {} 1005 + } 1006 + } 1007 + 899 1008 // Find the item at this position 900 1009 if let Some(item) = find_item_at_offset(lexicon, offset) { 901 1010 let name = get_item_name(item); ··· 1260 1369 } 1261 1370 1262 1371 tracing::debug!("Total completions: {}", completions.len()); 1372 + } 1373 + 1374 + MlfCompletionContext::Annotation => { 1375 + // Suggest common annotation names 1376 + let annotations = vec![ 1377 + ("deprecated", "Mark as deprecated"), 1378 + ("main", "Main definition for conflict resolution"), 1379 + ("key", "Specify record key type (e.g., @key(\"literal:self\"))"), 1380 + ("encoding", "Specify MIME type encoding (e.g., @encoding(\"application/cbor\"))"), 1381 + ("since", "Version when added (e.g., @since(1, 2, 0))"), 1382 + ("doc", "Documentation URL (e.g., @doc(\"https://example.com\"))"), 1383 + ("validate", "Validation rules (e.g., @validate(min: 0, max: 100))"), 1384 + ("cache", "Caching strategy (e.g., @cache(ttl: 3600))"), 1385 + ("indexed", "Mark field as indexed"), 1386 + ("sensitive", "Mark field as containing sensitive data"), 1387 + ]; 1388 + 1389 + for (label, detail) in annotations { 1390 + completions.push(CompletionItem { 1391 + label: label.to_string(), 1392 + kind: Some(CompletionItemKind::FUNCTION), 1393 + detail: Some(detail.to_string()), 1394 + ..Default::default() 1395 + }); 1396 + } 1397 + } 1398 + 1399 + MlfCompletionContext::AnnotationSelector => { 1400 + // Suggest generator selector names 1401 + let generators = vec![ 1402 + ("rust", "Rust code generator"), 1403 + ("typescript", "TypeScript code generator"), 1404 + ("go", "Go code generator"), 1405 + ("python", "Python code generator"), 1406 + ("java", "Java code generator"), 1407 + ]; 1408 + 1409 + for (label, detail) in generators { 1410 + completions.push(CompletionItem { 1411 + label: label.to_string(), 1412 + kind: Some(CompletionItemKind::MODULE), 1413 + detail: Some(detail.to_string()), 1414 + ..Default::default() 1415 + }); 1416 + } 1263 1417 } 1264 1418 1265 1419 MlfCompletionContext::TopLevel => {
+50
tree-sitter-mlf/grammar.js
··· 39 39 doc_comment: $ => token(seq('///', /.*/)), 40 40 comment: $ => token(seq('//', /.*/)), 41 41 42 + // Annotations 43 + annotation: $ => seq( 44 + '@', 45 + optional(field('selectors', $.annotation_selectors)), 46 + field('name', $.identifier), 47 + optional(field('args', $.annotation_args)) 48 + ), 49 + 50 + annotation_selectors: $ => seq( 51 + $.identifier, 52 + repeat(seq(',', $.identifier)), 53 + ':' 54 + ), 55 + 56 + annotation_args: $ => seq( 57 + '(', 58 + optional(seq( 59 + $.annotation_arg, 60 + repeat(seq(',', $.annotation_arg)), 61 + optional(',') 62 + )), 63 + ')' 64 + ), 65 + 66 + annotation_arg: $ => choice( 67 + // Named argument: name: value 68 + seq( 69 + field('name', $.identifier), 70 + ':', 71 + field('value', $.annotation_value) 72 + ), 73 + // Positional argument: value 74 + field('value', $.annotation_value) 75 + ), 76 + 77 + annotation_value: $ => choice( 78 + $.string, 79 + $.number, 80 + $.boolean 81 + ), 82 + 42 83 // Use statements 43 84 use_statement: $ => seq( 44 85 'use', ··· 68 109 69 110 // Record definition 70 111 record_definition: $ => seq( 112 + repeat($.annotation), 71 113 'record', 72 114 field('name', $.identifier), 73 115 field('body', $.record_body) ··· 81 123 82 124 field: $ => seq( 83 125 optional($.doc_comment), 126 + repeat($.annotation), 84 127 field('name', $.identifier), 85 128 optional('!'), 86 129 ':', ··· 90 133 91 134 // Inline type definition 92 135 inline_type_definition: $ => seq( 136 + repeat($.annotation), 93 137 'inline', 94 138 'type', 95 139 field('name', $.identifier), ··· 100 144 101 145 // Def type definition 102 146 def_type_definition: $ => seq( 147 + repeat($.annotation), 103 148 'def', 104 149 'type', 105 150 field('name', $.identifier), ··· 110 155 111 156 // Token definition 112 157 token_definition: $ => seq( 158 + repeat($.annotation), 113 159 'token', 114 160 field('name', $.identifier), 115 161 ';' ··· 117 163 118 164 // Query definition 119 165 query_definition: $ => seq( 166 + repeat($.annotation), 120 167 'query', 121 168 field('name', $.identifier), 122 169 field('params', $.parameter_list), ··· 127 174 128 175 // Procedure definition 129 176 procedure_definition: $ => seq( 177 + repeat($.annotation), 130 178 'procedure', 131 179 field('name', $.identifier), 132 180 field('params', $.parameter_list), ··· 137 185 138 186 // Subscription definition 139 187 subscription_definition: $ => seq( 188 + repeat($.annotation), 140 189 'subscription', 141 190 field('name', $.identifier), 142 191 field('params', $.parameter_list), ··· 156 205 ), 157 206 158 207 parameter: $ => seq( 208 + repeat($.annotation), 159 209 field('name', $.identifier), 160 210 optional('!'), 161 211 ':',
+12
tree-sitter-mlf/queries/highlights.scm
··· 87 87 (doc_comment) @comment.documentation 88 88 (comment) @comment 89 89 90 + ; Annotations 91 + "@" @punctuation.special 92 + 93 + (annotation 94 + name: (identifier) @attribute) 95 + 96 + (annotation_selectors 97 + (identifier) @namespace) 98 + 99 + (annotation_arg 100 + name: (identifier) @property) 101 + 90 102 ; Operators 91 103 [ 92 104 ":"
+254
tree-sitter-mlf/src/grammar.json
··· 77 77 ] 78 78 } 79 79 }, 80 + "annotation": { 81 + "type": "SEQ", 82 + "members": [ 83 + { 84 + "type": "STRING", 85 + "value": "@" 86 + }, 87 + { 88 + "type": "CHOICE", 89 + "members": [ 90 + { 91 + "type": "FIELD", 92 + "name": "selectors", 93 + "content": { 94 + "type": "SYMBOL", 95 + "name": "annotation_selectors" 96 + } 97 + }, 98 + { 99 + "type": "BLANK" 100 + } 101 + ] 102 + }, 103 + { 104 + "type": "FIELD", 105 + "name": "name", 106 + "content": { 107 + "type": "SYMBOL", 108 + "name": "identifier" 109 + } 110 + }, 111 + { 112 + "type": "CHOICE", 113 + "members": [ 114 + { 115 + "type": "FIELD", 116 + "name": "args", 117 + "content": { 118 + "type": "SYMBOL", 119 + "name": "annotation_args" 120 + } 121 + }, 122 + { 123 + "type": "BLANK" 124 + } 125 + ] 126 + } 127 + ] 128 + }, 129 + "annotation_selectors": { 130 + "type": "SEQ", 131 + "members": [ 132 + { 133 + "type": "SYMBOL", 134 + "name": "identifier" 135 + }, 136 + { 137 + "type": "REPEAT", 138 + "content": { 139 + "type": "SEQ", 140 + "members": [ 141 + { 142 + "type": "STRING", 143 + "value": "," 144 + }, 145 + { 146 + "type": "SYMBOL", 147 + "name": "identifier" 148 + } 149 + ] 150 + } 151 + }, 152 + { 153 + "type": "STRING", 154 + "value": ":" 155 + } 156 + ] 157 + }, 158 + "annotation_args": { 159 + "type": "SEQ", 160 + "members": [ 161 + { 162 + "type": "STRING", 163 + "value": "(" 164 + }, 165 + { 166 + "type": "CHOICE", 167 + "members": [ 168 + { 169 + "type": "SEQ", 170 + "members": [ 171 + { 172 + "type": "SYMBOL", 173 + "name": "annotation_arg" 174 + }, 175 + { 176 + "type": "REPEAT", 177 + "content": { 178 + "type": "SEQ", 179 + "members": [ 180 + { 181 + "type": "STRING", 182 + "value": "," 183 + }, 184 + { 185 + "type": "SYMBOL", 186 + "name": "annotation_arg" 187 + } 188 + ] 189 + } 190 + }, 191 + { 192 + "type": "CHOICE", 193 + "members": [ 194 + { 195 + "type": "STRING", 196 + "value": "," 197 + }, 198 + { 199 + "type": "BLANK" 200 + } 201 + ] 202 + } 203 + ] 204 + }, 205 + { 206 + "type": "BLANK" 207 + } 208 + ] 209 + }, 210 + { 211 + "type": "STRING", 212 + "value": ")" 213 + } 214 + ] 215 + }, 216 + "annotation_arg": { 217 + "type": "CHOICE", 218 + "members": [ 219 + { 220 + "type": "SEQ", 221 + "members": [ 222 + { 223 + "type": "FIELD", 224 + "name": "name", 225 + "content": { 226 + "type": "SYMBOL", 227 + "name": "identifier" 228 + } 229 + }, 230 + { 231 + "type": "STRING", 232 + "value": ":" 233 + }, 234 + { 235 + "type": "FIELD", 236 + "name": "value", 237 + "content": { 238 + "type": "SYMBOL", 239 + "name": "annotation_value" 240 + } 241 + } 242 + ] 243 + }, 244 + { 245 + "type": "FIELD", 246 + "name": "value", 247 + "content": { 248 + "type": "SYMBOL", 249 + "name": "annotation_value" 250 + } 251 + } 252 + ] 253 + }, 254 + "annotation_value": { 255 + "type": "CHOICE", 256 + "members": [ 257 + { 258 + "type": "SYMBOL", 259 + "name": "string" 260 + }, 261 + { 262 + "type": "SYMBOL", 263 + "name": "number" 264 + }, 265 + { 266 + "type": "SYMBOL", 267 + "name": "boolean" 268 + } 269 + ] 270 + }, 80 271 "use_statement": { 81 272 "type": "SEQ", 82 273 "members": [ ··· 267 458 "type": "SEQ", 268 459 "members": [ 269 460 { 461 + "type": "REPEAT", 462 + "content": { 463 + "type": "SYMBOL", 464 + "name": "annotation" 465 + } 466 + }, 467 + { 270 468 "type": "STRING", 271 469 "value": "record" 272 470 }, ··· 324 522 ] 325 523 }, 326 524 { 525 + "type": "REPEAT", 526 + "content": { 527 + "type": "SYMBOL", 528 + "name": "annotation" 529 + } 530 + }, 531 + { 327 532 "type": "FIELD", 328 533 "name": "name", 329 534 "content": { ··· 365 570 "type": "SEQ", 366 571 "members": [ 367 572 { 573 + "type": "REPEAT", 574 + "content": { 575 + "type": "SYMBOL", 576 + "name": "annotation" 577 + } 578 + }, 579 + { 368 580 "type": "STRING", 369 581 "value": "inline" 370 582 }, ··· 402 614 "type": "SEQ", 403 615 "members": [ 404 616 { 617 + "type": "REPEAT", 618 + "content": { 619 + "type": "SYMBOL", 620 + "name": "annotation" 621 + } 622 + }, 623 + { 405 624 "type": "STRING", 406 625 "value": "def" 407 626 }, ··· 439 658 "type": "SEQ", 440 659 "members": [ 441 660 { 661 + "type": "REPEAT", 662 + "content": { 663 + "type": "SYMBOL", 664 + "name": "annotation" 665 + } 666 + }, 667 + { 442 668 "type": "STRING", 443 669 "value": "token" 444 670 }, ··· 459 685 "query_definition": { 460 686 "type": "SEQ", 461 687 "members": [ 688 + { 689 + "type": "REPEAT", 690 + "content": { 691 + "type": "SYMBOL", 692 + "name": "annotation" 693 + } 694 + }, 462 695 { 463 696 "type": "STRING", 464 697 "value": "query" ··· 501 734 "type": "SEQ", 502 735 "members": [ 503 736 { 737 + "type": "REPEAT", 738 + "content": { 739 + "type": "SYMBOL", 740 + "name": "annotation" 741 + } 742 + }, 743 + { 504 744 "type": "STRING", 505 745 "value": "procedure" 506 746 }, ··· 541 781 "subscription_definition": { 542 782 "type": "SEQ", 543 783 "members": [ 784 + { 785 + "type": "REPEAT", 786 + "content": { 787 + "type": "SYMBOL", 788 + "name": "annotation" 789 + } 790 + }, 544 791 { 545 792 "type": "STRING", 546 793 "value": "subscription" ··· 640 887 "parameter": { 641 888 "type": "SEQ", 642 889 "members": [ 890 + { 891 + "type": "REPEAT", 892 + "content": { 893 + "type": "SYMBOL", 894 + "name": "annotation" 895 + } 896 + }, 643 897 { 644 898 "type": "FIELD", 645 899 "name": "name",
+206 -3
tree-sitter-mlf/src/node-types.json
··· 1 1 [ 2 2 { 3 + "type": "annotation", 4 + "named": true, 5 + "fields": { 6 + "args": { 7 + "multiple": false, 8 + "required": false, 9 + "types": [ 10 + { 11 + "type": "annotation_args", 12 + "named": true 13 + } 14 + ] 15 + }, 16 + "name": { 17 + "multiple": false, 18 + "required": true, 19 + "types": [ 20 + { 21 + "type": "identifier", 22 + "named": true 23 + } 24 + ] 25 + }, 26 + "selectors": { 27 + "multiple": false, 28 + "required": false, 29 + "types": [ 30 + { 31 + "type": "annotation_selectors", 32 + "named": true 33 + } 34 + ] 35 + } 36 + } 37 + }, 38 + { 39 + "type": "annotation_arg", 40 + "named": true, 41 + "fields": { 42 + "name": { 43 + "multiple": false, 44 + "required": false, 45 + "types": [ 46 + { 47 + "type": "identifier", 48 + "named": true 49 + } 50 + ] 51 + }, 52 + "value": { 53 + "multiple": false, 54 + "required": true, 55 + "types": [ 56 + { 57 + "type": "annotation_value", 58 + "named": true 59 + } 60 + ] 61 + } 62 + } 63 + }, 64 + { 65 + "type": "annotation_args", 66 + "named": true, 67 + "fields": {}, 68 + "children": { 69 + "multiple": true, 70 + "required": false, 71 + "types": [ 72 + { 73 + "type": "annotation_arg", 74 + "named": true 75 + } 76 + ] 77 + } 78 + }, 79 + { 80 + "type": "annotation_selectors", 81 + "named": true, 82 + "fields": {}, 83 + "children": { 84 + "multiple": true, 85 + "required": true, 86 + "types": [ 87 + { 88 + "type": "identifier", 89 + "named": true 90 + } 91 + ] 92 + } 93 + }, 94 + { 95 + "type": "annotation_value", 96 + "named": true, 97 + "fields": {}, 98 + "children": { 99 + "multiple": false, 100 + "required": true, 101 + "types": [ 102 + { 103 + "type": "boolean", 104 + "named": true 105 + }, 106 + { 107 + "type": "number", 108 + "named": true 109 + }, 110 + { 111 + "type": "string", 112 + "named": true 113 + } 114 + ] 115 + } 116 + }, 117 + { 3 118 "type": "array_literal", 4 119 "named": true, 5 120 "fields": {}, ··· 153 268 } 154 269 ] 155 270 } 271 + }, 272 + "children": { 273 + "multiple": true, 274 + "required": false, 275 + "types": [ 276 + { 277 + "type": "annotation", 278 + "named": true 279 + } 280 + ] 156 281 } 157 282 }, 158 283 { ··· 207 332 } 208 333 }, 209 334 "children": { 210 - "multiple": false, 335 + "multiple": true, 211 336 "required": false, 212 337 "types": [ 338 + { 339 + "type": "annotation", 340 + "named": true 341 + }, 213 342 { 214 343 "type": "doc_comment", 215 344 "named": true ··· 291 420 } 292 421 ] 293 422 } 423 + }, 424 + "children": { 425 + "multiple": true, 426 + "required": false, 427 + "types": [ 428 + { 429 + "type": "annotation", 430 + "named": true 431 + } 432 + ] 294 433 } 295 434 }, 296 435 { ··· 406 545 } 407 546 ] 408 547 } 548 + }, 549 + "children": { 550 + "multiple": true, 551 + "required": false, 552 + "types": [ 553 + { 554 + "type": "annotation", 555 + "named": true 556 + } 557 + ] 409 558 } 410 559 }, 411 560 { ··· 462 611 } 463 612 ] 464 613 } 614 + }, 615 + "children": { 616 + "multiple": true, 617 + "required": false, 618 + "types": [ 619 + { 620 + "type": "annotation", 621 + "named": true 622 + } 623 + ] 465 624 } 466 625 }, 467 626 { ··· 498 657 } 499 658 ] 500 659 } 660 + }, 661 + "children": { 662 + "multiple": true, 663 + "required": false, 664 + "types": [ 665 + { 666 + "type": "annotation", 667 + "named": true 668 + } 669 + ] 501 670 } 502 671 }, 503 672 { ··· 539 708 } 540 709 ] 541 710 } 711 + }, 712 + "children": { 713 + "multiple": true, 714 + "required": false, 715 + "types": [ 716 + { 717 + "type": "annotation", 718 + "named": true 719 + } 720 + ] 542 721 } 543 722 }, 544 723 { ··· 624 803 } 625 804 ] 626 805 } 806 + }, 807 + "children": { 808 + "multiple": true, 809 + "required": false, 810 + "types": [ 811 + { 812 + "type": "annotation", 813 + "named": true 814 + } 815 + ] 627 816 } 628 817 }, 629 818 { ··· 640 829 } 641 830 ] 642 831 } 832 + }, 833 + "children": { 834 + "multiple": true, 835 + "required": false, 836 + "types": [ 837 + { 838 + "type": "annotation", 839 + "named": true 840 + } 841 + ] 643 842 } 644 843 }, 645 844 { ··· 764 963 "named": false 765 964 }, 766 965 { 966 + "type": "@", 967 + "named": false 968 + }, 969 + { 767 970 "type": "[", 768 971 "named": false 769 972 }, ··· 849 1052 }, 850 1053 { 851 1054 "type": "string", 852 - "named": true 1055 + "named": false 853 1056 }, 854 1057 { 855 1058 "type": "string", 856 - "named": false 1059 + "named": true 857 1060 }, 858 1061 { 859 1062 "type": "subscription",
+54
website/syntaxes/mlf.sublime-syntax
··· 8 8 contexts: 9 9 main: 10 10 - include: comments 11 + - include: annotations 11 12 - include: keywords 12 13 - include: types 13 14 - include: strings ··· 26 27 push: 27 28 - meta_scope: comment.line.double-slash.mlf 28 29 - match: $ 30 + pop: true 31 + 32 + annotations: 33 + # Annotation with selectors and args: @rust,typescript:deprecated(true) 34 + - match: '@([a-zA-Z_][a-zA-Z0-9_]*(?:,[a-zA-Z_][a-zA-Z0-9_]*)*):([a-zA-Z_][a-zA-Z0-9_]*)' 35 + scope: meta.annotation.mlf 36 + captures: 37 + 1: entity.name.namespace.mlf 38 + 2: entity.name.function.annotation.mlf 39 + push: 40 + - match: '\(' 41 + scope: punctuation.section.arguments.begin.mlf 42 + set: 43 + - meta_scope: meta.annotation.arguments.mlf 44 + - match: '\)' 45 + scope: punctuation.section.arguments.end.mlf 46 + pop: true 47 + - match: '([a-zA-Z_][a-zA-Z0-9_]*)\s*(:)' 48 + captures: 49 + 1: variable.parameter.mlf 50 + 2: punctuation.separator.mlf 51 + - include: strings 52 + - include: numbers 53 + - match: '\b(true|false)\b' 54 + scope: constant.language.mlf 55 + - match: ',' 56 + scope: punctuation.separator.mlf 57 + - match: '(?=\S)' 58 + pop: true 59 + # Bare annotation with args: @deprecated(true) 60 + - match: '@([a-zA-Z_][a-zA-Z0-9_]*)' 61 + scope: meta.annotation.mlf 62 + captures: 63 + 1: entity.name.function.annotation.mlf 64 + push: 65 + - match: '\(' 66 + scope: punctuation.section.arguments.begin.mlf 67 + set: 68 + - meta_scope: meta.annotation.arguments.mlf 69 + - match: '\)' 70 + scope: punctuation.section.arguments.end.mlf 71 + pop: true 72 + - match: '([a-zA-Z_][a-zA-Z0-9_]*)\s*(:)' 73 + captures: 74 + 1: variable.parameter.mlf 75 + 2: punctuation.separator.mlf 76 + - include: strings 77 + - include: numbers 78 + - match: '\b(true|false)\b' 79 + scope: constant.language.mlf 80 + - match: ',' 81 + scope: punctuation.separator.mlf 82 + - match: '(?=\S)' 29 83 pop: true 30 84 31 85 keywords: