From d1054de3ced44903c7bdcf5886d8481eb40a948f Mon Sep 17 00:00:00 2001
From: Novus Nota <68142933+novusnota@users.noreply.github.com>
Date: Sun, 4 Feb 2024 02:09:11 +0100
Subject: [PATCH] feat: Add `Tact` language support (#9512)

Re-submitting
---
 book/src/generated/lang-support.md   |   1 +
 languages.toml                       |  19 ++
 runtime/queries/tact/highlights.scm  | 298 +++++++++++++++++++++++++++
 runtime/queries/tact/indents.scm     |  38 ++++
 runtime/queries/tact/injections.scm  |   5 +
 runtime/queries/tact/locals.scm      |  35 ++++
 runtime/queries/tact/textobjects.scm |  58 ++++++
 7 files changed, 454 insertions(+)
 create mode 100644 runtime/queries/tact/highlights.scm
 create mode 100644 runtime/queries/tact/indents.scm
 create mode 100644 runtime/queries/tact/injections.scm
 create mode 100644 runtime/queries/tact/locals.scm
 create mode 100644 runtime/queries/tact/textobjects.scm

diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md
index a78dd793..e7d29cc7 100644
--- a/book/src/generated/lang-support.md
+++ b/book/src/generated/lang-support.md
@@ -162,6 +162,7 @@
 | swift | ✓ |  |  | `sourcekit-lsp` |
 | t32 | ✓ |  |  |  |
 | tablegen | ✓ | ✓ | ✓ |  |
+| tact | ✓ | ✓ | ✓ |  |
 | task | ✓ |  |  |  |
 | templ | ✓ |  |  | `templ` |
 | tfvars | ✓ |  | ✓ | `terraform-ls` |
diff --git a/languages.toml b/languages.toml
index 1f26f168..e601c20d 100644
--- a/languages.toml
+++ b/languages.toml
@@ -3042,3 +3042,22 @@ indent = { tab-width = 2, unit = "  " }
 [[grammar]]
 name = "hocon"
 source = { git = "https://github.com/antosha417/tree-sitter-hocon", rev = "c390f10519ae69fdb03b3e5764f5592fb6924bcc" }
+
+[[language]]
+name = "tact"
+scope = "source.tact"
+injection-regex = "tact"
+file-types = ["tact"]
+comment-token = "//"
+indent = { tab-width = 4, unit = "    " }
+
+[language.auto-pairs]
+'"' = '"'
+'{' = '}'
+'(' = ')'
+'<' = '>'
+
+[[grammar]]
+name = "tact"
+source = { git = "https://github.com/tact-lang/tree-sitter-tact", rev = "ec57ab29c86d632639726631fb2bb178d23e1c91" }
+
diff --git a/runtime/queries/tact/highlights.scm b/runtime/queries/tact/highlights.scm
new file mode 100644
index 00000000..53bf985b
--- /dev/null
+++ b/runtime/queries/tact/highlights.scm
@@ -0,0 +1,298 @@
+; See: https://docs.helix-editor.com/master/themes.html#syntax-highlighting
+; -------------------------------------------------------------------------
+
+; attribute
+; ---------
+
+[
+  "@name"
+  "@interface"
+] @attribute
+
+; comment.line
+; ------------
+
+((comment) @comment.line
+  (#match? @comment.line "^//"))
+
+; comment.block
+; -------------
+
+(comment) @comment.block
+
+; function.builtin
+; ----------------
+
+((identifier) @function.builtin
+  (#any-of? @function.builtin
+    "send" "sender" "require" "now"
+    "myBalance" "myAddress" "newAddress"
+    "contractAddress" "contractAddressExt"
+    "emit" "cell" "ton"
+    "beginString" "beginComment" "beginTailString" "beginStringFromBuilder" "beginCell" "emptyCell"
+    "randomInt" "random"
+    "checkSignature" "checkDataSignature" "sha256"
+    "min" "max" "abs" "pow"
+    "throw" "dump" "getConfigParam"
+    "nativeThrowWhen" "nativeThrowUnless" "nativeReserve"
+    "nativeRandomize" "nativeRandomizeLt" "nativePrepareRandom" "nativeRandom" "nativeRandomInterval")
+  (#is-not? local))
+
+; function.method
+; ---------------
+
+(method_call_expression
+  name: (identifier) @function.method)
+
+; function
+; --------
+
+(func_identifier) @function
+
+(native_function
+  name: (identifier) @function)
+
+(static_function
+  name: (identifier) @function)
+
+(static_call_expression
+  name: (identifier) @function)
+
+(init_function
+  "init" @function.method)
+
+(receive_function
+  "receive" @function.method)
+
+(bounced_function
+  "bounced" @function.method)
+
+(external_function
+  "external" @function.method)
+
+(function
+  name: (identifier) @function.method)
+
+; keyword.control.conditional
+; ---------------------------
+
+[
+  "if" "else"
+] @keyword.control.conditional
+
+; keyword.control.repeat
+; ----------------------
+
+[
+  "while" "repeat" "do" "until"
+] @keyword.control.repeat
+
+; keyword.control.import
+; ----------------------
+
+"import" @keyword.control.import
+
+; keyword.control.return
+; ----------------------
+
+"return" @keyword.control.return
+
+; keyword.operator
+; ----------------
+
+"initOf" @keyword.operator
+
+; keyword.directive
+; -----------------
+
+"primitive" @keyword.directive
+
+; keyword.function
+; ----------------
+
+[
+  "fun"
+  "native"
+] @keyword.function
+
+; keyword.storage.type
+; --------------------
+
+[
+  "contract" "trait" "struct" "message" "with"
+  "const" "let"
+] @keyword.storage.type
+
+; keyword.storage.modifier
+; ------------------------
+
+[
+  "get" "mutates" "extends" "virtual" "override" "inline" "abstract"
+] @keyword.storage.modifier
+
+; keyword
+; -------
+
+[
+  "with"
+  ; "public" ; -- not used, but declared in grammar.ohm
+  ; "extend" ; -- not used, but declared in grammar.ohm
+] @keyword
+
+; constant.builtin.boolean
+; ------------------------
+
+(boolean) @constant.builtin.boolean
+
+; constant.builtin
+; ----------------
+
+((identifier) @constant.builtin
+  (#any-of? @constant.builtin
+    "SendPayGasSeparately"
+    "SendIgnoreErrors"
+    "SendDestroyIfZero"
+    "SendRemainingValue"
+    "SendRemainingBalance")
+  (#is-not? local))
+
+(null) @constant.builtin
+
+; constant.numeric.integer
+; ------------------------
+
+(integer) @constant.numeric.integer
+
+; constant
+; --------
+
+(constant
+  name: (identifier) @constant)
+
+; string.special.path
+; -------------------
+
+(import_statement
+  library: (string) @string.special.path)
+
+; string
+; ------
+
+(string) @string
+
+; type.builtin
+; ------------
+
+(tlb_serialization
+  "as" @keyword
+  type: (identifier) @type.builtin
+  (#any-of? @type.builtin
+    "int8" "int16" "int32" "int64" "int128" "int256" "int257"
+    "uint8" "uint16" "uint32" "uint64" "uint128" "uint256"
+    "coins" "remaining" "bytes32" "bytes64"))
+
+((type_identifier) @type.builtin
+  (#any-of? @type.builtin
+    "Address" "Bool" "Builder" "Cell" "Int" "Slice" "String" "StringBuilder"))
+
+(map_type
+  "map" @type.builtin
+  "<" @punctuation.bracket
+  ">" @punctuation.bracket)
+
+(bounced_type
+  "bounced" @type.builtin
+  "<" @punctuation.bracket
+  ">" @punctuation.bracket)
+
+((identifier) @type.builtin
+  (#eq? @type.builtin "SendParameters")
+  (#is-not? local))
+
+; type
+; ----
+
+(type_identifier) @type
+
+; constructor
+; -----------
+
+(instance_expression
+  name: (identifier) @constructor)
+
+(initOf
+  name: (identifier) @constructor)
+
+; operator
+; --------
+
+[
+  "-" "-="
+  "+" "+="
+  "*" "*="
+  "/" "/="
+  "%" "%="
+  "=" "=="
+  "!" "!=" "!!"
+  "<" "<=" "<<"
+  ">" ">=" ">>"
+  "&" "|"
+  "&&" "||"
+] @operator
+
+; punctuation.bracket
+; -------------------
+
+[
+  "(" ")"
+  "{" "}"
+] @punctuation.bracket
+
+; punctuation.delimiter
+; ---------------------
+
+[
+  ";"
+  ","
+  "."
+  ":"
+  "?"
+] @punctuation.delimiter
+
+; variable.other.member
+; ---------------------
+
+(field
+  name: (identifier) @variable.other.member)
+
+(contract_body
+  (constant
+    name: (identifier) @variable.other.member))
+
+(trait_body
+  (constant
+    name: (identifier) @variable.other.member))
+
+(field_access_expression
+  name: (identifier) @variable.other.member)
+
+(lvalue (_) (_) @variable.other.member)
+
+(instance_argument
+  name: (identifier) @variable.other.member)
+
+; variable.parameter
+; ------------------
+
+(parameter
+  name: (identifier) @variable.parameter)
+
+; variable.builtin
+; ----------------
+
+(self) @variable.builtin
+
+; variable
+; --------
+
+(identifier) @variable
diff --git a/runtime/queries/tact/indents.scm b/runtime/queries/tact/indents.scm
new file mode 100644
index 00000000..62c532b2
--- /dev/null
+++ b/runtime/queries/tact/indents.scm
@@ -0,0 +1,38 @@
+; indent
+; ------
+
+[
+  ; (..., ...)
+  (parameter_list)
+  (argument_list)
+
+  ; {..., ...}
+  (instance_argument_list)
+
+  ; {...; ...}
+  (message_body)
+  (struct_body)
+  (contract_body)
+  (trait_body)
+  (function_body)
+  (block_statement)
+
+  ; misc.
+  (binary_expression)
+  (return_statement)
+] @indent
+
+; outdent
+; -------
+
+[
+  "}"
+  ")"
+  ">"
+] @outdent
+
+; indent.always
+; outdent.always
+; align
+; extend
+; extend.prevent-once
\ No newline at end of file
diff --git a/runtime/queries/tact/injections.scm b/runtime/queries/tact/injections.scm
new file mode 100644
index 00000000..e61db3a5
--- /dev/null
+++ b/runtime/queries/tact/injections.scm
@@ -0,0 +1,5 @@
+; See: https://docs.helix-editor.com/guides/injection.html
+
+((comment) @injection.content
+ (#set! injection.language "comment")
+ (#match? @injection.content "^//"))
\ No newline at end of file
diff --git a/runtime/queries/tact/locals.scm b/runtime/queries/tact/locals.scm
new file mode 100644
index 00000000..f1b3e8de
--- /dev/null
+++ b/runtime/queries/tact/locals.scm
@@ -0,0 +1,35 @@
+; See: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#local-variables
+
+; Scopes       @local.scope
+; -------------------------
+
+[
+  (static_function)
+  (init_function)
+  (bounced_function)
+  (receive_function)
+  (external_function)
+  (function)
+  (block_statement)
+] @local.scope
+
+; Definitions  @local.definition
+; ------------------------------
+
+(let_statement
+  name: (identifier) @local.definition)
+
+(parameter
+  name: (identifier) @local.definition)
+
+(constant
+  name: (identifier) @local.definition)
+
+; References   @local.reference
+; -----------------------------
+
+(self) @local.reference
+
+(value_expression (identifier) @local.reference)
+
+(lvalue (identifier) @local.reference)
diff --git a/runtime/queries/tact/textobjects.scm b/runtime/queries/tact/textobjects.scm
new file mode 100644
index 00000000..54d07014
--- /dev/null
+++ b/runtime/queries/tact/textobjects.scm
@@ -0,0 +1,58 @@
+; function.inside & around
+; ------------------------
+
+(static_function
+  body: (_) @function.inside) @function.around
+
+(init_function
+  body: (_) @function.inside) @function.around
+
+(bounced_function
+  body: (_) @function.inside) @function.around
+
+(receive_function
+  body: (_) @function.inside) @function.around
+
+(external_function
+  body: (_) @function.inside) @function.around
+
+(function
+  body: (_) @function.inside) @function.around
+
+; class.inside & around
+; ---------------------
+
+(struct
+  body: (_) @class.inside) @class.around
+
+(message
+  body: (_) @class.inside) @class.around
+
+(contract
+  body: (_) @class.inside) @class.around
+
+; NOTE: Marked as @definition.interface in tags, as it's semantically correct
+(trait
+  body: (_) @class.inside) @class.around
+
+; parameter.inside & around
+; -------------------------
+
+(parameter_list
+  ((_) @parameter.inside . ","? @parameter.around) @parameter.around)
+
+(argument_list
+  ((_) @parameter.inside . ","? @parameter.around) @parameter.around)
+
+(instance_argument_list
+  ((_) @parameter.inside . ","? @parameter.around) @parameter.around)
+
+; comment.inside
+; --------------
+
+(comment) @comment.inside
+
+; comment.around
+; --------------
+
+(comment)+ @comment.around
\ No newline at end of file