From f69bb411691ba023951168e8ee865795328294bb Mon Sep 17 00:00:00 2001 From: Sophie Dankel <47993817+sdankel@users.noreply.github.com> Date: Sat, 25 Feb 2023 10:47:54 -0800 Subject: [PATCH] Add language support for sway (#6023) --- book/src/generated/lang-support.md | 1 + languages.toml | 14 ++ runtime/queries/sway/highlights.scm | 336 +++++++++++++++++++++++++++ runtime/queries/sway/indents.scm | 71 ++++++ runtime/queries/sway/injections.scm | 2 + runtime/queries/sway/locals.scm | 17 ++ runtime/queries/sway/textobjects.scm | 52 +++++ 7 files changed, 493 insertions(+) create mode 100644 runtime/queries/sway/highlights.scm create mode 100644 runtime/queries/sway/indents.scm create mode 100644 runtime/queries/sway/injections.scm create mode 100644 runtime/queries/sway/locals.scm create mode 100644 runtime/queries/sway/textobjects.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index cdecb9b04..3bd61d7a9 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -125,6 +125,7 @@ | sshclientconfig | ✓ | | | | | starlark | ✓ | ✓ | | | | svelte | ✓ | | | `svelteserver` | +| sway | ✓ | ✓ | ✓ | `forc` | | swift | ✓ | | | `sourcekit-lsp` | | tablegen | ✓ | ✓ | ✓ | | | task | ✓ | | | | diff --git a/languages.toml b/languages.toml index aa580dec5..b364de961 100644 --- a/languages.toml +++ b/languages.toml @@ -52,6 +52,20 @@ args = { attachCommands = [ "platform select remote-gdb-server", "platform conne name = "rust" source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" } +[[language]] +name = "sway" +scope = "source.sway" +injection-regex = "sway" +file-types = ["sw"] +language-server = { command = "forc", args = ["lsp"] } +roots = ["Forc.toml", "Forc.lock"] +indent = { tab-width = 4, unit = " " } +comment-token = "//" + +[[grammar]] +name = "sway" +source = { git = "https://github.com/FuelLabs/tree-sitter-sway", rev = "e491a005ee1d310f4c138bf215afd44cfebf959c" } + [[language]] name = "toml" scope = "source.toml" diff --git a/runtime/queries/sway/highlights.scm b/runtime/queries/sway/highlights.scm new file mode 100644 index 000000000..98f4d4493 --- /dev/null +++ b/runtime/queries/sway/highlights.scm @@ -0,0 +1,336 @@ +; ------- +; Tree-Sitter doesn't allow overrides in regards to captures, +; though it is possible to affect the child node of a captured +; node. Thus, the approach here is to flip the order so that +; overrides are unnecessary. +; ------- + +; ------- +; Types +; ------- + +; --- +; Primitives +; --- + +(escape_sequence) @constant.character.escape +(primitive_type) @type.builtin +(boolean_literal) @constant.builtin.boolean +(integer_literal) @constant.numeric.integer +(float_literal) @constant.numeric.float +(char_literal) @constant.character +[ + (string_literal) + (raw_string_literal) +] @string +[ + (line_comment) + (block_comment) +] @comment + +; --- +; Extraneous +; --- + +(self) @variable.builtin +(enum_variant (identifier) @type.enum.variant) + +(field_initializer + (field_identifier) @variable.other.member) +(shorthand_field_initializer + (identifier) @variable.other.member) +(shorthand_field_identifier) @variable.other.member + +(loop_label + "'" @label + (identifier) @label) + +; --- +; Punctuation +; --- + +[ + "::" + "." + ";" + "," +] @punctuation.delimiter + +[ + "(" + ")" + "[" + "]" + "{" + "}" + "#" +] @punctuation.bracket +(type_arguments + [ + "<" + ">" + ] @punctuation.bracket) +(type_parameters + [ + "<" + ">" + ] @punctuation.bracket) +(closure_parameters + "|" @punctuation.bracket) + +; --- +; Variables +; --- + +(let_declaration + pattern: [ + ((identifier) @variable) + ((tuple_pattern + (identifier) @variable)) + ]) + +; It needs to be anonymous to not conflict with `call_expression` further below. +(_ + value: (field_expression + value: (identifier)? @variable + field: (field_identifier) @variable.other.member)) + +(parameter + pattern: (identifier) @variable.parameter) +(closure_parameters + (identifier) @variable.parameter) + +; ------- +; Keywords +; ------- + +(for_expression + "for" @keyword.control.repeat) +((identifier) @keyword.control + (#match? @keyword.control "^yield$")) + +"in" @keyword.control + +[ + "match" + "if" + "else" +] @keyword.control.conditional + +[ + "while" +] @keyword.control.repeat + +[ + "break" + "continue" + "return" +] @keyword.control.return + +[ + "contract" + "script" + "predicate" +] @keyword.other + +"use" @keyword.control.import +(dep_item "dep" @keyword.control.import !body) +(use_as_clause "as" @keyword.control.import) + +(type_cast_expression "as" @keyword.operator) + +[ + "as" + "pub" + "dep" + + "abi" + "impl" + "where" + "trait" + "for" +] @keyword + +[ + "struct" + "enum" + "storage" + "configurable" +] @keyword.storage.type + +"let" @keyword.storage +"fn" @keyword.function +"abi" @keyword.function + +(mutable_specifier) @keyword.storage.modifier.mut + +(reference_type "&" @keyword.storage.modifier.ref) +(self_parameter "&" @keyword.storage.modifier.ref) + +[ + "const" + "ref" + "deref" + "move" +] @keyword.storage.modifier + +; TODO: variable.mut to highlight mutable identifiers via locals.scm + +; ------- +; Guess Other Types +; ------- + +((identifier) @constant + (#match? @constant "^[A-Z][A-Z\\d_]*$")) + +; --- +; PascalCase identifiers in call_expressions (e.g. `Ok()`) +; are assumed to be enum constructors. +; --- + +(call_expression + function: [ + ((identifier) @type.enum.variant + (#match? @type.enum.variant "^[A-Z]")) + (scoped_identifier + name: ((identifier) @type.enum.variant + (#match? @type.enum.variant "^[A-Z]"))) + ]) + +; --- +; Assume that types in match arms are enums and not +; tuple structs. Same for `if let` expressions. +; --- + +(match_pattern + (scoped_identifier + name: (identifier) @constructor)) +(tuple_struct_pattern + type: [ + ((identifier) @constructor) + (scoped_identifier + name: (identifier) @constructor) + ]) +(struct_pattern + type: [ + ((type_identifier) @constructor) + (scoped_type_identifier + name: (type_identifier) @constructor) + ]) + +; --- +; Other PascalCase identifiers are assumed to be structs. +; --- + +((identifier) @type + (#match? @type "^[A-Z]")) + +; ------- +; Functions +; ------- + +(call_expression + function: [ + ((identifier) @function) + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function) + ]) +(generic_function + function: [ + ((identifier) @function) + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function.method) + ]) + +(function_item + name: (identifier) @function) + +(function_signature_item + name: (identifier) @function) + +; ------- +; Operators +; ------- + +[ + "*" + "'" + "->" + "=>" + "<=" + "=" + "==" + "!" + "!=" + "%" + "%=" + "&" + "&=" + "&&" + "|" + "|=" + "||" + "^" + "^=" + "*" + "*=" + "-" + "-=" + "+" + "+=" + "/" + "/=" + ">" + "<" + ">=" + ">>" + "<<" + ">>=" + "<<=" + "@" + ".." + "..=" + "'" +] @operator + +; ------- +; Paths +; ------- + +(use_declaration + argument: (identifier) @namespace) +(use_wildcard + (identifier) @namespace) +(dep_item + name: (identifier) @namespace) +(scoped_use_list + path: (identifier)? @namespace) +(use_list + (identifier) @namespace) +(use_as_clause + path: (identifier)? @namespace + alias: (identifier) @namespace) + +; --- +; Remaining Paths +; --- + +(scoped_identifier + path: (identifier)? @namespace + name: (identifier) @namespace) +(scoped_type_identifier + path: (identifier) @namespace) + +; ------- +; Remaining Identifiers +; ------- + +"?" @special + +(type_identifier) @type +(identifier) @variable +(field_identifier) @variable.other.member diff --git a/runtime/queries/sway/indents.scm b/runtime/queries/sway/indents.scm new file mode 100644 index 000000000..e6902b62c --- /dev/null +++ b/runtime/queries/sway/indents.scm @@ -0,0 +1,71 @@ +[ + (use_list) + (block) + (match_block) + (arguments) + (parameters) + (declaration_list) + (field_declaration_list) + (field_initializer_list) + (struct_pattern) + (tuple_pattern) + (unit_expression) + (enum_variant_list) + (call_expression) + (binary_expression) + (field_expression) + (tuple_expression) + (array_expression) + (where_clause) + + (token_tree) +] @indent + +[ + "}" + "]" + ")" +] @outdent + +; Indent the right side of assignments. +; The #not-same-line? predicate is required to prevent an extra indent for e.g. +; an else-clause where the previous if-clause starts on the same line as the assignment. +(assignment_expression + . + (_) @expr-start + right: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(compound_assignment_expr + . + (_) @expr-start + right: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(let_declaration + . + (_) @expr-start + value: (_) @indent + alternative: (_)? @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) +(if_expression + . + (_) @expr-start + condition: (_) @indent + (#not-same-line? @indent @expr-start) + (#set! "scope" "all") +) + +; Some field expressions where the left part is a multiline expression are not +; indented by cargo fmt. +; Because this multiline expression might be nested in an arbitrary number of +; field expressions, this can only be matched using a Regex. +(field_expression + value: (_) @val + "." @outdent + (#match? @val "(\\A[^\\n\\r]+\\([\\t ]*(\\n|\\r).*)|(\\A[^\\n\\r]*\\{[\\t ]*(\\n|\\r))") +) diff --git a/runtime/queries/sway/injections.scm b/runtime/queries/sway/injections.scm new file mode 100644 index 000000000..e4509a5fd --- /dev/null +++ b/runtime/queries/sway/injections.scm @@ -0,0 +1,2 @@ +([(line_comment) (block_comment)] @injection.content + (#set! injection.language "comment")) diff --git a/runtime/queries/sway/locals.scm b/runtime/queries/sway/locals.scm new file mode 100644 index 000000000..262d609e9 --- /dev/null +++ b/runtime/queries/sway/locals.scm @@ -0,0 +1,17 @@ +; Scopes + +[ + (function_item) + (closure_expression) + (block) +] @local.scope + +; Definitions + +(parameter + (identifier) @local.definition) + +(closure_parameters (identifier) @local.definition) + +; References +(identifier) @local.reference diff --git a/runtime/queries/sway/textobjects.scm b/runtime/queries/sway/textobjects.scm new file mode 100644 index 000000000..15740bc85 --- /dev/null +++ b/runtime/queries/sway/textobjects.scm @@ -0,0 +1,52 @@ +(function_item + body: (_) @function.inside) @function.around(closure_expression body: (_) @function.inside) @function.around + +(struct_item + body: (_) @class.inside) @class.around + +(enum_item + body: (_) @class.inside) @class.around + +(trait_item + body: (_) @class.inside) @class.around + +(impl_item + body: (_) @class.inside) @class.around + +(parameters + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(type_parameters + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(type_arguments + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(closure_parameters + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +(arguments + ((_) @parameter.inside . ","? @parameter.around) @parameter.around) + +[ + (line_comment) + (block_comment) +] @comment.inside + +(line_comment)+ @comment.around + +(block_comment) @comment.around + +(; #[test] + (attribute_item + (attribute + (identifier) @_test_attribute)) + ; allow other attributes like #[should_panic] and comments + [ + (attribute_item) + (line_comment) + ]* + ; the test function + (function_item + body: (_) @test.inside) @test.around + (#eq? @_test_attribute "test"))