diff --git a/packages/language-css/grammars/modern-tree-sitter-css.cson b/packages/language-css/grammars/modern-tree-sitter-css.cson index 2c0f5ff507..101696e18e 100644 --- a/packages/language-css/grammars/modern-tree-sitter-css.cson +++ b/packages/language-css/grammars/modern-tree-sitter-css.cson @@ -8,7 +8,7 @@ fileTypes: [ ] treeSitter: - parserSource: 'github:tree-sitter/tree-sitter-css#98c7b3dceb24f1ee17f1322f3947e55638251c37' + parserSource: 'github:tree-sitter/tree-sitter-css#9af0bdd9d225edee12f489cfa8284e248321959b' grammar: 'tree-sitter/tree-sitter-css.wasm' highlightsQuery: 'tree-sitter/queries/highlights.scm' foldsQuery: 'tree-sitter/queries/folds.scm' diff --git a/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm b/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm index 711c7732fa..6399b8a0b5 100755 Binary files a/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm and b/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm differ diff --git a/packages/language-gfm/grammars/modern-tree-sitter-gfm-inline.cson b/packages/language-gfm/grammars/modern-tree-sitter-gfm-inline.cson index 7f44177667..dfa36a58af 100644 --- a/packages/language-gfm/grammars/modern-tree-sitter-gfm-inline.cson +++ b/packages/language-gfm/grammars/modern-tree-sitter-gfm-inline.cson @@ -5,6 +5,6 @@ parser: 'tree-sitter-markdown_inline' injectionRegex: '^(markdown-inline-internal)$' treeSitter: - parserSource: 'github:MDeiml/tree-sitter-markdown/tree-sitter-markdown-inline#28aa3baef73bd458d053b613b8bd10fd102b4405' + parserSource: 'github:tree-sitter-grammars/tree-sitter-markdown/tree-sitter-markdown-inline#a3481e1db0b5f9aecfa7124d362a6485286a8e72' grammar: 'tree-sitter-markdown-inline/tree-sitter-markdown-inline.wasm' highlightsQuery: 'tree-sitter-markdown-inline/highlights.scm' diff --git a/packages/language-gfm/grammars/modern-tree-sitter-gfm.cson b/packages/language-gfm/grammars/modern-tree-sitter-gfm.cson index b2cf67c6b5..57f0754820 100644 --- a/packages/language-gfm/grammars/modern-tree-sitter-gfm.cson +++ b/packages/language-gfm/grammars/modern-tree-sitter-gfm.cson @@ -6,7 +6,7 @@ parser: 'tree-sitter-markdown' injectionRegex: '^(markdown|MARKDOWN|gfm|GFM)$' treeSitter: - parserSource: 'github:MDeiml/tree-sitter-markdown/tree-sitter-markdown#28aa3baef73bd458d053b613b8bd10fd102b4405' + parserSource: 'github:tree-sitter-grammars/tree-sitter-markdown/tree-sitter-markdown#a3481e1db0b5f9aecfa7124d362a6485286a8e72' grammar: 'tree-sitter-markdown/tree-sitter-markdown.wasm' highlightsQuery: 'tree-sitter-markdown/highlights.scm' foldsQuery: 'tree-sitter-markdown/folds.scm' diff --git a/packages/language-gfm/grammars/tree-sitter-markdown-inline/tree-sitter-markdown-inline.wasm b/packages/language-gfm/grammars/tree-sitter-markdown-inline/tree-sitter-markdown-inline.wasm index 04618d113f..26a20f33fc 100755 Binary files a/packages/language-gfm/grammars/tree-sitter-markdown-inline/tree-sitter-markdown-inline.wasm and b/packages/language-gfm/grammars/tree-sitter-markdown-inline/tree-sitter-markdown-inline.wasm differ diff --git a/packages/language-gfm/grammars/tree-sitter-markdown/tree-sitter-markdown.wasm b/packages/language-gfm/grammars/tree-sitter-markdown/tree-sitter-markdown.wasm index e248b73355..ee99a043db 100755 Binary files a/packages/language-gfm/grammars/tree-sitter-markdown/tree-sitter-markdown.wasm and b/packages/language-gfm/grammars/tree-sitter-markdown/tree-sitter-markdown.wasm differ diff --git a/packages/language-html/grammars/modern-tree-sitter-ejs.cson b/packages/language-html/grammars/modern-tree-sitter-ejs.cson index 4b54d8bdd1..ebec49e1ca 100644 --- a/packages/language-html/grammars/modern-tree-sitter-ejs.cson +++ b/packages/language-html/grammars/modern-tree-sitter-ejs.cson @@ -11,7 +11,7 @@ fileTypes: [ injectionRegex: '^(ejs|EJS)$' treeSitter: - parserSource: 'github:tree-sitter/tree-sitter-embedded-template#203f7bd3c1bbfbd98fc19add4b8fcb213c059205' + parserSource: 'github:tree-sitter/tree-sitter-embedded-template#v0.23.0' grammar: 'tree-sitter-embedded-template/tree-sitter-embedded-template.wasm' highlightsQuery: 'tree-sitter-embedded-template/ejs/highlights.scm' foldsQuery: 'tree-sitter-embedded-template/ejs/folds.scm' diff --git a/packages/language-html/grammars/modern-tree-sitter-erb.cson b/packages/language-html/grammars/modern-tree-sitter-erb.cson index 1e7d28e6e7..3f4ca5275d 100644 --- a/packages/language-html/grammars/modern-tree-sitter-erb.cson +++ b/packages/language-html/grammars/modern-tree-sitter-erb.cson @@ -11,7 +11,7 @@ fileTypes: [ injectionRegex: '^(erb|ERB)$' treeSitter: - parserSource: 'github:tree-sitter/tree-sitter-embedded-template#203f7bd3c1bbfbd98fc19add4b8fcb213c059205' + parserSource: 'github:tree-sitter/tree-sitter-embedded-template#v0.23.0' grammar: 'tree-sitter-embedded-template/tree-sitter-embedded-template.wasm' highlightsQuery: 'tree-sitter-embedded-template/erb/highlights.scm' foldsQuery: 'tree-sitter-embedded-template/erb/folds.scm' diff --git a/packages/language-html/grammars/modern-tree-sitter-html.cson b/packages/language-html/grammars/modern-tree-sitter-html.cson index 55b856d1c7..cc36821ecd 100644 --- a/packages/language-html/grammars/modern-tree-sitter-html.cson +++ b/packages/language-html/grammars/modern-tree-sitter-html.cson @@ -6,7 +6,7 @@ parser: 'tree-sitter-html' injectionRegex: '(HTML|html|Html)$' treeSitter: - parserSource: 'github:tree-sitter/tree-sitter-html#d742025fa2d8e6100f134a6ea990443aa1f074b3' + parserSource: 'github:tree-sitter/tree-sitter-html#v0.23.0' grammar: 'tree-sitter-html/tree-sitter-html.wasm' highlightsQuery: 'tree-sitter-html/highlights.scm' foldsQuery: 'tree-sitter-html/folds.scm' diff --git a/packages/language-html/grammars/tree-sitter-embedded-template/tree-sitter-embedded-template.wasm b/packages/language-html/grammars/tree-sitter-embedded-template/tree-sitter-embedded-template.wasm index eec102a460..66a8cadcaa 100755 Binary files a/packages/language-html/grammars/tree-sitter-embedded-template/tree-sitter-embedded-template.wasm and b/packages/language-html/grammars/tree-sitter-embedded-template/tree-sitter-embedded-template.wasm differ diff --git a/packages/language-html/grammars/tree-sitter-html/tree-sitter-html.wasm b/packages/language-html/grammars/tree-sitter-html/tree-sitter-html.wasm index 2d2835ca4c..d0fca0826e 100755 Binary files a/packages/language-html/grammars/tree-sitter-html/tree-sitter-html.wasm and b/packages/language-html/grammars/tree-sitter-html/tree-sitter-html.wasm differ diff --git a/packages/language-java/grammars/tree-sitter-java/highlights.scm b/packages/language-java/grammars/tree-sitter-java/highlights.scm index 86a06a56a2..e1a61d011e 100644 --- a/packages/language-java/grammars/tree-sitter-java/highlights.scm +++ b/packages/language-java/grammars/tree-sitter-java/highlights.scm @@ -177,7 +177,10 @@ (throws "throws" @storage.modifier.throws.java) -(method_invocation (identifier) +(method_invocation object: (identifier) + @support.other.object.java) + +(method_invocation name: (identifier) @support.other.function.java) (field_access (identifier) @constant.other.java diff --git a/packages/language-javascript/grammars/modern-tree-sitter-javascript.cson b/packages/language-javascript/grammars/modern-tree-sitter-javascript.cson index 084577fd01..1bd9ff01c1 100644 --- a/packages/language-javascript/grammars/modern-tree-sitter-javascript.cson +++ b/packages/language-javascript/grammars/modern-tree-sitter-javascript.cson @@ -6,7 +6,7 @@ parser: 'tree-sitter-javascript' injectionRegex: '^(js|javascript|JS|JAVASCRIPT)$' treeSitter: - parserSource: 'github:tree-sitter/tree-sitter-javascript#f1e5a09b8d02f8209a68249c93f0ad647b228e6e' + parserSource: 'github:tree-sitter/tree-sitter-javascript#v0.23.0' grammar: 'tree-sitter/tree-sitter-javascript.wasm' highlightsQuery: 'tree-sitter/highlights.scm' localsQuery: 'tree-sitter/locals.scm' diff --git a/packages/language-javascript/grammars/tree-sitter/highlights.scm b/packages/language-javascript/grammars/tree-sitter/highlights.scm index 7d58a1ad83..f0527bb1c6 100644 --- a/packages/language-javascript/grammars/tree-sitter/highlights.scm +++ b/packages/language-javascript/grammars/tree-sitter/highlights.scm @@ -1,23 +1,29 @@ ; MISC ; ==== -; In the JSX construct ``, `FOO` should not be marked as -; `constant.other.js`. Block off identifiers within complex JSX tag names early -; to prevent this. +; In the JSX construct ``, `Foo.Bar` is treated as a +; `member_expression`. We don't want the ordinary rules for member expressions +; to apply, so we block them off. +; +; TODO: If we wanted to give these segments individual scopes, we'd do that +; here — replacing the `@_IGNORE_`s with scope names. (jsx_opening_element (member_expression (identifier) @_IGNORE_ + (property_identifier) @_IGNORE_ (#set! capture.final))) (jsx_closing_element (member_expression (identifier) @_IGNORE_ + (property_identifier) @_IGNORE_ (#set! capture.final))) (jsx_self_closing_element (member_expression (identifier) @_IGNORE_ + (property_identifier) @_IGNORE_ (#set! capture.final))) @@ -98,7 +104,7 @@ (assignment_expression left: (member_expression property: (property_identifier) @variable.other.assignment.property.js) - right: [(arrow_function) (function)] @_IGNORE_ + right: [(arrow_function) (function_expression)] @_IGNORE_ (#set! isFunctionProperty true)) ; The "bar" in `foo.bar = true`. @@ -117,10 +123,25 @@ (augmented_assignment_expression left: (identifier) @variable.other.assignment.js) +; The "bar" in `foo.bar += 1`. +(augmented_assignment_expression + left: (member_expression + property: (property_identifier) @variable.other.assignment.property.js) + (#is-not? test.rangeWithData isFunctionProperty) + (#set! capture.final)) + ; The "foo" in `foo++`. (update_expression argument: (identifier) @variable.other.assignment.js) +; The "bar" in `foo.bar++`. +(update_expression + argument: (member_expression + property: (property_identifier) @variable.other.assignment.property.js) + (#is-not? test.rangeWithData isFunctionProperty) + (#set! capture.final)) + + ; Public field definition in a class body: ; The "foo" in `foo = "bar";` (field_definition @@ -264,7 +285,7 @@ ; Named function expressions: ; the "foo" in `let bar = function foo () {` -(function +(function_expression name: (identifier) @entity.name.function.definition.js) ; Function definitions: @@ -298,27 +319,27 @@ left: (member_expression property: (property_identifier) @entity.name.function.definition.js (#set! capture.final true)) - right: [(arrow_function) (function)]) + right: [(arrow_function) (function_expression)]) ; Function variable assignment: ; The "foo" in `let foo = function () {` (variable_declarator name: (identifier) @entity.name.function.definition.js - value: [(function) (arrow_function)]) + value: [(function_expression) (arrow_function)]) ; Function variable reassignment: ; The "foo" in `foo = function () {` (assignment_expression left: (identifier) @function - right: [(function) (arrow_function)]) + right: [(function_expression) (arrow_function)]) ; Object key-value pair function: ; The "foo" in `{ foo: function () {} }` (pair key: (property_identifier) @entity.name.function.method.definition.js - value: [(function) (arrow_function)]) + value: [(function_expression) (arrow_function)]) -(function "function" @storage.type.function.js) +(function_expression "function" @storage.type.function.js) (function_declaration "function" @storage.type.function.js) (generator_function "function" @storage.type.function.js) @@ -995,7 +1016,7 @@ (#set! adjust.endAt lastChild.startPosition) (#set! capture.final true)) -(function +(function_expression body: (statement_block) @meta.block.function.js (#set! adjust.startAt firstChild.endPosition) (#set! adjust.endAt lastChild.startPosition) diff --git a/packages/language-javascript/grammars/tree-sitter/tags.scm b/packages/language-javascript/grammars/tree-sitter/tags.scm index 5063d183f9..c5113e0c7c 100644 --- a/packages/language-javascript/grammars/tree-sitter/tags.scm +++ b/packages/language-javascript/grammars/tree-sitter/tags.scm @@ -28,7 +28,7 @@ (comment)* @doc . [ - (function + (function_expression name: (identifier) @name) (function_declaration name: (identifier) @name) @@ -47,7 +47,7 @@ (lexical_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)]) @definition.function) + value: [(arrow_function) (function_expression)]) @definition.function) (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") (#select-adjacent! @doc @definition.function) ) @@ -58,7 +58,7 @@ (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)]) @definition.function) + value: [(arrow_function) (function_expression)]) @definition.function) (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") (#select-adjacent! @doc @definition.function) ) @@ -69,12 +69,12 @@ (member_expression property: (property_identifier) @name) ] - right: [(arrow_function) (function)] + right: [(arrow_function) (function_expression)] ) @definition.function (pair key: (property_identifier) @name - value: [(arrow_function) (function)]) @definition.function + value: [(arrow_function) (function_expression)]) @definition.function ( (call_expression diff --git a/packages/language-javascript/grammars/tree-sitter/tree-sitter-javascript.wasm b/packages/language-javascript/grammars/tree-sitter/tree-sitter-javascript.wasm index 5857388d3d..7cdd4da743 100755 Binary files a/packages/language-javascript/grammars/tree-sitter/tree-sitter-javascript.wasm and b/packages/language-javascript/grammars/tree-sitter/tree-sitter-javascript.wasm differ diff --git a/packages/language-javascript/lib/main.js b/packages/language-javascript/lib/main.js index 015c289b65..6433360293 100644 --- a/packages/language-javascript/lib/main.js +++ b/packages/language-javascript/lib/main.js @@ -21,7 +21,7 @@ exports.activate = function () { content(callExpression) { const { lastChild } = callExpression; if (lastChild.type === 'template_string') { - return lastChild; + return stringFragmentsOfTemplateString(lastChild); } } }); @@ -29,8 +29,8 @@ exports.activate = function () { atom.grammars.addInjectionPoint('source.js', { type: 'assignment_expression', - language(callExpression) { - const { firstChild } = callExpression; + language(expression) { + const { firstChild } = expression; if (firstChild.type === 'member_expression') { if (firstChild.lastChild.text === 'innerHTML') { return 'html'; @@ -38,10 +38,10 @@ exports.activate = function () { } }, - content(callExpression) { - const { lastChild } = callExpression; + content(expression) { + const { lastChild } = expression; if (lastChild.type === 'template_string') { - return lastChild; + return stringFragmentsOfTemplateString(lastChild); } } }); @@ -95,3 +95,9 @@ function languageStringForTemplateTag(tag) { return tag; } } + +function stringFragmentsOfTemplateString(templateStringNode) { + return templateStringNode.children.filter( + c => c.type === 'string_fragment' + ); +} diff --git a/packages/language-php/grammars/tree-sitter/queries/highlights.scm b/packages/language-php/grammars/tree-sitter/queries/highlights.scm index 9a2584db85..b04b4db097 100644 --- a/packages/language-php/grammars/tree-sitter/queries/highlights.scm +++ b/packages/language-php/grammars/tree-sitter/queries/highlights.scm @@ -408,7 +408,11 @@ (const_declaration (const_element) @variable.other.constant.php) ((name) @constant.language.php - (#match? @constant.language.php "^__[A-Z][A-Z\d_]+__$")) + (#match? @constant.language.php "\\b(__(FILE|DIR|FUNCTION|CLASS|METHOD|LINE|NAMESPACE)__|ON|OFF|YES|NO|NL|BR|TAB)\\b") + (#set! capture.final)) + +((name) @constant.language.php + (#match? @constant.language.php "^__[A-Z][A-Z\d_]+__$")) (argument name: (_) @variable.other.named-argument.php @@ -489,8 +493,34 @@ ; ========= (boolean) @constant.language.boolean._TEXT_.php + +; In PHP, `true` is technically no different from `TRUE` or `True`; they appear +; to be implemented as case-insensitive language constants. `tree-sitter-php` +; treats `true` as a `boolean` node, but `TRUE` and `True` as `name` nodes. +; +; This is silly — but, as usual, the origin of the silliness is PHP. +; +; `#match?` is case-sensitive and there's no way to opt out of that. At some +; point in the future, we might be able to fix that by defining a custom +; predicate for case-insensitive `#match?`; for now we'll just write a silly +; regex. + +((name) @constant.language.boolean.true.php + (#match? @constant.language.boolean.true.php "^[Tt][Rr][Uu][Ee]$") + (#set! capture.final)) + +((name) @constant.language.boolean.false.php + (#match? @constant.language.boolean.false.php "^[Ff][Aa][Ll][Ss][Ee]$") + (#set! capture.final)) + (null) @constant.language.null.php +; Likewise, `null` is a case-insensitive constant. `NULL` and `null` are common; +; others are less common, but we might as well cover them. +((name) @constant.language.null.php + (#match? @constant.language.null.php "^[Nn][Uu][Ll][Ll]") + (#set! capture.final)) + (integer) @constant.numeric.decimal.integer.php (float) @constant.numeric.decimal.float.php diff --git a/packages/language-typescript/grammars/common/highlights.scm b/packages/language-typescript/grammars/common/highlights.scm index f0e72d1d69..97a8614f41 100644 --- a/packages/language-typescript/grammars/common/highlights.scm +++ b/packages/language-typescript/grammars/common/highlights.scm @@ -110,16 +110,31 @@ ; The "bar" in `foo.bar = true` (assignment_expression left: (member_expression - property: (property_identifier) @variable.other.assignment.property._LANG_)) + property: (property_identifier) @variable.other.assignment.property._LANG_) + (#set! capture.final)) ; The "foo" in `foo += 1`. (augmented_assignment_expression left: (identifier) @variable.other.assignment._LANG_) +; The "bar" in `foo.bar += 1`. +(augmented_assignment_expression + left: (member_expression + property: (property_identifier) @variable.other.assignment.property._LANG_) + (#is-not? test.rangeWithData isFunctionProperty) + (#set! capture.final)) + ; The "foo" in `foo++`. (update_expression argument: (identifier) @variable.other.assignment._LANG_) +; The "bar" in `foo.bar++`. +(update_expression + argument: (member_expression + property: (property_identifier) @variable.other.assignment.property._LANG_) + (#is-not? test.rangeWithData isFunctionProperty) + (#set! capture.final)) + ; `object_pattern` appears to only be encountered in assignment expressions, so ; this won't match other uses of object/prop shorthand. ((object_pattern @@ -649,7 +664,7 @@ ; Named function expressions: ; the "foo" in `let bar = function foo () {` -(function +(function_expression name: (identifier) @entity.name.function.definition._LANG_) ; Function definitions: @@ -683,28 +698,28 @@ left: (member_expression property: (property_identifier) @entity.name.function.definition._LANG_ (#set! capture.final true)) - right: [(arrow_function) (function)]) + right: [(arrow_function) (function_expression)]) ; Function variable assignment: ; The "foo" in `let foo = function () {` (variable_declarator name: (identifier) @entity.name.function.definition._LANG_ - value: [(function) (arrow_function)]) + value: [(function_expression) (arrow_function)]) ; Function variable reassignment: ; The "foo" in `foo = function () {` (assignment_expression left: (identifier) @function - right: [(function) (arrow_function)]) + right: [(function_expression) (arrow_function)]) ; Object key-value pair function: ; The "foo" in `{ foo: function () {} }` (pair key: (property_identifier) @entity.name.function.method.definition._LANG_ - value: [(function) (arrow_function)]) + value: [(function_expression) (arrow_function)]) ; Function is `storage.type` because it's a core language construct. -(function "function" @storage.type.function._LANG_) +(function_expression "function" @storage.type.function._LANG_) (function_declaration "function" @storage.type.function._LANG_) (generator_function "function" @storage.type.function._LANG_) @@ -845,6 +860,7 @@ ["&" "|" "<<" ">>" ">>>" "~" "^"] @keyword.operator.bitwise.js (non_null_expression "!" @keyword.operator.non-null._LANG_) +(variable_declarator "!" @keyword.operator.non-null._LANG_) (unary_expression "!" @keyword.operator.unary._LANG_) [ @@ -970,7 +986,7 @@ body: (statement_block) @meta.block.function._LANG_ (#set! capture.final true)) -(function +(function_expression body: (statement_block) @meta.block.function._LANG_ (#set! capture.final true)) diff --git a/packages/language-typescript/grammars/common/tags.scm b/packages/language-typescript/grammars/common/tags.scm index f13b82313e..1087fd1741 100644 --- a/packages/language-typescript/grammars/common/tags.scm +++ b/packages/language-typescript/grammars/common/tags.scm @@ -2,7 +2,7 @@ name: (identifier) @name (#set! symbol.tag "function")) @definition.function -(function +(function_expression name: (identifier) @name (#set! symbol.tag "function")) @definition.function @@ -36,7 +36,7 @@ (lexical_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)]) @definition.function) + value: [(arrow_function) (function_expression)]) @definition.function) (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") (#select-adjacent! @doc @definition.function) ) diff --git a/packages/language-typescript/grammars/modern-tree-sitter-tsx.cson b/packages/language-typescript/grammars/modern-tree-sitter-tsx.cson index 3246caa399..ef5ec0c217 100644 --- a/packages/language-typescript/grammars/modern-tree-sitter-tsx.cson +++ b/packages/language-typescript/grammars/modern-tree-sitter-tsx.cson @@ -11,6 +11,7 @@ comments: start: '// ' treeSitter: + parserSource: 'github:tree-sitter/tree-sitter-typescript#v0.23.0' languageSegment: 'ts.tsx' grammar: 'tree-sitter-tsx/tree-sitter-tsx.wasm' highlightsQuery: [ diff --git a/packages/language-typescript/grammars/modern-tree-sitter-typescript.cson b/packages/language-typescript/grammars/modern-tree-sitter-typescript.cson index 915db3625f..ca41eca0ec 100644 --- a/packages/language-typescript/grammars/modern-tree-sitter-typescript.cson +++ b/packages/language-typescript/grammars/modern-tree-sitter-typescript.cson @@ -11,6 +11,7 @@ comments: start: '// ' treeSitter: + parserSource: 'github:tree-sitter/tree-sitter-typescript#v0.23.0' languageSegment: 'ts' grammar: 'tree-sitter-typescript/tree-sitter-typescript.wasm' highlightsQuery: 'common/highlights.scm' diff --git a/packages/language-typescript/grammars/tree-sitter-tsx/folds.scm b/packages/language-typescript/grammars/tree-sitter-tsx/folds.scm index 6af5811c2d..eb7e13f254 100644 --- a/packages/language-typescript/grammars/tree-sitter-tsx/folds.scm +++ b/packages/language-typescript/grammars/tree-sitter-tsx/folds.scm @@ -32,5 +32,5 @@ (#set! fold.endAt lastChild.previousSibling.endPosition)) ((jsx_self_closing_element) @fold - ; Exclude both the slash and angle bracket `/>` from the fold. - (#set! fold.endAt lastChild.previousSibling.startPosition)) + ; Exclude the slash and angle bracket `/>` from the fold. + (#set! fold.endAt lastChild.startPosition)) diff --git a/packages/language-typescript/grammars/tree-sitter-tsx/highlights.scm b/packages/language-typescript/grammars/tree-sitter-tsx/highlights.scm index 0baec15ffa..fed286e5d2 100644 --- a/packages/language-typescript/grammars/tree-sitter-tsx/highlights.scm +++ b/packages/language-typescript/grammars/tree-sitter-tsx/highlights.scm @@ -1,69 +1,64 @@ ; MISC ; ==== -; In the JSX construct ``, `FOO` should not be marked as -; `constant.other.ts.tsx`. Block off identifiers within complex JSX tag names -; early to prevent this. +; In the JSX construct ``, `Foo.Bar` is treated as a +; `member_expression`. We don't want the ordinary rules for member expressions +; to apply, so we block them off. +; +; TODO: If we wanted to give these segments individual scopes, we'd do that +; here — replacing the `@_IGNORE_`s with scope names. (jsx_opening_element - (nested_identifier + (member_expression (identifier) @_IGNORE_ + (property_identifier) @_IGNORE_ (#set! capture.final))) (jsx_closing_element - (nested_identifier + (member_expression (identifier) @_IGNORE_ + (property_identifier) @_IGNORE_ (#set! capture.final))) (jsx_self_closing_element - (nested_identifier + (member_expression (identifier) @_IGNORE_ + (property_identifier) @_IGNORE_ (#set! capture.final))) ; JSX ; === +[ + (jsx_self_closing_element) + (jsx_opening_element) + (jsx_closing_element) +] @meta.tag.ts.tsx + ; The "Foo" in ``. (jsx_self_closing_element - name: (_) @entity.name.tag.ts.tsx - ) @meta.tag.ts.tsx + name: (_) @entity.name.tag.ts.tsx) ; The "Foo" in ``. (jsx_opening_element - name: (identifier) @entity.name.tag.ts.tsx) @meta.tag.ts.tsx + name: (identifier) @entity.name.tag.ts.tsx) ; The "Foo.Bar" in ``. (jsx_opening_element - name: (nested_identifier) @entity.name.tag.ts.tsx) @meta.tag.ts.tsx + name: (member_expression) @entity.name.tag.ts.tsx) ; The "Foo" in ``. (jsx_closing_element - name: (identifier) @entity.name.tag.ts.tsx) @meta.tag.ts.tsx + name: (identifier) @entity.name.tag.ts.tsx) ; The "Foo.Bar" in ``. (jsx_closing_element - name: (nested_identifier) @entity.name.tag.ts.tsx) @meta.tag.ts.tsx + name: (member_expression) @entity.name.tag.ts.tsx) ; The "bar" in ``. (jsx_attribute (property_identifier) @entity.other.attribute-name.ts.tsx) -; The empty tag used as a shorthand for a fragment: `<>`. -((jsx_fragment) @meta.tag.fragment.ts.tsx - (#set! adjust.endAfterFirstMatchOf "^<>")) - -; The closing fragment tag: ``. -((jsx_fragment) @meta.tag.fragment.ts.tsx - (#set! adjust.startBeforeFirstMatchOf "$")) - -; (jsx_fragment) - -; The slashes in closing tags should not be interpreted as math operators. -(jsx_self_closing_element "/" @punctuation.definition.tag.end.ts.tsx - (#set! capture.final)) -(jsx_closing_element "/" @punctuation.definition.tag.end.ts.tsx - (#set! capture.final)) - ; All JSX expressions/interpolations within braces. ((jsx_expression) @meta.embedded.block.ts.tsx (#match? @meta.embedded.block.ts.tsx "\\n") @@ -74,31 +69,16 @@ (jsx_opening_element "<" @punctuation.definition.tag.begin.ts.tsx ">" @punctuation.definition.tag.end.ts.tsx) - +; (jsx_closing_element - "<" @punctuation.definition.tag.begin.ts.tsx - ">" @punctuation.definition.tag.end.ts.tsx) - -(jsx_fragment - "<" @punctuation.definition.tag.begin.ts.tsx + "" @punctuation.definition.tag.end.ts.tsx) -(jsx_fragment - "/" @punctuation.definition.tag.end.ts.tsx - (#set! capture.final)) - (jsx_self_closing_element "<" @punctuation.definition.tag.begin.ts.tsx + "/>" @punctuation.definition.tag.end.ts.tsx (#set! capture.final)) -((jsx_self_closing_element - ; The "/>" in ``, extended to cover both anonymous nodes at once. - "/") @punctuation.definition.tag.end.ts.tsx - (#set! adjust.startAt lastChild.previousSibling.startPosition) - (#set! adjust.endAt lastChild.endPosition) - (#set! capture.final true)) - - ; META ; ==== diff --git a/packages/language-typescript/grammars/tree-sitter-tsx/indents.scm b/packages/language-typescript/grammars/tree-sitter-tsx/indents.scm index 3a12735e84..c94f7188c3 100644 --- a/packages/language-typescript/grammars/tree-sitter-tsx/indents.scm +++ b/packages/language-typescript/grammars/tree-sitter-tsx/indents.scm @@ -34,7 +34,7 @@ ; point, so the usual heuristic won't work. Instead we set `indent.force` and ; use `test.lastTextOnRow` to ensure that the dedent fires exactly once while ; typing. -((jsx_self_closing_element ">" @dedent) +((jsx_self_closing_element "/>" @dedent) (#is-not? test.startsOnSameRowAs parent.firstChild.startPosition) (#is? test.lastTextOnRow) (#set! indent.force true)) diff --git a/packages/language-typescript/grammars/tree-sitter-tsx/tree-sitter-tsx.wasm b/packages/language-typescript/grammars/tree-sitter-tsx/tree-sitter-tsx.wasm index bf052655c2..5f0cba914f 100755 Binary files a/packages/language-typescript/grammars/tree-sitter-tsx/tree-sitter-tsx.wasm and b/packages/language-typescript/grammars/tree-sitter-tsx/tree-sitter-tsx.wasm differ diff --git a/packages/language-typescript/grammars/tree-sitter-typescript/tree-sitter-typescript.wasm b/packages/language-typescript/grammars/tree-sitter-typescript/tree-sitter-typescript.wasm index e34b771f88..86cca1dfa8 100755 Binary files a/packages/language-typescript/grammars/tree-sitter-typescript/tree-sitter-typescript.wasm and b/packages/language-typescript/grammars/tree-sitter-typescript/tree-sitter-typescript.wasm differ diff --git a/packages/language-typescript/lib/main.js b/packages/language-typescript/lib/main.js index 339496549d..9ef84aa33b 100644 --- a/packages/language-typescript/lib/main.js +++ b/packages/language-typescript/lib/main.js @@ -30,7 +30,7 @@ exports.activate = function () { content(callExpression) { const { lastChild } = callExpression; if (lastChild.type === 'template_string') { - return lastChild; + return stringFragmentsOfTemplateString(lastChild); } } }); @@ -50,7 +50,7 @@ exports.activate = function () { content(callExpression) { const { lastChild } = callExpression; if (lastChild.type === 'template_string') { - return lastChild; + return stringFragmentsOfTemplateString(lastChild); } } }); @@ -92,3 +92,9 @@ function languageStringForTemplateTag(tag) { return tag; } } + +function stringFragmentsOfTemplateString(templateStringNode) { + return templateStringNode.children.filter( + c => c.type === 'string_fragment' + ); +} diff --git a/packages/language-yaml/grammars/modern-tree-sitter-yaml.cson b/packages/language-yaml/grammars/modern-tree-sitter-yaml.cson index f82dd3eed6..97431793bc 100644 --- a/packages/language-yaml/grammars/modern-tree-sitter-yaml.cson +++ b/packages/language-yaml/grammars/modern-tree-sitter-yaml.cson @@ -6,6 +6,7 @@ parser: 'tree-sitter-yaml' injectionRegex: 'yml|yaml|YML|YAML' treeSitter: + parserSource: 'github:tree-sitter-grammars/tree-sitter-yaml#7b03feefd36b5f155465ca736c6304aca983b267' grammar: 'tree-sitter/tree-sitter-yaml.wasm' highlightsQuery: 'tree-sitter/highlights.scm' foldsQuery: 'tree-sitter/folds.scm' diff --git a/packages/language-yaml/grammars/tree-sitter/tree-sitter-yaml.wasm b/packages/language-yaml/grammars/tree-sitter/tree-sitter-yaml.wasm index 6b7218472a..901f841f37 100755 Binary files a/packages/language-yaml/grammars/tree-sitter/tree-sitter-yaml.wasm and b/packages/language-yaml/grammars/tree-sitter/tree-sitter-yaml.wasm differ diff --git a/packages/symbol-provider-tree-sitter/lib/tree-sitter-provider.js b/packages/symbol-provider-tree-sitter/lib/tree-sitter-provider.js index eba6d9d58f..b3cf51db84 100644 --- a/packages/symbol-provider-tree-sitter/lib/tree-sitter-provider.js +++ b/packages/symbol-provider-tree-sitter/lib/tree-sitter-provider.js @@ -77,8 +77,7 @@ class TreeSitterProvider { let tagsQuery = layer.queries?.tagsQuery ?? layer.tagsQuery; let captures = tagsQuery.captures( layer.tree.rootNode, - extent.start, - extent.end + { startPosition: extent.start, endPosition: extent.end } ); results.push( diff --git a/packages/symbol-provider-tree-sitter/spec/symbol-provider-tree-sitter-spec.js b/packages/symbol-provider-tree-sitter/spec/symbol-provider-tree-sitter-spec.js index ad8b7fdf3d..37e2d8f741 100644 --- a/packages/symbol-provider-tree-sitter/spec/symbol-provider-tree-sitter-spec.js +++ b/packages/symbol-provider-tree-sitter/spec/symbol-provider-tree-sitter-spec.js @@ -179,7 +179,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) ) @definition.function ` ); @@ -211,7 +211,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) ) @definition.function ( @@ -255,7 +255,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.context "something") ) `); @@ -303,7 +303,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.icon "book") ) @@ -325,7 +325,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.tag "class") (#set! symbol.icon "book") ) @@ -346,7 +346,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.tag "class") (#set! symbol.icon "book") ) @definition.namespace @@ -369,7 +369,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.tag "class") ) `); @@ -391,7 +391,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.tag "class") ) @definition.namespace `); @@ -415,7 +415,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.strip "ort$") ) `); @@ -438,7 +438,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.prepend "Foo: ") ) `); @@ -461,7 +461,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) (#set! symbol.append " (foo)") ) @@ -485,8 +485,8 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) - (#set! test.onlyIfDescendantOfType function) + value: [(arrow_function) (function_expression)])) + (#set! test.onlyIfDescendantOfType function_expression) (#set! symbol.prependTextForNode "parent.parent.parent.parent.parent.firstNamedChild") (#set! symbol.joiner ".") (#set! test.final true) @@ -495,7 +495,7 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) + value: [(arrow_function) (function_expression)])) ) `); }); @@ -518,8 +518,8 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) - (#set! test.onlyIfNotDescendantOfType function) + value: [(arrow_function) (function_expression)])) + (#set! test.onlyIfNotDescendantOfType function_expression) (#set! symbol.prepend "ROOT: ") (#set! test.final true) ) @@ -528,8 +528,8 @@ describe('TreeSitterProvider', () => { (variable_declaration (variable_declarator name: (identifier) @name - value: [(arrow_function) (function)])) - (#set! test.onlyIfDescendantOfType function) + value: [(arrow_function) (function_expression)])) + (#set! test.onlyIfDescendantOfType function_expression) (#set! symbol.prependSymbolForNode "parent.parent.parent.parent.parent.firstNamedChild") (#set! symbol.joiner ".") (#set! test.final true) diff --git a/spec/scope-resolver-spec.js b/spec/scope-resolver-spec.js index 0aee61c607..fdacce042e 100644 --- a/spec/scope-resolver-spec.js +++ b/spec/scope-resolver-spec.js @@ -38,7 +38,7 @@ async function getAllCapturesWithScopeResolver(grammar, languageMode, scopeResol let { start, end } = languageMode.buffer.getRange(); let { tree } = layer; return { - captures: query.captures(tree.rootNode, start, end), + captures: query.captures(tree.rootNode, { startPosition: start, endPosition: end }), scopeResolver }; } @@ -142,7 +142,7 @@ describe('ScopeResolver', () => { let tokens = []; const original = grammar.idForScope.bind(grammar); - grammar.idForScope = function(scope, text) { + grammar.idForScope = function (scope, text) { if (text) { tokens.push(text); } @@ -972,7 +972,7 @@ describe('ScopeResolver', () => { expect(matched.length).toBe(1); expect(matched.every(cap => { - return cap.name === 'messed-up-statement-block' && cap.node.hasError(); + return cap.name === 'messed-up-statement-block' && cap.node.hasError; })).toBe(true); }); diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 832c482a53..18fd19bf83 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -8326,11 +8326,11 @@ describe('TextEditor', () => { 'program', 'variable_declaration', 'variable_declarator', - 'function', + 'function_expression', 'statement_block', 'variable_declaration', 'variable_declarator', - 'function', + 'function_expression', 'statement_block', 'while_statement', 'parenthesized_expression', diff --git a/spec/wasm-tree-sitter-language-mode-spec.js b/spec/wasm-tree-sitter-language-mode-spec.js index cb1b45a197..60aee9b1d6 100644 --- a/spec/wasm-tree-sitter-language-mode-spec.js +++ b/spec/wasm-tree-sitter-language-mode-spec.js @@ -766,13 +766,14 @@ describe('WASMTreeSitterLanguageMode', () => { `); jsGrammar.addInjectionPoint(HTML_TEMPLATE_LITERAL_INJECTION_POINT); + jsGrammar.addInjectionPoint(HTML_INNERHTML_ASSIGNMENT_INJECTION_POINT); jsGrammar.addInjectionPoint(JSDOC_INJECTION_POINT); let tempHtmlConfig = { ...htmlConfig }; htmlGrammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, tempHtmlConfig); await htmlGrammar.setQueryForTest('highlightsQuery', ` - (fragment) @html + (document) @html (tag_name) @tag (attribute_name) @attr `); @@ -784,7 +785,7 @@ describe('WASMTreeSitterLanguageMode', () => { jasmine.useRealClock(); atom.grammars.addGrammar(jsGrammar); atom.grammars.addGrammar(htmlGrammar); - buffer.setText('node.innerHTML = html `\na ${b}\n`;'); + buffer.setText('node.x = html `\na ${b}\n`;'); const languageMode = new WASMTreeSitterLanguageMode({ grammar: jsGrammar, @@ -800,7 +801,7 @@ describe('WASMTreeSitterLanguageMode', () => { expectTokensToEqual(editor, [ [ { text: 'node.', scopes: [] }, - { text: 'innerHTML', scopes: ['property'] }, + { text: 'x', scopes: ['property'] }, { text: ' = ', scopes: [] }, { text: 'html', scopes: ['function'] }, { text: ' ', scopes: [] }, @@ -829,7 +830,7 @@ describe('WASMTreeSitterLanguageMode', () => { expectTokensToEqual(editor, [ [ { text: 'node.', scopes: [] }, - { text: 'innerHTML', scopes: ['property'] }, + { text: 'x', scopes: ['property'] }, { text: ' = ', scopes: [] }, { text: 'xml', scopes: ['function'] }, { text: ' ', scopes: [] }, @@ -893,7 +894,7 @@ describe('WASMTreeSitterLanguageMode', () => { jasmine.useRealClock(); atom.grammars.addGrammar(jsGrammar); - buffer.setText('node.innerHTML = html `\na ${b}\n`;'); + buffer.setText('node.innerHTML = `\na ${b}\n`;'); const languageMode = new WASMTreeSitterLanguageMode({ grammar: jsGrammar, buffer, @@ -908,8 +909,6 @@ describe('WASMTreeSitterLanguageMode', () => { { text: 'node.', scopes: [] }, { text: 'innerHTML', scopes: ['property'] }, { text: ' = ', scopes: [] }, - { text: 'html', scopes: ['function'] }, - { text: ' ', scopes: [] }, { text: '`', scopes: ['string'] } ], [ @@ -931,8 +930,6 @@ describe('WASMTreeSitterLanguageMode', () => { { text: 'node.', scopes: [] }, { text: 'innerHTML', scopes: ['property'] }, { text: ' = ', scopes: [] }, - { text: 'html', scopes: ['function'] }, - { text: ' ', scopes: [] }, { text: '`', scopes: ['string'] }, { text: '', scopes: ['string', 'html'] } ], @@ -1057,6 +1054,8 @@ describe('WASMTreeSitterLanguageMode', () => { '
' ); await languageMode.nextTransaction; + expect(buffer.getText()).toEqual(`text = html \`
\`;`); + await wait(100); expectTokensToEqual(editor, [ [ { text: 'text = ', scopes: [] }, @@ -1352,7 +1351,7 @@ describe('WASMTreeSitterLanguageMode', () => { let customHtmlGrammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, customHtmlConfig); await htmlGrammar.setQueryForTest('highlightsQuery', ` - (fragment) @html + (document) @html (tag_name) @tag (attribute_name) @attr `); @@ -1400,7 +1399,7 @@ describe('WASMTreeSitterLanguageMode', () => { let customHtmlGrammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, customHtmlConfig); await htmlGrammar.setQueryForTest('highlightsQuery', ` - (fragment) @html + (document) @html (tag_name) @tag (attribute_name) @attr `); @@ -1451,7 +1450,7 @@ describe('WASMTreeSitterLanguageMode', () => { let customHtmlGrammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, customHtmlConfig); await htmlGrammar.setQueryForTest('highlightsQuery', ` - (fragment) @html + (document) @html (tag_name) @tag (attribute_name) @attr `); @@ -1504,7 +1503,7 @@ describe('WASMTreeSitterLanguageMode', () => { let customHtmlGrammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, customHtmlConfig); await htmlGrammar.setQueryForTest('highlightsQuery', ` - (fragment) @html + (document) @html (tag_name) @tag (attribute_name) @attr `); @@ -3086,7 +3085,7 @@ describe('WASMTreeSitterLanguageMode', () => { .getScopesArray() ).toEqual([ 'text.html.basic', - 'fragment', + 'document', 'element', 'script_element', 'raw_text', @@ -3094,7 +3093,7 @@ describe('WASMTreeSitterLanguageMode', () => { 'expression_statement', 'call_expression', 'template_string', - 'fragment', + 'document', 'element', 'template_substitution', 'member_expression', @@ -3834,7 +3833,7 @@ describe('WASMTreeSitterLanguageMode', () => { htmlConfig ); await htmlGrammar.setQueryForTest('highlightsQuery', ` - (fragment) @html + (document) @html (tag_name) @tag (attribute_name) @attr `); @@ -3988,7 +3987,7 @@ describe('WASMTreeSitterLanguageMode', () => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` "{" @indent "}" @dedent `); @@ -4024,7 +4023,7 @@ describe('WASMTreeSitterLanguageMode', () => { // Pretend we're in a universe where lines after comments should be // dedented. - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` (comment) @dedent.next `); @@ -4041,11 +4040,45 @@ describe('WASMTreeSitterLanguageMode', () => { ).toEqual('(1, 0)'); }); + it('allows @match.next to decrease the indent of the next line before any typing takes place', async () => { + const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); + + // When the comparison row contains the end of a lexical declaration, we + // want the next line to match the indentation of whichever line _began_ + // that lexical declaration. (But for this test we'll add an offset of 1 + // so we can be sure we're not just defaulting to column 0 for some other + // reason.) + await grammar.setQueryForTest('indentsQuery', scm` + ((lexical_declaration) @match.next + (#is? indent.matchesComparisonRow endPosition) + (#set! indent.match startPosition) + (#set! indent.offset 1) + ) + `); + + const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer }); + buffer.setLanguageMode(languageMode); + await languageMode.ready; + + buffer.setText(dedent` + let foo = longMethodWithArguments(1, 2, 3, 4, 5, + 6, 7, 8); + `); + + await languageMode.atTransactionEnd(); + + editor.setCursorBufferPosition([1, 21]); + editor.insertText('\n', { autoIndentNewline: true }); + expect( + editor.getLastCursor().getBufferPosition().toString() + ).toEqual('(2, 2)'); + }); + it('resolves @match captures', async () => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` (template_string "\`" @match (#is? test.last true) @@ -4081,11 +4114,11 @@ describe('WASMTreeSitterLanguageMode', () => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` (template_string "\`" @dedent @match (#is? test.last true) - (#set! indent.matchIndentOf parent.firstChild.startPosition)) + (#set! indent.match parent.firstChild.startPosition)) `); buffer.setText(dedent` @@ -4119,7 +4152,7 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.getUndoGroupingInterval()).toBe(300); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4175,7 +4208,7 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.getUndoGroupingInterval()).toBe(300); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4232,7 +4265,7 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.getUndoGroupingInterval()).toBe(300); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4286,7 +4319,7 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.getUndoGroupingInterval()).toBe(300); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4340,7 +4373,7 @@ describe('WASMTreeSitterLanguageMode', () => { // Pretend we're in a universe where a line comment should cause the next // line to be indented, but only in a class body. - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent ((comment) @indent @@ -4417,7 +4450,7 @@ describe('WASMTreeSitterLanguageMode', () => { // Pretend we're in a universe where a line comment should cause the next // line to be indented, but only in a class body. - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent ((comment) @indent @@ -4464,7 +4497,7 @@ describe('WASMTreeSitterLanguageMode', () => { jasmine.useRealClock(); editor.updateAutoIndent(true); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4503,7 +4536,7 @@ describe('WASMTreeSitterLanguageMode', () => { atom.config.set('whitespace.removeTrailingWhitespace', true); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4549,7 +4582,7 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.getUndoGroupingInterval()).toBe(300); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4595,7 +4628,7 @@ describe('WASMTreeSitterLanguageMode', () => { atom.config.set('whitespace.removeTrailingWhitespace', false); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4641,7 +4674,7 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.getUndoGroupingInterval()).toBe(300); - await grammar.setQueryForTest('indentsQuery', ` + await grammar.setQueryForTest('indentsQuery', scm` ["{"] @indent ["}"] @dedent `); @@ -4788,6 +4821,27 @@ function expectTokensToEqual(editor, expectedTokenLines) { editor.displayLayer.getScreenLines(0, Infinity); } +const HTML_INNERHTML_ASSIGNMENT_INJECTION_POINT = { + type: 'assignment_expression', + + language(callExpression) { + const { firstChild } = callExpression; + if (firstChild.type === 'member_expression') { + if (firstChild.lastChild.text === 'innerHTML') { + return 'html'; + } + } + }, + + content(callExpression) { + const { lastChild } = callExpression; + if (lastChild.type === 'template_string') { + return stringFragmentsOfTemplateString(lastChild); + } + }, +}; + + const HTML_TEMPLATE_LITERAL_INJECTION_POINT = { type: 'call_expression', language(node) { @@ -4799,7 +4853,7 @@ const HTML_TEMPLATE_LITERAL_INJECTION_POINT = { } }, content(node) { - return node?.lastChild; + return stringFragmentsOfTemplateString(node.lastChild); } }; @@ -4822,3 +4876,10 @@ const JSDOC_INJECTION_POINT = { return comment; } }; + + +function stringFragmentsOfTemplateString(templateStringNode) { + return templateStringNode.children.filter( + c => c.type === 'string_fragment' + ); +} diff --git a/src/scope-resolver.js b/src/scope-resolver.js index 8e8df72fae..8f6eb8c379 100644 --- a/src/scope-resolver.js +++ b/src/scope-resolver.js @@ -641,7 +641,7 @@ ScopeResolver.TESTS = { // Passes only if the node contains any descendant ERROR nodes. hasError(node) { - return node.hasError(); + return node.hasError; }, // Passes when the node's tree belongs to an injection layer, rather than the @@ -803,6 +803,7 @@ ScopeResolver.TESTS = { // position. Accepts a node position descriptor. startsOnSameRowAs(node, descriptor) { let otherNodePosition = resolveNodePosition(node, descriptor); + if (!otherNodePosition) return false return otherNodePosition.row === node.startPosition.row; }, @@ -810,6 +811,7 @@ ScopeResolver.TESTS = { // position. Accepts a node position descriptor. endsOnSameRowAs(node, descriptor) { let otherNodePosition = resolveNodePosition(node, descriptor); + if (!otherNodePosition) return false return otherNodePosition.row === node.endPosition.row; }, diff --git a/src/wasm-tree-sitter-grammar.js b/src/wasm-tree-sitter-grammar.js index 55bbb22cb2..7753fdf4f1 100644 --- a/src/wasm-tree-sitter-grammar.js +++ b/src/wasm-tree-sitter-grammar.js @@ -1,5 +1,7 @@ const fs = require('fs'); const path = require('path'); +const Grim = require('grim'); +const dedent = require('dedent'); const Parser = require('./web-tree-sitter'); const { CompositeDisposable, Emitter } = require('event-kit'); const { File } = require('pathwatcher'); @@ -7,6 +9,62 @@ const { normalizeDelimiters } = require('./comment-utils.js'); const parserInitPromise = Parser.init(); +function isPosition(obj) { + return ('row' in obj && 'column' in obj); +} + +const ZERO_POINT = Object.freeze({ row: 0, column: 0 }); + +const QUERY_CAPTURES_DEPRECATION_EXPLANATION = dedent`\ + The \`captures\` method available on Tree-sitter query objects uses a new + function signature; the old signature is deprecated. The new signature is + \`(node, options)\`. If you want to limit a query to a specific range, + specify \`startPosition\` and \`endPosition\` properties within \`options\`. +`; + +let didWrapQueryCaptures = false; + +// When `web-tree-sitter` harmonized its API with that of `node-tree-sitter`, +// some function signatures changed. The most impactful one for us is probably +// `Query#captures`, since two crucial positional arguments were moved into a +// trailing options argument. +// +// We've changed all of our usages, but it's possible some community packages +// won't have been able to update yet. We should emit a deprecation message in +// those cases and restructure the arguments on the fly. +function wrapQueryCaptures(query) { + didWrapQueryCaptures = true; + let QueryPrototype = Object.getPrototypeOf(query); + let originalCaptures = QueryPrototype.captures; + // We put `node` into its own argument so that this new function’s `length` + // property matches that of the old function. (Both are inaccurate, but they + // should nonetheless agree.) + QueryPrototype.captures = function captures(node, ...args) { + // When do we think a consumer is using the old signature? + if ( + // If there are too many arguments and either the second or third + // argument looks like a position… + args.length >= 2 && (isPosition(args[0]) || isPosition(args[1])) || + // …or if the second argument looks like a position instead of an options + // object. + isPosition(args[0]) + ) { + Grim.deprecate(QUERY_CAPTURES_DEPRECATION_EXPLANATION); + let startPosition = isPosition(args[0]) ? args[0] : ZERO_POINT; + let endPosition = isPosition(args[1]) ? args[1] : args[0]; + let originalOptions = args[2] ?? {}; + let newOptions = { + ...originalOptions, + startPosition, + endPosition + }; + return originalCaptures.call(this, node, newOptions); + } else { + return originalCaptures.call(this, node, ...args); + } + }; +} + // Extended: This class holds an instance of a Tree-sitter grammar. module.exports = class WASMTreeSitterGrammar { constructor(registry, grammarPath, params) { @@ -274,6 +332,15 @@ module.exports = class WASMTreeSitterGrammar { // if (inDevMode) { console.time(timeTag); } query = language.query(this[queryType]); + // We want to augment the `Query` class to add backward compatibility + // for the `captures` method. But since `web-tree-sitter` doesn’t + // export references to these inner Tree-sitter classes, we have to + // wait until we’re holding an instance of a `Query` and grab its + // prototype. Luckily, we still only have to do this once. + if (!didWrapQueryCaptures) { + wrapQueryCaptures(query); + } + // if (inDevMode) { console.timeEnd(timeTag); } this.queryCache.set(queryType, query); resolve(query); diff --git a/src/wasm-tree-sitter-language-mode.js b/src/wasm-tree-sitter-language-mode.js index 6ec91ae196..3bc5f253d0 100644 --- a/src/wasm-tree-sitter-language-mode.js +++ b/src/wasm-tree-sitter-language-mode.js @@ -12,6 +12,8 @@ const { commentStringsFromDelimiters, getDelimitersForScope } = require('./comme const createTree = require('./rb-tree'); +const ONE_CHAR_FORWARD_TRAVERSAL = Object.freeze(Point(0, 1)); + const FEATURE_ASYNC_INDENT = true; const FEATURE_ASYNC_PARSE = true; @@ -83,7 +85,7 @@ function resolveNodePosition(node, descriptor) { return result[lastPart]; } -// Patch tree-sitter syntax nodes the same way `TreeSitterLanguageMode` did so +// Patch Tree-sitter syntax nodes the same way `TreeSitterLanguageMode` did so // that we don't break anything that relied on `range` being present. function ensureNodeIsPatched(node) { let done = node.range && node.range instanceof Range; @@ -108,7 +110,7 @@ function ensureNodeIsPatched(node) { }); } -// Compares “informal” points like the ones in a tree-sitter tree; saves us +// Compares “informal” points like the ones in a Tree-sitter tree; saves us // from having to convert them to actual `Point`s. function comparePoints(a, b) { const rows = a.row - b.row; @@ -200,6 +202,24 @@ class WASMTreeSitterLanguageMode { // performed and injections are populated. this.resolveNextTransactionPromise(); + // In contrast to `FoldResolver`s — which exist on each layer — we create a + // single `IndentResolver` per language mode. + // + // We do this because folds aggregate, so we can always either (a) delegate + // a job to an arbitrary layer's `FoldResolver`, or (b) ask _all_ layers to + // do something and then assemble the results. + // + // Indentation tasks, on the other hand, cannot be divided into work that + // considers only a single `LanguageLayer` at a time. For instance, a given + // indentation task might consult one layer's indentation query to know + // whether to indent a line, but another layer's indentation query to know + // whether to dedent the line. There are no simplicity gains to be made. + // + // `IndentResolver` _could_ therefore fold its methods into + // `WASMTreeSitterLanguageMode`, but is separate from it for reasons of + // code organization. + this.indentResolver = new IndentResolver(this.buffer, this); + this.ready = this.grammar.getLanguage() .then(language => { this.rootLanguage = language; @@ -411,7 +431,7 @@ class WASMTreeSitterLanguageMode { return true; } - // Resolves the next time that all tree-sitter trees are clean — or + // Resolves the next time that all Tree-sitter trees are clean — or // immediately, if they're clean at the time of invocation. // // Resolves with metadata about the previous transaction that may be useful @@ -443,7 +463,7 @@ class WASMTreeSitterLanguageMode { } // Alias for `atTransactionEnd` for packages that used the implementation - // details of the legacy tree-sitter system. + // details of the legacy Tree-sitter system. parseCompletePromise() { return this.atTransactionEnd(); } @@ -517,7 +537,7 @@ class WASMTreeSitterLanguageMode { } // Behaves like `scopeDescriptorForPosition`, but returns a list of - // tree-sitter node names. Useful for understanding tree-sitter parsing or + // Tree-sitter node names. Useful for understanding Tree-sitter parsing or // for writing syntax highlighting query files. syntaxTreeScopeDescriptorForPosition(point) { point = this.normalizePointForPositionQuery(point); @@ -555,7 +575,7 @@ class WASMTreeSitterLanguageMode { ); let scopes = matches.map(({ node }) => ( - node.isNamed() ? node.type : `"${node.type}"` + node.isNamed ? node.type : `"${node.type}"` )); scopes.unshift(this.grammar.scopeName); @@ -1170,7 +1190,8 @@ class WASMTreeSitterLanguageMode { // than just returning `commentDelimiters` data because we still want to // take the adjusted range into account if we can.) const { grammar } = this.getSyntaxNodeAndGrammarContainingRange(range); - const { grammar: originalPositionGrammar } = this.getSyntaxNodeAndGrammarContainingRange(new Range(position, position)); + const { grammar: originalPositionGrammar } = this.getSyntaxNodeAndGrammarContainingRange( + new Range(position, position)); if (grammar && grammar.getCommentDelimiters) { let delimiters = grammar.getCommentDelimiters(); @@ -1251,45 +1272,49 @@ class WASMTreeSitterLanguageMode { shouldUseAsyncIndent() { let result = true; if (!this.useAsyncParsing || !this.useAsyncIndent) result = false; + // If `currentTransactionReparseBudgetMs` somehow isn’t initialized yet, we + // can initialize it here for this transaction. + this.currentTransactionReparseBudgetMs ??= this.transactionReparseBudgetMs; if (this.currentTransactionReparseBudgetMs > 0) { result = false; } return result; } - // Get the suggested indentation level for an existing line in the buffer. + // Essential: Get the suggested indentation level for an existing line in the + // buffer. // - // * `bufferRow` - A {Number} indicating the buffer row. - // * `tabLength` - A {Number} signifying the length of a tab, in spaces, + // * `bufferRow` A {Number} indicating the buffer row. + // * `tabLength` A {Number} signifying the length of a tab, in spaces, // according to the current settings of the buffer. - // * `options` (optional) - An {Object} will the following properties,all of - // which are optional: - // * `options.skipBlankLines`: {Boolean} indicating whether to ignore blank - // lines when determining which row to use as a reference row. Default is - // `true`. Irrelevant if `options.comparisonRow` is specified. - // * `options.skipDedentCheck`: {Boolean} indicationg whether to skip the - // second phase of the check and determine only if the current row should - // _start out_ indented from the reference row. - // * `options.preserveLeadingWhitespace`: {Boolean} indicating whether to - // adjust the returned number to account for the indentation level of any - // whitespace that may already be on the row. Defaults to `false`. - // * `options.forceTreeParse`: {Boolean} indicating whether to force this - // method to synchronously parse the buffer into a tree, even if it - // otherwise would not. Defaults to `false`. - // * `options.comparisonRow`: A {Number} specifying the row to use as a - // reference row. Must be a valid row that occurs earlier in the buffer - // than `row`. If omitted, this method will determine the reference row on - // its own. + // * `options` An optional {Object} with the following properties, all of + // which are themselves optional: + // * `options.skipBlankLines`: {Boolean} indicating whether to ignore blank + // lines when determining which row to use as a reference row. Default is + // `true`. Irrelevant if `options.comparisonRow` is specified. + // * `options.skipDedentCheck`: {Boolean} indicating whether to skip the + // second phase of the check and determine only if the current row should + // _start out_ indented from the reference row. + // * `options.preserveLeadingWhitespace`: {Boolean} indicating whether to + // adjust the returned number to account for the indentation level of any + // whitespace that may already be on the row. Defaults to `false`. + // * `options.forceTreeParse`: {Boolean} indicating whether to force this + // method to synchronously parse the buffer into a tree, even if it + // otherwise would not. Defaults to `false`. + // * `options.comparisonRow`: A {Number} specifying the row to use as a + // reference row. Must be a valid row that occurs earlier in the buffer + // than `row`. If omitted, this method will determine the reference row + // on its own. // - // Will return either an immediate result or a {Promise}, depending on - // whether it can make a synchronous decision. + // Returns a {Number}, {null}, or a {Promise} that will resolve with either + // a {Number} or {undefined}. // - // When acting synchronously, this method returns a {Number}, or {null} if - // this method cannot make a suggestion. It will return a synchronous result - // if (a) the tree is clean, (b) the language mode decides it can afford to - // do a synchronous re-parse of the buffer, or (c) `options.forceTreeParse` - // is `true`. Otherwise, this method will wait until the end of the current - // buffer transaction. + // This method will return a synchronous result if (a) the tree is clean, (b) + // the language mode decides it can afford to do a synchronous re-parse of + // the buffer, or (c) `options.forceTreeParse` is `true`. Otherwise, this + // method will wait until the end of the current buffer transaction. If this + // method synchronously returns {null}, it means that this method cannot make + // a suggestion. // // When acting asynchronously, this method may or may not be able to give an // answer. If it can, it will return a {Promise} that resolves with a @@ -1299,3555 +1324,3973 @@ class WASMTreeSitterLanguageMode { // signalling that a fallback style of indentation adjustment should take // place. // - suggestedIndentForBufferRow(row, tabLength, rawOptions = {}) { - // NOTE: This method is hundreds of lines long, but so much of that total - // consists of comments like this one — because this is a hard thing to - // intuit. This code needs lots of explanation, but that doesn't mean - // that the logic is impossibly complex. - let root = this.rootLanguageLayer; - if (row === 0) { return 0; } - if (!root || !root.tree || !root.ready) { return null; } - - let options = { - skipBlankLines: true, - skipDedentCheck: false, - preserveLeadingWhitespace: false, - indentationLevels: null, - forceTreeParse: false, - ...rawOptions - }; + suggestedIndentForBufferRow(...args) { + return this.indentResolver.suggestedIndentForBufferRow(...args); + } - let originalControllingLayer = options.controllingLayer; + // Given a range of buffer rows, retrieves the suggested indent for each one + // while re-using the same tree. Computing these results in bulk may prevent + // a tree re-parse after each individual line adjustment when auto-indenting + // multiple rows at once. + // + // * `startRow` The row {Number} to start at. + // * `endRow` The row {Number} to end at. + // + // Returns either a {Map} or {null}. If a {Map}, its keys will be row numbers + // and its values will be desired indentation levels. May not return the + // entire range of requested rows, in which case the caller should + // auto-indent the remaining rows through another means. If {null}, signifies + // that no auto-indent should be attempted at all for the given range. + suggestedIndentForBufferRows(...args) { + return this.indentResolver.suggestedIndentForBufferRows(...args); + } - let comparisonRow = options.comparisonRow; - if (comparisonRow === undefined) { - comparisonRow = row - 1; - if (options.skipBlankLines) { - // It usually makes no sense to compare to a blank row, so we'll move - // upward until we find a line with text on it. - while (this.buffer.isRowBlank(comparisonRow) && comparisonRow > 0) { - comparisonRow--; - } - } - } + // Get the suggested indentation level for a line in the buffer on which the + // user is currently typing. This may return a different result from + // {::suggestedIndentForBufferRow} in order to avoid unexpected changes in + // indentation. It may also return undefined if no change should be made. + // + // * row - The row {Number} + // + // Returns a {Number}. + suggestedIndentForEditedBufferRow(...args) { + return this.indentResolver.suggestedIndentForEditedBufferRow(...args); + } - let existingIndent = 0; - if (options.preserveLeadingWhitespace) { - // When this option is true, the indent level we return will be _added - // to_ however much indentation is already present on the line. Whatever - // the purpose of this option, we can't just pretend it isn't there, - // because it will produce silly outcomes. Instead, let's account for - // that level of indentation and try to subtract it from whatever level - // we return later on. - // - // Sadly, if the row is _more_ indented than we need it to be, we won't - // be able to dedent it into the correct position. This option probably - // needs to be revisited. - existingIndent = this.indentLevelForLine( - this.buffer.lineForRow(row), tabLength); - } - let comparisonRowIndent = options.comparisonRowIndent; - if (comparisonRowIndent === undefined) { - comparisonRowIndent = this.indentLevelForLine( - this.buffer.lineForRow(comparisonRow), tabLength); - } + // Get the suggested indentation level for a given line of text, if it were + // inserted at the given row in the buffer. + // + // * bufferRow - A {Number} indicating the buffer row + // + // Returns a {Number}. + suggestedIndentForLineAtBufferRow(row, _line, tabLength) { + // We can't answer this question accurately for text that isn't yet in the + // tree, so instead we'll just note that this request was made and try to + // correct indentation when the transaction is over. + this.autoIndentRequests++; + return this.suggestedIndentForBufferRow(row, tabLength); + } - // What's the right place to measure from? Often we're here because the - // user just hit Enter, which means we'd run before injection layers have - // been re-parsed. Hence the injection's language layer might not know - // whether it controls the point at the cursor. So instead we look for the - // layer that controls the point at the end of the comparison row. This may - // not always be correct, but we'll find out. - let comparisonRowEnd = new Point( - comparisonRow, - this.buffer.lineLengthForRow(comparisonRow) - ); + // Private - // Find the deepest layer that actually has an indents query. (Layers that - // don't define one, such as specialized injection grammars, are telling us - // they don't care about indentation. If a grammar wants to _prevent_ a - // shallower layer from controlling indentation, it should define an empty - // `indents.scm`, perhaps with an explanatory comment.) - let controllingLayer = this.controllingLayerAtPoint( - comparisonRowEnd, - (layer) => { - if (!layer.queries.indentsQuery) return false; - // We want to exclude layers with a content range that _begins at_ the - // cursor position. Why? Because the content that starts at the cursor - // is about to shift down to the next line. It'd be odd if that layer - // was in charge of the indentation hint if it didn't have any content - // on the preceding line. - // - // So first we test for containment exclusive of endpoints… - if (layer.containsPoint(comparisonRowEnd, true)) { - return true; - } + getAllInjectionLayers() { + let markers = this.injectionsMarkerLayer.getMarkers(); + return markers.map(m => m.languageLayer); + } - // …but we'll still accept layers that have a content range which - // _ends_ at the cursor position. - return layer.getCurrentRanges()?.some(r => { - return r.end.compare(comparisonRowEnd) === 0; - }); - } - ); + getAllLanguageLayers(where = null) { + let layers = [ + this.rootLanguageLayer, + ...this.getAllInjectionLayers() + ]; + if (!where) return layers; - if (!controllingLayer) { - // There's no layer with an indents query to help us out. The default - // behavior in this situation with any grammar — even plain text — is to - // match the previous line's indentation. - return comparisonRowIndent - existingIndent; + let results = []; + for (let layer of layers) { + if (!where(layer)) continue; + results.push(layer); } - let { queries: { indentsQuery }, scopeResolver } = controllingLayer; + return results; + } - // TODO: We use `ScopeResolver` here so that we can use its tests. Maybe we - // need a way to share those tests across different kinds of capture - // resolvers. - scopeResolver.reset(); + // Given a {Point}, returns all injection {LanguageLayer}s that include that + // point. Does not include the root language layer. + // + // A {LanguageLayer} can have multiple content ranges. Its “extent” is a + // single contiguous {Range} that includes all of its content ranges. To + // return only layers with a content range that spans the given point, pass + // `{ exact: true }` as the second argument. + // + // * point - A {Point} representing a buffer position. + // * options - An {Object} containing these keys: + // * exact - {Boolean} that, when `true`, checks for containment within the + // layer's _content ranges_ instead of its _extent_ (see description + // above). + injectionLayersAtPoint(point, { exact = false } = {}) { + let injectionMarkers = this.injectionsMarkerLayer.findMarkers({ + containsPosition: point + }); - let indentTree = null; - if (options.tree && originalControllingLayer === controllingLayer) { - // Make sure this tree belongs to the layer we expect it to. - indentTree = options.tree; - } + injectionMarkers.sort((a, b) => { + return a.getRange().compare(b.getRange()) || + b.depth - a.depth; + }); - if (!indentTree) { - if (!controllingLayer.treeIsDirty || options.forceTreeParse || !this.shouldUseAsyncIndent()) { - // If we're in this code path, it either means the tree is clean (the - // `get` path) or that we're willing to spend the time to do a - // synchronous reparse (the `parse` path). Either way, we'll be able to - // deliver a synchronous answer to the question. - indentTree = controllingLayer.getOrParseTree(); - } else { - // We can't answer this yet because we don't yet have a new syntax - // tree, and are unwilling to spend time doing a synchronous re-parse. - // Return a promise that will fulfill once the transaction is over. - // - // TODO: For async, we might need an approach where we suggest a - // preliminary indent level and then follow up later with a more - // accurate one. It's a bit disorienting that the editor falls back to - // an indent level of `0` when a newline is inserted. - let comparisonRowText = this.buffer.lineForRow(comparisonRow) - let rowText = this.buffer.lineForRow(row) - return this.atTransactionEnd().then(({ changeCount }) => { - let shouldFallback = false; - // If this was the only change in the transaction, then we can - // definitely adjust the indentation level after the fact. If not, - // then we might still be able to make indentation decisions in cases - // where they do not affect one another. - // - // Hence if neither the comparison row nor the current row has had - // its contents change in any way since we were first called, we will - // assume it's safe to adjust the indentation level after the fact. - // Otherwise we'll fall back to a single transaction-wide indentation - // adjustment — fewer tree parses, but more likely to produce unusual - // results. - if (changeCount > 1) { - if (comparisonRowText !== this.buffer.lineForRow(comparisonRow)) { - shouldFallback = true; - } - if (rowText !== this.buffer.lineForRow(row)) { - shouldFallback = true; - } - } - if (shouldFallback) { - // We're now revisiting this indentation question at the end of the - // transaction. Other changes may have taken place since we were - // first asked what the indent level should be for this line. So - // how do we know if the question is still relevant? After all, the - // text that was on this row earlier might be on some other row - // now. - // - // So we compare the text that was on the row when we were first - // called… to the text that is on the row now that the transaction - // is over. If they're the same, that's a _strong_ indicator that - // the result we return will still be relevant. - // - // If not, as is the case in this code path, we return `undefined`, - // signalling to the `TextEditor` that its only recourse is to - // auto-indent the whole extent of the transaction instead. - return undefined; - } + let results = injectionMarkers.map(m => m.languageLayer); - // If we get this far, it's safe to auto-indent this line. Either it - // was the only change in its transaction or the other changes - // happened on different lines. But we've retained the original - // values for `comparisonRow` and `comparisonRowIndent` because - // that's the proper basis from which to determine the given row's - // indent level. - let result = this.suggestedIndentForBufferRow(row, tabLength, { - ...rawOptions, - comparisonRow: comparisonRow, - comparisonRowIndent: comparisonRowIndent, - tree: controllingLayer.tree, - controllingLayer - }); - return result; - }); - } + if (exact) { + results = results.filter(l => l.containsPoint(point)); } + return results; + } - let positionSet = new Set; - - // Capture in two phases. The first phase covers any captures from the - // comparison row that can cause the _following_ row to be indented. - let indentCaptures = indentsQuery.captures( - indentTree.rootNode, - { row: comparisonRow, column: 0 }, - { row: row, column: 0 } - ); + // Given a {Point}, returns all {LanguageLayer}s that include that point, + // including the root language layer. + // + // A {LanguageLayer} can have multiple content ranges. Its “extent” is a + // single contiguous {Range} that includes all of its content ranges. To + // return only layers with a content range that spans the given point, pass + // `{ exact: true }` as the second argument. + // + // * point - A {Point} representing a buffer position. + // * options - An {Object} containing these keys: * exact - {Boolean} that, + // when `true`, checks for containment within the layer's _content ranges_ + // instead of its _extent_ (see description above). + languageLayersAtPoint(point, { exact = false } = {}) { + let injectionLayers = this.injectionLayersAtPoint(point, { exact }); + return [ + this.rootLanguageLayer, + ...injectionLayers + ]; + } - let indentCapturePosition = null; - let indentDelta = 0; - let dedentNextDelta = 0; + // Returns the deepest language layer at a given point, or optionally the + // deepest layer to fulfill a criterion. + // + // Will ignore any layer whose content ranges do not include the point, even if + // the point is within its extent. + controllingLayerAtPoint(point, where = FUNCTION_TRUE) { + let layers = this.languageLayersAtPoint(point, { exact: true }); - for (let capture of indentCaptures) { - let { node, name, setProperties: props = {} } = capture; + // Deeper layers go first. + layers.sort((a, b) => b.depth - a.depth); + return layers.find(layer => where(layer)) ?? null; + } - // Ignore “phantom” nodes that aren't present in the buffer. - if (node.text === '' && !props['indent.allowEmpty']) { - continue; - } + firstNonWhitespaceRange(row) { + return this.buffer.findInRangeSync( + /\S/, + new Range(new Point(row, 0), new Point(row, Infinity)) + ); + } - // Ignore anything that isn't actually on the row. - if (node.endPosition.row < comparisonRow) { continue; } - if (node.startPosition.row > comparisonRow) { continue; } + // DEPRECATED - // Ignore anything that fails a scope test. - if (!scopeResolver.store(capture)) { continue; } + // Implemented for parity with `TextMateLanguageMode`. If you want to analyze + // the content of a row or other buffer range, you can inspect a Tree-sitter + // tree or run queries against it. + tokenizedLineForRow(row) { + const lineText = this.buffer.lineForRow(row); + const tokens = []; - // Only consider a given combination of capture name and buffer range - // once, even if it's captured more than once in `indents.scm`. - let key = `${name}/${node.startIndex}/${node.endIndex}`; - if (positionSet.has(key)) { continue; } - positionSet.add(key); + const iterator = this.buildHighlightIterator(); + let start = { row, column: 0 }; - if (name === 'indent') { - if (indentCapturePosition === null) { - indentCapturePosition = node.endPosition; - } - indentDelta++; - } else if (name === 'dedent.next') { - // This isn't often needed, but it's a way for the current line to - // signal that the _next_ line should be dedented no matter what its - // content is. - dedentNextDelta++; - } else if (name === 'dedent') { - // `dedent` tokens don't count for anything unless they happen - // after the first `indent` token. They only tell us whether an indent - // that _seems_ like it should happen is cancelled out. - // - // Consider: - // - // } else if (foo) { - // - // We should still indent the succeeding line because the initial `}` - // does not cancel out the `{` at the end of the line. On the other - // hand: - // - // } else if (foo) {} - // - // The second `}` _does_ cancel out the first occurrence of `{` because - // it comes later. - if (!indentCapturePosition || comparePoints(node.startPosition, indentCapturePosition) < 0) { - // This capture either happened before the first indent capture on - // the row or is _the same node_ as the indent capture, in which case - // we should construe the dedent as happening _before_ the indent. - // - // For example: the "elsif" node in Ruby triggers a dedent on its own - // line, but also signals an indent on the next line. The dedent - // shouldn't cancel out the indent. - continue; - } - indentDelta--; - if (indentDelta < 0) { - // In the _indent_ phase, the delta won't ever go lower than `0`. - // This is because we assume that the previous line is correctly - // indented! The only function that `dedent` serves for us in this - // phase is canceling out an earlier `indent` and preventing false - // positives. - // - // So no matter how many `dedent` tokens we see on a particular line… - // if the _last_ token we see is an `indent` token, then it hints - // that the next line should be indented by one level. - indentDelta = 0; + const scopes = iterator.seek(start, row) || []; + + // eslint-disable-next-line no-constant-condition + while (true) { + const end = { ...iterator.getPosition() }; + if (end.row > row) { + end.row = row; + end.column = lineText.length; + } + + if (end.column > start.column) { + tokens.push( + new Token({ + value: lineText.substring(start.column, end.column), + scopes: scopes.map(s => this.scopeNameForScopeId(s)) + }) + ); + } + + if (end.column < lineText.length) { + const closeScopeCount = iterator.getCloseScopeIds().length; + for (let i = 0; i < closeScopeCount; i++) { + scopes.pop(); } + scopes.push(...iterator.getOpenScopeIds()); + start = end; + iterator.moveToSuccessor(); + } else { + break; + } + } + + return new TokenizedLine({ + openScopes: [], + text: lineText, + tokens, + tags: [], + ruleStack: [], + lineEnding: this.buffer.lineEndingForRow(row), + tokenIterator: this.tokenIterator, + grammar: this.grammar + }); + } + + // Implemented for parity with `TextMateLanguageMode`. If you want to analyze + // the content of a point, you can inspect a Tree-sitter tree or run queries + // against it. + tokenForPosition(point) { + if (Array.isArray(point)) { + point = new Point(...point); + } + const scopes = this.scopeDescriptorForPosition(point).getScopesArray(); + let range = this.bufferRangeForScopeAtPosition( + last(scopes), + point + ); + return new Token({ + scopes, + value: this.buffer.getTextInRange(range) + }); + } +} +// Responsible for deciding the ranges of folds on a given language layer. +// +// Understands two kinds of folds: +// +// * A “simple” fold is one with a capture name of `@fold` in a folds query. It +// can be described with only one capture. It starts at the end of the row +// that the captured node starts on, and ends at a configurable position +// controlled by the `fold.endAt` adjustment (which defaults to +// `lastChild.startPosition`). +// +// Simple folds should be used whenever you're able to predict the end of a +// fold range simply from holding a reference to its starting node. +// +// * A “divided” fold is one where the two ends of the fold must be described +// in two separate query captures. It starts at the end of the row of a node +// captured with the name of `@fold.start`, and it ends at the very next +// `@fold.end` that it encounters in the document. +// +// When determining the end of a fold that is marked with `@fold.start`, +// Pulsar will search the buffer for the next “balanced” occurrence of +// `@fold.end`. For instance, when trying to find a match for a `@fold.start` +// on row 9, Pulsar might encounter another `@fold.start` on row 10, +// and would then understand that the next `@fold.end` it sees will end +// _that_ fold and not the one we're looking for. If Pulsar _does not_ find a +// matching `@fold.end`, the given line will not be considered to be +// foldable. +// +// Because they can trigger a buffer-wide search, divided folds are +// not recommended to use unless they're truly needed. Use them only when the +// structure of the syntax tree doesn't allow you to determine the end of the +// fold without applying your own heuristic. +// +class FoldResolver { + constructor(buffer, layer) { + this.buffer = buffer; + this.layer = layer; + + this.boundaries = null; + this.boundariesStartingPosition = null; + } + + // Retrieve the first valid fold range for this row in this language layer — + // that is, the first fold range that spans more than one row. + getFoldRangeForRow(row) { + if (!this.layer.tree || !this.layer.queries.foldsQuery) { return null; } + let start = Point.fromObject({ row, column: 0 }); + let end = Point.fromObject({ row: row + 1, column: 0 }); + + let tree = this.layer.getOrParseTree({ force: false }); + // Search for folds that begin somewhere on the given row. + let iterator = this.getOrCreateBoundariesIterator(tree.rootNode, start, end); + + // More than one fold can match for a given row, so we'll stop as soon as + // we find the fold that starts earliest on the row. (The fold itself will + // be “resolved” in such a way that it doesn't begin until the end of the + // row, but we still consider the intrinsic range of the fold capture when + // deciding which one to honor.) + while (iterator.key) { + if (comparePoints(iterator.key.position, end) >= 0) { break; } + let capture = iterator.value; + let { name } = capture; + if (name === 'fold') { + let range = this.resolveRangeForSimpleFold(capture); + if (this.isValidFold(range)) { return range; } + } else if (name === 'fold.start') { + let range = this.resolveRangeForDividedFold(capture); + if (this.isValidFold(range)) { return range; } } + iterator.next(); } - // `@indent` and `@dedent` can increase the next line's indent level by one - // at most, and can't decrease the next line's indent level at all on their - // own. - // - // Why? There are few coding patterns in the wild that would cause us to - // indent more than one level based on tokens found on the _previous_ line. - // And there are also few scenarios in which we'd want to dedent a certain - // line before we even know the content of that line. - // - // Hence we distill the results above into a net indentation level change - // of either 1 or 0, depending on whether we saw more `@indent`s than - // `@dedent`s. - // - // If there's a genuine need to dedent the current row based solely on the - // content of the comparison row, then `@dedent.next` can be used. - // - // And if a language needs to indent more than one level from one line to - // the next, then `@match` captures can be used to specify an exact level - // of indentation relative to another specific node. If a `@match` capture - // exists, we'll catch it in the dedent captures phase, and these - // heuristics will be ignored. - // - indentDelta = clamp(indentDelta, 0, 1); + return null; + } - // Process `@dedent.next` captures as a last step; they act as a strong - // hint about the next line's indentation. - indentDelta -= clamp(dedentNextDelta, 0, 1); + isValidFold(range) { + return range && range.end.row > range.start.row; + } - let dedentDelta = 0; - let line = this.buffer.lineForRow(row); - let rowStartingColumn = Math.max(line.search(/\S/), 0); + // Returns all valid fold ranges in this language layer. + // + // There are two rules about folds that we can't change: + // + // 1. A fold must collapse at least one line’s worth of content. + // 2. The UI for expanding and collapsing folds envisions that each line can + // manage a maximum of _one_ fold. + // + // Hence a fold range is “valid” when it + // * resolves to a range that spans more than one line; + // * starts on a line that hasn't already been promised to an earlier fold. + getAllFoldRanges() { + if (!this.layer.tree || !this.layer.queries.foldsQuery) { return []; } + let range = this.layer.getExtent(); + // We use a Tree-sitter query to find folds; then we arrange the folds in + // buffer order. The first valid fold we find on a given line is included + // in the list; any other folds on the line are ignored. + let iterator = this.getOrCreateBoundariesIterator( + this.layer.tree.rootNode, range.start, range.end); - if (!options.skipDedentCheck) { - scopeResolver.reset(); + let results = []; + let lastValidFoldRange = null; + while (iterator.key) { + let capture = iterator.value; + let { name } = capture; + let range; + if (name === 'fold') { + range = this.resolveRangeForSimpleFold(capture); + } else if (name === 'fold.start') { + range = this.resolveRangeForDividedFold(capture); + } + if (this.isValidFold(range)) { + // Recognize only the first fold for each row. + if (lastValidFoldRange?.start?.row !== range.start.row) { + results.push(range); + lastValidFoldRange = range; + } + } + iterator.next(); + } + + return results; + } + + // Invalidates the fold resolver's cached boundary data in response to a + // change in the document. + reset() { + this.boundaries = null; + this.boundariesRange = null; + } - // The controlling layer on the previous line gets to decide what our - // starting indent is on the current line. But it might not extend to the - // current line, so we should determine which layer is in charge of the - // second phase. + canReuseBoundaries(start, end) { + if (!this.boundariesRange) { return false; } + return this.boundariesRange.containsRange( + new Range(start, end) + ); + } + + prefillFoldCache(range) { + if (!this.layer.tree || !this.layer.queries.foldsQuery) { return; } + this.getOrCreateBoundariesIterator( + this.layer.tree.rootNode, + range.start, + range.end + ); + } + + getOrCreateBoundariesIterator(rootNode, start, end) { + if (!this.layer.tree || !this.layer.queries.foldsQuery) { return null; } + if (this.canReuseBoundaries(start, end)) { + let result = this.boundaries.ge(start); + return result; + } + + let scopeResolver = this.layer.scopeResolver; + scopeResolver.reset(); + + // Instead of keying off of a plain buffer position, this tree also + // considers whether the boundary is a fold start or a fold end. If one + // boundary ends at the same point that another one starts, the ending + // boundary will be visited first. + let boundaries = createTree(compareBoundaries); + let captures = this.layer.queries.foldsQuery.captures( + rootNode, + { startPosition: start, endPosition: end } + ); + + for (let capture of captures) { + // NOTE: Currently, the first fold to match for a given starting position + // is the only one considered. That's because we use a version of a + // red-black tree in which we silently ignore any attempts to add a key + // that is equivalent in value to that of a previously added key. // - // The comparison point we use is that of the first character on the - // line. If we start earlier than that, we might not pick up on the - // presence of an injection layer. - let rowStart = new Point(row, rowStartingColumn); - let dedentControllingLayer = this.controllingLayerAtPoint( - rowStart, - (layer) => { - if (!layer.queries.indentsQuery) return false; - // We're inverting the logic from above: now we want to allow layers - // that _begin_ at the cursor and exclude layers that _end_ at the - // cursor. Because we'll be analyzing content that comes _after_ the - // cursor to understand whether to dedent! - // - // So first we test for containment exclusive of endpoints… - if (layer.containsPoint(rowStart, true)) { - return true; - } + // Attempts to use `capture.final` and `capture.shy` won't harm anything, + // but they'll be redundant. Other types of custom predicates, however, + // should work just fine. + let result = scopeResolver.store(capture); + if (!result) { continue; } - // …but we'll still accept layers that have a content range which - // _starts_ at the cursor position. - return layer.getCurrentRanges()?.some(r => { - return r.start.compare(rowStart) === 0; - }); - } - ); + // Some folds are unusual enough that they can flip from valid to + // invalid, or vice versa, based on edits to rows other than their + // starting row. We need to keep track of these nodes so that we can + // invalidate the fold cache properly when edits happen inside of them. + if (scopeResolver.shouldInvalidateFoldOnChange(capture)) { + this.layer.foldNodesToInvalidateOnChange.add(capture.node.id); + } - if (dedentControllingLayer && dedentControllingLayer !== controllingLayer) { - // If this layer is different from the one we used above, then we - // should run this layer's indents query against its own tree. (If _no_ - // layers qualify at this position, we won't hit this code path, so - // we'll reluctantly still use the original layer and tree.) - indentsQuery = dedentControllingLayer.queries.indentsQuery; - indentTree = dedentControllingLayer.getOrParseTree(); + if (capture.node.startPosition.row < start.row) { + // This fold starts before the range we're interested in. We needed to + // run these nodes through the scope resolver for various reasons, but + // they're not relevant to our iterator. + continue; } + if (capture.name === 'fold') { + boundaries = boundaries.insert({ + position: capture.node.startPosition, + boundary: 'start' + }, capture); + } else if (capture.name.startsWith('fold.')) { + let key = this.keyForDividedFold(capture); + boundaries = boundaries.insert(key, capture); + } + } - // The second phase covers any captures on the current line that can - // cause the current line to be indented or dedented. - let dedentCaptures = indentsQuery.captures( - indentTree.rootNode, - { row: row, column: 0 }, - { row: row + 1, column: 0 } - ); + scopeResolver.reset(); - let currentRowText = this.buffer.lineForRow(row); - currentRowText = currentRowText.trim(); - positionSet.clear(); + this.boundaries = boundaries; + this.boundariesRange = new Range(start, end); - for (let capture of dedentCaptures) { - let { name, node, setProperties: props = {} } = capture; - let { text } = node; + return boundaries.ge(start); + } - // Ignore “phantom” nodes that aren't present in the buffer. - if (text === '' && !props['indent.allowEmpty']) { continue; } + // Given a `@fold.start` capture, queries the rest of the layer's extent to + // find a matching `@fold.end`. + resolveRangeForDividedFold(capture) { + let { name } = capture; + let key = this.keyForDividedFold(capture); + if (name !== 'fold.start') { return null; } - // Ignore anything that isn't actually on the row. - if (node.endPosition.row < row) { continue; } - if (node.startPosition.row > row) { continue; } + let extent = this.layer.getExtent(); - // Ignore anything that fails a scope test. - if (!scopeResolver.store(capture)) { continue; } + let iterator = this.getOrCreateBoundariesIterator( + this.layer.tree.rootNode, + key.position, + extent.end + ); - // Imagine you've got: - // - // { ^foo, bar } = something - // - // and the caret represents the cursor. Pressing Enter will move - // everything after the cursor to a new line and _should_ indent the - // line, even though there's a closing brace on the new line that would - // otherwise mark a dedent. - // - // Thus we don't want to honor a `@dedent` or `@match` capture unless - // it's the first non-whitespace content in the line. We'll use similar - // logic for `suggestedIndentForEditedBufferRow`. - // - // If a capture is confident it knows what it's doing, it can opt out - // of this behavior with `(#set! indent.force true)`. - if (!props['indent.force'] && !currentRowText.startsWith(text)) { continue; } + let depth = 0; + let matchedEndCapture = null; + + while (iterator.key && comparePoints(iterator.key.position, extent.end) <= 0) { + let { name, node } = iterator.value; + let isSelf = node.id === capture.node.id; + if (name === 'fold.end' && !isSelf) { + if (depth === 0) { + matchedEndCapture = iterator.value; + break; + } else { + depth--; + } + } else if (name === 'fold.start' && !isSelf) { + // A later `fold.start` has occurred, so the next `fold.end` will pair + // with it, not with ours. + depth++; + } + iterator.next(); + } + + // There's no guarantee that a matching `@fold.end` will even appear, so if + // it doesn't, then this row does not contain a valid fold. + if (!matchedEndCapture) { return null; } + + return new Range( + this.resolvePositionForDividedFold(capture), + this.resolvePositionForDividedFold(matchedEndCapture) + ); + } + + keyForDividedFold(capture) { + let { name, node } = capture; + if (name === 'fold.start') { + // Eventually we'll alter this position to occur at the end of the given + // row, but we keep the original value around for a while because we want + // to honor whichever fold technically happens “earliest” on a given row. + return { position: node.startPosition, boundary: 'start' }; + } else if (name === 'fold.end') { + return { position: node.startPosition, boundary: 'end' }; + } else { + return null; + } + } + + // Returns `true` if there is no non-whitespace content on this position's + // row before this position's column. + positionIsNotPrecededByTextOnLine(position) { + let textForRow = this.buffer.lineForRow(position.row) + let precedingText = textForRow.substring(0, position.column) + return !(/\S/.test(precedingText)) + } + + resolvePositionForDividedFold(capture) { + let { name, node } = capture; + if (name === 'fold.start') { + return new Point(node.startPosition.row, Infinity); + } else if (name === 'fold.end') { + let end = node.startPosition; + if (end.column === 0 || this.positionIsNotPrecededByTextOnLine(end)) { + // If the fold ends at the start of the line, adjust it so that it + // actually ends at the end of the previous line. This behavior is + // implied in the existing specs. + return new Point(end.row - 1, Infinity); + } else { + return new Point.fromObject(end, true); + } + } else { + return null; + } + } + + normalizeFoldProperty(prop) { + if (prop.startsWith('fold.')) { + prop = prop.replace(/^fold./, ''); + } + return prop; + } + + capturePropertyIsFoldAdjustment(prop) { + prop = this.normalizeFoldProperty(prop); + return prop in FoldResolver.ADJUSTMENTS; + } + + applyFoldAdjustment(prop, ...args) { + prop = this.normalizeFoldProperty(prop); + return FoldResolver.ADJUSTMENTS[prop](...args); + } - // The '@match' capture short-circuits a lot of this logic by pointing - // us to a different node and asking us to match the indentation of - // whatever row that node starts on. - if (name === 'match') { - let matchIndentLevel = this.resolveIndentMatchCapture( - capture, row, tabLength, options.indentationLevels); - if (typeof matchIndentLevel === 'number') { - scopeResolver.reset(); - return Math.max(matchIndentLevel - existingIndent, 0); - } - } else if (name === 'none') { - scopeResolver.reset(); - return 0; - } + resolveRangeForSimpleFold(capture) { + let { node, setProperties: props } = capture; + if (node.type === 'ERROR') { return null; } + let start = new Point(node.startPosition.row, Infinity); + let end = node.endPosition; - // Only `@dedent` or `@match` captures can change this line's - // indentation. We handled `@match` above, so we'll filter out all non- - // `@dedent`s now. - if (name !== 'dedent') { continue; } + let defaultOptions = { 'fold.endAt': 'lastChild.startPosition' }; + let options = { ...defaultOptions, ...props }; - // Only consider a given range once, even if it's marked with multiple - // captures. - let key = `${node.startIndex}/${node.endIndex}`; - if (positionSet.has(key)) { continue; } - positionSet.add(key); - dedentDelta--; + try { + for (let key in options) { + if (!this.capturePropertyIsFoldAdjustment(key)) { continue; } + let value = options[key]; + end = this.applyFoldAdjustment(key, end, node, value, props, this.layer); } + if (!end) { return null; } - // `@indent`/`@dedent` captures, no matter how many there are, can - // dedent the current line by one level at most. To indent more than - // that, one must use a `@match` capture. - dedentDelta = clamp(dedentDelta, -1, 0); + end = Point.fromObject(end, true); + end = this.buffer.clipPosition(end); + + if (end.row <= start.row) { return null; } + return new Range(start, end); + } catch (error) { + // If any of our assumptions are violated, fall back to an end point that we know can't fail: the end of the captured node itself. + console.warn("Error resolving fold range:"); + console.warn(error.message); + return new Range(start, node.range.end); } + } +} - scopeResolver.reset(); - let finalIndent = comparisonRowIndent + indentDelta + dedentDelta + existingIndent; - // console.log('score:', comparisonRowIndent, '+', indentDelta, '-', ((dedentDelta < 0) ? -dedentDelta : dedentDelta), '=', finalIndent); +FoldResolver.ADJUSTMENTS = { + // Use a node position descriptor to describe where the fold should end. + // Overrides the default descriptor of `lastChild.startPosition`. + endAt(end, node, value) { + end = resolveNodePosition(node, value); + return end; + }, - return Math.max(finalIndent - existingIndent, 0); - } + // Adjust the end point by a fixed number of characters in either direction. + // Will cross rows if necessary. + offsetEnd(end, _node, value, _props, layer) { + let { languageMode } = layer; + value = Number(value); + if (isNaN(value)) { return end; } + return languageMode.adjustPositionByOffset(end, value); + }, - // Given a range of buffer rows, retrieves the suggested indent for each one - // while re-using the same tree. Prevents a tree re-parse after each - // individual line adjustment when auto-indenting. - // - // * startRow - The row {Number} to start at - // * endRow - The row {Number} to end at - // - // Returns a {Map} whose keys are rows and whose values are desired - // indentation levels. May not return the entire range of requested rows, in - // which case the caller should auto-indent the remaining rows through - // another means. May also return {null} to signify that no auto-indent - // should be attempted at all for the given range. - suggestedIndentForBufferRows(startRow, endRow, tabLength, options = {}) { - let root = this.rootLanguageLayer; - if (!root || !root.tree) { - let results = new Map(); - for (let row = startRow; row <= endRow; row++) { - results.set(row, null); - } - return results; - } + // Adjust the column of the fold's end point. Use `0` to end the fold at the + // start of the line. + adjustEndColumn(end, _node, value, _props, layer) { + let column = Number(value); + if (isNaN(column)) { return end; } + let newEnd = Point.fromObject({ column, row: end.row }); + return layer.buffer.clipPosition(newEnd); + }, - let results = new Map(); - let comparisonRow = null; - let comparisonRowIndent = null; + // Adjust the end point to be immediately before the current line begins. + // Useful if the end line also contains the start of a fold and thus should + // stay on a separate screen line. + adjustToEndOfPreviousRow(end) { + return new Point(end.row - 1, Infinity); + } +}; - let { isPastedText = false } = options; - let indentDelta; +class NullLanguageModeHighlightIterator { + seek() { + return []; + } + compare() { + return 1; + } + moveToSuccessor() {} + getPosition() { + return Point.INFINITY; + } + getOpenScopeIds() { + return []; + } + getCloseScopeIds() { + return []; + } +} - for (let row = startRow; row <= endRow; row++) { - // If this row were being indented by `suggestedIndentForBufferRow`, it'd - // look at the end of the previous row to find the controlling layer, - // because we start at the previous row to find the suggested indent for - // the current row. - let controllingLayer = this.controllingLayerAtPoint( - this.buffer.clipPosition(new Point(row - 1, Infinity)), - // This query isn't as precise as the one we end up making later, but - // that's OK. This is just a first pass. - (layer) => !!layer.queries.indentsQuery && !!layer.tree - ); - if (isPastedText) { - // In this mode, we're not trying to auto-indent every line; instead, - // we're trying to auto-indent the _first_ line of a region of text - // that's just been pasted, while trying to preserve the relative - // levels of indentation within the pasted region. So if the - // auto-indent of the first line increases its indent by one level, - // all other lines should also be increased by one level — without even - // consulting their own suggested indent levels. - if (row === startRow) { - // The only time we consult the indents query is for the first row, - // so we're not going to insist that the _entire range_ fall under - // the control of a layer with an indents query — just the row we - // need. - if (!controllingLayer) { return null; } - let tree = controllingLayer.getOrParseTree(); +class NullLayerHighlightIterator { + seek() { + return [false, new OpenScopeMap]; + } + compare() { + return 1; + } + moveToSuccessor() {} + getPosition() { + return Point.INFINITY; + } + getOpenScopeIds() { + return []; + } + getCloseScopeIds() { + return []; + } +} - let firstLineCurrentIndent = this.indentLevelForLine( - this.buffer.lineForRow(row), tabLength); +let lastIteratorId = 0; - let firstLineIdealIndent = this.suggestedIndentForBufferRow( - row, - tabLength, - { - ...options, - controllingLayer, - tree - } - ); +// An iterator for marking boundaries in the buffer to apply syntax +// highlighting. +// +// Manages a collection of `LayerHighlightIterators`, which are the classes +// doing the real work of marking boundaries. `HighlightIterator` is in charge +// of understanding, at any given point, which of the iterators needs to be +// advanced next. +class HighlightIterator { + constructor(languageMode) { + this.id = lastIteratorId++; + this.languageMode = languageMode; + this.iterators = null; + } - if (firstLineIdealIndent == null) { - // If we decline to suggest an indent level for the first line, - // then there's no change to be made here. Keep the whole region - // the way it is. - return null; - } else { - indentDelta = firstLineIdealIndent - firstLineCurrentIndent; - if (indentDelta === 0) { - // If the first row doesn't have to be adjusted, neither do any - // others. - return null; - } - results.set(row, firstLineIdealIndent); - } - continue; - } + inspect() { + return `[HighlightIterator id=${this.id} iterators=${this.iterators?.length ?? 0}]`; + } - // All rows other than the first are easy — just apply the delta. - let actualIndent = this.indentLevelForLine( - this.buffer.lineForRow(row), tabLength); + seek(start, endRow) { + if (!(start instanceof Point)) { + start = Point.fromObject(start, true); + } + let { buffer, rootLanguageLayer } = this.languageMode; + if (!rootLanguageLayer) { return []; } - results.set(row, actualIndent + indentDelta); - continue; - } + let end = { + row: endRow, + column: buffer.lineLengthForRow(endRow) + }; - // For line X to know its appropriate indentation level, it needs row X-1, - // if it exists, to be indented properly. That's why `TextEditor` wants to - // indent each line atomically. Instead, we'll determine the right level - // for the first row, then supply the result for the previous row when we - // call `suggestedIndentForBufferRow` for the _next_ row, and so on, so - // that `suggestedIndentForBufferRow` doesn't try to look up the comparison - // row itself and find out we haven't actually fixed any of the previous - // rows' indentations yet. - let indent; - if (controllingLayer) { - let tree = controllingLayer.getOrParseTree(); - let rowOptions = { - ...options, - tree, - comparisonRow: comparisonRow ?? undefined, - comparisonRowIndent: comparisonRowIndent ?? undefined, - indentationLevels: results - }; - indent = this.suggestedIndentForBufferRow(row, tabLength, rowOptions); - if (indent === null) { - // We could not retrieve the correct indentation level for this row - // without re-parsing the tree. We should give up and return what we - // have so that `TextEditor` can finish the job through a less - // efficient means. - return results; - } - } else { - // We could not retrieve the correct indentation level for this row - // because it isn't governed by any layer that has an indents query. - return results; - } - results.set(row, indent); - comparisonRow = row; - comparisonRowIndent = indent; + this.end = end; + this.iterators = []; + + if (Math.max(start.column, end.column) > LINE_LENGTH_LIMIT_FOR_HIGHLIGHTING) { + return []; } - return results; - } + let injectionMarkers = this.languageMode.injectionsMarkerLayer.findMarkers( + { + intersectsRange: new Range( + start, + new Point(endRow + 1, 0) + ) + } + ); - // Get the suggested indentation level for a line in the buffer on which the - // user is currently typing. This may return a different result from - // {::suggestedIndentForBufferRow} in order to avoid unexpected changes in - // indentation. It may also return undefined if no change should be made. - // - // * row - The row {Number} - // - // Returns a {Number}. - suggestedIndentForEditedBufferRow(row, tabLength, options = {}) { - const line = this.buffer.lineForRow(row); - const currentRowIndent = this.indentLevelForLine(line, tabLength); + injectionMarkers.sort((a, b) => { + // Shallower injections get visited first. + return a.languageLayer.depth - b.languageLayer.depth; + }); - // If the row is not indented at all, we have nothing to do, because we can - // only dedent a line at this phase. - if (currentRowIndent === 0) { return; } + const iterator = rootLanguageLayer.buildHighlightIterator(); - // If we're on the first row, we have no preceding line to compare - // ourselves to. We should do nothing. - if (row === 0) { return; } + // We need to know which open scopes were contributed by which language + // layer so that we know which ones to “cover” if we see an injection with + // the `coverShallowerScopes` option. + // + // What's worse is that we need to know _where_ each of those open scopes + // would've been added, because that open scope shouldn't truly be added + // _if_ it would've been suppressed by a deeper layer. + let openScopesByLayer = new Map(); - // By the time this function runs, we probably know enough to be sure of - // which layer controls the beginning of this row, even if we don't know - // which one owns the position at the cursor. + // The contract of `LayerHighlightIterator#seek` is different from the + // contract of `HighlightIterator#seek`. Instead of an array of open + // scopes, we have it return an two-item array. // - // Use the position of the first text on the line as the reference point. - let rowStartingColumn = Math.max(line.search(/\S/), 0); - let controllingLayer = this.controllingLayerAtPoint( - new Point(row, rowStartingColumn), - (layer) => !!layer.queries.indentsQuery - ); + // The first item is a boolean that indicates whether the iterator needs to + // mark anything _within_ the range that will be highlighted. If not, it + // doesn't need to go into the list of iterators. + // + // The second item is an `OpenScopeMap` (rather than just an array of + // already open scopes) that retains information about where each open + // scope was opened, because we'll need that information in a moment. + // + let [result, openScopes] = iterator.seek(start, endRow); - if (!controllingLayer) { return undefined; } + if (rootLanguageLayer?.tree?.rootNode.hasChanges) { + // The tree is dirty. We should keep going — if we stop now, then the + // user will see a flash of unhighlighted text over this whole range. But + // we should also schedule a re-highlight at the end of the transaction, + // once the tree will be clean again. + let range = new Range(start, iterator._getEndPosition(endRow)); + this.languageMode.atTransactionEnd().then(() => { + this.languageMode.emitRangeUpdate(range); + }); + } - let { queries: { indentsQuery }, scopeResolver } = controllingLayer; - if (!indentsQuery) { return undefined; } + // An iterator can contribute to the list of already open scopes even if it + // has no boundaries to mark within the range of this highlighting job. + openScopesByLayer.set(iterator, openScopes); - // TODO: We use `ScopeResolver` here so that we can use its tests. Maybe we - // need a way to share those tests across different kinds of capture - // resolvers. - scopeResolver.reset(); + if (result) { + this.iterators.push(iterator); + } - // Ideally, we're running when the tree is clean, but if not, we must - // re-parse the tree in order to make an accurate indents query. - let indentTree = options.tree; - if (!indentTree) { - if (!controllingLayer.treeIsDirty || options.forceTreeParse || !this.useAsyncIndent || !this.useAsyncParsing) { - indentTree = controllingLayer.getOrParseTree(); - } else { - return this.atTransactionEnd().then(({ changeCount }) => { - if (changeCount > 1) { - // Unlike `suggestedIndentForBufferRow`, we should not return - // `undefined` here and implicitly tell `TextEditor` to handle the - // auto-indent itself. If there were several changes in this - // transaction, we missed our chance to dedent this row, and should - // return `null` to signal that `TextEditor` should do nothing - // about it. - return null; - } - let result = this.suggestedIndentForEditedBufferRow(row, tabLength, { - ...options, - tree: controllingLayer.tree - }); - if (currentRowIndent === result) { - // Return `null` here so that `TextEditor` realizes that no work - // needs to be done. - return null; + // The logic that we use for `coverShallowerScopes` needs to be respected + // here. As we build these iterators, if we find that any of them cover + // their parent iterator at this position, we need to revisit the list of + // open scopes, removing any that aren't their layer's base language scope. + for (const marker of injectionMarkers) { + const iterator = marker.languageLayer.buildHighlightIterator(); + let [result, openScopes] = iterator.seek(start, endRow); + + // Just as with the root layer, any injection layer may need to add to + // the list of open scopes whether or not they need to mark anything + // within this range. + if (result) { this.iterators.push(iterator); } + + if (iterator?.languageLayer?.injectionPoint?.coverShallowerScopes) { + // The procedure we follow for covering scopes in the _middle_ of a + // highlighting task needs to be emulated when deciding which scopes + // are already open at the _start_ of the task. This layer wants to + // cover shallower scopes, so we need to re-assess our list of already + // open scopes to see if any of them _would've_ been covered at the + // point when they were opened. + let ranges = iterator.languageLayer.getCurrentRanges(); + for (let [earlierIterator, earlierOpenScopes] of openScopesByLayer) { + // It's possible, though uncommon, for injections to overlap, because + // there's no mechanism that prevents it. Since we sorted the layers + // by depth earlier, this iterator won't have a lower depth than the + // previous one. But it may have _the same_ depth, so we need to + // account for that. + if (earlierIterator.depth >= iterator.depth) { continue; } + + // For each of the open scopes that an earlier iterator gave us, we + // need to look at the point at which that scope would've been added. + // If that point falls within the purview of our newer iterator, then + // we should suppress that scope unless it's the layer's base + // language scope. + let languageScopeId = earlierIterator.languageLayer.languageScopeId; + for (let [point, scopes] of earlierOpenScopes) { + let pointIsCoveredByNewIterator = ranges.some(r => r.containsPoint(point)); + if (!pointIsCoveredByNewIterator) { continue; } + earlierOpenScopes.set(point, scopes.filter(id => id === languageScopeId)); } - return result; - }); + } } + openScopesByLayer.set(iterator, openScopes); } - if (!indentTree) { - console.error(`No indent tree!`, controllingLayer.inspect()); - return undefined; - } + // Sort the iterators so that the last one in the array is the earliest + // in the document, and represents the current position. + this.iterators.sort((a, b) => b.compare(a)); - const indents = indentsQuery.captures( - indentTree.rootNode, - { row: row, column: 0 }, - { row: row + 1, column: 0 } - ); + this.detectCoveredScope(); - let lineText = this.buffer.lineForRow(row).trim(); + // Our nightmare is almost over, but one chore remains. The ordering of + // already open scopes should be consistent; scopes added earlier in the + // buffer should appear in the list before scopes added later. This ensures + // that, e.g., `scopeDescriptorForPosition` returns scopes in the proper + // hierarchy. + let sortedOpenScopes = []; - // This is the indent level that is suggested from context — the level we'd - // have if this row were completely blank. We won't alter the indent level - // of the current row — even if it's “wrong” — unless typing triggers a - // dedent. But once a dedent is triggered, we should dedent one level from - // this value, not from the current row indent. - // - // If more than one level of dedent is needed, a `@match` capture must be - // used so that indent level can be expressed in absolute terms. - const originalRowIndent = this.suggestedIndentForBufferRow(row, tabLength, { - skipBlankLines: true, - skipDedentCheck: true, - tree: indentTree - }); + // First we'll gather all the point/scope-list pairs into a flat list… + let unsortedScopeBundles = []; + for (let [iterator, layerOpenScopeMap] of openScopesByLayer) { + for (let [point, scopes] of layerOpenScopeMap) { + unsortedScopeBundles.push({ point, scopes, iterator }); + } + } - let seenDedent = false; - for (let indent of indents) { - let { node, setProperties: props = {} } = indent; - // Ignore captures that aren't on this row. - if (node.startPosition.row !== row) { continue; } - // Ignore captures that fail their scope tests. - if (!scopeResolver.store(indent)) { continue; } + // …then sort them by buffer position, with shallower layers first in case + // of ties. + unsortedScopeBundles.sort((a, b) => ( + a.point.compare(b.point) || a.iterator.depth - b.iterator.depth + )); - // For all captures — even `@match` captures — we get one bite at the - // apple, and it's when the text of the capture is the only - // non-whitespace text on the line. - // - // Otherwise, this capture will assert itself after every keystroke, and - // the user has no way to opt out of the correction. - // - // If the capture is confident it knows what it's doing, and is using - // some other mechanism to ensure the adjustment will happen exactly - // once, it can bypass this behavior with `(#set! indent.force true)`. - // - if (!props['indent.force'] && node.text !== lineText) { continue; } + // Now we can flatten all the scopes themselves, preserving order. + for (let { scopes } of unsortedScopeBundles) { + sortedOpenScopes.push(...scopes); + } - // `@match` is authoritative; honor the first one we see and ignore other - // captures. - if (indent.name === 'match') { - let matchIndentLevel = this.resolveIndentMatchCapture(indent, row, tabLength); - if (typeof matchIndentLevel === 'number') { - scopeResolver.reset(); - return matchIndentLevel; - } - } else if (indent.name === 'none') { - scopeResolver.reset(); - return 0; + return sortedOpenScopes; + } + + moveToSuccessor() { + // `this.iterators` is _always_ sorted from farthest position to nearest + // position, so the last item in the collection is always the next one to + // act. + let leader = last(this.iterators); + if (!leader) { return; } + + if (leader.moveToSuccessor()) { + // It was able to move to a successor, so now we have to "file" it into + // the right place in `this.iterators` so that the sorting is correct. + const leaderIndex = this.iterators.length - 1; + let i = leaderIndex; + while (i > 0 && this.iterators[i - 1].compare(leader) < 0) { + i--; } + if (i < leaderIndex) { + this.iterators.splice(i, 0, this.iterators.pop()); + } + } else { + // It was not able to move to a successor, so it must be done. Remove it + // from the collection. + this.iterators.pop(); + } - if (indent.name !== 'dedent') { continue; } + this.detectCoveredScope(); + } - // Even after we've seen a `@dedent`, we allow the loop to continue, - // because we'd prefer a `@match` capture over this `@dedent` capture - // even if it happened to come later in the loop. - seenDedent = true; + getPosition() { + let iterator = last(this.iterators || []); + if (iterator) { + // this.logPosition(); + return iterator.getPosition(); + } else { + return Point.INFINITY; } + } - scopeResolver.reset(); - - if (seenDedent) { - return Math.max(0, originalRowIndent - 1); + getCloseScopeIds() { + let iterator = last(this.iterators); + // if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'close') { + // console.log( + // iterator.name, + // iterator.depth, + // 'would close', + // iterator._inspectScopes( + // iterator.getCloseScopeIds() + // ), + // 'at', + // iterator.getPosition().toString(), + // 'but scope is covered!' + // ); + // } else { + // console.log( + // iterator.name, + // iterator.depth, + // 'CLOSING', + // iterator.getPosition().toString(), + // iterator._inspectScopes( + // iterator.getCloseScopeIds() + // ) + // ); + // } + if (iterator) { + // If this iterator is covered completely, or if it's covered in a + // position that prevents us from closing scopes… + if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'close') { + // …then the only closing scope we're allowed to apply is one that ends + // the base scope of an injection range. + return iterator.getCloseScopeIds().filter(id => { + return iterator.languageLayer.languageScopeId === id; + }); + } else { + return iterator.getCloseScopeIds(); + } } - - return currentRowIndent; + return []; } - // Get the suggested indentation level for a given line of text, if it were - // inserted at the given row in the buffer. - // - // * bufferRow - A {Number} indicating the buffer row - // - // Returns a {Number}. - suggestedIndentForLineAtBufferRow(row, line, tabLength) { - // We can't answer this question accurately for text that isn't yet in the - // tree, so instead we'll just note that this request was made and try to - // correct indentation when the transaction is over. - this.autoIndentRequests++; - return this.suggestedIndentForBufferRow(row, tabLength); + getOpenScopeIds() { + let iterator = last(this.iterators); + // let ids = iterator.getOpenScopeIds(); + // if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'open') { + // console.log( + // iterator.name, + // iterator.depth, + // 'would open', + // iterator._inspectScopes( + // iterator.getOpenScopeIds() + // ), + // 'at', + // iterator.getPosition().toString(), + // 'but scope is covered!' + // ); + // } else { + // console.log( + // iterator.name, + // iterator.depth, + // 'OPENING', + // iterator.getPosition().toString(), + // iterator._inspectScopes( + // iterator.getOpenScopeIds() + // ) + // ); + // } + if (iterator) { + // If this iterator is covered completely, or if it's covered in a + // position that prevents us from opening scopes… + if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'open') { + // …then the only opening scope we're allowed to apply is one that ends + // the base scope of an injection range. + return iterator.getOpenScopeIds().filter(id => { + return iterator.languageLayer.languageScopeId === id; + }); + } else { + return iterator.getOpenScopeIds(); + } + } + return []; } - // Private - - // Given a `@match` capture, attempts to resolve it to an absolute - // indentation level. - resolveIndentMatchCapture(capture, currentRow, tabLength, indentationLevels = null) { - let { node, setProperties: props = {} } = capture; - - // A `@match` capture must specify - // - // (#set! indent.matchIndentOf foo) - // - // where "foo" is a node descriptor. It may optionally specify - // - // (#set! indent.offsetIndent X) - // - // where "X" is a number, positive or negative. - // - if (!props['indent.matchIndentOf']) { return undefined; } - let offsetIndent = props['indent.offsetIndent'] ?? "0"; - offsetIndent = Number(offsetIndent); - if (isNaN(offsetIndent)) { offsetIndent = 0; } - - // Follow a node descriptor to a target node. - let targetPosition = resolveNodePosition(node, props['indent.matchIndentOf']); - - // That node must start on a row earlier than ours. - let targetRow = targetPosition?.row; - if (typeof targetRow !== 'number' || targetRow >= currentRow) { - return undefined; - } + // Detect whether the iterator that's about to act has its scopes covered by + // a different iterator asserting a `coverShallowerScopes` option. + detectCoveredScope() { + const layerCount = this.iterators.length; + if (layerCount > 1) { + const rest = [...this.iterators]; + const leader = rest.pop(); + let covers = false; + for (let it of rest) { + let iteratorCovers = it.coversIteratorAtPosition(leader, leader.getPosition()); + if (iteratorCovers !== false) { + covers = iteratorCovers; + break; + } + } - let baseIndent; - if (indentationLevels) { - // If we were given this table of indentation levels, it means we're in a - // “batch” mode where we're trying to cut down on the number of tree - // re-parses. In this scenario, if the row we want is represented in the - // table, we should use the level indicated by the table. If it isn't, - // that's a sign that the line is outside of the range being - // batch-indented, which would mean that it's safe to look up its level - // directly. - baseIndent = indentationLevels.get(targetRow); - } - if (!baseIndent) { - baseIndent = this.indentLevelForLine( - this.buffer.lineForRow(targetRow), tabLength); + if (covers) { + this.currentIteratorIsCovered = covers; + return; + } } - // An offset can optionally be applied to the target. - let result = baseIndent + offsetIndent; - - return Math.max(result, 0); + this.currentIteratorIsCovered = false; } - getAllInjectionLayers() { - let markers = this.injectionsMarkerLayer.getMarkers(); - return markers.map(m => m.languageLayer); + logPosition() { + let iterator = last(this.iterators); + iterator?.logPosition(); } +} - getAllLanguageLayers(where = null) { - let layers = [ - this.rootLanguageLayer, - ...this.getAllInjectionLayers() - ]; - if (!where) return layers; - - let results = []; - for (let layer of layers) { - if (!where(layer)) continue; - results.push(layer); - } +const EMPTY_SCOPES = Object.freeze([]); - return results; - } +// Iterates through everything that a `LanguageLayer` is responsible for, +// marking boundaries for scope insertion. +class LayerHighlightIterator { + constructor(languageLayer) { + this.languageLayer = languageLayer; + this.name = languageLayer.grammar.scopeName; + this.depth = languageLayer.depth; - // Given a {Point}, returns all injection {LanguageLayer}s whose extent - // includes that point. Does not include the root language layer. - // - // A {LanguageLayer} can have multiple content ranges. Its “extent” is a - // single contiguous {Range} that includes all of its content ranges. To - // return only layers with a content range that spans the given point, pass - // `{ exact: true }` as the second argument. - // - // * point - A {Point} representing a buffer position. - // * options - An {Object} containing these keys: - // * exact - {Boolean} that checks content ranges instead of extent. - injectionLayersAtPoint(point, { exact = false } = {}) { - let injectionMarkers = this.injectionsMarkerLayer.findMarkers({ - containsPosition: point - }); + let { injectionPoint } = this.languageLayer; - injectionMarkers.sort((a, b) => { - return a.getRange().compare(b.getRange()) || - b.depth - a.depth; - }); + this.coverShallowerScopes = injectionPoint?.coverShallowerScopes ?? false + } - let results = injectionMarkers.map(m => m.languageLayer); + // If this isn't the root language layer, we need to make sure this iterator + // doesn't try to go past its marker boundary. + _getEndPosition(endRow) { + let { marker } = this.languageLayer; + let { buffer } = this.languageLayer.languageMode; + let naiveEndPoint = new Point( + endRow, + buffer.lineLengthForRow(endRow) + ); - if (exact) { - results = results.filter(l => l.containsPoint(point)); + if (marker) { + return Point.min(marker.getRange().end, naiveEndPoint) + } else { + return buffer.clipPosition(naiveEndPoint); } - return results; } - // Given a {Point}, returns all {LanguageLayer}s whose extent includes that - // point. + // TODO: This still doesn't make much sense, so I suppose it's good that + // we've now made it an opt-in feature. // - // A {LanguageLayer} can have multiple content ranges. Its “extent” is a - // single contiguous {Range} that includes all of its content ranges. To - // return only layers with a content range that spans the given point, pass - // `{ exact: true }` as the second argument. + // The main problem with this logic is that it runs the risk of covering only + // one half of a pair of boundaries. If a scope range from the root layer is + // coterminous with a scope range from an injection layer, that's easy to + // detect and handle; but what if the root layer's range starts at the same + // point but ends later? We'd prevent the root layer from opening the scope + // but not closing it. // - // * point - A {Point} representing a buffer position. - // * options - An {Object} containing these keys: - // * exact - {Boolean} that checks content ranges instead of extent. - languageLayersAtPoint(point, { exact = false } = {}) { - let injectionLayers = this.injectionLayersAtPoint(point, { exact }); - return [ - this.rootLanguageLayer, - ...injectionLayers - ]; - } - - // Returns the deepest language layer at a given point, or optionally the - // deepest layer to fulfill a criterion. + // I still don't fully understand the use cases for `detectCoveredScope`, + // though I assume there are at least a few. I am quite sure, however, that + // if we want an injection layer to veto a shallower layer's scope, it ought + // to happen in a way that either prevents _both_ boundaries or allows _both_ + // boundaries. I'm not sure how to pull that off at this point, though. // - // Will ignore any layer whose content ranges do not include the point, even if - // the point is within its extent. - controllingLayerAtPoint(point, where = FUNCTION_TRUE) { - let layers = this.languageLayersAtPoint(point, { exact: true }); - - // Deeper layers go first. - layers.sort((a, b) => b.depth - a.depth); - return layers.find(layer => where(layer)) ?? null; - } - - firstNonWhitespaceRange(row) { - return this.buffer.findInRangeSync( - /\S/, - new Range(new Point(row, 0), new Point(row, Infinity)) - ); - } - - // DEPRECATED - - tokenizedLineForRow(row) { - const lineText = this.buffer.lineForRow(row); - const tokens = []; - - const iterator = this.buildHighlightIterator(); - let start = { row, column: 0 }; + // https://github.com/atom/atom/pull/19556 has good discussion about the + // impetus for this feature. + coversIteratorAtPosition(iterator, position) { + // When does a layer prevent another layer from applying scopes? - const scopes = iterator.seek(start, row) || []; + // When the option is asserted… + if (!this.coverShallowerScopes) { return false; } - // eslint-disable-next-line no-constant-condition - while (true) { - const end = { ...iterator.getPosition() }; - if (end.row > row) { - end.row = row; - end.column = lineText.length; - } + // …and this iterator is deeper than the other… + if (iterator.depth >= this.depth) { return false; } - if (end.column > start.column) { - tokens.push( - new Token({ - value: lineText.substring(start.column, end.column), - scopes: scopes.map(s => this.scopeNameForScopeId(s)) - }) - ); - } + // …and one of this iterator's content ranges actually includes this + // position. (With caveats!) + let ranges = this.languageLayer.getCurrentRanges(); + if (ranges) { + // A given layer's content ranges aren't allowed to overlap each other. + // So only a single range from this list can possibly match. + let overlappingRange = ranges.find(range => range.containsPoint(position)) + if (!overlappingRange) return false; - if (end.column < lineText.length) { - const closeScopeCount = iterator.getCloseScopeIds().length; - for (let i = 0; i < closeScopeCount; i++) { - scopes.pop(); - } - scopes.push(...iterator.getOpenScopeIds()); - start = end; - iterator.moveToSuccessor(); + // If the current position is right in the middle of an injection's + // range, then it should cover all attempts to apply scopes. But what if + // we're on one of its edges? Since closing scopes act before opening + // scopes, + // + // * if this iterator _starts_ a range at position X, it doesn't get to + // prevent another iterator from _ending_ a scope at position X; + // * if this iterator _ends_ a range at position X, it doesn't get to + // prevent another iterator from _starting_ a scope at position X. + // + // So at a given position, `currentIteratorIsCovered` can be `true` (all + // scopes suppressed), `false` (none suppressed), `"close"` (only closing + // scopes suppressed), or `"open"` (only opening scopes suppressed). + if (overlappingRange.end.compare(position) === 0) { + // We're at the right edge of the injection range. We want to prevent + // iterators from closing scopes, but not from opening them. + return 'close'; + } else if (overlappingRange.start.compare(position) === 0) { + // We're at the left edge of the injection range. We want to prevent + // iterators from opening scopes, but not from closing them. + return 'open'; } else { - break; + return true; } } - - return new TokenizedLine({ - openScopes: [], - text: lineText, - tokens, - tags: [], - ruleStack: [], - lineEnding: this.buffer.lineEndingForRow(row), - tokenIterator: this.tokenIterator, - grammar: this.grammar - }); } - tokenForPosition(point) { - if (Array.isArray(point)) { - point = new Point(...point); - } - const scopes = this.scopeDescriptorForPosition(point).getScopesArray(); - let range = this.bufferRangeForScopeAtPosition( - last(scopes), - point - ); - return new Token({ - scopes, - value: this.buffer.getTextInRange(range) - }); - } -} + seek(start, endRow) { + let end = this._getEndPosition(endRow); + // let isDevMode = atom.inDevMode(); -// Responsible for deciding the ranges of folds on a given language layer. -// -// Understands two kinds of folds: -// -// * A “simple” fold is one with a capture name of `@fold` in a folds query. It -// can be described with only one capture. It starts at the end of the row -// that the captured node starts on, and ends at a configurable position -// controlled by the `fold.endAt` adjustment (which defaults to -// `lastChild.startPosition`). -// -// Simple folds should be used whenever you're able to predict the end of a -// fold range simply from holding a reference to its starting node. -// -// * A “divided” fold is one where the two ends of the fold must be described -// in two separate query captures. It starts at the end of the row of a node -// captured with the name of `@fold.start`, and it ends at the very next -// `@fold.end` that it encounters in the document. -// -// When determining the end of a fold that is marked with `@fold.start`, -// Pulsar will search the buffer for the next “balanced” occurrence of -// `@fold.end`. For instance, when trying to find a match for a `@fold.start` -// on row 9, Pulsar might encounter another `@fold.start` on row 10, -// and would then understand that the next `@fold.end` it sees will end -// _that_ fold and not the one we're looking for. If Pulsar _does not_ find a -// matching `@fold.end`, the given line will not be considered to be -// foldable. -// -// Because they can trigger a buffer-wide search, divided folds are -// not recommended to use unless they're truly needed. Use them only when the -// structure of the syntax tree doesn't allow you to determine the end of the -// fold without applying your own heuristic. -// -class FoldResolver { - constructor(buffer, layer) { - this.buffer = buffer; - this.layer = layer; + // let timeKey; + // if (isDevMode) { + // timeKey = `${this.name} getSyntaxBoundaries`; + // console.time(timeKey); + // } + let [boundaries, openScopes] = this.languageLayer.getSyntaxBoundaries( + start, end); - this.boundaries = null; - this.boundariesStartingPosition = null; - } + this.iterator = boundaries?.begin; + if (!this.iterator?.key) { + return [false, openScopes]; + } - // Retrieve the first valid fold range for this row in this language layer — - // that is, the first fold range that spans more than one row. - getFoldRangeForRow(row) { - if (!this.layer.tree || !this.layer.queries.foldsQuery) { return null; } - let start = Point.fromObject({ row, column: 0 }); - let end = Point.fromObject({ row: row + 1, column: 0 }); + this.start = Point.fromObject(start, true); + this.end = end; + // if (isDevMode) { console.timeEnd(timeKey); } + return [true, openScopes]; + } - let tree = this.layer.getOrParseTree({ force: false }); - // Search for folds that begin somewhere on the given row. - let iterator = this.getOrCreateBoundariesIterator(tree.rootNode, start, end); + isAtInjectionBoundary() { + let position = Point.fromObject(this.iterator.key.position); + return position.isEqual(this.start) || position.isEqual(this.end); + } - // More than one fold can match for a given row, so we'll stop as soon as - // we find the fold that starts earliest on the row. (The fold itself will - // be “resolved” in such a way that it doesn't begin until the end of the - // row, but we still consider the intrinsic range of the fold capture when - // deciding which one to honor.) - while (iterator.key) { - if (comparePoints(iterator.key.position, end) >= 0) { break; } - let capture = iterator.value; - let { name } = capture; - if (name === 'fold') { - let range = this.resolveRangeForSimpleFold(capture); - if (this.isValidFold(range)) { return range; } - } else if (name === 'fold.start') { - let range = this.resolveRangeForDividedFold(capture); - if (this.isValidFold(range)) { return range; } - } - iterator.next(); + _inspectScopes(ids) { + if (Array.isArray(ids)) { + return ids.map(id => this._inspectScopes(id)).join(', ') } - - return null; + return this.languageLayer.languageMode.scopeNameForScopeId(ids); } - isValidFold(range) { - return range && range.end.row > range.start.row; + getOpenScopeIds() { + let { key, value } = this.iterator; + return key.boundary === 'end' ? EMPTY_SCOPES : [...value.scopeIds]; } - // Returns all valid fold ranges in this language layer. - // - // There are two rules about folds that we can't change: - // - // 1. A fold must collapse at least one line’s worth of content. - // 2. The UI for expanding and collapsing folds envisions that each line can - // manage a maximum of _one_ fold. - // - // Hence a fold range is “valid” when it - // * resolves to a range that spans more than one line; - // * starts on a line that hasn't already been promised to an earlier fold. - getAllFoldRanges() { - if (!this.layer.tree || !this.layer.queries.foldsQuery) { return []; } - let range = this.layer.getExtent(); - // We use a Tree-sitter query to find folds; then we arrange the folds in - // buffer order. The first valid fold we find on a given line is included - // in the list; any other folds on the line are ignored. - let iterator = this.getOrCreateBoundariesIterator( - this.layer.tree.rootNode, range.start, range.end); + getCloseScopeIds() { + let { key, value } = this.iterator; + return key.boundary === 'start' ? EMPTY_SCOPES : [...value.scopeIds]; + } - let results = []; - let lastValidFoldRange = null; - while (iterator.key) { - let capture = iterator.value; - let { name } = capture; - let range; - if (name === 'fold') { - range = this.resolveRangeForSimpleFold(capture); - } else if (name === 'fold.start') { - range = this.resolveRangeForDividedFold(capture); - } - if (this.isValidFold(range)) { - // Recognize only the first fold for each row. - if (lastValidFoldRange?.start?.row !== range.start.row) { - results.push(range); - lastValidFoldRange = range; - } - } - iterator.next(); - } + opensScopes() { + return this.iterator?.key?.boundary === 'start'; + } - return results; + closesScopes() { + return this.iterator?.key?.boundary === 'end'; } - // Invalidates the fold resolver's cached boundary data in response to a - // change in the document. - reset() { - this.boundaries = null; - this.boundariesRange = null; + getPosition() { + return this.iterator?.key?.position ?? Point.INFINITY; } - canReuseBoundaries(start, end) { - if (!this.boundariesRange) { return false; } - return this.boundariesRange.containsRange( - new Range(start, end) + logPosition() { + let pos = this.getPosition(); + let { key, value } = this.iterator; + + let { languageMode } = this.languageLayer; + let verb = key.boundary === 'end' ? 'close' : 'open'; + + console.log( + `[highlight] (${pos.row}, ${pos.column})`, + verb, + value.scopeIds.map(id => languageMode.scopeNameForScopeId(id)), + 'next?', + this.iterator.hasNext ); } - prefillFoldCache(range) { - if (!this.layer.tree || !this.layer.queries.foldsQuery) { return; } - this.getOrCreateBoundariesIterator( - this.layer.tree.rootNode, - range.start, - range.end + compare(other) { + // First, favor the one whose current position is earlier. + const result = comparePoints( + this.iterator.key.position, + other.iterator.key.position ); - } - - getOrCreateBoundariesIterator(rootNode, start, end) { - if (!this.layer.tree || !this.layer.queries.foldsQuery) { return null; } - if (this.canReuseBoundaries(start, end)) { - let result = this.boundaries.ge(start); - return result; - } - - let scopeResolver = this.layer.scopeResolver; - scopeResolver.reset(); + if (result !== 0) { return result; } - // Instead of keying off of a plain buffer position, this tree also - // considers whether the boundary is a fold start or a fold end. If one - // boundary ends at the same point that another one starts, the ending - // boundary will be visited first. - let boundaries = createTree(compareBoundaries); - let captures = this.layer.queries.foldsQuery.captures(rootNode, start, end); + // Failing that, favor iterators that need to close scopes over those that + // don't. + let ourBoundary = this.iterator.key.boundary; + let theirBoundary = other.iterator.key.boundary; + let bothClosing = ourBoundary === 'end' && theirBoundary === 'end'; - for (let capture of captures) { - // NOTE: Currently, the first fold to match for a given starting position - // is the only one considered. That's because we use a version of a - // red-black tree in which we silently ignore any attempts to add a key - // that is equivalent in value to that of a previously added key. - // - // Attempts to use `capture.final` and `capture.shy` won't harm anything, - // but they'll be redundant. Other types of custom predicates, however, - // should work just fine. - let result = scopeResolver.store(capture); - if (!result) { continue; } + if (ourBoundary === 'end' && !bothClosing) { + return -1; + } else if (theirBoundary === 'end' && !bothClosing) { + return 1; + } - // Some folds are unusual enough that they can flip from valid to - // invalid, or vice versa, based on edits to rows other than their - // starting row. We need to keep track of these nodes so that we can - // invalidate the fold cache properly when edits happen inside of them. - if (scopeResolver.shouldInvalidateFoldOnChange(capture)) { - this.layer.foldNodesToInvalidateOnChange.add(capture.node.id); - } + if (bothClosing) { + // When both iterators are closing scopes, the deeper layer should act + // first. + return other.languageLayer.depth - this.languageLayer.depth; + } else { + // When both iterators are opening scopes, the shallower layer should act + // first. + return this.languageLayer.depth - other.languageLayer.depth; + } + } - if (capture.node.startPosition.row < start.row) { - // This fold starts before the range we're interested in. We needed to - // run these nodes through the scope resolver for various reasons, but - // they're not relevant to our iterator. - continue; - } - if (capture.name === 'fold') { - boundaries = boundaries.insert({ - position: capture.node.startPosition, - boundary: 'start' - }, capture); - } else if (capture.name.startsWith('fold.')) { - let key = this.keyForDividedFold(capture); - boundaries = boundaries.insert(key, capture); - } + moveToSuccessor() { + if (!this.iterator.hasNext || this.done) { + return false; } + this.iterator.next(); + this.done = this.isDone(); + return true; + } - scopeResolver.reset(); + peekAtSuccessor() { + if (!this.iterator.hasNext) { return null; } + this.iterator.next(); + let key = this.iterator.key; + this.iterator.prev(); + return key; + } - this.boundaries = boundaries; - this.boundariesRange = new Range(start, end); + isDone() { + if (!this.iterator.hasNext) { return true; } + if (!this.end) { return false; } - return boundaries.ge(start); + let next = this.peekAtSuccessor(); + return comparePoints(next.position, this.end) > 0; } +} - // Given a `@fold.start` capture, queries the rest of the layer's extent to - // find a matching `@fold.end`. - resolveRangeForDividedFold(capture) { - let { name } = capture; - let key = this.keyForDividedFold(capture); - if (name !== 'fold.start') { return null; } - - let extent = this.layer.getExtent(); +class GrammarLoadError extends Error { + constructor(grammar, queryType) { + super(`Grammar ${grammar.scopeName} failed to load its ${queryType}. Please fix this error or contact the maintainer.`); + this.name = 'GrammarLoadError'; + this.queryType = queryType; + } +} - let iterator = this.getOrCreateBoundariesIterator( - this.layer.tree.rootNode, - key.position, - extent.end - ); +// Manages all aspects of a given language's parsing duties over a given region +// of the buffer. +// +// The base `LanguageLayer` that's in charge of the entire buffer is the "root" +// `LanguageLayer`. Other `LanguageLayer`s are created when injections are +// required. Those injected languages may require injections themselves, +// meaning a layer could be of arbitrary depth. +// +// For example: a PHP file could inject an HTML grammar, which in turn injects +// a JavaScript grammar for `SCRIPT` blocks, which in turn injects a regex +// grammar for regular expressions. +// +// Thus, for many editor-related tasks that depend on the context of the +// cursor, we should figure out how many different `LanguageLayer`s are +// operating in that particular region, and either (a) compose their output or +// (b) choose the output of the most specific layer that meets our needs, +// depending on the task. +// +class LanguageLayer { + constructor(marker, languageMode, grammar, depth, injectionPoint) { + this.marker = marker; + this.languageMode = languageMode; + this.buffer = this.languageMode.buffer; + this.grammar = grammar; + this.depth = depth; + this.injectionPoint = injectionPoint; + this.rangeList = new RangeList(); - let depth = 0; - let matchedEndCapture = null; + this.nodesToInvalidateOnChange = new Set(); + this.foldNodesToInvalidateOnChange = new Set(); - while (iterator.key && comparePoints(iterator.key.position, extent.end) <= 0) { - let { name, node } = iterator.value; - let isSelf = node.id === capture.node.id; - if (name === 'fold.end' && !isSelf) { - if (depth === 0) { - matchedEndCapture = iterator.value; - break; - } else { - depth--; - } - } else if (name === 'fold.start' && !isSelf) { - // A later `fold.start` has occurred, so the next `fold.end` will pair - // with it, not with ours. - depth++; - } - iterator.next(); - } + this.tree = null; + this.lastSyntaxTree = null; + this.temporaryTrees = []; + this.patchSinceCurrentParseStarted = null; - // There's no guarantee that a matching `@fold.end` will even appear, so if - // it doesn't, then this row does not contain a valid fold. - if (!matchedEndCapture) { return null; } + this.subscriptions = new CompositeDisposable; - return new Range( - this.resolvePositionForDividedFold(capture), - this.resolvePositionForDividedFold(matchedEndCapture) - ); - } + this.currentRangesLayer = this.buffer.addMarkerLayer(); + this.ready = false; + this.queries = {}; - keyForDividedFold(capture) { - let { name, node } = capture; - if (name === 'fold.start') { - // Eventually we'll alter this position to occur at the end of the given - // row, but we keep the original value around for a while because we want - // to honor whichever fold technically happens “earliest” on a given row. - return { position: node.startPosition, boundary: 'start' }; - } else if (name === 'fold.end') { - return { position: node.startPosition, boundary: 'end' }; - } else { - return null; - } - } + // A constructor can't go async, so all our async administrative tasks hang + // off this promise. We can `await this.languageLoaded` later on. + this.languageLoaded = this.grammar.getLanguage().then(language => { + this.language = language; + // All queries are optional. Regular expression language layers, for + // instance, don't really have a need for any queries other than + // `highlightsQuery`, and some kinds of layers don't even need + // `highlightsQuery`. + let queries = ['highlightsQuery', 'foldsQuery', 'indentsQuery', 'localsQuery', 'tagsQuery']; + let promises = []; - // Returns `true` if there is no non-whitespace content on this position's - // row before this position's column. - positionIsNotPrecededByTextOnLine(position) { - let textForRow = this.buffer.lineForRow(position.row) - let precedingText = textForRow.substring(0, position.column) - return !(/\S/.test(precedingText)) - } + for (let queryType of queries) { + if (grammar[queryType]) { + let promise = this.grammar.getQuery(queryType).then(query => { + this.queries[queryType] = query; + }).catch(() => { + throw new GrammarLoadError(grammar, queryType); + }); + promises.push(promise); + } + } + return Promise.all(promises); + }).catch((err) => { + if (err.name === 'GrammarLoadError') { + console.warn(err.message); + if (err.queryType === 'highlightsQuery') { + // Recover by setting an empty `highlightsQuery` so that we don't + // propagate errors. + // + // TODO: Warning? + grammar.setQueryForTest( + 'highlightsQuery', + `; (placeholder)` + ); + } + } else { + throw err; + } + }).then(() => { + // This used to be called only in dev mode. But there are other use cases + // for dynamically reloading queries, so now we observer for changes in a + // grammar's queries in all cases. + // + // The grammar itself, however, will still only watch the query files + // themselves for changes if we're in dev mode. + this.observeQueryChanges(); - resolvePositionForDividedFold(capture) { - let { name, node } = capture; - if (name === 'fold.start') { - return new Point(node.startPosition.row, Infinity); - } else if (name === 'fold.end') { - let end = node.startPosition; - if (end.column === 0 || this.positionIsNotPrecededByTextOnLine(end)) { - // If the fold ends at the start of the line, adjust it so that it - // actually ends at the end of the previous line. This behavior is - // implied in the existing specs. - return new Point(end.row - 1, Infinity); + this.tree = null; + this.scopeResolver = new ScopeResolver( + this, + (name, text) => this.languageMode.idForScope(name, text) + ); + this.foldResolver = new FoldResolver(this.buffer, this); + + // What should our language scope name be? Should we even have one? + let languageScope; + if (depth === 0) { + languageScope = this.grammar.scopeName; } else { - return new Point.fromObject(end, true); + // Injections can control the base scope name of the grammar being + // injected. + languageScope = injectionPoint.languageScope; + + // The `languageScope` parameter can be a function. That means we won't + // decide now; we'll decide later on a range-by-range basis. + + // Honor an explicit `null`, but fall back to the default scope name + // otherwise. + if (languageScope === undefined) { + languageScope = this.grammar.scopeName; + } } - } else { - return null; - } - } - normalizeFoldProperty(prop) { - if (prop.startsWith('fold.')) { - prop = prop.replace(/^fold./, ''); - } - return prop; + this.languageScope = languageScope; + if (languageScope === null || typeof languageScope === 'function') { + // If `languageScope` is a function, we'll still often end up with a + // `languageScopeId` (or several); we just won't be able to compute it + // ahead of time. + this.languageScopeId = null; + } else { + this.languageScopeId = this.languageMode.idForScope(languageScope); + } + this.ready = true; + }); } - capturePropertyIsFoldAdjustment(prop) { - prop = this.normalizeFoldProperty(prop); - return prop in FoldResolver.ADJUSTMENTS; - } + // Previously we were storing compiled queries directly on the language + // layer. Now we store them on a `queries` object instead. + // + // All internal usages have been changed, but packages might still try to + // find these queries at their old locations. For that reason, we'll define + // shims for backward compatibility. + get highlightsQuery() { return this.queries.highlightsQuery; } + set highlightsQuery(value) { this.queries.highlightsQuery = value; } - applyFoldAdjustment(prop, ...args) { - prop = this.normalizeFoldProperty(prop); - return FoldResolver.ADJUSTMENTS[prop](...args); - } + get indentsQuery() { return this.queries.indentsQuery; } + set indentsQuery(value) { this.queries.indentsQuery = value; } - resolveRangeForSimpleFold(capture) { - let { node, setProperties: props } = capture; - if (node.type === 'ERROR') { return null; } - let start = new Point(node.startPosition.row, Infinity); - let end = node.endPosition; + get foldsQuery() { return this.queries.foldsQuery; } + set foldsQuery(value) { this.queries.foldsQuery = value; } - let defaultOptions = { 'fold.endAt': 'lastChild.startPosition' }; - let options = { ...defaultOptions, ...props }; + get tagsQuery() { return this.queries.tagsQuery; } + set tagsQuery(value) { this.queries.tagsQuery = value; } - try { - for (let key in options) { - if (!this.capturePropertyIsFoldAdjustment(key)) { continue; } - let value = options[key]; - end = this.applyFoldAdjustment(key, end, node, value, props, this.layer); - } - if (!end) { return null; } + get localsQuery() { return this.queries.localsQuery; } + set localsQuery(value) { this.queries.localsQuery = value; } - end = Point.fromObject(end, true); - end = this.buffer.clipPosition(end); + isDirty() { + if (!this.tree) { return false; } + return this.tree.rootNode.hasChanges; + } - if (end.row <= start.row) { return null; } - return new Range(start, end); - } catch (error) { - // If any of our assumptions are violated, fall back to an end point that we know can't fail: the end of the captured node itself. - console.warn("Error resolving fold range:"); - console.warn(error.message); - return new Range(start, node.range.end); - } + inspect() { + let { scopeName } = this.grammar; + return `[LanguageLayer ${scopeName || '(anonymous)'} depth=${this.depth} file=${this.buffer.getPath()}]`; } -} -FoldResolver.ADJUSTMENTS = { - // Use a node position descriptor to describe where the fold should end. - // Overrides the default descriptor of `lastChild.startPosition`. - endAt(end, node, value) { - end = resolveNodePosition(node, value); - return end; - }, + destroy() { + if (this.destroyed) { return; } + this.destroyed = true; - // Adjust the end point by a fixed number of characters in either direction. - // Will cross rows if necessary. - offsetEnd(end, node, value, props, layer) { - let { languageMode } = layer; - value = Number(value); - if (isNaN(value)) { return end; } - return languageMode.adjustPositionByOffset(end, value); - }, + // Clean up all Tree-sitter trees. + let temporaryTrees = this.temporaryTrees ?? []; + let trees = new Set([this.tree, this.lastSyntaxTree, ...temporaryTrees]); + trees = [...trees]; - // Adjust the column of the fold's end point. Use `0` to end the fold at the - // start of the line. - adjustEndColumn(end, node, value, props, layer) { - let column = Number(value); - if (isNaN(column)) { return end; } - let newEnd = Point.fromObject({ column, row: end.row }); - return layer.buffer.clipPosition(newEnd); - }, + this.tree = null; + this.lastSyntaxTree = null; + this.temporaryTrees = []; - // Adjust the end point to be immediately before the current line begins. - // Useful if the end line also contains the start of a fold and thus should - // stay on a separate screen line. - adjustToEndOfPreviousRow(end) { - return new Point(end.row - 1, Infinity); - } -}; + while (trees.length > 0) { + let tree = trees.pop(); + if (!tree) { continue; } + tree.delete(); + } -class NullLanguageModeHighlightIterator { - seek() { - return []; - } - compare() { - return 1; - } - moveToSuccessor() {} - getPosition() { - return Point.INFINITY; - } - getOpenScopeIds() { - return []; - } - getCloseScopeIds() { - return []; - } -} + this.marker?.destroy(); + this.currentRangesLayer?.destroy(); + this.foldResolver?.reset(); + this.scopeResolver?.destroy(); + this.subscriptions.dispose(); -class NullLayerHighlightIterator { - seek() { - return [false, new OpenScopeMap]; - } - compare() { - return 1; - } - moveToSuccessor() {} - getPosition() { - return Point.INFINITY; - } - getOpenScopeIds() { - return []; - } - getCloseScopeIds() { - return []; + for (const marker of this.languageMode.injectionsMarkerLayer.getMarkers()) { + if (marker.parentLanguageLayer === this) { + marker.languageLayer.destroy(); + } + } } -} -let lastIteratorId = 0; + // Reload a query of a given type from the grammar. + async reloadGrammarQuery(queryType) { + if (!this.queries[queryType]) { return; } + let originalQuery = this.queries[queryType]; + try { + let query = await this.grammar.getQuery(queryType); + this.queries[queryType] = query; -// An iterator for marking boundaries in the buffer to apply syntax -// highlighting. -// -// Manages a collection of `LayerHighlightIterators`, which are the classes -// doing the real work of marking boundaries. `HighlightIterator` is in charge -// of understanding, at any given point, which of the iterators needs to be -// advanced next. -class HighlightIterator { - constructor(languageMode) { - this.id = lastIteratorId++; - this.languageMode = languageMode; - this.iterators = null; + // Force a re-highlight of this layer's entire region. + let range = this.getExtent(); + this.languageMode.emitRangeUpdate(range); + this.nodesToInvalidateOnChange.clear(); + this.foldNodesToInvalidateOnChange.clear(); + } catch (error) { + this.queries[queryType] = originalQuery; + console.error(`Error parsing query file: ${queryType}`); + console.error(error); + } } - inspect() { - return `[HighlightIterator id=${this.id} iterators=${this.iterators?.length ?? 0}]`; + // Observe the grammar for changes in queries. + // + // This won't happen very often. It can happen if a user edits a grammar’s + // query files, but those edits are only monitored in dev mode. + // + // It can also happen if a community package uses an API on + // {WASMTreeSitterGrammar} to modify a query after initial load. + observeQueryChanges() { + this.grammar.onDidChangeQuery(async ({ queryType }) => { + if (this._pendingQueryFileChange) { return; } + // Debounce the reloading. Sometimes multiple callbacks fire when a query + // file is saved. + this._pendingQueryFileChange = true; + await this.reloadGrammarQuery(queryType); + this._pendingQueryFileChange = false; + }) } - seek(start, endRow) { - if (!(start instanceof Point)) { - start = Point.fromObject(start, true); + getExtent() { + return this.marker?.getRange() ?? this.languageMode.buffer.getRange(); + } + + // Run a highlights query for the given range and process the raw captures + // through a `ScopeResolver`. + getSyntaxBoundaries(from, to) { + let { buffer } = this.languageMode; + if (!(this.language && this.tree)) { + return [[], new OpenScopeMap()]; } - let { buffer, rootLanguageLayer } = this.languageMode; - if (!rootLanguageLayer) { return []; } - let end = { - row: endRow, - column: buffer.lineLengthForRow(endRow) - }; + from = buffer.clipPosition(Point.fromObject(from, true)); + to = buffer.clipPosition(Point.fromObject(to, true)); - this.end = end; - this.iterators = []; + let boundaries = createTree(compareBoundaries); + let extent = this.getExtent(); - if (Math.max(start.column, end.column) > LINE_LENGTH_LIMIT_FOR_HIGHLIGHTING) { - return []; - } + let captures = this.queries.highlightsQuery?.captures( + this.tree.rootNode, + { startPosition: from, endPosition: to } + ) ?? []; + this.scopeResolver.reset(); - let injectionMarkers = this.languageMode.injectionsMarkerLayer.findMarkers( - { - intersectsRange: new Range( - start, - new Point(endRow + 1, 0) - ) + for (let capture of captures) { + let { node } = capture; + if (this.scopeResolver.shouldInvalidateOnChange(capture)) { + // This node wants to be invalidated in its entirety whenever a change + // happens within it. This setting should be added to a query whenever + // (a) a certain node's scope name can change just from its contents + // changing, and (b) that node can possibly span more than one line. + // + // Without this setting, we're liable to invalidate only the line that + // a change happened on, because we have no other way of knowing how + // significant of a change it is. If we were able to do so, we'd set + // this property automatically on any capture that (a) involved a + // `#match?` predicate, and (b) started and ended on different lines. + this.nodesToInvalidateOnChange.add(node.id); } - ); + // Phantom nodes invented by the parse tree. Indentation captures can use + // `allowEmpty` to force these to be considered, but for marking scopes, + // there's no need for it; it'd just cause us to open and close a scope + // in the same position. + if (node.childCount === 0 && node.text === '') { continue; } - injectionMarkers.sort((a, b) => { - // Shallower injections get visited first. - return a.languageLayer.depth - b.languageLayer.depth; - }); + // Ask the `ScopeResolver` to process each capture in turn. Some captures + // will be ignored if they fail certain tests, and some will have their + // original range altered. + this.scopeResolver.store(capture); + } - const iterator = rootLanguageLayer.buildHighlightIterator(); + // A `HighlightIterator` will want to know which scopes were already open + // when this range began. Sadly, we also have to keep track of the point in + // the buffer at which each of those scopes would've been added, because + // it's possible that later we'll find out that a deeper iterator has + // suppressed the application of that scope boundary at that position. + let alreadyOpenScopes = new OpenScopeMap(); - // We need to know which open scopes were contributed by which language - // layer so that we know which ones to “cover” if we see an injection with - // the `coverShallowerScopes` option. + // How do we add a layer's root scope? There's no easy answer. // - // What's worse is that we need to know _where_ each of those open scopes - // would've been added, because that open scope shouldn't truly be added - // _if_ it would've been suppressed by a deeper layer. - let openScopesByLayer = new Map(); - - // The contract of `LayerHighlightIterator#seek` is different from the - // contract of `HighlightIterator#seek`. Instead of an array of open - // scopes, we have it return an two-item array. + // If we rely on the grammar author to map the tree's root node to the + // language's root scope, they could forget to do it, or map a scope that + // isn't the same as the one given in the grammar's config file. We'd also + // limit the ability of that `highlights.scm` to be used in multiple + // contexts — for example, the HTML grammar would always carry around a + // `text.html.basic` scope name no matter where we put it. // - // The first item is a boolean that indicates whether the iterator needs to - // mark anything _within_ the range that will be highlighted. If not, it - // doesn't need to go into the list of iterators. + // If we manage it ourselves, we invite the complexity of _manually_ + // applying a root scope — plus knowing when it actually _shouldn't_ be + // applied, and making sure that `scopeDescriptorForPosition` returns + // results that agree with reality. // - // The second item is an `OpenScopeMap` (rather than just an array of - // already open scopes) that retains information about where each open - // scope was opened, because we'll need that information in a moment. + // Option B is the one we reluctantly choose because it gives us more + // control. Here's roughly how this works: // - let [result, openScopes] = iterator.seek(start, endRow); - - if (rootLanguageLayer?.tree?.rootNode.hasChanges()) { - // The tree is dirty. We should keep going — if we stop now, then the - // user will see a flash of unhighlighted text over this whole range. But - // we should also schedule a re-highlight at the end of the transaction, - // once the tree will be clean again. - let range = new Range(start, iterator._getEndPosition(endRow)); - this.languageMode.atTransactionEnd().then(() => { - this.languageMode.emitRangeUpdate(range); - }); - } - - // An iterator can contribute to the list of already open scopes even if it - // has no boundaries to mark within the range of this highlighting job. - openScopesByLayer.set(iterator, openScopes); - - if (result) { - this.iterators.push(iterator); - } - - // The logic that we use for `coverShallowerScopes` needs to be respected - // here. As we build these iterators, if we find that any of them cover - // their parent iterator at this position, we need to revisit the list of - // open scopes, removing any that aren't their layer's base language scope. - for (const marker of injectionMarkers) { - const iterator = marker.languageLayer.buildHighlightIterator(); - let [result, openScopes] = iterator.seek(start, endRow); - - // Just as with the root layer, any injection layer may need to add to - // the list of open scopes whether or not they need to mark anything - // within this range. - if (result) { this.iterators.push(iterator); } + // * The base language scope on the root layer will always be applied. + // * The base language scope on an injection layer will be applied in areas + // where that injection is active, unless we were told otherwise in + // `addInjectionPoint`. That method's `languageScope` property can + // define another scope name to use instead… or pass `null`, signaling + // that we should skip the root scope altogether. (This is the best + // approach for special-purpose injections like `todo` and `hyperlink`.) + // * A grammar can still opt to place a root scope conditionally based on + // whether it's on an injection layer. This also allows a grammar to + // apply that root scope more selectively in injection contexts if + // desired. - if (iterator?.languageLayer?.injectionPoint?.coverShallowerScopes) { - // The procedure we follow for covering scopes in the _middle_ of a - // highlighting task needs to be emulated when deciding which scopes - // are already open at the _start_ of the task. This layer wants to - // cover shallower scopes, so we need to re-assess our list of already - // open scopes to see if any of them _would've_ been covered at the - // point when they were opened. - let ranges = iterator.languageLayer.getCurrentRanges(); - for (let [earlierIterator, earlierOpenScopes] of openScopesByLayer) { - // It's possible, though uncommon, for injections to overlap, because - // there's no mechanism that prevents it. Since we sorted the layers - // by depth earlier, this iterator won't have a lower depth than the - // previous one. But it may have _the same_ depth, so we need to - // account for that. - if (earlierIterator.depth >= iterator.depth) { continue; } + // Ensure the whole source file (or whole bounds of the injection) is + // annotated with the language's base scope name. We _do not_ want to leave + // this up to the grammar author; it's too important. Also ensure that the + // base scope name of an injection covers the true boundaries of where that + // injection is active. + // + // NOTE: If an injection is active over a number of disjoint ranges, this + // may have some surprising effects. For instance, areas where PHP is + // injected into HTML… + // + //

+ // + // …will include ``, but may exclude the + // spaces between those tokens. This is a consequence of the design of + // a particular Tree-sitter parser and should be mitigated with the + // `includeAdjacentWhitespace` option of `addInjectionPoint`. + // + let includedRanges = this.depth === 0 ? [extent] : this.getCurrentRanges(); - // For each of the open scopes that an earlier iterator gave us, we - // need to look at the point at which that scope would've been added. - // If that point falls within the purview of our newer iterator, then - // we should suppress that scope unless it's the layer's base - // language scope. - let languageScopeId = earlierIterator.languageLayer.languageScopeId; - for (let [point, scopes] of earlierOpenScopes) { - let pointIsCoveredByNewIterator = ranges.some(r => r.containsPoint(point)); - if (!pointIsCoveredByNewIterator) { continue; } - earlierOpenScopes.set(point, scopes.filter(id => id === languageScopeId)); - } + let languageScopeIdForRange = () => this.languageScopeId; + if (typeof this.languageScope === 'function') { + languageScopeIdForRange = (range) => { + let scopeName = this.languageScope(this.grammar, this.languageMode.buffer, range); + if (Array.isArray(scopeName)) { + return scopeName.map(s => this.languageMode.idForScope(s)); + } else { + return this.languageMode.idForScope(scopeName); } - } - openScopesByLayer.set(iterator, openScopes); - } - - // Sort the iterators so that the last one in the array is the earliest - // in the document, and represents the current position. - this.iterators.sort((a, b) => b.compare(a)); - - this.detectCoveredScope(); - - // Our nightmare is almost over, but one chore remains. The ordering of - // already open scopes should be consistent; scopes added earlier in the - // buffer should appear in the list before scopes added later. This ensures - // that, e.g., `scopeDescriptorForPosition` returns scopes in the proper - // hierarchy. - let sortedOpenScopes = []; - - // First we'll gather all the point/scope-list pairs into a flat list… - let unsortedScopeBundles = []; - for (let [iterator, layerOpenScopeMap] of openScopesByLayer) { - for (let [point, scopes] of layerOpenScopeMap) { - unsortedScopeBundles.push({ point, scopes, iterator }); - } + }; } - // …then sort them by buffer position, with shallower layers first in case - // of ties. - unsortedScopeBundles.sort((a, b) => ( - a.point.compare(b.point) || a.iterator.depth - b.iterator.depth - )); + if (this.languageScopeId || typeof this.languageScope === 'function') { + for (let range of includedRanges) { + // Filter out ranges that have no intersection with ours. + if (range.end.isLessThanOrEqual(from)) { continue; } + if (range.start.isGreaterThanOrEqual(to)) { continue; } - // Now we can flatten all the scopes themselves, preserving order. - for (let { scopes } of unsortedScopeBundles) { - sortedOpenScopes.push(...scopes); - } + let languageScopeIds = languageScopeIdForRange(range); + if (!languageScopeIds) continue; - return sortedOpenScopes; - } + if (!Array.isArray(languageScopeIds)) { + languageScopeIds = [languageScopeIds]; + } - moveToSuccessor() { - // `this.iterators` is _always_ sorted from farthest position to nearest - // position, so the last item in the collection is always the next one to - // act. - let leader = last(this.iterators); - if (!leader) { return; } + if (range.start.isLessThan(from)) { + // If we get this far, we know that the base language scope was open + // when our range began. + alreadyOpenScopes.set( + range.start, + languageScopeIds + ); + } else { + // Range start must be between `from` and `to`, or else equal `from` + // exactly. + for (let id of languageScopeIds) { + this.scopeResolver.setBoundary( + range.start, + id, + 'open', + { root: true, length: Infinity } + ); + } + } - if (leader.moveToSuccessor()) { - // It was able to move to a successor, so now we have to "file" it into - // the right place in `this.iterators` so that the sorting is correct. - const leaderIndex = this.iterators.length - 1; - let i = leaderIndex; - while (i > 0 && this.iterators[i - 1].compare(leader) < 0) { - i--; - } - if (i < leaderIndex) { - this.iterators.splice(i, 0, this.iterators.pop()); + if (range.end.isGreaterThan(to)) { + // Do nothing; we don't need to set this boundary. + } else { + // The range must end somewhere within our range. + // + // Close the boundaries in the opposite order of how we opened them. + for (let i = languageScopeIds.length - 1; i >= 0; i--) { + this.scopeResolver.setBoundary( + range.end, + languageScopeIds[i], + 'close', + { root: true, length: Infinity } + ); + } + } } - } else { - // It was not able to move to a successor, so it must be done. Remove it - // from the collection. - this.iterators.pop(); } - this.detectCoveredScope(); - } + // `ScopeResolver` ensures that these points will be iterated in buffer + // order. + for (let [point, data] of this.scopeResolver) { + // The boundaries that occur before the start of our range will tell us + // which scopes should already be open when our range starts. + if (point.isLessThan(from)) { + alreadyOpenScopes.set(point, data.open); + for (let c of data.close) { + alreadyOpenScopes.removeLastOccurrenceOf(c); + } + continue; + } else if (point.isGreaterThan(to)) { + continue; + } - getPosition() { - let iterator = last(this.iterators || []); - if (iterator) { - // this.logPosition(); - return iterator.getPosition(); - } else { - return Point.INFINITY; - } - } + let OPEN_KEY = { position: point, boundary: 'start' }; + let CLOSE_KEY = { position: point, boundary: 'end' }; - getCloseScopeIds() { - let iterator = last(this.iterators); - // if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'close') { - // console.log( - // iterator.name, - // iterator.depth, - // 'would close', - // iterator._inspectScopes( - // iterator.getCloseScopeIds() - // ), - // 'at', - // iterator.getPosition().toString(), - // 'but scope is covered!' - // ); - // } else { - // console.log( - // iterator.name, - // iterator.depth, - // 'CLOSING', - // iterator.getPosition().toString(), - // iterator._inspectScopes( - // iterator.getCloseScopeIds() - // ) - // ); - // } - if (iterator) { - // If this iterator is covered completely, or if it's covered in a - // position that prevents us from closing scopes… - if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'close') { - // …then the only closing scope we're allowed to apply is one that ends - // the base scope of an injection range. - return iterator.getCloseScopeIds().filter(id => { - return iterator.languageLayer.languageScopeId === id; + // Each point will contain either one or more scopes to _close_… + if (data.close.length > 0) { + boundaries = boundaries.insert(CLOSE_KEY, { + scopeIds: Object.freeze(data.close) }); - } else { - return iterator.getCloseScopeIds(); } - } - return []; - } - getOpenScopeIds() { - let iterator = last(this.iterators); - // let ids = iterator.getOpenScopeIds(); - // if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'open') { - // console.log( - // iterator.name, - // iterator.depth, - // 'would open', - // iterator._inspectScopes( - // iterator.getOpenScopeIds() - // ), - // 'at', - // iterator.getPosition().toString(), - // 'but scope is covered!' - // ); - // } else { - // console.log( - // iterator.name, - // iterator.depth, - // 'OPENING', - // iterator.getPosition().toString(), - // iterator._inspectScopes( - // iterator.getOpenScopeIds() - // ) - // ); - // } - if (iterator) { - // If this iterator is covered completely, or if it's covered in a - // position that prevents us from opening scopes… - if (this.currentIteratorIsCovered === true || this.currentIteratorIsCovered === 'open') { - // …then the only opening scope we're allowed to apply is one that ends - // the base scope of an injection range. - return iterator.getOpenScopeIds().filter(id => { - return iterator.languageLayer.languageScopeId === id; + // …or one or more scopes to _open_. One point is not allowed to close + // one scope and open another; there's always a chance that a different + // injection layer needs to act in between. + if (data.open.length > 0) { + boundaries = boundaries.insert(OPEN_KEY, { + scopeIds: Object.freeze(data.open) }); - } else { - return iterator.getOpenScopeIds(); - } - } - return []; - } - - // Detect whether the iterator that's about to act has its scopes covered by - // a different iterator asserting a `coverShallowerScopes` option. - detectCoveredScope() { - const layerCount = this.iterators.length; - if (layerCount > 1) { - const rest = [...this.iterators]; - const leader = rest.pop(); - let covers = false; - for (let it of rest) { - let iteratorCovers = it.coversIteratorAtPosition(leader, leader.getPosition()); - if (iteratorCovers !== false) { - covers = iteratorCovers; - break; - } - } - - if (covers) { - this.currentIteratorIsCovered = covers; - return; } } - this.currentIteratorIsCovered = false; + return [boundaries, alreadyOpenScopes]; } - logPosition() { - let iterator = last(this.iterators); - iterator?.logPosition(); + buildHighlightIterator() { + if (this.tree) { + return new LayerHighlightIterator(this, this.tree); + } else { + return new NullLayerHighlightIterator(); + } } -} -const EMPTY_SCOPES = Object.freeze([]); + handleTextChange(edit, oldText, newText) { + // Any text change within the layer invalidates our cached fold boundary + // tree. This usually isn't a big deal because the language mode's own cache + // is able to adjust when content shifts up and down, so typically only the + // ranges that actually change will have fold data re-queried. + if (this.foldResolver) { this.foldResolver.reset(); } -// Iterates through everything that a `LanguageLayer` is responsible for, -// marking boundaries for scope insertion. -class LayerHighlightIterator { - constructor(languageLayer) { - this.languageLayer = languageLayer; - this.name = languageLayer.grammar.scopeName; - this.depth = languageLayer.depth; + const { + startPosition, + oldEndPosition, + newEndPosition + } = edit; - let { injectionPoint } = this.languageLayer; + if (this.tree) { + this.tree.edit(edit); + if (this.lastSyntaxTree && this.tree !== this.lastSyntaxTree) { + // This happens after an off-schedule parse that we've decided we can + // re-use at the end of the current transaction. But when that happens, + // we'll call `getChangedRanges` between `lastSyntaxTree` and the new + // tree, so `lastSyntaxTree` needs to receive the same tree edits. + this.lastSyntaxTree.edit(edit); + } + // We're tentatively marking this tree as dirty; we won't know if it + // needs to be reparsed until the transaction is done. If it doesn't, + // that means that the edits didn't encroach on the contents of the + // layer, and we'll mark those trees as clean at the end of the + // transaction. + this.treeIsDirty = true; + if (this.editedRange) { + if (startPosition.isLessThan(this.editedRange.start)) { + this.editedRange.start = startPosition; + } if (oldEndPosition.isLessThan(this.editedRange.end)) { + this.editedRange.end = newEndPosition.traverse( + this.editedRange.end.traversalFrom(oldEndPosition) + ); + } else { + this.editedRange.end = newEndPosition; + } + } else { + this.editedRange = new Range(startPosition, newEndPosition); + } + } - this.coverShallowerScopes = injectionPoint?.coverShallowerScopes ?? false + if (this.patchSinceCurrentParseStarted) { + this.patchSinceCurrentParseStarted.splice( + startPosition, + oldEndPosition.traversalFrom(startPosition), + newEndPosition.traversalFrom(startPosition), + oldText, + newText + ) + } } - // If this isn't the root language layer, we need to make sure this iterator - // doesn't try to go past its marker boundary. - _getEndPosition(endRow) { - let { marker } = this.languageLayer; - let { buffer } = this.languageLayer.languageMode; - let naiveEndPoint = new Point( - endRow, - buffer.lineLengthForRow(endRow) - ); + async update(nodeRangeSet, params = {}) { + if (!this.languageMode.useAsyncParsing) { + // Practically speaking, updates that affect _only this layer_ will happen + // synchronously, because we've made sure not to call this method until the + // root grammar's Tree-sitter parser has been loaded. But we can't load any + // potential injection layers' languages because we don't know which ones + // we'll need _until_ we parse this layer's tree for the first time. + // + // Thus the first call to `_populateInjections` will probably go async + // while we wait for the injections' parsers to load, and the user might + // notice the delay. But once that happens, all subsequent updates _should_ + // be synchronous, except for a case where a change in the buffer causes us + // to need a new kind of injection whose parser hasn't yet been loaded. + if (!this.ready) { await this.languageLoaded; } + await this._performUpdate(nodeRangeSet); + return true; + } - if (marker) { - return Point.min(marker.getRange().end, naiveEndPoint) + if (!this.currentParsePromise) { + do { + params = { ...params, async: false }; + if (!this.ready) { + params.async = true; + await this.languageLoaded; + // While we were waiting for this language to load, another update may + // have been scheduled. + if (this.currentParsePromise) return false; + } + this.currentParsePromise = this._performUpdate(nodeRangeSet, params); + if (!params.async) { break; } + await this.currentParsePromise; + } while ( + !this.destroyed && + (!this.tree || this.tree.rootNode.hasChanges) + ); + + this.currentParsePromise = null; + // `true` means that this update occurs in its own distinct transaction. + return true; } else { - return buffer.clipPosition(naiveEndPoint); + // `false` means that the previous transaction isn't done, so this + // transaction's work will be subsumed into it. + return false; } } - // TODO: This still doesn't make much sense, so I suppose it's good that - // we've now made it an opt-in feature. - // - // The main problem with this logic is that it runs the risk of covering only - // one half of a pair of boundaries. If a scope range from the root layer is - // coterminous with a scope range from an injection layer, that's easy to - // detect and handle; but what if the root layer's range starts at the same - // point but ends later? We'd prevent the root layer from opening the scope - // but not closing it. - // - // I still don't fully understand the use cases for `detectCoveredScope`, - // though I assume there are at least a few. I am quite sure, however, that - // if we want an injection layer to veto a shallower layer's scope, it ought - // to happen in a way that either prevents _both_ boundaries or allows _both_ - // boundaries. I'm not sure how to pull that off at this point, though. - // - // https://github.com/atom/atom/pull/19556 has good discussion about the - // impetus for this feature. - coversIteratorAtPosition(iterator, position) { - // When does a layer prevent another layer from applying scopes? - - // When the option is asserted… - if (!this.coverShallowerScopes) { return false; } + getLocalReferencesAtPoint(point) { + if (!this.queries.localsQuery) { return []; } + let captures = this.queries.localsQuery.captures( + this.tree.rootNode, + { + startPosition: point, + endPosition: point.translate(ONE_CHAR_FORWARD_TRAVERSAL) + } + ); - // …and this iterator is deeper than the other… - if (iterator.depth > this.depth) { return false; } + captures = captures.filter(cap => { + if (cap.name !== 'local.reference') { return false; } + if (!rangeForNode(cap.node).containsPoint(point)) { + return false; + } + return true; + }); - // …and one of this iterator's content ranges actually includes this - // position. (With caveats!) - let ranges = this.languageLayer.getCurrentRanges(); - if (ranges) { - // A given layer's content ranges aren't allowed to overlap each other. - // So only a single range from this list can possibly match. - let overlappingRange = ranges.find(range => range.containsPoint(position)) - if (!overlappingRange) return false; + let nodes = captures.map(cap => cap.node); + nodes = nodes.sort((a, b) => b.range.compare(a.range)); - // If the current position is right in the middle of an injection's - // range, then it should cover all attempts to apply scopes. But what if - // we're on one of its edges? Since closing scopes act before opening - // scopes, - // - // * if this iterator _starts_ a range at position X, it doesn't get to - // prevent another iterator from _ending_ a scope at position X; - // * if this iterator _ends_ a range at position X, it doesn't get to - // prevent another iterator from _starting_ a scope at position X. - // - // So at a given position, `currentIteratorIsCovered` can be `true` (all - // scopes suppressed), `false` (none suppressed), `"close"` (only closing - // scopes suppressed), or `"open"` (only opening scopes suppressed). - if (overlappingRange.end.compare(position) === 0) { - // We're at the right edge of the injection range. We want to prevent - // iterators from closing scopes, but not from opening them. - return 'close'; - } else if (overlappingRange.start.compare(position) === 0) { - // We're at the left edge of the injection range. We want to prevent - // iterators from opening scopes, but not from closing them. - return 'open'; - } else { - return true; - } - } + return nodes; } - seek(start, endRow) { - let end = this._getEndPosition(endRow); - // let isDevMode = atom.inDevMode(); - - // let timeKey; - // if (isDevMode) { - // timeKey = `${this.name} getSyntaxBoundaries`; - // console.time(timeKey); - // } - let [boundaries, openScopes] = this.languageLayer.getSyntaxBoundaries( - start, end); + // EXPERIMENTAL: Given a local reference node, tries to find the node that + // defines it. + findDefinitionForLocalReference(node, captures = null) { + if (!this.queries.localsQuery) { return []; } + let name = node.text; + if (!name) { return []; } + let localRange = rangeForNode(node); + let globalScope = this.tree.rootNode; - this.iterator = boundaries?.begin; - if (!this.iterator?.key) { - return [false, openScopes]; + if (!captures) { + let { startPosition, endPosition } = globalScope; + captures = this.groupLocalsCaptures( + this.queries.localsQuery.captures( + globalScope, + { startPosition, endPosition } + ) + ); } - this.start = Point.fromObject(start, true); - this.end = end; - // if (isDevMode) { console.timeEnd(timeKey); } - return [true, openScopes]; - } + let { scopes, definitions } = captures; - isAtInjectionBoundary() { - let position = Point.fromObject(this.iterator.key.position); - return position.isEqual(this.start) || position.isEqual(this.end); - } + // Consider only the scopes that can influence our local node. + let relevantScopes = scopes.filter((scope) => { + let range = rangeForNode(scope); + return range.containsRange(localRange); + }).sort((a, b) => { + a.range.compare(b.range) + }); - _inspectScopes(ids) { - if (Array.isArray(ids)) { - return ids.map(id => this._inspectScopes(id)).join(', ') - } - return this.languageLayer.languageMode.scopeNameForScopeId(ids); - } + relevantScopes.push(globalScope); - getOpenScopeIds() { - let { key, value } = this.iterator; - return key.boundary === 'end' ? EMPTY_SCOPES : [...value.scopeIds]; - } + // Consider only the definitions whose names match the target's. + let relevantDefinitions = definitions.filter( + (def) => def.text === name + ); + if (relevantDefinitions.length === 0) { return []; } - getCloseScopeIds() { - let { key, value } = this.iterator; - return key.boundary === 'start' ? EMPTY_SCOPES : [...value.scopeIds]; - } + let definitionsByBaseScope = new Index(); + for (let rDef of relevantDefinitions) { + // Find all the scopes that include this definition. The largest of those + // scopes will be its "base" scope. If there are no scopes that include + // this definition, it must have been defined globally. + let rDefScopes = scopes.filter(s => { + return isBetweenPoints( + rDef.startPosition, + s.startPosition, + s.endPosition + ); + }).sort((a, b) => { + return rangeForNode(b).compare(rangeForNode(a)); + }); - opensScopes() { - return this.iterator?.key?.boundary === 'start'; - } + let baseScope = rDefScopes[0] ?? globalScope; - closesScopes() { - return this.iterator?.key?.boundary === 'end'; - } + // Group each definition by its scope. Since any variable can be + // redefined an arbitrary number of times, each scope might include + // multiple definitions of this identifier. + definitionsByBaseScope.add(baseScope, rDef); + } - getPosition() { - return this.iterator?.key?.position ?? Point.INFINITY; - } + // Moving from smallest to largest scope, get definitions that were made in + // that scope, and return the closest one to the reference. + for (let scope of relevantScopes) { + let definitionsInScope = definitionsByBaseScope.get(scope) ?? []; + let { length } = definitionsInScope; + if (length === 0) { continue; } + if (length === 1) { return definitionsInScope[0]; } - logPosition() { - let pos = this.getPosition(); - let { key, value } = this.iterator; + // Here's how we want to sort these candidates: + // + // * In each scope, look for a definitions that happen before the local's + // position. The closest such definition in the narrowest scope is our + // ideal target. + // * Failing that, take note of all the definitions that happened _after_ + // the local's position in all relevant scopes. Choose the closest to + // the local. + // + let definitionsBeforeLocal = []; + let definitionsAfterLocal = []; - let { languageMode } = this.languageLayer; - let verb = key.boundary === 'end' ? 'close' : 'open'; + for (let def of definitionsInScope) { + let result = comparePoints(def.startPosition, localRange.start); - console.log( - `[highlight] (${pos.row}, ${pos.column})`, - verb, - value.scopeIds.map(id => languageMode.scopeNameForScopeId(id)), - 'next?', - this.iterator.hasNext - ); - } + let bucket = result < 0 ? + definitionsBeforeLocal : + definitionsAfterLocal; - compare(other) { - // First, favor the one whose current position is earlier. - const result = comparePoints( - this.iterator.key.position, - other.iterator.key.position - ); - if (result !== 0) { return result; } + bucket.push(def); + } - // Failing that, favor iterators that need to close scopes over those that - // don't. - let ourBoundary = this.iterator.key.boundary; - let theirBoundary = other.iterator.key.boundary; - let bothClosing = ourBoundary === 'end' && theirBoundary === 'end'; + if (definitionsBeforeLocal.length > 0) { + let maxBeforeLocal; + for (let def of definitionsBeforeLocal) { + if (!maxBeforeLocal) { + maxBeforeLocal = def; + continue; + } + + let result = comparePoints(def, maxBeforeLocal); + if (result > 0) { + maxBeforeLocal = def; + } + } + return maxBeforeLocal; + } - if (ourBoundary === 'end' && !bothClosing) { - return -1; - } else if (theirBoundary === 'end' && !bothClosing) { - return 1; - } + // TODO: For definitions that happen after the local in the buffer, it's + // not 100% clear what the right answer should be. I imagine it varies by + // language. Best guess for now is the one that's closest to the local + // reference. + let minAfterLocal; + for (let def of definitionsAfterLocal) { + if (!minAfterLocal) { + minAfterLocal = def; + continue; + } - if (bothClosing) { - // When both iterators are closing scopes, the deeper layer should act - // first. - return other.languageLayer.depth - this.languageLayer.depth; - } else { - // When both iterators are opening scopes, the shallower layer should act - // first. - return this.languageLayer.depth - other.languageLayer.depth; - } - } + let result = comparePoints(def, minAfterLocal); + if (result < 0) { + minAfterLocal = def; + } + } - moveToSuccessor() { - if (!this.iterator.hasNext || this.done) { - return false; + return minAfterLocal; } - this.iterator.next(); - this.done = this.isDone(); - return true; - } - - peekAtSuccessor() { - if (!this.iterator.hasNext) { return null; } - this.iterator.next(); - let key = this.iterator.key; - this.iterator.prev(); - return key; } - isDone() { - if (!this.iterator.hasNext) { return true; } - if (!this.end) { return false; } + groupLocalsCaptures(captures) { + let scopes = []; + let definitions = []; + let references = []; - let next = this.peekAtSuccessor(); - return comparePoints(next.position, this.end) > 0; - } -} + for (let capture of captures) { + let { name, node } = capture; + switch (name) { + case 'local.scope': + scopes.push(node); + break; + case 'local.definition': + definitions.push(node); + break; + case 'local.reference': + references.push(node); + break; + } + } -class GrammarLoadError extends Error { - constructor(grammar, queryType) { - super(`Grammar ${grammar.scopeName} failed to load its ${queryType}. Please fix this error or contact the maintainer.`); - this.name = 'GrammarLoadError'; - this.queryType = queryType; + return { scopes, definitions, references }; } -} - -// Manages all aspects of a given language's parsing duties over a given region -// of the buffer. -// -// The base `LanguageLayer` that's in charge of the entire buffer is the "root" -// `LanguageLayer`. Other `LanguageLayer`s are created when injections are -// required. Those injected languages may require injections themselves, -// meaning a layer could be of arbitrary depth. -// -// For example: a PHP file could inject an HTML grammar, which in turn injects -// a JavaScript grammar for `SCRIPT` blocks, which in turn injects a regex -// grammar for regular expressions. -// -// Thus, for many editor-related tasks that depend on the context of the -// cursor, we should figure out how many different `LanguageLayer`s are -// operating in that particular region, and either (a) compose their output or -// (b) choose the output of the most specific layer that meets our needs, -// depending on the task. -// -class LanguageLayer { - constructor(marker, languageMode, grammar, depth, injectionPoint) { - this.marker = marker; - this.languageMode = languageMode; - this.buffer = this.languageMode.buffer; - this.grammar = grammar; - this.depth = depth; - this.injectionPoint = injectionPoint; - this.rangeList = new RangeList(); - this.nodesToInvalidateOnChange = new Set(); - this.foldNodesToInvalidateOnChange = new Set(); - - this.tree = null; - this.lastSyntaxTree = null; - this.temporaryTrees = []; - this.patchSinceCurrentParseStarted = null; + // Given a range and a `Set` of node IDs, test if any of those nodes' ranges + // overlap with the given range. + // + // We use this to test if a given edit should trigger the behavior indicated + // by `(fold|highlight).invalidateOnChange`. + searchForNodesInRange(range, nodeIdSet) { + let node = this.getSyntaxNodeContainingRange( + range, + n => nodeIdSet.has(n.id) + ); - this.subscriptions = new CompositeDisposable; + if (node) { + // One of this node's ancestors might also be in our list, so we'll + // traverse upwards and find out. + let ancestor = node.parent; + while (ancestor) { + if (nodeIdSet.has(ancestor.id)) { + node = ancestor; + } + ancestor = ancestor.parent; + } + return node; + } + return null; + } - this.currentRangesLayer = this.buffer.addMarkerLayer(); - this.ready = false; - this.queries = {}; + async _performUpdate(nodeRangeSet, params = {}) { + // It's much more common in specs than in real life, but it's always + // possible for a layer to get destroyed during the async period between + // layer updates. + if (this.destroyed) return; - // A constructor can't go async, so all our async administrative tasks hang - // off this promise. We can `await this.languageLoaded` later on. - this.languageLoaded = this.grammar.getLanguage().then(language => { - this.language = language; - // All queries are optional. Regular expression language layers, for - // instance, don't really have a need for any queries other than - // `highlightsQuery`, and some kinds of layers don't even need - // `highlightsQuery`. - let queries = ['highlightsQuery', 'foldsQuery', 'indentsQuery', 'localsQuery', 'tagsQuery']; - let promises = []; + let includedRanges = null; + this.rangeList.clear(); - for (let queryType of queries) { - if (grammar[queryType]) { - let promise = this.grammar.getQuery(queryType).then(query => { - this.queries[queryType] = query; - }).catch(() => { - throw new GrammarLoadError(grammar, queryType); - }); - promises.push(promise); - } + if (nodeRangeSet) { + includedRanges = nodeRangeSet.getRanges(this.languageMode.buffer); + if (includedRanges.length === 0) { + // We have no ranges to inject into. This layer should no longer exist. + const range = this.marker.getRange(); + this.destroy(); + this.languageMode.emitRangeUpdate(range); + return; } - return Promise.all(promises); - }).catch((err) => { - if (err.name === 'GrammarLoadError') { - console.warn(err.message); - if (err.queryType === 'highlightsQuery') { - // Recover by setting an empty `highlightsQuery` so that we don't - // propagate errors. - // - // TODO: Warning? - grammar.setQueryForTest( - 'highlightsQuery', - `; (placeholder)` - ); - } - } else { - throw err; + } + + this.patchSinceCurrentParseStarted = new Patch(); + let language = this.grammar.getLanguageSync(); + let tree; + if (this.languageMode.useAsyncParsing) { + tree = this.languageMode.parseAsync( + language, + this.tree, + includedRanges, + // { tag: `Parsing ${this.inspect()} (async) ${params.id}` } + ); + if (tree.then) { + params.async = true; + tree = await tree; } - }).then(() => { - // This used to be called only in dev mode. But there are other use cases - // for dynamically reloading queries, so now we observer for changes in a - // grammar's queries in all cases. - // - // The grammar itself, however, will still only watch the query files - // themselves for changes if we're in dev mode. - this.observeQueryChanges(); + } else { + tree = this.languageMode.parse( + language, + this.tree, + includedRanges, + // { tag: `Parsing ${this.inspect()} (sync) ${params.id}` } + ); + } - this.tree = null; - this.scopeResolver = new ScopeResolver( - this, - (name, text) => this.languageMode.idForScope(name, text) + let changes = this.patchSinceCurrentParseStarted.getChanges(); + this.patchSinceCurrentParseStarted = null; + + for (let change of changes) { + let newExtent = Point.fromObject(change.newEnd).traversalFrom(change.newStart); + tree.edit( + this._treeEditForBufferChange( + change.newStart, + change.oldEnd, + Point.fromObject(change.oldStart).traverse(newExtent), + change.oldText, + change.newText + ) ); - this.foldResolver = new FoldResolver(this.buffer, this); + } - // What should our language scope name be? Should we even have one? - let languageScope; - if (depth === 0) { - languageScope = this.grammar.scopeName; - } else { - // Injections can control the base scope name of the grammar being - // injected. - languageScope = injectionPoint.languageScope; + if (includedRanges) { + this.setCurrentRanges(includedRanges); + } + + let affectedRange = this.editedRange; + this.lastTransactionEditedRange = this.editedRange; + this.editedRange = null; - // The `languageScope` parameter can be a function. That means we won't - // decide now; we'll decide later on a range-by-range basis. + let foldRangeList = new RangeList(); - // Honor an explicit `null`, but fall back to the default scope name - // otherwise. - if (languageScope === undefined) { - languageScope = this.grammar.scopeName; - } - } + // Look for a node that was marked with `invalidateOnChange`. If we find + // one, we should invalidate that node's entire buffer region. + if (affectedRange) { - this.languageScope = languageScope; - if (languageScope === null || typeof languageScope === 'function') { - // If `languageScope` is a function, we'll still often end up with a - // `languageScopeId` (or several); we just won't be able to compute it - // ahead of time. - this.languageScopeId = null; - } else { - this.languageScopeId = this.languageMode.idForScope(languageScope); + // First look for nodes that were previously marked with + // `highlight.invalidateOnChange`; those will specify ranges for which + // we'll need to force a re-highlight. + let node = this.searchForNodesInRange( + affectedRange, + this.nodesToInvalidateOnChange + ); + if (node) { + this.rangeList.add(node.range); } - this.ready = true; - }); - } - // Previously we were storing compiled queries directly on the language - // layer. Now we store them on a `queries` object instead. - // - // All internal usages have been changed, but packages might still try to - // find these queries at their old locations. For that reason, we'll define - // shims for backward compatibility. - get highlightsQuery() { return this.queries.highlightsQuery; } - set highlightsQuery(value) { this.queries.highlightsQuery = value; } + // Now look for nodes that were previously marked with + // `fold.invalidateOnChange`; those will specify ranges that need their + // fold cache updated even when highlighting is unaffected. + let foldNode = this.searchForNodesInRange( + affectedRange, + this.foldNodesToInvalidateOnChange + ); + if (foldNode) { + foldRangeList.add(foldNode.range); + } + } - get indentsQuery() { return this.queries.indentsQuery; } - set indentsQuery(value) { this.queries.indentsQuery = value; } + this.nodesToInvalidateOnChange.clear(); + this.foldNodesToInvalidateOnChange.clear(); - get foldsQuery() { return this.queries.foldsQuery; } - set foldsQuery(value) { this.queries.foldsQuery = value; } + if (this.lastSyntaxTree) { + const rangesWithSyntaxChanges = this.lastSyntaxTree.getChangedRanges(tree); - get tagsQuery() { return this.queries.tagsQuery; } - set tagsQuery(value) { this.queries.tagsQuery = value; } + let oldSyntaxTree = this.lastSyntaxTree; + this.lastSyntaxTree = tree; - get localsQuery() { return this.queries.localsQuery; } - set localsQuery(value) { this.queries.localsQuery = value; } + let oldTree = this.tree; + this.tree = tree; + this.treeIsDirty = false; - isDirty() { - if (!this.tree) { return false; } - return this.tree.rootNode.hasChanges(); - } + oldTree?.delete(); + oldSyntaxTree?.delete(); - inspect() { - let { scopeName } = this.grammar; - return `[LanguageLayer ${scopeName || '(anonymous)'} depth=${this.depth} file=${this.buffer.getPath()}]`; - } + while (this.temporaryTrees.length > 0) { + let tree = this.temporaryTrees.pop(); + tree.delete(); + } - destroy() { - if (this.destroyed) { return; } - this.destroyed = true; + if (rangesWithSyntaxChanges.length > 0) { + for (const range of rangesWithSyntaxChanges) { + this.rangeList.add(rangeForNode(range)); + } - // Clean up all tree-sitter trees. - let temporaryTrees = this.temporaryTrees ?? []; - let trees = new Set([this.tree, this.lastSyntaxTree, ...temporaryTrees]); - trees = [...trees]; + const combinedRangeWithSyntaxChange = new Range( + rangesWithSyntaxChanges[0].startPosition, + last(rangesWithSyntaxChanges).endPosition + ); - this.tree = null; - this.lastSyntaxTree = null; - this.temporaryTrees = []; + if (affectedRange) { + this.rangeList.add(affectedRange); + affectedRange = affectedRange.union(combinedRangeWithSyntaxChange); + } else { + affectedRange = combinedRangeWithSyntaxChange; + } + } + } else { + this.tree = tree; + this.treeIsDirty = false; - while (trees.length > 0) { - let tree = trees.pop(); - if (!tree) { continue; } - tree.delete(); - } + // Store a reference to this tree so we can compare it with the next + // transaction's tree later on. + this.lastSyntaxTree = tree; - this.marker?.destroy(); - this.currentRangesLayer?.destroy(); - this.foldResolver?.reset(); - this.scopeResolver?.destroy(); - this.subscriptions.dispose(); + // Like legacy Tree-sitter, we're patching syntax nodes so that they have + // a `range` property that returns a `Range`. We're doing this for + // compatibility, but we can't get a reference to the node class itself; + // we have to wait until we have an instance and grab the prototype from + // there. + // + // This is the earliest place in the editor lifecycle where we're + // guaranteed to be holding an instance of `Node`. Once we patch it here, + // we're good to go. + // + ensureNodeIsPatched(tree.rootNode); - for (const marker of this.languageMode.injectionsMarkerLayer.getMarkers()) { - if (marker.parentLanguageLayer === this) { - marker.languageLayer.destroy(); + this.rangeList.add(rangeForNode(tree.rootNode)); + if (includedRanges) { + affectedRange = new Range( + includedRanges[0].startPosition, + last(includedRanges).endPosition + ); + } else { + affectedRange = MAX_RANGE; } } - } - - // Reload a query of a given type from the grammar. - async reloadGrammarQuery(queryType) { - if (!this.queries[queryType]) { return; } - let originalQuery = this.queries[queryType]; - try { - let query = await this.grammar.getQuery(queryType); - this.queries[queryType] = query; - // Force a re-highlight of this layer's entire region. - let range = this.getExtent(); + // Now that we've assembled and coalesced all the ranges that need + // invalidating, we'll invalidate them in buffer order. + for (let range of this.rangeList) { this.languageMode.emitRangeUpdate(range); - this.nodesToInvalidateOnChange.clear(); - this.foldNodesToInvalidateOnChange.clear(); - } catch (error) { - this.queries[queryType] = originalQuery; - console.error(`Error parsing query file: ${queryType}`); - console.error(error); } - } - // Observe the grammar for changes in queries. - // - // This won't happen very often. It can happen if a user edits a grammar’s - // query files, but those edits are only monitored in dev mode. - // - // It can also happen if a community package uses an API on - // {WASMTreeSitterGrammar} to modify a query after initial load. - observeQueryChanges() { - this.grammar.onDidChangeQuery(async ({ queryType }) => { - if (this._pendingQueryFileChange) { return; } - // Debounce the reloading. Sometimes multiple callbacks fire when a query - // file is saved. - this._pendingQueryFileChange = true; - await this.reloadGrammarQuery(queryType); - this._pendingQueryFileChange = false; - }) - } + for (let range of foldRangeList) { + // The fold cache is automatically cleared for any range that needs + // re-highlighting. But sometimes we need to go further and invalidate + // rows that don't even need highlighting changes. + this.languageMode.emitFoldUpdate(range); + } - getExtent() { - return this.marker?.getRange() ?? this.languageMode.buffer.getRange(); + if (affectedRange) { + let injectionPromise = this._populateInjections(affectedRange, nodeRangeSet); + if (injectionPromise) { + params.async = true; + return injectionPromise; + } + } } - // Run a highlights query for the given range and process the raw captures - // through a `ScopeResolver`. - getSyntaxBoundaries(from, to) { - let { buffer } = this.languageMode; - if (!(this.language && this.tree)) { - return [[], new OpenScopeMap()]; + setCurrentRanges(includedRanges) { + if (this.depth === 0) { return; } + let oldRangeMarkers = this.currentRangesLayer.getMarkers(); + for (let marker of oldRangeMarkers) { + marker.destroy(); } - from = buffer.clipPosition(Point.fromObject(from, true)); - to = buffer.clipPosition(Point.fromObject(to, true)); - - let boundaries = createTree(compareBoundaries); - let extent = this.getExtent(); + // These are the “official” ranges, received right after the parent layer's + // tree parse. We'll get a new set of official ranges at the end of the + // next transaction, but until then, we should try our best to adapt to + // buffer changes, and to allow each range to shift or grow or shrink so + // that off-schedule parses are more likely to be accurate. + for (let range of includedRanges) { + range = rangeForNode(range); + this.currentRangesLayer.markRange(range); + } + } - let captures = this.queries.highlightsQuery?.captures(this.tree.rootNode, from, to) ?? []; - this.scopeResolver.reset(); + getCurrentRanges() { + let markers = this.currentRangesLayer?.getMarkers(); + if (!markers || markers.length === 0) { return null; } + return markers.map(m => m.getRange()); + } - for (let capture of captures) { - let { node } = capture; - if (this.scopeResolver.shouldInvalidateOnChange(capture)) { - // This node wants to be invalidated in its entirety whenever a change - // happens within it. This setting should be added to a query whenever - // (a) a certain node's scope name can change just from its contents - // changing, and (b) that node can possibly span more than one line. - // - // Without this setting, we're liable to invalidate only the line that - // a change happened on, because we have no other way of knowing how - // significant of a change it is. If we were able to do so, we'd set - // this property automatically on any capture that (a) involved a - // `#match?` predicate, and (b) started and ended on different lines. - this.nodesToInvalidateOnChange.add(node.id); - } - // Phantom nodes invented by the parse tree. Indentation captures can use - // `allowEmpty` to force these to be considered, but for marking scopes, - // there's no need for it; it'd just cause us to open and close a scope - // in the same position. - if (node.childCount === 0 && node.text === '') { continue; } + // Checks whether a given {Point} lies within one of this layer's content + // ranges — not just its extent. The optional `exclusive` flag will return + // `false` if the point lies on a boundary of a content range. + containsPoint(point, exclusive = false) { + let ranges = this.getCurrentRanges() ?? [this.getExtent()]; + return ranges.some(r => r.containsPoint(point, exclusive)); + } - // Ask the `ScopeResolver` to process each capture in turn. Some captures - // will be ignored if they fail certain tests, and some will have their - // original range altered. - this.scopeResolver.store(capture); + // Returns a syntax tree for the current buffer. + // + // By default, this method will either return the current tree (if it's up to + // date) or synchronously parse the buffer into a new tree (if it isn't). + // + // If you don't want to force a re-parse and don't mind that the current tree + // might be stale, pass `force: false` as an option. + // + // In certain circumstances, the new tree might be promoted to the canonical + // tree for this layer. To prevent this, pass `anonymous: false` as an option. + // + // All trees returned by this method are managed by this language layer and + // will be deleted when the next transaction is complete. Retaining a + // reference to the returned tree will not prevent this from happening. To + // opt into managing the life cycle of the returned tree, copy it immediately + // when you receive it. + // + getOrParseTree({ force = true, anonymous = false } = {}) { + if (this.tree && (!this.treeIsDirty || !force)) { return this.tree; } + + // Eventually we'll take this out, but for now it serves as an indicator of + // how often we have to manually re-parse in between transactions — + // something we'd like to do as little as possible. + if (atom.inDevMode()) { + console.warn('Re-parsing tree!', this.inspect(), this.treeIsDirty); } - // A `HighlightIterator` will want to know which scopes were already open - // when this range began. Sadly, we also have to keep track of the point in - // the buffer at which each of those scopes would've been added, because - // it's possible that later we'll find out that a deeper iterator has - // suppressed the application of that scope boundary at that position. - let alreadyOpenScopes = new OpenScopeMap(); + let ranges = null; + if (this.depth > 0) { + ranges = this.getCurrentRanges().map(r => { + return rangeToTreeSitterRangeSpec(r, this.buffer); + }); + } - // How do we add a layer's root scope? There's no easy answer. - // - // If we rely on the grammar author to map the tree's root node to the - // language's root scope, they could forget to do it, or map a scope that - // isn't the same as the one given in the grammar's config file. We'd also - // limit the ability of that `highlights.scm` to be used in multiple - // contexts — for example, the HTML grammar would always carry around a - // `text.html.basic` scope name no matter where we put it. + // The goal here is that, if a re-parse is needed in between transactions, + // we assign the result back to `this.tree` so that we can at least cut + // down on the incremental amount of work that the end-of-transaction parse + // has to do — it can pick up where we left off. So for the root language + // layer, this represents more of a shifting of work than a duplication. // - // If we manage it ourselves, we invite the complexity of _manually_ - // applying a root scope — plus knowing when it actually _shouldn't_ be - // applied, and making sure that `scopeDescriptorForPosition` returns - // results that agree with reality. + // But this isn't safe to do for injection layers because `ranges` may be + // stale, despite our efforts to keep them fresh through markers. The + // stakes are low enough for indents that we can attempt a tree parse and + // act on the results even if we're not certain they're accurate — but when + // we do another scheduled incremental parse, we have to be 100% sure that + // we're working from an accurate tree. // - // Option B is the one we reluctantly choose because it gives us more - // control. Here's roughly how this works: + // Re-parsing of an injection layer can only safely happen when we know its + // true ranges, and that cannot be determined except through the process + // that an injection layer's parent goes through during the + // end-of-transaction update, unless we're willing to do an off-schedule + // parse of _all_ language layers in this layer's ancestry. That's not + // completely out of the question for the future — but, failing that, there + // probably isn't a way to “fix” this for injection layers except through + // cutting down on off-schedule parses. // - // * The base language scope on the root layer will always be applied. - // * The base language scope on an injection layer will be applied in areas - // where that injection is active, unless we were told otherwise in - // `addInjectionPoint`. That method's `languageScope` property can - // define another scope name to use instead… or pass `null`, signaling - // that we should skip the root scope altogether. (This is the best - // approach for special-purpose injections like `todo` and `hyperlink`.) - // * A grammar can still opt to place a root scope conditionally based on - // whether it's on an injection layer. This also allows a grammar to - // apply that root scope more selectively in injection contexts if - // desired. + let then = performance.now() + let tree = this.languageMode.parse( + this.language, + this.tree, + ranges, + // { tag: `Re-parsing ${this.inspect()}` } + ); + let now = performance.now() - // Ensure the whole source file (or whole bounds of the injection) is - // annotated with the language's base scope name. We _do not_ want to leave - // this up to the grammar author; it's too important. Also ensure that the - // base scope name of an injection covers the true boundaries of where that - // injection is active. - // - // NOTE: If an injection is active over a number of disjoint ranges, this - // may have some surprising effects. For instance, areas where PHP is - // injected into HTML… - // - //

- // - // …will include ``, but may exclude the - // spaces between those tokens. This is a consequence of the design of - // a particular tree-sitter parser and should be mitigated with the - // `includeAdjacentWhitespace` option of `addInjectionPoint`. + let parseTime = now - then; + + // Since we can't look into the future, we don't know how many times during + // this transaction we'll be asked to make indentation sugestions. If we + // knew ahead of time, we'd be able to decide at the beginning of a + // transaction whether we could afford to do synchronous indentation. // - let includedRanges = this.depth === 0 ? [extent] : this.getCurrentRanges(); + // Instead, we do the next best thing: we start out doing synchronous + // indentation, then fall back to asynchronous indentation once we've + // exceeded our time budget. So we keep track of how long each reparse + // takes and subtract it from our budget. + this.languageMode.currentTransactionReparseBudgetMs -= parseTime; - let languageScopeIdForRange = () => this.languageScopeId; - if (typeof this.languageScope === 'function') { - languageScopeIdForRange = (range) => { - let scopeName = this.languageScope(this.grammar, this.languageMode.buffer, range); - if (Array.isArray(scopeName)) { - return scopeName.map(s => this.languageMode.idForScope(s)); - } else { - return this.languageMode.idForScope(scopeName); - } - }; + if (this.depth === 0 && !anonymous) { + this.tree = tree; + this.treeIsDirty = false; + } else { + // Keep track of any off-schedule trees we generate so that we can GC them + // when the next transaction is done. + this.temporaryTrees.push(tree); } + return tree; + } - if (this.languageScopeId || typeof this.languageScope === 'function') { - for (let range of includedRanges) { - // Filter out ranges that have no intersection with ours. - if (range.end.isLessThanOrEqual(from)) { continue; } - if (range.start.isGreaterThanOrEqual(to)) { continue; } + getText() { + let { buffer } = this.languageMode; + if (!this.marker) { + return buffer.getText(); + } else { + return buffer.getTextInRange(this.marker.getRange()); + } + } - let languageScopeIds = languageScopeIdForRange(range); - if (!languageScopeIds) continue; + // Given a point, return all syntax captures that are active at that point. + // Used by `bufferRangeForScopeAtPosition`. + scopeMapAtPosition(point) { + if (!this.language || !this.tree) { return []; } + let { scopeResolver } = this; + scopeResolver.reset(); - if (!Array.isArray(languageScopeIds)) { - languageScopeIds = [languageScopeIds]; - } + // If the cursor is resting before column X, we want all scopes that cover + // the character in column X. + let captures = this.queries.highlightsQuery?.captures( + this.tree.rootNode, + { + startPosition: point, + endPosition: point.translate(ONE_CHAR_FORWARD_TRAVERSAL) + } + ) ?? []; - if (range.start.isLessThan(from)) { - // If we get this far, we know that the base language scope was open - // when our range began. - alreadyOpenScopes.set( - range.start, - languageScopeIds - ); - } else { - // Range start must be between `from` and `to`, or else equal `from` - // exactly. - for (let id of languageScopeIds) { - this.scopeResolver.setBoundary( - range.start, - id, - 'open', - { root: true, length: Infinity } - ); - } - } + let results = []; + for (let capture of captures) { + // Storing the capture will return its range (after any potential + // adjustments) — or `false`, to signify that the capture was ignored. + let range = scopeResolver.store(capture); + if (!range) { continue; } - if (range.end.isGreaterThan(to)) { - // Do nothing; we don't need to set this boundary. - } else { - // The range must end somewhere within our range. - // - // Close the boundaries in the opposite order of how we opened them. - for (let i = languageScopeIds.length - 1; i >= 0; i--) { - this.scopeResolver.setBoundary( - range.end, - languageScopeIds[i], - 'close', - { root: true, length: Infinity } - ); - } - } + // Since the range might have been adjusted, we wait until after + // resolution. + if (comparePoints(range.endPosition, point) === 0) { continue; } + if (isBetweenPoints(point, range.startPosition, range.endPosition)) { + results.push({ capture, adjustedRange: range }); } } - // `ScopeResolver` ensures that these points will be iterated in buffer - // order. - for (let [point, data] of this.scopeResolver) { - // The boundaries that occur before the start of our range will tell us - // which scopes should already be open when our range starts. - if (point.isLessThan(from)) { - alreadyOpenScopes.set(point, data.open); - for (let c of data.close) { - alreadyOpenScopes.removeLastOccurrenceOf(c); - } - continue; - } else if (point.isGreaterThan(to)) { - continue; - } + scopeResolver.reset(); - let OPEN_KEY = { position: point, boundary: 'start' }; - let CLOSE_KEY = { position: point, boundary: 'end' }; + // Sort from biggest to smallest. + results = results.sort((a, b) => { + return nodeBreadth(b.adjustedRange) - nodeBreadth(a.adjustedRange); + }); - if (data.close.length > 0) { - boundaries = boundaries.insert(CLOSE_KEY, { - scopeIds: Object.freeze(data.close) - }); - } + return results; + } - if (data.open.length > 0) { - boundaries = boundaries.insert(OPEN_KEY, { - scopeIds: Object.freeze(data.open) - }); + // Like `WASMTreeSitterLanguageMode#getSyntaxNodeAtPosition`, but for just this + // layer. + getSyntaxNodeAtPosition(position, where = FUNCTION_TRUE) { + if (!this.language || !this.tree) { return null; } + let { buffer } = this.languageMode; + + let index = buffer.characterIndexForPosition(position); + let node = this.tree.rootNode.descendantForIndex(index); + + while (node) { + if (where(node, this.grammar)) { + return node; } + node = node.parent; } - return [boundaries, alreadyOpenScopes]; + return null; } - buildHighlightIterator() { - if (this.tree) { - return new LayerHighlightIterator(this, this.tree); - } else { - return new NullLayerHighlightIterator(); + // Used to find the most specific node affected by an edited range. + getSyntaxNodeContainingRange(range, where = FUNCTION_TRUE) { + if (!this.language || !this.tree) { return null; } + let { buffer } = this.languageMode; + + if (range.start.isEqual(range.end)) { + return this.getSyntaxNodeAtPosition(range.start, where); } + + let indexStart = buffer.characterIndexForPosition(range.start); + let indexEnd = buffer.characterIndexForPosition(range.end); + + let rangeBreadth = indexEnd - indexStart; + let node = this.getSyntaxNodeAtPosition( + range.start, + (node) => { + let breadth = node.endIndex - node.startIndex; + let qualifies = node.startIndex <= indexEnd && + node.endIndex >= indexEnd && + breadth >= rangeBreadth; + return qualifies && where(node); + } + ); + + return node ?? null; } - handleTextChange(edit, oldText, newText) { - // Any text change within the layer invalidates our cached fold boundary - // tree. This usually isn't a big deal because the language mode's own cache - // is able to adjust when content shifts up and down, so typically only the - // ranges that actually change will have fold data re-queried. - if (this.foldResolver) { this.foldResolver.reset(); } + _populateInjections(range, nodeRangeSet) { + if (!this.tree) { return; } + const promises = []; - const { - startPosition, - oldEndPosition, - newEndPosition - } = edit; + // We won't touch _all_ injections, but we will touch any injection that + // could possibly have been affected by this layer's update. + let existingInjectionMarkers = this.languageMode.injectionsMarkerLayer + .findMarkers({ intersectsRange: range }) + .filter(marker => marker.parentLanguageLayer === this); - if (this.tree) { - this.tree.edit(edit); - if (this.lastSyntaxTree && this.tree !== this.lastSyntaxTree) { - // This happens after an off-schedule parse that we've decided we can - // re-use at the end of the current transaction. But when that happens, - // we'll call `getChangedRanges` between `lastSyntaxTree` and the new - // tree, so `lastSyntaxTree` needs to receive the same tree edits. - this.lastSyntaxTree.edit(edit); - } - // We're tentatively marking this tree as dirty; we won't know if it - // needs to be reparsed until the transaction is done. If it doesn't, - // that means that the edits didn't encroach on the contents of the - // layer, and we'll mark those trees as clean at the end of the - // transaction. - this.treeIsDirty = true; - if (this.editedRange) { - if (startPosition.isLessThan(this.editedRange.start)) { - this.editedRange.start = startPosition; - } if (oldEndPosition.isLessThan(this.editedRange.end)) { - this.editedRange.end = newEndPosition.traverse( - this.editedRange.end.traversalFrom(oldEndPosition) - ); - } else { - this.editedRange.end = newEndPosition; + if (existingInjectionMarkers.length > 0) { + // Enlarge our range to contain all of the injection zones in the + // affected buffer range. + let earliest = range.start, latest = range.end; + for (let marker of existingInjectionMarkers) { + range = marker.getRange(); + if (range.start.compare(earliest) === -1) { + earliest = range.start; + } + if (range.end.compare(latest) === 1) { + latest = range.end; } - } else { - this.editedRange = new Range(startPosition, newEndPosition); } - } - if (this.patchSinceCurrentParseStarted) { - this.patchSinceCurrentParseStarted.splice( - startPosition, - oldEndPosition.traversalFrom(startPosition), - newEndPosition.traversalFrom(startPosition), - oldText, - newText - ) + range = range.union(new Range(earliest, latest)); } - } - async update(nodeRangeSet, params = {}) { - if (!this.languageMode.useAsyncParsing) { - // Practically speaking, updates that affect _only this layer_ will happen - // synchronously, because we've made sure not to call this method until the - // root grammar's tree-sitter parser has been loaded. But we can't load any - // potential injection layers' languages because we don't know which ones - // we'll need _until_ we parse this layer's tree for the first time. - // - // Thus the first call to `_populateInjections` will probably go async - // while we wait for the injections' parsers to load, and the user might - // notice the delay. But once that happens, all subsequent updates _should_ - // be synchronous, except for a case where a change in the buffer causes us - // to need a new kind of injection whose parser hasn't yet been loaded. - if (!this.ready) { await this.languageLoaded; } - await this._performUpdate(nodeRangeSet); - return true; - } + // Why do we have to do this explicitly? Because `descendantsOfType` will + // incorrectly return nodes if the range runs from (0, 0) to (0, 0). All + // other empty ranges seem not to have this problem. Upon cursory + // inspection, this bug doesn't seem to be limited to `web-tree-sitter`. + if (range.isEmpty()) { return; } - if (!this.currentParsePromise) { - do { - params = { ...params, async: false }; - if (!this.ready) { - params.async = true; - await this.languageLoaded; - } - this.currentParsePromise = this._performUpdate(nodeRangeSet, params); - if (!params.async) { break; } - await this.currentParsePromise; - } while ( - !this.destroyed && - (!this.tree || this.tree.rootNode.hasChanges()) - ); + // Now that we've enlarged the range, we might have more existing injection + // markers to consider. But check for containment rather than intersection + // so that we don't have to enlarge it again. + existingInjectionMarkers = this.languageMode.injectionsMarkerLayer + .findMarkers({ startsInRange: range, endsInRange: range }) + .filter(marker => marker.parentLanguageLayer === this); - this.currentParsePromise = null; - // `true` means that this update occurs in its own distinct transaction. - return true; - } else { - // `false` means that the previous transaction isn't done, so this - // transaction's work will be subsumed into it. - return false; - } - } + const markersToUpdate = new Map(); - getLocalReferencesAtPoint(point) { - if (!this.queries.localsQuery) { return []; } - let captures = this.queries.localsQuery.captures( - this.tree.rootNode, - point, - point + 1 + // Query for all the nodes that could possibly prompt the creation of + // injection points. + const nodes = this.tree.rootNode.descendantsOfType( + Object.keys(this.grammar.injectionPointsByType), + range.start, + range.end ); - captures = captures.filter(cap => { - if (cap.name !== 'local.reference') { return false; } - if (!rangeForNode(cap.node).containsPoint(point)) { - return false; - } - return true; - }); + let existingInjectionMarkerIndex = 0; + let newLanguageLayers = 0; + for (const node of nodes) { + // A given node can be the basis for an arbitrary number of injection + // points, but first it has to pass our gauntlet of tests: + for (const injectionPoint of this.grammar.injectionPointsByType[node.type]) { + // Does it give us a language string? + const languageName = injectionPoint.language(node); + if (!languageName) { continue; } - let nodes = captures.map(cap => cap.node); - nodes = nodes.sort((a, b) => b.range.compare(a.range)); + // Does that string match up with a grammar that we recognize? + const grammar = this.languageMode.grammarForLanguageString( + languageName); + if (!grammar) { continue; } - return nodes; - } + // Does it offer us a node, or array of nodes, which a new injection + // layer should use for its content? + const contentNodes = injectionPoint.content(node, this.buffer); + if (!contentNodes) { continue; } - // EXPERIMENTAL: Given a local reference node, tries to find the node that - // defines it. - findDefinitionForLocalReference(node, captures = null) { - if (!this.queries.localsQuery) { return []; } - let name = node.text; - if (!name) { return []; } - let localRange = rangeForNode(node); - let globalScope = this.tree.rootNode; + const injectionNodes = [].concat(contentNodes); + if (!injectionNodes.length) continue; - if (!captures) { - captures = this.groupLocalsCaptures( - this.queries.localsQuery.captures( - globalScope, - globalScope.startPosition, - globalScope.endPosition - ) - ); - } + const injectionRange = node.range; - let { scopes, definitions } = captures; + let marker; + + // It's surprisingly hard to match up the injection point that we now + // know we need… with the one that may already exist that was created + // or updated based on the state of the tree from the last keystroke. + // There is no continuity between the previous tree and the new tree + // that we can rely on. Unless the marker and the base node of the + // injection point agree on an exact range, we can't be sure enough to + // re-use an existing layer. + // + // This isn't a huge deal because (a) markers are good at adapting to + // changes, so those two things will agree more often than you think; + // (b) even when they don't agree, it's not very costly to destroy and + // recreate another `LanguageLayer`. + // + // Since both `existingInjectionMarkers` and `nodes` are guaranteed to + // be sorted in buffer order, we can take shortcuts in how we pair them + // up. + // + for ( + let i = existingInjectionMarkerIndex, + n = existingInjectionMarkers.length; + i < n; + i++ + ) { + const existingMarker = existingInjectionMarkers[i]; + const comparison = existingMarker.getRange().compare(injectionRange); + if (comparison > 0) { + // This marker seems to occur after the range we want to inject + // into, meaning there's a good chance it's not ours. And it means + // that none of the remaining markers will likely be our candidate, + // either; so we should give up and create a new one. + break; + } else if (comparison === 0) { + // Luckily, the range matches up exactly, so this is almost + // certainly a previous version of the same intended injection. It + // also means that any markers before this point in the list have + // either already matched with candidate injection points or cannot + // possibly match up; thus we can ignore them for the rest of the + // matching process. + existingInjectionMarkerIndex = i; + if (existingMarker.languageLayer.grammar === grammar) { + marker = existingMarker; + break; + } + } else { + // This marker occurs before our range. Since all injection + // candidates from this point forward are guaranteed to be of an + // equal or later range, there's no chance of this marker matching + // any candidates from this point forward. We can ignore it, and + // anything before it, in subsequent trips through the loop. + existingInjectionMarkerIndex = i; + } + } - // Consider only the scopes that can influence our local node. - let relevantScopes = scopes.filter((scope) => { - let range = rangeForNode(scope); - return range.containsRange(localRange); - }).sort((a, b) => { - a.range.compare(b.range) - }); + if (!marker) { + // If we didn't match up with an existing marker/layer, we'll have to + // create them. + marker = this.languageMode.injectionsMarkerLayer.markRange( + injectionRange); - relevantScopes.push(globalScope); + marker.languageLayer = new LanguageLayer( + marker, + this.languageMode, + grammar, + this.depth + 1, + injectionPoint + ); - // Consider only the definitions whose names match the target's. - let relevantDefinitions = definitions.filter( - (def) => def.text === name - ); - if (relevantDefinitions.length === 0) { return []; } + marker.parentLanguageLayer = this; + // eslint-disable-next-line no-unused-vars + newLanguageLayers++; + } - let definitionsByBaseScope = new Index(); - for (let rDef of relevantDefinitions) { - // Find all the scopes that include this definition. The largest of those - // scopes will be its "base" scope. If there are no scopes that include - // this definition, it must have been defined globally. - let rDefScopes = scopes.filter(s => { - return isBetweenPoints( - rDef.startPosition, - s.startPosition, - s.endPosition + markersToUpdate.set( + marker, + new NodeRangeSet( + nodeRangeSet, + injectionNodes, + injectionPoint + ) ); - }).sort((a, b) => { - return rangeForNode(b).compare(rangeForNode(a)); - }); + } + } - let baseScope = rDefScopes[0] ?? globalScope; + let staleLanguageLayers = 0; + for (const marker of existingInjectionMarkers) { + // Any markers that didn't get matched up with injection points are now + // stale and should be destroyed. + if (!markersToUpdate.has(marker)) { + this.languageMode.emitRangeUpdate(marker.getRange()); + marker.languageLayer.destroy(); + // eslint-disable-next-line no-unused-vars + staleLanguageLayers++; + } + } - // Group each definition by its scope. Since any variable can be - // redefined an arbitrary number of times, each scope might include - // multiple definitions of this identifier. - definitionsByBaseScope.add(baseScope, rDef); + if (markersToUpdate.size > 0) { + for (const [marker, nodeRangeSet] of markersToUpdate) { + promises.push(marker.languageLayer.update(nodeRangeSet)); + } } - // Moving from smallest to largest scope, get definitions that were made in - // that scope, and return the closest one to the reference. - for (let scope of relevantScopes) { - let definitionsInScope = definitionsByBaseScope.get(scope) ?? []; - let { length } = definitionsInScope; - if (length === 0) { continue; } - if (length === 1) { return definitionsInScope[0]; } + return Promise.all(promises); + } - // Here's how we want to sort these candidates: - // - // * In each scope, look for a definitions that happen before the local's - // position. The closest such definition in the narrowest scope is our - // ideal target. - // * Failing that, take note of all the definitions that happened _after_ - // the local's position in all relevant scopes. Choose the closest to - // the local. - // - let definitionsBeforeLocal = []; - let definitionsAfterLocal = []; + _treeEditForBufferChange(start, oldEnd, newEnd, oldText, newText) { + let startIndex = this.buffer.characterIndexForPosition(start); + return { + startIndex, + oldEndIndex: startIndex + oldText.length, + newEndIndex: startIndex + newText.length, + startPosition: start, + oldEndPosition: oldEnd, + newEndPosition: newEnd + }; + } +} - for (let def of definitionsInScope) { - let result = comparePoints(def.startPosition, localRange.start); +// An injection `LanguageLayer` may need to parse and highlight a strange +// subset of its stated range — for instance, all the descendants within a +// parent that are of a particular type. A `NodeRangeSet` is how that strange +// subset is expressed. +class NodeRangeSet { + constructor(previous, nodes, injectionPoint) { + this.previous = previous; + this.newlinesBetween = injectionPoint.newlinesBetween; + this.includeAdjacentWhitespace = injectionPoint.includeAdjacentWhitespace; + this.includeChildren = injectionPoint.includeChildren; - let bucket = result < 0 ? - definitionsBeforeLocal : - definitionsAfterLocal; + // We shouldn't retain references to nodes here because the tree might get + // disposed of later. Let's compile the information we need now while we're + // sure the tree is fresh. + this.nodeSpecs = []; + for (let node of nodes) { + this.nodeSpecs.push(this.getNodeSpec(node, true)); + } + } - bucket.push(def); + getNodeSpec(node, getChildren) { + let { startIndex, endIndex, startPosition, endPosition, id } = node; + let result = { startIndex, endIndex, startPosition, endPosition, id }; + if (getChildren && node.childCount > 0) { + result.children = []; + for (let child of node.children) { + result.children.push(this.getNodeSpec(child, false)); } + } + return result; + } - if (definitionsBeforeLocal.length > 0) { - let maxBeforeLocal; - for (let def of definitionsBeforeLocal) { - if (!maxBeforeLocal) { - maxBeforeLocal = def; - continue; - } + getRanges(buffer) { + const previousRanges = this.previous?.getRanges(buffer); + let result = []; - let result = comparePoints(def, maxBeforeLocal); - if (result > 0) { - maxBeforeLocal = def; + for (let node of this.nodeSpecs) { + // An injection point isn't given the point at which the buffer ends, so + // it's free to return an `endIndex` of `Infinity` here and rely on us to + // clip it to the boundary of the buffer. + if (node.endIndex === Infinity) { + node = this._clipRange(node, buffer); + } + let position = node.startPosition, index = node.startIndex; + + if (node.children && !this.includeChildren) { + // If `includeChildren` is `false`, we're effectively collecting all + // the disjoint text nodes that are direct descendants of this node. + for (const child of node.children) { + const nextIndex = child.startIndex; + if (nextIndex > index) { + this._pushRange(buffer, previousRanges, result, { + startIndex: index, + endIndex: nextIndex, + startPosition: position, + endPosition: child.startPosition + }); } + position = child.endPosition; + index = child.endIndex; } - return maxBeforeLocal; } - // TODO: For definitions that happen after the local in the buffer, it's - // not 100% clear what the right answer should be. I imagine it varies by - // language. Best guess for now is the one that's closest to the local - // reference. - let minAfterLocal; - for (let def of definitionsAfterLocal) { - if (!minAfterLocal) { - minAfterLocal = def; - continue; + if (node.endIndex > index) { + this._pushRange(buffer, previousRanges, result, { + startIndex: index, + endIndex: node.endIndex, + startPosition: position, + endPosition: node.endPosition + }); + } + } + + let whitespaceRanges = []; + if (this.includeAdjacentWhitespace && result.length > 1) { + // Look at the region between each pair of results. If it's entirely + // whitespace, include it in the range. + for (let i = 1; i < result.length; i++) { + let current = result[i], previous = result[i - 1]; + if (current.startIndex === previous.endIndex) { continue; } + let pseudoRange = { + startPosition: previous.endPosition, + startIndex: previous.endIndex, + endPosition: current.startPosition, + endIndex: current.startIndex + }; + let rangeText = buffer.getTextInRange(rangeForNode(pseudoRange)); + if (!/\S/.test(rangeText)) { + whitespaceRanges.push(pseudoRange); } + } + result.push(...whitespaceRanges); + result = result.sort((a, b) => { + return a.startIndex - b.startIndex || + a.endIndex - b.endIndex; + }); + } + return this._consolidateRanges(result); + } - let result = comparePoints(def, minAfterLocal); - if (result < 0) { - minAfterLocal = def; + _clipRange(range, buffer) { + // Convert this range spec to an actual `Range`, clip it, then convert it + // back to a range spec with accurate `startIndex` and `endIndex` values. + let clippedRange = buffer.clipRange(rangeForNode(range)); + return rangeToTreeSitterRangeSpec(clippedRange, buffer); + } + + // Combine adjacent ranges to minimize the number of boundaries. + _consolidateRanges(ranges) { + if (ranges.length === 1) { return ranges; } + let consolidated = []; + let candidate; + let lastIndex = ranges.length - 1; + for (let i = 0; i < ranges.length; i++) { + let range = ranges[i]; + if (!candidate) { + candidate = range; + continue; + } + if (candidate.endIndex === range.startIndex) { + // Keep enlarging the last node for as long as subsequent nodes are + // adjacent to it. + candidate = { + startIndex: candidate.startIndex, + startPosition: candidate.startPosition, + endIndex: range.endIndex, + endPosition: range.endPosition + }; + if (i === lastIndex) { + consolidated.push(candidate); + } + } else { + // We found a disjoint range, so push our candidate into the result set + // and promote a new candidate (unless we're at the end). + consolidated.push(candidate); + if (i === lastIndex) { + consolidated.push(range); + } else { + candidate = range; } } - - return minAfterLocal; } + + return consolidated; } - groupLocalsCaptures(captures) { - let scopes = []; - let definitions = []; - let references = []; + coversRange(candidateRange) { + let ranges = this.getRanges().map(r => rangeForNode(r)); + return ranges.some(range => { + return range.containsRange(candidateRange); + }); + } - for (let capture of captures) { - let { name, node } = capture; - switch (name) { - case 'local.scope': - scopes.push(node); - break; - case 'local.definition': - definitions.push(node); - break; - case 'local.reference': - references.push(node); - break; + _pushRange(buffer, previousRanges, newRanges, newRange) { + if (!previousRanges) { + if (this.newlinesBetween) { + const { startIndex, startPosition } = newRange; + this._ensureNewline(buffer, newRanges, startIndex, startPosition); } + newRanges.push(newRange); + return; } - return { scopes, definitions, references }; - } - - // Given a range and a `Set` of node IDs, test if any of those nodes' ranges - // overlap with the given range. - // - // We use this to test if a given edit should trigger the behavior indicated - // by `(fold|highlight).invalidateOnChange`. - searchForNodesInRange(range, nodeIdSet) { - let node = this.getSyntaxNodeContainingRange( - range, - n => nodeIdSet.has(n.id) - ); - - if (node) { - // One of this node's ancestors might also be in our list, so we'll - // traverse upwards and find out. - let ancestor = node.parent; - while (ancestor) { - if (nodeIdSet.has(ancestor.id)) { - node = ancestor; - } - ancestor = ancestor.parent; + for (const previousRange of previousRanges) { + if (previousRange.endIndex <= newRange.startIndex) continue; + if (previousRange.startIndex >= newRange.endIndex) break; + const startIndex = Math.max( + previousRange.startIndex, + newRange.startIndex + ); + const endIndex = Math.min(previousRange.endIndex, newRange.endIndex); + const startPosition = Point.max( + previousRange.startPosition, + newRange.startPosition + ); + const endPosition = Point.min( + previousRange.endPosition, + newRange.endPosition + ); + if (this.newlinesBetween) { + this._ensureNewline(buffer, newRanges, startIndex, startPosition); } - return node; + newRanges.push({ startIndex, endIndex, startPosition, endPosition }); } - return null; } - async _performUpdate(nodeRangeSet, params = {}) { - // It's much more common in specs than in real life, but it's always - // possible for a layer to get destroyed during the async period between - // layer updates. - if (this.destroyed) return; + // For injection points with `newlinesBetween` enabled, ensure that a + // newline is included between each disjoint range. + _ensureNewline(buffer, newRanges, startIndex, startPosition) { + const lastRange = last(newRanges); + if (lastRange && lastRange.endPosition.row < startPosition.row) { + newRanges.push({ + startPosition: new Point( + startPosition.row - 1, + buffer.lineLengthForRow(startPosition.row - 1) + ), + endPosition: new Point(startPosition.row, 0), + startIndex: startIndex - startPosition.column - 1, + endIndex: startIndex - startPosition.column + }); + } + } +} - let includedRanges = null; - this.rangeList.clear(); +// A subclass of map that associates a set of scope names with the editor +// locations at which they are opened. +// +// In some complicated scenarios, we need to know where a scope was opened when +// deciding how to handle it. +class OpenScopeMap extends Map { + constructor() { + super(); + } - if (nodeRangeSet) { - includedRanges = nodeRangeSet.getRanges(this.languageMode.buffer); - if (includedRanges.length === 0) { - // We have no ranges to inject into. This layer should no longer exist. - const range = this.marker.getRange(); - this.destroy(); - this.languageMode.emitRangeUpdate(range); - return; - } + getScopesArray() { + let results = []; + let keys = [...this.keys()]; + keys.sort(comparePoints); + for (let key of keys) { + let value = this.get(key); + results.push(...value); } + return results; + } - this.patchSinceCurrentParseStarted = new Patch(); - let language = this.grammar.getLanguageSync(); - let tree; - if (this.languageMode.useAsyncParsing) { - tree = this.languageMode.parseAsync( - language, - this.tree, - includedRanges, - // { tag: `Parsing ${this.inspect()} (async) ${params.id}` } - ); - if (tree.then) { - params.async = true; - tree = await tree; + removeLastOccurrenceOf(scopeId) { + let candidateKey; + // Of the keys whose values include this scope, find the one that occurs + // latest in the document. + for (let key of this.keys()) { + let value = this.get(key); + if (!value.includes(scopeId)) continue; + if (!candidateKey || comparePoints(key, candidateKey) === 1) { + candidateKey = key; } - } else { - tree = this.languageMode.parse( - language, - this.tree, - includedRanges, - // { tag: `Parsing ${this.inspect()} (sync) ${params.id}` } - ); } + if (!candidateKey) return false; + removeLastOccurrenceOf(this.get(candidateKey), scopeId); + return true; + } +} - let changes = this.patchSinceCurrentParseStarted.getChanges(); - this.patchSinceCurrentParseStarted = null; - - for (let change of changes) { - let newExtent = Point.fromObject(change.newEnd).traversalFrom(change.newStart); - tree.edit( - this._treeEditForBufferChange( - change.newStart, - change.oldEnd, - Point.fromObject(change.oldStart).traverse(newExtent), - change.oldText, - change.newText - ) - ); - } +// Like a map, but expects each key to have multiple values. +class Index extends Map { + constructor() { + super(); + } - if (includedRanges) { - this.setCurrentRanges(includedRanges); + add(key, ...values) { + let existing = this.get(key); + if (!existing) { + existing = []; + this.set(key, existing); } + existing.push(...values); + } +} - let affectedRange = this.editedRange; - this.lastTransactionEditedRange = this.editedRange; - this.editedRange = null; - - let foldRangeList = new RangeList(); - // Look for a node that was marked with `invalidateOnChange`. If we find - // one, we should invalidate that node's entire buffer region. - if (affectedRange) { +// A class designed to aggregate and normalize a set of ranges. Each time a +// buffer range is added, it's compared to the existing list; if there are +// intersections with range already in the list, those intersections are +// combined into one larger range. +// +// Assumes all ranges are instances of `Range` rather than Tree-sitter range +// specs. +class RangeList { + constructor() { + this.ranges = []; + } - // First look for nodes that were previously marked with - // `highlight.invalidateOnChange`; those will specify ranges for which - // we'll need to force a re-highlight. - let node = this.searchForNodesInRange( - affectedRange, - this.nodesToInvalidateOnChange - ); - if (node) { - this.rangeList.add(node.range); - } + clear() { + this.ranges.length = 0; + } - // Now look for nodes that were previously marked with - // `fold.invalidateOnChange`; those will specify ranges that need their - // fold cache updated even when highlighting is unaffected. - let foldNode = this.searchForNodesInRange( - affectedRange, - this.foldNodesToInvalidateOnChange - ); - if (foldNode) { - foldRangeList.add(foldNode.range); + add(newRange) { + let intersecting = []; + for (let range of this.ranges) { + if (newRange.intersectsWith(range)) { + intersecting.push(range); } } - this.nodesToInvalidateOnChange.clear(); - this.foldNodesToInvalidateOnChange.clear(); - - if (this.lastSyntaxTree) { - const rangesWithSyntaxChanges = this.lastSyntaxTree.getChangedRanges(tree); - - let oldSyntaxTree = this.lastSyntaxTree; - this.lastSyntaxTree = tree; + for (let i = intersecting.length - 1; i >= 0; i--) { + let index = this.ranges.indexOf(intersecting[i]); + this.ranges.splice(index, 1); + } + while (intersecting.length > 0) { + newRange = newRange.union(intersecting.shift()); + } + this.insertOrdered(newRange); + } - let oldTree = this.tree; - this.tree = tree; - this.treeIsDirty = false; + insertOrdered(newRange) { + let index = this.ranges.findIndex(r => { + return r.start.compare(newRange.start) > 0; + }); + this.ranges.splice(index, 0, newRange); + } - oldTree?.delete(); - oldSyntaxTree?.delete(); + inspect() { + let ranges = this.ranges.map(r => r.toString()); + return `[RangeList: ${ranges.join(', ')}]`; + } - while (this.temporaryTrees.length > 0) { - let tree = this.temporaryTrees.pop(); - tree.delete(); - } + *[Symbol.iterator]() { + for (let range of this.ranges) { + yield range; + } + } +} - if (rangesWithSyntaxChanges.length > 0) { - for (const range of rangesWithSyntaxChanges) { - this.rangeList.add(rangeForNode(range)); - } +// Private: A class that manages indentation hinting for a single editor. +// +// Each instance of `WASMTreeSitterLanguageMode` has exactly one instance of +// `IndentResolver`; the purpose of this class is to encapsulate indentation +// logic instead of having it dominate the language mode for those +// familiarizing themselves with the code. +class IndentResolver { + constructor(buffer, languageMode) { + this.buffer = buffer; + this.languageMode = languageMode; + this.emitter = new Emitter(); + } - const combinedRangeWithSyntaxChange = new Range( - rangesWithSyntaxChanges[0].startPosition, - last(rangesWithSyntaxChanges).endPosition - ); + // Get the suggested indentation level for an existing line in the + // buffer. + // + // See {WASMTreeSitterLanguageMode::suggestedIndentForBufferRow}. + suggestedIndentForBufferRow(row, tabLength, rawOptions = {}) { + if (row === 0) { return 0; } + let root = this.languageMode.rootLanguageLayer; + if (!root || !root.tree || !root.ready) { return null; } + let { languageMode } = this; + let options = { + skipEvent: false, + skipBlankLines: true, + skipDedentCheck: false, + preserveLeadingWhitespace: false, + indentationLevels: null, + forceTreeParse: false, + ...rawOptions + }; - if (affectedRange) { - this.rangeList.add(affectedRange); - affectedRange = affectedRange.union(combinedRangeWithSyntaxChange); - } else { - affectedRange = combinedRangeWithSyntaxChange; - } - } - } else { - this.tree = tree; - this.treeIsDirty = false; + let originalControllingLayer = options.controllingLayer; - // Store a reference to this tree so we can compare it with the next - // transaction's tree later on. - this.lastSyntaxTree = tree; + // Indentation hinting is a two-phase process. + // + // In phase 1, we determine `row`’s starting indent considering only the + // content of the previous row. + // + // In phase 2, we consider `row`’s own content to see if any of it suggests + // an alteration from the phase 1 value. + let comparisonRow = options.comparisonRow ?? this.getComparisonRow(row, options); - // Like legacy tree-sitter, we're patching syntax nodes so that they have - // a `range` property that returns a `Range`. We're doing this for - // compatibility, but we can't get a reference to the node class itself; - // we have to wait until we have an instance and grab the prototype from - // there. - // - // This is the earliest place in the editor lifecycle where we're - // guaranteed to be holding an instance of `Node`. Once we patch it here, - // we're good to go. + let existingIndent = 0; + if (options.preserveLeadingWhitespace) { + // When this option is true, the indent level we return will be _added + // to_ however much indentation is already present on the line. Whatever + // the purpose of this option, we can't just pretend it isn't there, + // because it will produce silly outcomes. Instead, let's account for + // that level of indentation and try to subtract it from whatever level + // we return later on. // - ensureNodeIsPatched(tree.rootNode); - - this.rangeList.add(rangeForNode(tree.rootNode)); - if (includedRanges) { - affectedRange = new Range( - includedRanges[0].startPosition, - last(includedRanges).endPosition - ); - } else { - affectedRange = MAX_RANGE; - } + // Sadly, if the row is _more_ indented than we need it to be, we won't + // be able to dedent it into the correct position. This option probably + // needs to be revisited. + existingIndent = this.indentLevelForLine( + this.buffer.lineForRow(row), tabLength); } - // Now that we've assembled and coalesced all the ranges that need - // invalidating, we'll invalidate them in buffer order. - for (let range of this.rangeList) { - this.languageMode.emitRangeUpdate(range); + let comparisonRowIndent = options.comparisonRowIndent; + if (comparisonRowIndent === undefined) { + comparisonRowIndent = languageMode.indentLevelForLine( + this.buffer.lineForRow(comparisonRow), tabLength); } - for (let range of foldRangeList) { - // The fold cache is automatically cleared for any range that needs - // re-highlighting. But sometimes we need to go further and invalidate - // rows that don't even need highlighting changes. - this.languageMode.emitFoldUpdate(range); - } + // What's the right place to measure from? Often we're here because the + // user just hit Enter, which means we'd run before injection layers have + // been re-parsed. Hence the injection's language layer might not know + // whether it controls the point at the cursor. So instead we look for the + // layer that controls the point at the end of the comparison row. This may + // not always be correct, but we'll find out. + let comparisonRowEnd = new Point( + comparisonRow, + this.buffer.lineLengthForRow(comparisonRow) + ); - if (affectedRange) { - let injectionPromise = this._populateInjections(affectedRange, nodeRangeSet); - if (injectionPromise) { - params.async = true; - return injectionPromise; + // Phase 1 + // ------- + // + // Find the controlling layer and perform an indentation query that starts + // at the beginning of the comparison row and ends at the beginning of the + // current row. + + // Find the deepest layer that actually has an indents query. (Layers that + // don't define one, such as specialized injection grammars, are telling us + // they don't care about indentation. If a grammar wants to _prevent_ a + // shallower layer from controlling indentation, it should define an empty + // `indents.scm`, perhaps with an explanatory comment.) + let controllingLayer = languageMode.controllingLayerAtPoint( + comparisonRowEnd, + (layer) => { + if (!layer.queries.indentsQuery) return false; + // We want to exclude layers with a content range that _begins at_ the + // cursor position. Why? Because the content that starts at the cursor + // is about to shift down to the next line. It'd be odd if that layer + // was in charge of the indentation hint if it didn't have any content + // on the preceding line. + // + // So first we test for containment exclusive of endpoints… + if (layer.containsPoint(comparisonRowEnd, true)) { + return true; + } + + // …but we'll still accept layers that have a content range which + // _ends_ at the cursor position. + return layer.getCurrentRanges()?.some(r => { + return r.end.compare(comparisonRowEnd) === 0; + }); } - } - } + ); - setCurrentRanges(includedRanges) { - if (this.depth === 0) { return; } - let oldRangeMarkers = this.currentRangesLayer.getMarkers(); - for (let marker of oldRangeMarkers) { - marker.destroy(); + if (!controllingLayer) { + // There's no layer with an indents query to help us out. The default + // behavior in this situation with any grammar — even plain text — is to + // match the previous line's indentation. + return comparisonRowIndent - existingIndent; } - // These are the “official” ranges, received right after the parent layer's - // tree parse. We'll get a new set of official ranges at the end of the - // next transaction, but until then, we should try our best to adapt to - // buffer changes, and to allow each range to shift or grow or shrink so - // that off-schedule parses are more likely to be accurate. - for (let range of includedRanges) { - range = rangeForNode(range); - this.currentRangesLayer.markRange(range); - } - } + let { queries: { indentsQuery }, scopeResolver } = controllingLayer; - getCurrentRanges() { - let markers = this.currentRangesLayer?.getMarkers(); - if (!markers || markers.length === 0) { return null; } - return markers.map(m => m.getRange()); - } + // TODO: We use `ScopeResolver` here so that we can use its tests. Maybe we + // need a way to share those tests across different kinds of capture + // resolvers. + scopeResolver.reset(); - // Checks whether a given {Point} lies within one of this layer's content - // ranges — not just its extent. The optional `exclusive` flag will return - // `false` if the point lies on a boundary of a content range. - containsPoint(point, exclusive = false) { - let ranges = this.getCurrentRanges() ?? [this.getExtent()]; - return ranges.some(r => r.containsPoint(point, exclusive)); - } + let indentTree = null; + if (options.tree && originalControllingLayer === controllingLayer) { + // Make sure this tree belongs to the layer we expect it to. + indentTree = options.tree; + } - // Returns a syntax tree for the current buffer. - // - // By default, this method will either return the current tree (if it's up to - // date) or synchronously parse the buffer into a new tree (if it isn't). - // - // If you don't want to force a re-parse and don't mind that the current tree - // might be stale, pass `force: false` as an option. - // - // In certain circumstances, the new tree might be promoted to the canonical - // tree for this layer. To prevent this, pass `anonymous: false` as an option. - // - // All trees returned by this method are managed by this language layer and - // will be deleted when the next transaction is complete. Retaining a - // reference to the returned tree will not prevent this from happening. To - // opt into managing the life cycle of the returned tree, copy it immediately - // when you receive it. - // - getOrParseTree({ force = true, anonymous = false } = {}) { - if (this.tree && (!this.treeIsDirty || !force)) { return this.tree; } + if (!indentTree) { + if (!controllingLayer.treeIsDirty || options.forceTreeParse || !languageMode.shouldUseAsyncIndent()) { + // If we're in this code path, it either means the tree is clean (the + // `get` path) or that we're willing to spend the time to do a + // synchronous reparse (the `parse` path). Either way, we'll be able to + // deliver a synchronous answer to the question. + indentTree = controllingLayer.getOrParseTree(); + } else { + // We can't answer this yet because we don't yet have a new syntax + // tree, and are unwilling to spend time doing a synchronous re-parse. + // Return a promise that will fulfill once the transaction is over. + // + // TODO: For async, we might need an approach where we suggest a + // preliminary indent level and then follow up later with a more + // accurate one. It's a bit disorienting that the editor falls back to + // an indent level of `0` when a newline is inserted. + let comparisonRowText = this.buffer.lineForRow(comparisonRow) + let rowText = this.buffer.lineForRow(row) + return languageMode.atTransactionEnd().then(({ changeCount }) => { + let shouldFallback = false; + // If this was the only change in the transaction, then we can + // definitely adjust the indentation level after the fact. If not, + // then we might still be able to make indentation decisions in cases + // where they do not affect one another. + // + // Hence if neither the comparison row nor the current row has had + // its contents change in any way since we were first called, we will + // assume it's safe to adjust the indentation level after the fact. + // Otherwise we'll fall back to a single transaction-wide indentation + // adjustment — fewer tree parses, but more likely to produce unusual + // results. + if (changeCount > 1) { + if (comparisonRowText !== this.buffer.lineForRow(comparisonRow)) { + shouldFallback = true; + } + if (rowText !== this.buffer.lineForRow(row)) { + shouldFallback = true; + } + } + if (shouldFallback) { + // We're now revisiting this indentation question at the end of the + // transaction. Other changes may have taken place since we were + // first asked what the indent level should be for this line. So + // how do we know if the question is still relevant? After all, the + // text that was on this row earlier might be on some other row + // now. + // + // So we compare the text that was on the row when we were first + // called… to the text that is on the row now that the transaction + // is over. If they're the same, that's a _strong_ indicator that + // the result we return will still be relevant. + // + // If not, as is the case in this code path, we return `undefined`, + // signalling to the `TextEditor` that its only recourse is to + // auto-indent the whole extent of the transaction instead. + return undefined; + } - // Eventually we'll take this out, but for now it serves as an indicator of - // how often we have to manually re-parse in between transactions — - // something we'd like to do as little as possible. - if (atom.inDevMode()) { - console.warn('Re-parsing tree!', this.inspect(), this.treeIsDirty); + // If we get this far, it's safe to auto-indent this line. Either it + // was the only change in its transaction or the other changes + // happened on different lines. But we've retained the original + // values for `comparisonRow` and `comparisonRowIndent` because + // that's the proper basis from which to determine the given row's + // indent level. + let result = this.suggestedIndentForBufferRow(row, tabLength, { + ...rawOptions, + comparisonRow: comparisonRow, + comparisonRowIndent: comparisonRowIndent, + tree: controllingLayer.tree, + controllingLayer + }); + return result; + }); + } } - let ranges = null; - if (this.depth > 0) { - ranges = this.getCurrentRanges().map(r => { - return rangeToTreeSitterRangeSpec(r, this.buffer); - }); - } + // Keep track of the range of each capture so we can filter out duplicates. + let positionSet = new Set; - // The goal here is that, if a re-parse is needed in between transactions, - // we assign the result back to `this.tree` so that we can at least cut - // down on the incremental amount of work that the end-of-transaction parse - // has to do — it can pick up where we left off. So for the root language - // layer, this represents more of a shifting of work than a duplication. - // - // But this isn't safe to do for injection layers because `ranges` may be - // stale, despite our efforts to keep them fresh through markers. The - // stakes are low enough for indents that we can attempt a tree parse and - // act on the results even if we're not certain they're accurate — but when - // we do another scheduled incremental parse, we have to be 100% sure that - // we're working from an accurate tree. - // - // Re-parsing of an injection layer can only safely happen when we know its - // true ranges, and that cannot be determined except through the process - // that an injection layer's parent goes through during the - // end-of-transaction update, unless we're willing to do an off-schedule - // parse of _all_ language layers in this layer's ancestry. That's not - // completely out of the question for the future — but, failing that, there - // probably isn't a way to “fix” this for injection layers except through - // cutting down on off-schedule parses. - // - let then = performance.now() - let tree = this.languageMode.parse( - this.language, - this.tree, - ranges, - // { tag: `Re-parsing ${this.inspect()}` } + // Perform the Phase 1 capture. + let indentCaptures = indentsQuery.captures( + indentTree.rootNode, + { + startPosition: { row: comparisonRow, column: 0 }, + endPosition: { row: row, column: 0 } + } ); - let now = performance.now() - let parseTime = now - then; + // Keep track of the first `@indent` capture on the line. When balancing + // `@indent`s and `@dedent`s, any `@dedent`s that occur before the first + // `@indent` should be ignored. + let indentCapturePosition = null; + // Three different capture styles can influence the Phase 1 output: + // the `@indent`/`@dedent` balancing… + let indentDelta = 0; + // …the `@dedent.next` capture… + let dedentNextDelta = 0; + // …and the `@match.next` capture, which acts as a special sort of override + // much like Phase 2’s `@match` capture. + let matchNextResult = null; - // Since we can't look into the future, we don't know how many times during - // this transaction we'll be asked to make indentation sugestions. If we - // knew ahead of time, we'd be able to decide at the beginning of a - // transaction whether we could afford to do synchronous indentation. - // - // Instead, we do the next best thing: we start out doing synchronous - // indentation, then fall back to asynchronous indentation once we've - // exceeded our time budget. So we keep track of how long each reparse - // takes and subtract it from our budget. - this.languageMode.currentTransactionReparseBudgetMs -= parseTime; + for (let capture of indentCaptures) { + let { node, name } = capture; + // Captures that have no content are ignored by default because they + // typically are “phantom” nodes inserted by Tree-sitter as part of error + // recovery, but we'll allow them if the query file explicitly tells us + // to. + let allowEmpty = this.getProperty(capture, 'allowEmpty', 'boolean', false); + if (node.text === '' && !allowEmpty) { + continue; + } - if (this.depth === 0 && !anonymous) { - this.tree = tree; - this.treeIsDirty = false; - } else { - // Keep track of any off-schedule trees we generate so that we can GC them - // when the next transaction is done. - this.temporaryTrees.push(tree); - } - return tree; - } + // Ignore anything that isn't actually on the row. + if (node.endPosition.row < comparisonRow) { continue; } + if (node.startPosition.row > comparisonRow) { continue; } - getText() { - let { buffer } = this.languageMode; - if (!this.marker) { - return buffer.getText(); - } else { - return buffer.getTextInRange(this.marker.getRange()); - } - } + // Ignore anything that fails a scope test. This applies all the tests of + // the form `(#is? test.foo)`. + if (!scopeResolver.store(capture)) { continue; } + // Apply indentation-specific scope tests and skip this capture if any + // tests fail. + let passed = this.applyTests(capture, { + currentRow: row, + comparisonRow, + tabLength + }); + if (!passed) { continue; } - // Given a point, return all syntax captures that are active at that point. - // Used by `bufferRangeForScopeAtPosition`. - scopeMapAtPosition(point) { - if (!this.language || !this.tree) { return []; } - let { scopeResolver } = this; - scopeResolver.reset(); + // Only consider a given combination of capture name and buffer range + // once, even if it's captured more than once in `indents.scm`. + let key = `${name}/${node.startIndex}/${node.endIndex}`; + if (positionSet.has(key)) { continue; } + positionSet.add(key); - // If the cursor is resting before column X, we want all scopes that cover - // the character in column X. - let captures = this.queries.highlightsQuery?.captures( - this.tree.rootNode, - point, - { row: point.row, column: point.column + 1 } - ) ?? []; + if (name === 'indent') { + if (indentCapturePosition === null) { + indentCapturePosition = node.endPosition; + } + indentDelta++; + } else if (name === 'dedent.next') { + // This isn't often needed, but it's a way for the current line to + // signal that the _next_ line should be dedented no matter what its + // content is. + dedentNextDelta++; + } else if (name === 'match.next') { + // `@match.next` tells us that the current row’s baseline should match + // that of a given position descriptor. + matchNextResult = this.resolveMatch(capture, { + currentRow: row, + comparisonRow, + tabLength, + indentationLevels: options.indentationLevels + }) ?? null; + if (matchNextResult !== null) { + // If we succeed in resolving this value, it’ll supersede any other + // kinds of captures, so we can skip the rest of the capture + // processing. + break; + } + } else if (name === 'dedent') { + // `dedent` tokens don't count for anything unless they happen + // after the first `indent` token. They only tell us whether an indent + // that _seems_ like it should happen is cancelled out. + // + // Consider: + // + // } else if (foo) { + // + // We should still indent the succeeding line because the initial `}` + // does not cancel out the `{` at the end of the line. On the other + // hand: + // + // } else if (foo) {} + // + // The second `}` _does_ cancel out the first occurrence of `{` because + // it comes later. + if (!indentCapturePosition || comparePoints(node.startPosition, indentCapturePosition) < 0) { + // This capture either happened before the first indent capture on + // the row or is _the same node_ as the indent capture, in which case + // we should construe the dedent as happening _before_ the indent. + // + // For example: the "elsif" node in Ruby triggers a dedent on its own + // line, but also signals an indent on the next line. The dedent + // shouldn't cancel out the indent. + continue; + } + // Now that we've filtered out all the `@dedent`s we should ignore, we + // can decrement `indentDelta`. + indentDelta--; + if (indentDelta < 0) { + // In the _indent_ phase, the delta won't ever go lower than `0`. + // This is because we assume that the previous line is correctly + // indented! The only function that `dedent` serves for us in this + // phase is canceling out an earlier `indent` and preventing false + // positives. + // + // So no matter how many `dedent` tokens we see on a particular line… + // if the _last_ token we see is an `indent` token, then it hints + // that the next line should be indented by one level. + // + // The only ways for Phase 1 to produce a baseline indent that’s + // _less_ than the comparison row’s indent are via `@dedent.next` and + // `@match.next`. + indentDelta = 0; + } + } + } - let results = []; - for (let capture of captures) { - // Storing the capture will return its range (after any potential - // adjustments) — or `false`, to signify that the capture was ignored. - let range = scopeResolver.store(capture); - if (!range) { continue; } + // `@indent` and `@dedent` can increase the next line's indent level by one + // at most, and can't decrease the next line's indent level at all on their + // own. + // + // Why? There are few coding patterns in the wild that would cause us to + // indent more than one level based on tokens found on the _previous_ line. + // And there are also few scenarios in which we'd want to dedent a certain + // line before we even know the content of that line. + // + // Hence we distill the results above into a net indentation level change + // of either 1 or 0, depending on whether we saw more `@indent`s than + // `@dedent`s. + // + // If there's a genuine need to dedent the current row based solely on the + // content of the comparison row, then `@dedent.next` or `@match.next` can + // be used. + // + indentDelta = clamp(indentDelta, 0, 1); - // Since the range might have been adjusted, we wait until after - // resolution. - if (comparePoints(range.endPosition, point) === 0) { continue; } - if (isBetweenPoints(point, range.startPosition, range.endPosition)) { - results.push({ capture, adjustedRange: range }); - } + // Process `@dedent.next` captures after the `@indent`/`@dedent` balancing; + // they act as a strong hint about the next line's indentation. + indentDelta -= clamp(dedentNextDelta, 0, 1); + + // On the other hand, if we got a result from a `@match.next` capture, that + // supersedes any other results. Set `indentDelta` to `0`; we'll instead + // use `matchNextResult` as the baseline to which we'll add any further + // deltas. + if (matchNextResult !== null) { + indentDelta = 0; } - scopeResolver.reset(); + // Phase 2 + // ------- + // + // Find the controlling layer and perform an indentation query that starts + // at the beginning of the current row and ends at the beginning of the + // next row. - // Sort from biggest to smallest. - results = results.sort((a, b) => { - return nodeBreadth(b.adjustedRange) - nodeBreadth(a.adjustedRange); - }); + let dedentDelta = 0; + let lineText = this.buffer.lineForRow(row); + let rowStartingColumn = Math.max(lineText.search(/\S/), 0); - return results; - } + if (!options.skipDedentCheck) { + scopeResolver.reset(); - // Like `WASMTreeSitterLanguageMode#getSyntaxNodeAtPosition`, but for just this - // layer. - getSyntaxNodeAtPosition(position, where = FUNCTION_TRUE) { - if (!this.language || !this.tree) { return null; } - let { buffer } = this.languageMode; + // The controlling layer on the previous line got to decide what our + // starting indent was on the current line. But it might not extend to + // the current line, so we should determine which layer is in charge of + // the second phase. + // + // The comparison point we use is that of the first non-whitespace + // character on the line. If we start earlier than that, we might not + // pick up on the presence of an injection layer. + let rowStart = new Point(row, rowStartingColumn); + let dedentControllingLayer = languageMode.controllingLayerAtPoint( + rowStart, + (layer) => { + if (!layer.queries.indentsQuery) return false; + // We're inverting the logic from above: now we want to allow layers + // that _begin_ at the cursor and exclude layers that _end_ at the + // cursor. Because we'll be analyzing content that comes _after_ the + // cursor to understand whether to dedent! + // + // So first we test for containment exclusive of endpoints… + if (layer.containsPoint(rowStart, true)) { + return true; + } - let index = buffer.characterIndexForPosition(position); - let node = this.tree.rootNode.descendantForIndex(index); + // …but we'll still accept layers that have a content range which + // _starts_ at the cursor position. + return layer.getCurrentRanges()?.some(r => { + return r.start.compare(rowStart) === 0; + }); + } + ); - while (node) { - if (where(node, this.grammar)) { - return node; + if (dedentControllingLayer && dedentControllingLayer !== controllingLayer) { + // If this layer is different from the one we used above, then we + // should run this layer's indents query against its own tree. (If _no_ + // layers qualify at this position, we won't hit this code path, so + // we'll reluctantly still use the original layer and tree.) + indentsQuery = dedentControllingLayer.queries.indentsQuery; + indentTree = dedentControllingLayer.getOrParseTree(); } - node = node.parent; - } - return null; - } + // Perform the Phase 2 capture. + let dedentCaptures = indentsQuery.captures( + indentTree.rootNode, + { + startPosition: { row: row - 1, column: Infinity }, + endPosition: { row: row + 1, column: 0 } + } + ); - // Used to find the most specific node affected by an edited range. - getSyntaxNodeContainingRange(range, where = FUNCTION_TRUE) { - if (!this.language || !this.tree) { return null; } - let { buffer } = this.languageMode; + let currentRowText = lineText.trim(); + // We can reuse the position set we created for Phase 1. + positionSet.clear(); - if (range.start.isEqual(range.end)) { - return this.getSyntaxNodeAtPosition(range.start, where); - } + for (let capture of dedentCaptures) { + let { name, node } = capture; + let { text } = node; - let indexStart = buffer.characterIndexForPosition(range.start); - let indexEnd = buffer.characterIndexForPosition(range.end); + // As in Phase 1, we allow captures to opt into being recognized even + // when they're empty. + let allowEmpty = this.getProperty(capture, 'allowEmpty', 'boolean', false); + if (text === '' && !allowEmpty) { continue; } - let rangeBreadth = indexEnd - indexStart; - let node = this.getSyntaxNodeAtPosition( - range.start, - (node) => { - let breadth = node.endIndex - node.startIndex; - let qualifies = node.startIndex <= indexEnd && - node.endIndex >= indexEnd && - breadth >= rangeBreadth; - return qualifies && where(node); - } - ); + // `(#set! indent.force)` acts more aggressively, signaling dedent even + // when the capture isn't the first content on the row. This should be + // used with care. + let force = this.getProperty(capture, 'force', 'boolean', false); - return node ?? null; - } + // Ignore anything that isn't actually on the row. + if (node.endPosition.row < row) { continue; } + if (node.startPosition.row > row) { continue; } - _populateInjections(range, nodeRangeSet) { - if (!this.tree) { return; } - const promises = []; + // Ignore anything that fails a scope test. + if (!scopeResolver.store(capture)) { continue; } + // Apply indentation-specific scope tests and skip this capture if any + // tests fail. + let passed = this.applyTests(capture, { + currentRow: row, + comparisonRow, + tabLength + }); + if (!passed) { continue; } - // We won't touch _all_ injections, but we will touch any injection that - // could possibly have been affected by this layer's update. - let existingInjectionMarkers = this.languageMode.injectionsMarkerLayer - .findMarkers({ intersectsRange: range }) - .filter(marker => marker.parentLanguageLayer === this); + // Imagine you've got: + // + // { ^foo, bar } = something + // + // and the caret represents the cursor. Pressing Enter will move + // everything after the cursor to a new line and _should_ indent the + // line, even though there's a closing brace on the new line that would + // otherwise mark a dedent. + // + // Thus we don't want to honor a `@dedent` or `@match` capture unless + // it's the first non-whitespace content in the line. We'll use similar + // logic for `suggestedIndentForEditedBufferRow`. + // + // If a capture is confident it knows what it's doing, it can opt out + // of this behavior with `(#set! indent.force true)`. + if (!force && !currentRowText.startsWith(text)) { continue; } - if (existingInjectionMarkers.length > 0) { - // Enlarge our range to contain all of the injection zones in the - // affected buffer range. - let earliest = range.start, latest = range.end; - for (let marker of existingInjectionMarkers) { - range = marker.getRange(); - if (range.start.compare(earliest) === -1) { - earliest = range.start; - } - if (range.end.compare(latest) === 1) { - latest = range.end; + // The `@match` capture short-circuits nearly all indentation logic by + // pointing us to a different node and asking us to match the + // indentation of whatever row that node starts on. + if (name === 'match') { + let matchIndentLevel = this.resolveMatch( + capture, { row, comparisonRow, tabLength, indentationLevels: options.indentationLevels }); + if (typeof matchIndentLevel === 'number') { + // We were able to resolve the `@match` capture, so we’ll be + // returning early. + scopeResolver.reset(); + let finalIndent = Math.max(matchIndentLevel - Math.floor(existingIndent), 0); + if (!options.skipEvent) { + this.emitter.emit('did-suggest-indent', { + currentRow: row, + comparisonRow, + matchIndentLevel, + finalIndent, + captureMode: 'match' + }); + } + return finalIndent; + } + } else if (name === 'none') { + // TODO: `@none` is an experiment for any situation in which the + // current line’s indent should be reset to `0`. This is obviously + // rarely needed and I can’t remember exactly what the envisioned use + // case was, but we’ll leave it in for now. + scopeResolver.reset(); + if (!options.skipEvent) { + this.emitter.emit('did-suggest-indent', { + currentRow: row, + comparisonRow, + finalIndent: 0, + captureMode: 'none' + }); + } + return 0; } + + // Only the captures handled above and `@dedent` can change this line's + // indentation. So now we’ll filter out all non-`@dedent`s. + if (name !== 'dedent') { continue; } + + // Only consider a given range once, even if it's marked with multiple + // captures. + let key = `${node.startIndex}/${node.endIndex}`; + if (positionSet.has(key)) { continue; } + positionSet.add(key); + dedentDelta--; } - range = range.union(new Range(earliest, latest)); + // `@indent`/`@dedent` captures, no matter how many there are, can + // dedent the current line by one level at most. To indent more than + // that, one must use a `@match` capture. + dedentDelta = clamp(dedentDelta, -1, 0); } - // Why do we have to do this explicitly? Because `descendantsOfType` will - // incorrectly return nodes if the range runs from (0, 0) to (0, 0). All - // other empty ranges seem not to have this problem. Upon cursory - // inspection, this bug doesn't seem to be limited to `web-tree-sitter`. - if (range.isEmpty()) { return; } + scopeResolver.reset(); - // Now that we've enlarged the range, we might have more existing injection - // markers to consider. But check for containment rather than intersection - // so that we don't have to enlarge it again. - existingInjectionMarkers = this.languageMode.injectionsMarkerLayer - .findMarkers({ startsInRange: range, endsInRange: range }) - .filter(marker => marker.parentLanguageLayer === this); + // Both phases are complete, so let's put the pieces together. - const markersToUpdate = new Map(); + // Where are we starting from? Most of the time it's the indentation level + // of the comparison row, but a `@match.next` capture can override this. + let baseline = matchNextResult !== null ? matchNextResult : comparisonRowIndent; - // Query for all the nodes that could possibly prompt the creation of - // injection points. - const nodes = this.tree.rootNode.descendantsOfType( - Object.keys(this.grammar.injectionPointsByType), - range.start, - range.end - ); + // Now we add the deltas from the two phases. This will nearly always + // produce a difference of either `-1`, `0`, or `1` from `baseline`. + // + // When `@match.next` produces a baseline, `indentDelta` will always be `0` + // to signify that other Phase 1 logic was ignored altogether. + let finalIndent = baseline + indentDelta + dedentDelta; - let existingInjectionMarkerIndex = 0; - let newLanguageLayers = 0; - for (const node of nodes) { - // A given node can be the basis for an arbitrary number of injection - // points, but first it has to pass our gauntlet of tests: - for (const injectionPoint of this.grammar.injectionPointsByType[node.type]) { - // Does it give us a language string? - const languageName = injectionPoint.language(node); - if (!languageName) { continue; } + // Finally, we might have to adjust for the existing leading whitespace if + // `options.preserveLeadingWhitespace` is `true`. + // + // We call `Math.floor` because we should only subtract whole units of + // indentation here. “Leading whitespace” seems not to consider (for + // example) a single leading space character if `editor.tabLength` is `2`. + let adjustedIndent = Math.max(finalIndent - Math.floor(existingIndent), 0); + + // Emit an event with all this information. This makes it possible for + // tooling to help a grammar author understand the indentation logic + // without necessarily having to step through it in a debugger. + if (!options.skipEvent) { + this.emitter.emit('did-suggest-indent', { + currentRow: row, + comparisonRow, + comparisonRowIndent, + indentDelta, + dedentDelta, + finalIndent, + adjustedIndent, + captureMode: 'normal' + }); + } - // Does that string match up with a grammar that we recognize? - const grammar = this.languageMode.grammarForLanguageString( - languageName); - if (!grammar) { continue; } + return adjustedIndent; + } - // Does it offer us a node, or array of nodes, which a new injection - // layer should use for its content? - const contentNodes = injectionPoint.content(node, this.buffer); - if (!contentNodes) { continue; } + // Extended: Register a callback that fires when {IndentResolver} suggests an + // indentation level. + // + // This callback is merely a glimpse into the indentation life-cycle and does + // not offer the callback any opportunity to change the value being + // suggested. Its goal is to report metadata that may make it easier to + // diagnose _why_ a particular indentation level is being suggested without + // having to step through the logic in a debugger. + // + // Nearly all exit paths for {::suggestedIndentForBufferRow} and + // {::suggestedIndentForEditedBufferRow} invoke this callback. + // + // One indentation “level” consists of either (a) one tab character, or (b) + // one multiple of `editor.tabLength` spaces (if `editor.softTabs` is + // `true`). + // + // - `callback` A {Function} that takes one parameter: + // - `meta` An {Object} that consisting of _some subset_ of the following + // properties: + // - `captureMode` A {String} describing one of several different modes + // which influence a capture: + // - A value of `normal` means that an indentation level was determined + // through the normal two-phase process. + // - A value of `match` means that an indentation level was determined + // when we encountered a `@match` capture. `@match` captures are + // considered in Phase 2, but use the syntax tree to override earlier + // logic and give a definitive answer on a row’s indentation level. + // - A value of `none` means that a `@none` capture was encountered in + // Phase 2. `@none` is an extremely rare capture that, when used, + // instantly signals a suggested indent level of `0`, overriding all + // other logic. + // - `currentRow` The {Number} of the row whose indentation was suggested. + // (Zero-indexed, so you must add one to match the row number displayed + // in the gutter.) + // - `comparisonRow` The {Number} of the row that was consulted to + // determine the baseline indentation of the target row. This is + // often the row directly above `row`, but can be an earlier row if + // the target row was preceded by whitespace. + // - `comparisonRowIndent` {Number} The indentation level of the + // comparison row. + // - `indentDelta` {Number} The amount of indentation (in increments) + // suggested during the first phase of indent analysis. This phase + // determines the baseline indentation of the target row by querying + // the content on the comparison row. (For instance, if the comparison + // row ends with `(`, `indentDelta` will typically be `1`.) Since + // the first phase can only maintain or increase the indentation level, + // this value will be either `0` or `1`. + // - `dedentDelta` {Number} The amount of indentation (in increments) + // suggested during the second phase of indent analysis. This phase + // determines whether any content on the target line suggests that we + // should dedent the line by one level. (For instance, if the target + // line starts with `)`, `dedentDelta` will often be `-1`.) Since the + // second phase can only maintain or decrease the indentation level, + // this value will be either `0` or `-1`. + // - `matchIndentLevel` {Number} A number representing the ideal amount + // of indentation as determined by a `@match` capture. A `@match` + // capture tries to match the indentation level of a previous line in + // the buffer — one that it has a semantic relationship with — instead + // of determining indentation in relative terms. When it's present, it + // overrides the conventional indentation logic. + // - `finalIndent` {Number} A number representing the final value that + // will shortly be returned from a call to + // `suggestedIndentForBufferRow`. This value accounts for the possible + // presence of the `preserveLeadingWhitespace` option. For instance, + // if `suggestedIndentForBufferRow` would return `5`, but the target + // row already has an indent level of `3`, `finalIndent` will instead + // be `2`. + // - `adjustedIndent` {Number} A `finalIndent`, but takes existing + // indentation level into account if the `preserveLeadingWhitespace` + // option was enabled. For instance, if `suggestedIndentForBufferRow` + // would return `5`, but the target row already has an indent level of + // `3`, `adjustedIndent` will instead be `2`. If + // `preserveLeadingWhitespace` is `false`, `finalIndent` and + // `adjustedIndent` will be identical. + // + onDidSuggestIndent(callback) { + return this.emitter.on('did-suggest-indent', callback); + } - const injectionNodes = [].concat(contentNodes); - if (!injectionNodes.length) continue; + suggestedIndentForBufferRows(startRow, endRow, tabLength, options = {}) { + let { languageMode } = this; + let root = languageMode.rootLanguageLayer; + if (!root || !root.tree) { + let results = new Map(); + for (let row = startRow; row <= endRow; row++) { + results.set(row, null); + } + return results; + } - const injectionRange = node.range; + let results = new Map(); + let comparisonRow = null; + let comparisonRowIndent = null; - let marker; + let { isPastedText = false } = options; + let indentDelta; - // It's surprisingly hard to match up the injection point that we now - // know we need… with the one that may already exist that was created - // or updated based on the state of the tree from the last keystroke. - // There is no continuity between the previous tree and the new tree - // that we can rely on. Unless the marker and the base node of the - // injection point agree on an exact range, we can't be sure enough to - // re-use an existing layer. - // - // This isn't a huge deal because (a) markers are good at adapting to - // changes, so those two things will agree more often than you think; - // (b) even when they don't agree, it's not very costly to destroy and - // recreate another `LanguageLayer`. - // - // Since both `existingInjectionMarkers` and `nodes` are guaranteed to - // be sorted in buffer order, we can take shortcuts in how we pair them - // up. - // - for ( - let i = existingInjectionMarkerIndex, - n = existingInjectionMarkers.length; - i < n; - i++ - ) { - const existingMarker = existingInjectionMarkers[i]; - const comparison = existingMarker.getRange().compare(injectionRange); - if (comparison > 0) { - // This marker seems to occur after the range we want to inject - // into, meaning there's a good chance it's not ours. And it means - // that none of the remaining markers will likely be our candidate, - // either; so we should give up and create a new one. - break; - } else if (comparison === 0) { - // Luckily, the range matches up exactly, so this is almost - // certainly a previous version of the same intended injection. It - // also means that any markers before this point in the list have - // either already matched with candidate injection points or cannot - // possibly match up; thus we can ignore them for the rest of the - // matching process. - existingInjectionMarkerIndex = i; - if (existingMarker.languageLayer.grammar === grammar) { - marker = existingMarker; - break; + for (let row = startRow; row <= endRow; row++) { + // If this row were being indented by `suggestedIndentForBufferRow`, it'd + // look at the end of the previous row to find the controlling layer, + // because we start at the previous row to find the suggested indent for + // the current row. + let controllingLayer = languageMode.controllingLayerAtPoint( + this.buffer.clipPosition(new Point(row - 1, Infinity)), + // This query isn't as precise as the one we end up making later, but + // that's OK. This is just a first pass. + (layer) => !!layer.queries.indentsQuery && !!layer.tree + ); + if (isPastedText) { + // In this mode, we're not trying to auto-indent every line; instead, + // we're trying to auto-indent the _first_ line of a region of text + // that's just been pasted, while trying to preserve the relative + // levels of indentation within the pasted region. So if the + // auto-indent of the first line increases its indent by one level, + // all other lines should also be increased by one level — without even + // consulting their own suggested indent levels. + if (row === startRow) { + // The only time we consult the indents query is for the first row, + // so we're not going to insist that the _entire range_ fall under + // the control of a layer with an indents query — just the row we + // need. + if (!controllingLayer) { return null; } + let tree = controllingLayer.getOrParseTree(); + + let firstLineCurrentIndent = this.indentLevelForLine( + this.buffer.lineForRow(row), tabLength); + + let firstLineIdealIndent = this.suggestedIndentForBufferRow( + row, + tabLength, + { + ...options, + controllingLayer, + tree + } + ); + + if (firstLineIdealIndent == null) { + // If we decline to suggest an indent level for the first line, + // then there's no change to be made here. Keep the whole region + // the way it is. + return null; + } else { + indentDelta = firstLineIdealIndent - firstLineCurrentIndent; + if (indentDelta === 0) { + // If the first row doesn't have to be adjusted, neither do any + // others. + return null; } - } else { - // This marker occurs before our range. Since all injection - // candidates from this point forward are guaranteed to be of an - // equal or later range, there's no chance of this marker matching - // any candidates from this point forward. We can ignore it, and - // anything before it, in subsequent trips through the loop. - existingInjectionMarkerIndex = i; + results.set(row, firstLineIdealIndent); } + continue; } - if (!marker) { - // If we didn't match up with an existing marker/layer, we'll have to - // create them. - marker = this.languageMode.injectionsMarkerLayer.markRange( - injectionRange); + // All rows other than the first are easy — just apply the delta. + let actualIndent = this.indentLevelForLine( + this.buffer.lineForRow(row), tabLength); - marker.languageLayer = new LanguageLayer( - marker, - this.languageMode, - grammar, - this.depth + 1, - injectionPoint - ); + results.set(row, actualIndent + indentDelta); + continue; + } - marker.parentLanguageLayer = this; - // eslint-disable-next-line no-unused-vars - newLanguageLayers++; + // For line X to know its appropriate indentation level, it needs row X-1, + // if it exists, to be indented properly. That's why `TextEditor` wants to + // indent each line atomically. Instead, we'll determine the right level + // for the first row, then supply the result for the previous row when we + // call `suggestedIndentForBufferRow` for the _next_ row, and so on, so + // that `suggestedIndentForBufferRow` doesn't try to look up the comparison + // row itself and find out we haven't actually fixed any of the previous + // rows' indentations yet. + let indent; + if (controllingLayer) { + let tree = controllingLayer.getOrParseTree(); + let rowOptions = { + ...options, + tree, + comparisonRow: comparisonRow ?? undefined, + comparisonRowIndent: comparisonRowIndent ?? undefined, + indentationLevels: results + }; + indent = this.suggestedIndentForBufferRow(row, tabLength, rowOptions); + if (indent === null) { + // We could not retrieve the correct indentation level for this row + // without re-parsing the tree. We should give up and return what we + // have so that `TextEditor` can finish the job through a less + // efficient means. + return results; } - - markersToUpdate.set( - marker, - new NodeRangeSet( - nodeRangeSet, - injectionNodes, - injectionPoint - ) - ); + } else { + // We could not retrieve the correct indentation level for this row + // because it isn't governed by any layer that has an indents query. + return results; } + results.set(row, indent); + comparisonRow = row; + comparisonRowIndent = indent; } - let staleLanguageLayers = 0; - for (const marker of existingInjectionMarkers) { - // Any markers that didn't get matched up with injection points are now - // stale and should be destroyed. - if (!markersToUpdate.has(marker)) { - this.languageMode.emitRangeUpdate(marker.getRange()); - marker.languageLayer.destroy(); - // eslint-disable-next-line no-unused-vars - staleLanguageLayers++; - } - } + return results; + } - if (markersToUpdate.size > 0) { - for (const [marker, nodeRangeSet] of markersToUpdate) { - promises.push(marker.languageLayer.update(nodeRangeSet)); - } - } + suggestedIndentForEditedBufferRow(row, tabLength, options = {}) { + let { languageMode } = this; + const line = this.buffer.lineForRow(row); + const currentRowIndent = this.indentLevelForLine(line, tabLength); + let comparisonRow = options.comparisonRow ?? this.getComparisonRow(row, options); - return Promise.all(promises); - } + // If the row is not indented at all, we have nothing to do, because we can + // only dedent a line at this phase. + if (currentRowIndent === 0) { return; } - _treeEditForBufferChange(start, oldEnd, newEnd, oldText, newText) { - let startIndex = this.buffer.characterIndexForPosition(start); - return { - startIndex, - oldEndIndex: startIndex + oldText.length, - newEndIndex: startIndex + newText.length, - startPosition: start, - oldEndPosition: oldEnd, - newEndPosition: newEnd - }; - } -} + // If we're on the first row, we have no preceding line to compare + // ourselves to. We should do nothing. + if (row === 0) { return; } -// An injection `LanguageLayer` may need to parse and highlight a strange -// subset of its stated range — for instance, all the descendants within a -// parent that are of a particular type. A `NodeRangeSet` is how that strange -// subset is expressed. -class NodeRangeSet { - constructor(previous, nodes, injectionPoint) { - this.previous = previous; - this.newlinesBetween = injectionPoint.newlinesBetween; - this.includeAdjacentWhitespace = injectionPoint.includeAdjacentWhitespace; - this.includeChildren = injectionPoint.includeChildren; + // By the time this function runs, we probably know enough to be sure of + // which layer controls the beginning of this row, even if we don't know + // which one owns the position at the cursor. + // + // Use the position of the first text on the line as the reference point. + let rowStartingColumn = Math.max(line.search(/\S/), 0); + let controllingLayer = languageMode.controllingLayerAtPoint( + new Point(row, rowStartingColumn), + (layer) => !!layer.queries.indentsQuery + ); - // We shouldn't retain references to nodes here because the tree might get - // disposed of layer. Let's compile the information we need now while we're - // sure the tree is fresh. - this.nodeSpecs = []; - for (let node of nodes) { - this.nodeSpecs.push(this.getNodeSpec(node, true)); - } - } + if (!controllingLayer) { return undefined; } - getNodeSpec(node, getChildren) { - let { startIndex, endIndex, startPosition, endPosition, id } = node; - let result = { startIndex, endIndex, startPosition, endPosition, id }; - if (getChildren && node.childCount > 0) { - result.children = []; - for (let child of node.children) { - result.children.push(this.getNodeSpec(child, false)); + let { queries: { indentsQuery }, scopeResolver } = controllingLayer; + if (!indentsQuery) { return undefined; } + + // TODO: We use `ScopeResolver` here so that we can use its tests. Maybe we + // need a way to share those tests across different kinds of capture + // resolvers. + scopeResolver.reset(); + + // Ideally, we're running when the tree is clean, but if not, we must + // re-parse the tree in order to make an accurate indents query. + let indentTree = options.tree; + if (!indentTree) { + if (!controllingLayer.treeIsDirty || options.forceTreeParse || !this.useAsyncIndent || !this.useAsyncParsing) { + indentTree = controllingLayer.getOrParseTree(); + } else { + return this.atTransactionEnd().then(({ changeCount }) => { + if (changeCount > 1) { + // Unlike `suggestedIndentForBufferRow`, we should not return + // `undefined` here and implicitly tell `TextEditor` to handle the + // auto-indent itself. If there were several changes in this + // transaction, we missed our chance to dedent this row, and should + // return `null` to signal that `TextEditor` should do nothing + // about it. + return null; + } + let result = this.suggestedIndentForEditedBufferRow(row, tabLength, { + ...options, + tree: controllingLayer.tree + }); + if (currentRowIndent === result) { + // Return `null` here so that `TextEditor` realizes that no work + // needs to be done. + return null; + } + return result; + }); } } - return result; - } - getRanges(buffer) { - const previousRanges = this.previous?.getRanges(buffer); - let result = []; + if (!indentTree) { + console.error(`No indent tree!`, controllingLayer.inspect()); + return undefined; + } - for (let node of this.nodeSpecs) { - // An injection point isn't given the point at which the buffer ends, so - // it's free to return an `endIndex` of `Infinity` here and rely on us to - // clip it to the boundary of the buffer. - if (node.endIndex === Infinity) { - node = this._clipRange(node, buffer); + const indents = indentsQuery.captures( + indentTree.rootNode, + { + startPosition: { row: row - 1, column: Infinity }, + endPosition: { row: row + 1, column: 0 } } - let position = node.startPosition, index = node.startIndex; + ); - if (node.children && !this.includeChildren) { - // If `includeChildren` is `false`, we're effectively collecting all - // the disjoint text nodes that are direct descendants of this node. - for (const child of node.children) { - const nextIndex = child.startIndex; - if (nextIndex > index) { - this._pushRange(buffer, previousRanges, result, { - startIndex: index, - endIndex: nextIndex, - startPosition: position, - endPosition: child.startPosition - }); - } - position = child.endPosition; - index = child.endIndex; - } - } + let lineText = this.buffer.lineForRow(row).trim(); + + // This is the indent level that is suggested from context — the level we'd + // have if this row were completely blank. We won't alter the indent level + // of the current row — even if it's “wrong” — unless typing triggers a + // dedent. But once a dedent is triggered, we should dedent one level from + // this value, not from the current row indent. + // + // If more than one level of dedent is needed, a `@match` capture must be + // used so that indent level can be expressed in absolute terms. + const originalRowIndent = this.suggestedIndentForBufferRow(row, tabLength, { + skipBlankLines: true, + skipDedentCheck: true, + skipEvent: true, + tree: indentTree + }); + + let seenDedent = false; + for (let indent of indents) { + let { node } = indent; + // Ignore captures that aren't on this row. + if (node.startPosition.row !== row) { continue; } + // Ignore captures that fail their scope tests. + if (!scopeResolver.store(indent)) { continue; } + // Apply indentation-specific scope tests and skip this capture if any + // tests fail. + let passed = this.applyTests(indent, { + currentRow: row, + comparisonRow, + tabLength + }); + if (!passed) return; + + let force = this.getProperty(indent, 'force', 'boolean', false); + + // For all captures — even `@match` captures — we get one bite at the + // apple, and it's when the text of the capture is the only + // non-whitespace text on the line. + // + // Otherwise, this capture will assert itself after every keystroke, and + // the user has no way to opt out of the correction. + // + // If the capture is confident it knows what it's doing, and is using + // some other mechanism to ensure the adjustment will happen exactly + // once, it can bypass this behavior with `(#set! indent.force true)`. + // + if (!force && node.text !== lineText) { continue; } - if (node.endIndex > index) { - this._pushRange(buffer, previousRanges, result, { - startIndex: index, - endIndex: node.endIndex, - startPosition: position, - endPosition: node.endPosition + // `@match` is authoritative; honor the first one we see and ignore other + // captures. + if (indent.name === 'match') { + let matchIndentLevel = this.resolveMatch(indent, { + currentRow: row, + comparisonRow, + tabLength }); - } - } - - let whitespaceRanges = []; - if (this.includeAdjacentWhitespace && result.length > 1) { - // Look at the region between each pair of results. If it's entirely - // whitespace, include it in the range. - for (let i = 1; i < result.length; i++) { - let current = result[i], previous = result[i - 1]; - if (current.startIndex === previous.endIndex) { continue; } - let pseudoRange = { - startPosition: previous.endPosition, - startIndex: previous.endIndex, - endPosition: current.startPosition, - endIndex: current.startIndex - }; - let rangeText = buffer.getTextInRange(rangeForNode(pseudoRange)); - if (!/\S/.test(rangeText)) { - whitespaceRanges.push(pseudoRange); + if (typeof matchIndentLevel === 'number') { + scopeResolver.reset(); + this.emitter.emit('did-suggest-indent', { + currentRow: row, + comparisonRow, + matchIndentLevel, + finalIndent: matchIndentLevel, + captureMode: 'match' + }); + return matchIndentLevel; } + } else if (indent.name === 'none') { + scopeResolver.reset(); + this.emitter.emit('did-suggest-indent', { + currentRow: row, + comparisonRow, + finalIndent: 0, + captureMode: 'none' + }); + return 0; } - result.push(...whitespaceRanges); - result = result.sort((a, b) => { - return a.startIndex - b.startIndex || - a.endIndex - b.endIndex; - }); - } - return this._consolidateRanges(result); - } - _clipRange(range, buffer) { - // Convert this range spec to an actual `Range`, clip it, then convert it - // back to a range spec with accurate `startIndex` and `endIndex` values. - let clippedRange = buffer.clipRange(rangeForNode(range)); - return rangeToTreeSitterRangeSpec(clippedRange, buffer); - } + if (indent.name !== 'dedent') { continue; } - // Combine adjacent ranges to minimize the number of boundaries. - _consolidateRanges(ranges) { - if (ranges.length === 1) { return ranges; } - let consolidated = []; - let candidate; - let lastIndex = ranges.length - 1; - for (let i = 0; i < ranges.length; i++) { - let range = ranges[i]; - if (!candidate) { - candidate = range; - continue; - } - if (candidate.endIndex === range.startIndex) { - // Keep enlarging the last node for as long as subsequent nodes are - // adjacent to it. - candidate = { - startIndex: candidate.startIndex, - startPosition: candidate.startPosition, - endIndex: range.endIndex, - endPosition: range.endPosition - }; - if (i === lastIndex) { - consolidated.push(candidate); - } - } else { - // We found a disjoint range, so push our candidate into the result set - // and promote a new candidate (unless we're at the end). - consolidated.push(candidate); - if (i === lastIndex) { - consolidated.push(range); - } else { - candidate = range; - } - } + // Even after we've seen a `@dedent`, we allow the loop to continue, + // because we'd prefer a `@match` capture over this `@dedent` capture + // even if it happened to come later in the loop. + seenDedent = true; } - return consolidated; - } + scopeResolver.reset(); - coversRange(candidateRange) { - let ranges = this.getRanges().map(r => rangeForNode(r)); - return ranges.some(range => { - return range.containsRange(candidateRange); + let finalIndent = seenDedent ? Math.max(0, originalRowIndent - 1) : currentRowIndent; + + this.emitter.emit('did-suggest-indent', { + currentRow: row, + comparisonRow, + finalIndent, + captureMode: 'normal' }); + + return finalIndent; } - _pushRange(buffer, previousRanges, newRanges, newRange) { - if (!previousRanges) { - if (this.newlinesBetween) { - const { startIndex, startPosition } = newRange; - this._ensureNewline(buffer, newRanges, startIndex, startPosition); + getComparisonRow(row, { skipBlankLines = true } = {}) { + let comparisonRow = row - 1; + if (skipBlankLines) { + // It usually makes no sense to compare to a blank row, so we'll move + // upward until we find a line with text on it. + while (this.buffer.isRowBlank(comparisonRow) && comparisonRow > 0) { + comparisonRow--; } - newRanges.push(newRange); - return; } + return comparisonRow; + } - for (const previousRange of previousRanges) { - if (previousRange.endIndex <= newRange.startIndex) continue; - if (previousRange.startIndex >= newRange.endIndex) break; - const startIndex = Math.max( - previousRange.startIndex, - newRange.startIndex - ); - const endIndex = Math.min(previousRange.endIndex, newRange.endIndex); - const startPosition = Point.max( - previousRange.startPosition, - newRange.startPosition - ); - const endPosition = Point.min( - previousRange.endPosition, - newRange.endPosition - ); - if (this.newlinesBetween) { - this._ensureNewline(buffer, newRanges, startIndex, startPosition); + indentLevelForLine(line, tabLength) { + let indentLength = 0; + for (let i = 0, { length } = line; i < length; i++) { + const char = line[i]; + if (char === '\t') { + indentLength += tabLength - (indentLength % tabLength); + } else if (char === ' ') { + indentLength++; + } else { + break; } - newRanges.push({ startIndex, endIndex, startPosition, endPosition }); } + return indentLength / tabLength } - // For injection points with `newlinesBetween` enabled, ensure that a - // newline is included between each disjoint range. - _ensureNewline(buffer, newRanges, startIndex, startPosition) { - const lastRange = last(newRanges); - if (lastRange && lastRange.endPosition.row < startPosition.row) { - newRanges.push({ - startPosition: new Point( - startPosition.row - 1, - buffer.lineLengthForRow(startPosition.row - 1) - ), - endPosition: new Point(startPosition.row, 0), - startIndex: startIndex - startPosition.column - 1, - endIndex: startIndex - startPosition.column - }); - } - } -} + resolveMatch(capture, { currentRow, tabLength, indentationLevels }) { + let { node } = capture; -class OpenScopeMap extends Map { - constructor() { - super(); - } + // `indent.match` used to be called `indent.matchIndentOf`. + let matchIndentOf = this.getProperty(capture, ['match', 'matchIndentOf'], 'string', null); + // `indent.offset` used to be called `indent.offsetIndent`. + let offsetIndent = this.getProperty(capture, ['offset', 'offsetIndent'], 'number', 0); - getScopesArray() { - let results = []; - let keys = [...this.keys()]; - keys.sort(comparePoints); - for (let key of keys) { - let value = this.get(key); - results.push(...value); - } - return results; - } + // A `@match` or `@match.next` capture must have an `indent.match` + // predicate. If it’s missing, the capture is invalid and we should pretend + // it wasn’t there at all. + if (!matchIndentOf) return undefined; - removeLastOccurrenceOf(scopeId) { - let keys = [...this.keys()]; - keys.reverse(); - for (let key of keys) { - let value = this.get(key); - if (value.includes(scopeId)) { - removeLastOccurrenceOf(value, scopeId); - return true; - } + // Turn an `indent.match` predicate into a node position. + let targetPosition = resolveNodePosition(node, matchIndentOf); + let targetRow = targetPosition?.row; + // If we fail to resolve the node position, it means the path described + // doesn't exist. We should behave as though this `@match` capture wasn’t + // present at all. + if (typeof targetRow !== 'number' || targetRow >= currentRow) { + return undefined; } - return false; - } -} - -// Like a map, but expects each key to have multiple values. -class Index extends Map { - constructor() { - super(); - } - add(key, ...values) { - let existing = this.get(key); - if (!existing) { - existing = []; - this.set(key, existing); + let baseIndent; + if (indentationLevels) { + baseIndent = indentationLevels.get(targetRow); } - existing.push(...values); - } -} + baseIndent ??= this.languageMode.indentLevelForLine( + this.buffer.lineForRow(targetRow), tabLength); + let result = baseIndent + offsetIndent; -// A class designed to aggregate and normalize a set of ranges. Each time a -// buffer range is added, it's compared to the existing list; if there are -// intersections with range already in the list, those intersections are -// combined into one larger range. -// -// Assumes all ranges are instances of `Range` rather than tree-sitter range -// specs. -class RangeList { - constructor() { - this.ranges = []; - } - - clear() { - this.ranges.length = 0; + // Because `indent.offset` can be any number, we can wind up with a + // negative number here, which is invalid. + return Math.max(result, 0); } - add(newRange) { - let intersecting = []; - for (let range of this.ranges) { - if (newRange.intersectsWith(range)) { - intersecting.push(range); + // Look up an `indent.` capture property applied with a `#set!` directive, + // optionally coercing to a specified type or falling back to a default + // value. + // + // `names` can be an array in cases where the property may have several + // aliases. The first one that exists will be returned. (Omit the leading + // `indent.` when passing property names.) + getProperty(capture, names, coercion = null, fallback = null) { + let { setProperties: props = {} } = capture; + if (typeof names === 'string') { names = [names]; } + for (let name of names) { + let fullName = `indent.${name}`; + if (!(fullName in props)) { continue; } + return this.coerce(props[fullName], coercion) ?? fallback; + } + return fallback; + } + + coerce(value, coercion) { + switch (coercion) { + case String: + case 'string': + if (value == null) return ""; + return value; + case Number: + case 'number': { + let number = Number(value); + if (isNaN(number)) return null; + return number; } + case Boolean: + case 'boolean': + if (value == null) return null; + return true; + default: + return value; + } + } + + applyTests(capture, meta) { + let { + node, + assertedProperties: asserted = {}, + refutedProperties: refuted = {} + } = capture; + for (let [name, test] of Object.entries(IndentResolver.TESTS)) { + let fullName = `indent.${name}` + let passed = true; + if (asserted[fullName]) { + passed = test(node, asserted[fullName], meta); + } else if (refuted[fullName]) { + passed = !test(node, asserted[fullName], meta); + } + if (!passed) return false; } - - for (let i = intersecting.length - 1; i >= 0; i--) { - let index = this.ranges.indexOf(intersecting[i]); - this.ranges.splice(index, 1); - } - while (intersecting.length > 0) { - newRange = newRange.union(intersecting.shift()); - } - this.insertOrdered(newRange); - } - - insertOrdered(newRange) { - let index = this.ranges.findIndex(r => { - return r.start.compare(newRange.start) > 0; - }); - this.ranges.splice(index, 0, newRange); + return true; } +} - inspect() { - let ranges = this.ranges.map(r => r.toString()); - return `[RangeList: ${ranges.join(', ')}]`; - } +// Indentation queries have a small number of query tests. These can't be +// implemented as generic scope tests because they expose metadata that only +// makes sense in an indentation context. +IndentResolver.TESTS = { + // Returns `true` if the position descriptor's row equals that of the current + // row (the row whose indentation level is being suggested). + // + // For example: + // + // (#is? indent.matchesCurrentRow startPosition) + // + // in a `@match` capture will pass if the captured node starts on the current + // row. + matchesCurrentRow(node, value, { currentRow }) { + let position = resolveNodePosition(node, value); + if (!position) return null; + return position.row === currentRow; + }, - *[Symbol.iterator]() { - for (let range of this.ranges) { - yield range; - } + // Returns `true` if the position descriptor's row equals that of the + // comparison row (the row used as a reference when determining the + // indentation level of the current row). + // + // For example: + // + // (#is? indent.matchesComparisonRow endPosition) + // + // in a `@match` capture will pass if the captured node ends on the + // comparison row. + matchesComparisonRow(node, value, { comparisonRow }) { + let position = resolveNodePosition(node, value); + if (!position) return null; + return position.row === comparisonRow; } } diff --git a/vendor/web-tree-sitter/README.md b/vendor/web-tree-sitter/README.md index 93ad77292d..6b3db8db5a 100644 --- a/vendor/web-tree-sitter/README.md +++ b/vendor/web-tree-sitter/README.md @@ -1,74 +1,84 @@ # Building a custom web-tree-sitter -Tree-sitter parsers often use external C scanners, and those scanners sometimes use functions in the C standard library. For this to work in a WASM environment, web-tree-sitter needs to have anticipated which stdlib functions will need to be available. If a tree-sitter parser uses stdlib function X, but X is not included in [this list of exports](https://github.com/tree-sitter/tree-sitter/blob/master/lib/binding_web/exports.json), the parser will fail to work and will throw an error whenever it hits a code path that uses the rogue function. +Tree-sitter parsers often use external C scanners, and those scanners sometimes use functions in the C standard library. For this to work in a WASM environment, `web-tree-sitter` needs to have anticipated which stdlib functions will need to be available. If a Tree-sitter parser uses stdlib function X, but X is not included in [this list of symbols](https://github.com/tree-sitter/tree-sitter/blob/master/lib/src/wasm/stdlib-symbols.txt), the parser will fail to work and will throw an error whenever it hits a code path that uses the rogue function. -For this reason, Pulsar builds a custom web-tree-sitter. Every time someone tries to integrate a new tree-sitter parser into a Pulsar grammar, they might find that the parser relies on some stdlib function we haven’t included yet — in which case they can let us know and we’ll be able to update our web-tree-sitter build so that it can export that function. +For this reason, Pulsar builds a custom `web-tree-sitter`. Every time someone tries to integrate a new tree-sitter parser into a Pulsar grammar, they might find that the parser relies on some stdlib function we haven’t included yet — in which case they can let us know and we’ll be able to update our `web-tree-sitter` build so that it can export that function. -Pulsar will need to do this until [tree-sitter#949](https://github.com/tree-sitter/tree-sitter/issues/949) is addressed in some way. +The need to do this will decrease over time as C++ scanners are deprecated and as parsers are increasingly encouraged to use a fixed subset of possible stdlib exports, but it’s still necessary right now. + +We also take advantage of the custom build by adding a check for a common failure scenario — a parser trying to use a stdlib function that _hasn’t_ been exported — so that we can log a helpful error message to the console when it happens. ## Check out the modified branch for the version we’re targeting -At time of writing, Pulsar was targeting web-tree-sitter version 0.20.7, so a branch exists [on our fork](https://github.com/pulsar-edit/tree-sitter/tree/v0-20-7-modified) called `v0-20-7-modified`. That branch contains a modified `exports.json` file and a modified script for building web-tree-sitter. +At time of writing, Pulsar was targeting `web-tree-sitter` version **0.23.0**, so a branch exists [on our fork](https://github.com/pulsar-edit/tree-sitter/tree/v0-23-0-modified) called `v0-23-0-modified`. That branch contains a modified `stdlib-symbols.txt` file and a modified script for building `web-tree-sitter`. -When we target a newer version of web-tree-sitter, a similar branch should be created against the corresponding upstream tag. The commits that were applied on the previous modified branch should be able to be cherry-picked onto the new one rather easily. +When we target a newer version of `web-tree-sitter`, a similar branch should be created against the corresponding upstream tag. The commits that were applied on the previous modified branch should be able to be cherry-picked onto the new one rather easily. -## Add whatever methods are needed to `exports.json` +## Add whatever methods are needed to `stdlib-symbols.txt` -For instance, tree-sitter-ruby introduced a new dependency on the C stdlib function `iswupper` a while back, and web-tree-sitter doesn’t export that one by default. So we can add the line +For instance, one of the parsers we use depends on the C stdlib function `isalnum`, and `web-tree-sitter` doesn’t export that one by default. So we can add the line ``` - "_iswupper", + "isalnum", ``` -in an appropriate place in `exports.json`, then rebuild web-tree-sitter so that the WASM-built version of the tree-sitter-ruby parser has that function available to it. +in an appropriate place in `stdlib-symbols.txt`, then rebuild `web-tree-sitter` so that the WASM-built version of that parser has that function available to it. -If a third-party tree-sitter grammar needs something more esoteric, our default position should be to add it to the build. If the export results in a major change in file size or — somehow — performance, then the change can be discussed. +If a third-party tree-sitter grammar needs something more esoteric, we should encourage them to follow current best practices for Tree-sitter parsers. But we may still want to add that dependency to the build. ## Run `script/build-wasm` from the root -To build web-tree-sitter for a particular version, make sure you’re using the appropriate version of Emscripten. [This document](https://github.com/sogaiu/ts-questions/blob/master/questions/which-version-of-emscripten-should-be-used-for-the-playground/README.md) is useful at matching up tree-sitter versions with Emscripten versions. - -The default `build-wasm` script performs minification with terser. That’s easy enough to turn off — and we do — but even without minifcation, emscripten generates a JS file that doesn’t have line breaks or indentation. We fix this by running `js-beautify` as a final step. +To build `web-tree-sitter` for a particular version, make sure you’re using the appropriate version of Emscripten. [This document](https://github.com/sogaiu/ts-questions/blob/master/questions/which-version-of-emscripten-should-be-used-for-the-playground/README.md) is useful at matching up tree-sitter versions with Emscripten versions. -Pulsar, as a desktop app, doesn’t gain a _lot_ from minification, and ultimately it’s better to have a source file that the user can more easily debug if necessary. And it makes the next change a bit easier: +The default `build-wasm` script now skips minification, so we no longer have to un-minify the JavaScript output. ## Add a warning message -When a parser tries to use a stdlib function that isn’t exported by web-tree-sitter, the error that’s thrown is not very useful. So we try to detect when that scenario is going to happen and insert a warning in the console to help users that might otherwise be befuddled. +When a parser tries to use a stdlib function that isn’t exported by `web-tree-sitter`, the error that’s thrown is not very useful. So we try to detect when that scenario is going to happen and insert a warning in the console to help users that might otherwise be befuddled. -This may be automated in the future, but for now you can modify `tree-sitter.js` to include the `checkForAsmVersion` function: +This may be automated in the future, but for now you can modify `tree-sitter.js` so that this function… ```js -var Module = typeof Module !== "undefined" ? Module : {}; -var TreeSitter = function() { - - function checkForAsmVersion(prop) { - if (!(prop in Module['asm'])) { - console.warn(`Warning: parser wants to call function ${prop}, but it is not defined. If parsing fails, this is probably the reason why. Please report this to the Pulsar team so that this parser can be supported properly.`); - } +function resolveSymbol(sym) { + var resolved = resolveGlobalSymbol(sym).sym; + if (!resolved && localScope) { + resolved = localScope[sym]; } - - var initPromise; - var document = typeof window == "object" ? { - currentScript: window.document.currentScript - } : null; + if (!resolved) { + resolved = moduleExports[sym]; + } + return resolved; +} ``` -You can then search for this line +…has an extra check at the end: ```js -if (!resolved) resolved = resolveSymbol(prop, true); +function resolveSymbol(sym) { + var resolved = resolveGlobalSymbol(sym).sym; + if (!resolved && localScope) { + resolved = localScope[sym]; + } + if (!resolved) { + resolved = moduleExports[sym]; + } + if (!resolved) { + console.warn(`Warning: parser wants to call function ${sym}, but it is not defined. If parsing fails, this is probably the reason why. Please report this to the Pulsar team so that this parser can be supported properly.`); + } + return resolved; +} ``` -and add the following line right below it: - -```js -checkForAsmVersion(prop); -``` -The line in question is [generated by emscripten](https://github.com/emscripten-core/emscripten/blob/67ebee3261629f7e3c2bd24b61098af0c730d8d9/src/library_dylink.js#L699), so if it changes in the future, you should be able to look up its equivalent in the correct version of emscripten. +The function in question is [generated by emscripten](https://github.com/emscripten-core/emscripten/blob/127fb03dad7288a71f51bd46be49f1da8bdb0fa8/src/library_dylink.js#L665-L677) and is the rough equivalent of what we’d get if we built with assertions enabled (though less generic and more tailored to Pulsar). If the implementation changes on the emscripten side, you should still be able to find the equivalent logic. ## Copy it to `vendor` Under `lib/binding_web` you’ll find the built files `tree-sitter.js` and `tree-sitter.wasm`. Copy both to Pulsar’s `vendor/tree-sitter` directory. Relaunch Pulsar and do a smoke test with a couple of existing grammars to make sure you didn’t break anything. + +## Commit it + +Be sure to mention the version you’re upgrading to **in the commit message** so grammar authors have some way of discerning the version of `web-tree-sitter` they should target. + +(It’s a stretch goal to include this information in a more structured format so that it can be inspected at runtime.) diff --git a/vendor/web-tree-sitter/tree-sitter.js b/vendor/web-tree-sitter/tree-sitter.js index 5758ed51a3..db72adc2ad 100644 --- a/vendor/web-tree-sitter/tree-sitter.js +++ b/vendor/web-tree-sitter/tree-sitter.js @@ -1,3223 +1,3283 @@ +// include: shell.js +// The Module object: Our interface to the outside world. We import +// and export values on it. There are various ways Module can be used: +// 1. Not defined. We create it here +// 2. A function parameter, function(moduleArg) => Promise +// 3. pre-run appended it, var Module = {}; ..generated code.. +// 4. External script tag defines var Module. +// We need to check if Module already exists (e.g. case 3 above). +// Substitution will be replaced with actual code on later stage of the build, +// this way Closure Compiler will not mangle it (e.g. case 4. above). +// Note that if you want to run closure, and also to use Module +// after the generated code, you will need to define var Module = {}; +// before the code. Then that object will be used in the code, and you +// can continue to use Module afterwards as well. var Module = typeof Module != "undefined" ? Module : {}; -var TreeSitter = function() { - function checkForAsmVersion(prop) { - if (!(prop in Module['asm'])) { - console.warn(`Warning: parser wants to call function ${prop}, but it is not defined. If parsing fails, this is probably the reason why. Please report this to the Pulsar team so that this parser can be supported properly.`); - } - } - var initPromise; - var document = typeof window == "object" ? { - currentScript: window.document.currentScript - } : null; - class Parser { - constructor() { - this.initialize() - } - initialize() { - throw new Error("cannot construct a Parser before calling `init()`") - } - static init(moduleOptions) { - if (initPromise) return initPromise; - Module = Object.assign({}, Module, moduleOptions); - return initPromise = new Promise(resolveInitPromise => { - var moduleOverrides = Object.assign({}, Module); - var arguments_ = []; - var thisProgram = "./this.program"; - var quit_ = (status, toThrow) => { - throw toThrow - }; - var ENVIRONMENT_IS_WEB = typeof window == "object"; - var ENVIRONMENT_IS_WORKER = typeof importScripts == "function"; - var ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; - var scriptDirectory = ""; - - function locateFile(path) { - if (Module["locateFile"]) { - return Module["locateFile"](path, scriptDirectory) - } - return scriptDirectory + path - } - var read_, readAsync, readBinary, setWindowTitle; - if (ENVIRONMENT_IS_NODE) { - var fs = require("fs"); - var nodePath = require("path"); - if (ENVIRONMENT_IS_WORKER) { - scriptDirectory = nodePath.dirname(scriptDirectory) + "/" - } else { - scriptDirectory = __dirname + "/" - } - read_ = (filename, binary) => { - filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); - return fs.readFileSync(filename, binary ? undefined : "utf8") - }; - readBinary = filename => { - var ret = read_(filename, true); - if (!ret.buffer) { - ret = new Uint8Array(ret) - } - return ret - }; - readAsync = (filename, onload, onerror, binary = true) => { - filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); - fs.readFile(filename, binary ? undefined : "utf8", (err, data) => { - if (err) onerror(err); - else onload(binary ? data.buffer : data) - }) - }; - if (!Module["thisProgram"] && process.argv.length > 1) { - thisProgram = process.argv[1].replace(/\\/g, "/") - } - arguments_ = process.argv.slice(2); - if (typeof module != "undefined") { - module["exports"] = Module - } - quit_ = (status, toThrow) => { - process.exitCode = status; - throw toThrow - }; - Module["inspect"] = () => "[Emscripten Module object]" - } else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { - if (ENVIRONMENT_IS_WORKER) { - scriptDirectory = self.location.href - } else if (typeof document != "undefined" && document.currentScript) { - scriptDirectory = document.currentScript.src - } - if (scriptDirectory.indexOf("blob:") !== 0) { - scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1) - } else { - scriptDirectory = "" - } { - read_ = url => { - var xhr = new XMLHttpRequest; - xhr.open("GET", url, false); - xhr.send(null); - return xhr.responseText - }; - if (ENVIRONMENT_IS_WORKER) { - readBinary = url => { - var xhr = new XMLHttpRequest; - xhr.open("GET", url, false); - xhr.responseType = "arraybuffer"; - xhr.send(null); - return new Uint8Array(xhr.response) - } - } - readAsync = (url, onload, onerror) => { - var xhr = new XMLHttpRequest; - xhr.open("GET", url, true); - xhr.responseType = "arraybuffer"; - xhr.onload = () => { - if (xhr.status == 200 || xhr.status == 0 && xhr.response) { - onload(xhr.response); - return - } - onerror() - }; - xhr.onerror = onerror; - xhr.send(null) - } - } - setWindowTitle = title => document.title = title - } else {} - var out = Module["print"] || console.log.bind(console); - var err = Module["printErr"] || console.warn.bind(console); - Object.assign(Module, moduleOverrides); - moduleOverrides = null; - if (Module["arguments"]) arguments_ = Module["arguments"]; - if (Module["thisProgram"]) thisProgram = Module["thisProgram"]; - if (Module["quit"]) quit_ = Module["quit"]; - var dynamicLibraries = Module["dynamicLibraries"] || []; - var wasmBinary; - if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"]; - var noExitRuntime = Module["noExitRuntime"] || true; - if (typeof WebAssembly != "object") { - abort("no native wasm support detected") - } - var wasmMemory; - var ABORT = false; - var EXITSTATUS; - - function assert(condition, text) { - if (!condition) { - abort(text) - } - } - var HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64; - - function updateMemoryViews() { - var b = wasmMemory.buffer; - Module["HEAP8"] = HEAP8 = new Int8Array(b); - Module["HEAP16"] = HEAP16 = new Int16Array(b); - Module["HEAP32"] = HEAP32 = new Int32Array(b); - Module["HEAPU8"] = HEAPU8 = new Uint8Array(b); - Module["HEAPU16"] = HEAPU16 = new Uint16Array(b); - Module["HEAPU32"] = HEAPU32 = new Uint32Array(b); - Module["HEAPF32"] = HEAPF32 = new Float32Array(b); - Module["HEAPF64"] = HEAPF64 = new Float64Array(b) - } - var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 33554432; - assert(INITIAL_MEMORY >= 65536, "INITIAL_MEMORY should be larger than STACK_SIZE, was " + INITIAL_MEMORY + "! (STACK_SIZE=" + 65536 + ")"); - if (Module["wasmMemory"]) { - wasmMemory = Module["wasmMemory"] - } else { - wasmMemory = new WebAssembly.Memory({ - "initial": INITIAL_MEMORY / 65536, - "maximum": 2147483648 / 65536 - }) - } - updateMemoryViews(); - INITIAL_MEMORY = wasmMemory.buffer.byteLength; - var wasmTable = new WebAssembly.Table({ - "initial": 25, - "element": "anyfunc" - }); - var __ATPRERUN__ = []; - var __ATINIT__ = []; - var __ATMAIN__ = []; - var __ATPOSTRUN__ = []; - var __RELOC_FUNCS__ = []; - var runtimeInitialized = false; - var runtimeKeepaliveCounter = 0; - - function keepRuntimeAlive() { - return noExitRuntime || runtimeKeepaliveCounter > 0 - } - - function preRun() { - if (Module["preRun"]) { - if (typeof Module["preRun"] == "function") Module["preRun"] = [Module["preRun"]]; - while (Module["preRun"].length) { - addOnPreRun(Module["preRun"].shift()) - } - } - callRuntimeCallbacks(__ATPRERUN__) - } - - function initRuntime() { - runtimeInitialized = true; - callRuntimeCallbacks(__RELOC_FUNCS__); - callRuntimeCallbacks(__ATINIT__) - } - - function preMain() { - callRuntimeCallbacks(__ATMAIN__) - } - - function postRun() { - if (Module["postRun"]) { - if (typeof Module["postRun"] == "function") Module["postRun"] = [Module["postRun"]]; - while (Module["postRun"].length) { - addOnPostRun(Module["postRun"].shift()) - } - } - callRuntimeCallbacks(__ATPOSTRUN__) - } - - function addOnPreRun(cb) { - __ATPRERUN__.unshift(cb) - } - - function addOnInit(cb) { - __ATINIT__.unshift(cb) - } - - function addOnPostRun(cb) { - __ATPOSTRUN__.unshift(cb) - } - var runDependencies = 0; - var runDependencyWatcher = null; - var dependenciesFulfilled = null; - - function getUniqueRunDependency(id) { - return id - } - - function addRunDependency(id) { - runDependencies++; - if (Module["monitorRunDependencies"]) { - Module["monitorRunDependencies"](runDependencies) - } - } - - function removeRunDependency(id) { - runDependencies--; - if (Module["monitorRunDependencies"]) { - Module["monitorRunDependencies"](runDependencies) - } - if (runDependencies == 0) { - if (runDependencyWatcher !== null) { - clearInterval(runDependencyWatcher); - runDependencyWatcher = null - } - if (dependenciesFulfilled) { - var callback = dependenciesFulfilled; - dependenciesFulfilled = null; - callback() - } - } - } - - function abort(what) { - if (Module["onAbort"]) { - Module["onAbort"](what) - } - what = "Aborted(" + what + ")"; - err(what); - ABORT = true; - EXITSTATUS = 1; - what += ". Build with -sASSERTIONS for more info."; - var e = new WebAssembly.RuntimeError(what); - throw e - } - var dataURIPrefix = "data:application/octet-stream;base64,"; - - function isDataURI(filename) { - return filename.startsWith(dataURIPrefix) - } - - function isFileURI(filename) { - return filename.startsWith("file://") - } - var wasmBinaryFile; - wasmBinaryFile = "tree-sitter.wasm"; - if (!isDataURI(wasmBinaryFile)) { - wasmBinaryFile = locateFile(wasmBinaryFile) - } - - function getBinary(file) { - try { - if (file == wasmBinaryFile && wasmBinary) { - return new Uint8Array(wasmBinary) - } - if (readBinary) { - return readBinary(file) - } - throw "both async and sync fetching of the wasm failed" - } catch (err) { - abort(err) - } - } - - function getBinaryPromise(binaryFile) { - if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) { - if (typeof fetch == "function" && !isFileURI(binaryFile)) { - return fetch(binaryFile, { - credentials: "same-origin" - }).then(response => { - if (!response["ok"]) { - throw "failed to load wasm binary file at '" + binaryFile + "'" - } - return response["arrayBuffer"]() - }).catch(() => getBinary(binaryFile)) - } else { - if (readAsync) { - return new Promise((resolve, reject) => { - readAsync(binaryFile, response => resolve(new Uint8Array(response)), reject) - }) - } - } - } - return Promise.resolve().then(() => getBinary(binaryFile)) - } - - function instantiateArrayBuffer(binaryFile, imports, receiver) { - return getBinaryPromise(binaryFile).then(binary => { - return WebAssembly.instantiate(binary, imports) - }).then(instance => { - return instance - }).then(receiver, reason => { - err("failed to asynchronously prepare wasm: " + reason); - abort(reason) - }) - } - - function instantiateAsync(binary, binaryFile, imports, callback) { - if (!binary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(binaryFile) && !isFileURI(binaryFile) && !ENVIRONMENT_IS_NODE && typeof fetch == "function") { - return fetch(binaryFile, { - credentials: "same-origin" - }).then(response => { - var result = WebAssembly.instantiateStreaming(response, imports); - return result.then(callback, function(reason) { - err("wasm streaming compile failed: " + reason); - err("falling back to ArrayBuffer instantiation"); - return instantiateArrayBuffer(binaryFile, imports, callback) - }) - }) - } else { - return instantiateArrayBuffer(binaryFile, imports, callback) - } - } - - function createWasm() { - var info = { - "env": wasmImports, - "wasi_snapshot_preview1": wasmImports, - "GOT.mem": new Proxy(wasmImports, GOTHandler), - "GOT.func": new Proxy(wasmImports, GOTHandler) - }; - - function receiveInstance(instance, module) { - var exports = instance.exports; - exports = relocateExports(exports, 1024); - var metadata = getDylinkMetadata(module); - if (metadata.neededDynlibs) { - dynamicLibraries = metadata.neededDynlibs.concat(dynamicLibraries) - } - mergeLibSymbols(exports, "main"); - Module["asm"] = exports; - addOnInit(Module["asm"]["__wasm_call_ctors"]); - __RELOC_FUNCS__.push(Module["asm"]["__wasm_apply_data_relocs"]); - removeRunDependency("wasm-instantiate"); - return exports - } - addRunDependency("wasm-instantiate"); - - function receiveInstantiationResult(result) { - receiveInstance(result["instance"], result["module"]) - } - if (Module["instantiateWasm"]) { - try { - return Module["instantiateWasm"](info, receiveInstance) - } catch (e) { - err("Module.instantiateWasm callback failed with error: " + e); - return false - } - } - instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult); - return {} - } - var tempDouble; - var tempI64; - var ASM_CONSTS = {}; - - function ExitStatus(status) { - this.name = "ExitStatus"; - this.message = "Program terminated with exit(" + status + ")"; - this.status = status - } - var GOT = {}; - var currentModuleWeakSymbols = new Set([]); - var GOTHandler = { - get: function(obj, symName) { - var rtn = GOT[symName]; - if (!rtn) { - rtn = GOT[symName] = new WebAssembly.Global({ - "value": "i32", - "mutable": true - }) - } - if (!currentModuleWeakSymbols.has(symName)) { - rtn.required = true - } - return rtn - } - }; - - function callRuntimeCallbacks(callbacks) { - while (callbacks.length > 0) { - callbacks.shift()(Module) - } - } - var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder("utf8") : undefined; - - function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) { - var endIdx = idx + maxBytesToRead; - var endPtr = idx; - while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; - if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { - return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)) - } - var str = ""; - while (idx < endPtr) { - var u0 = heapOrArray[idx++]; - if (!(u0 & 128)) { - str += String.fromCharCode(u0); - continue - } - var u1 = heapOrArray[idx++] & 63; - if ((u0 & 224) == 192) { - str += String.fromCharCode((u0 & 31) << 6 | u1); - continue - } - var u2 = heapOrArray[idx++] & 63; - if ((u0 & 240) == 224) { - u0 = (u0 & 15) << 12 | u1 << 6 | u2 - } else { - u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | heapOrArray[idx++] & 63 - } - if (u0 < 65536) { - str += String.fromCharCode(u0) - } else { - var ch = u0 - 65536; - str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023) - } - } - return str - } - - function getDylinkMetadata(binary) { - var offset = 0; - var end = 0; - - function getU8() { - return binary[offset++] - } - - function getLEB() { - var ret = 0; - var mul = 1; - while (1) { - var byte = binary[offset++]; - ret += (byte & 127) * mul; - mul *= 128; - if (!(byte & 128)) break - } - return ret - } - - function getString() { - var len = getLEB(); - offset += len; - return UTF8ArrayToString(binary, offset - len, len) - } - - function failIf(condition, message) { - if (condition) throw new Error(message) - } - var name = "dylink.0"; - if (binary instanceof WebAssembly.Module) { - var dylinkSection = WebAssembly.Module.customSections(binary, name); - if (dylinkSection.length === 0) { - name = "dylink"; - dylinkSection = WebAssembly.Module.customSections(binary, name) - } - failIf(dylinkSection.length === 0, "need dylink section"); - binary = new Uint8Array(dylinkSection[0]); - end = binary.length - } else { - var int32View = new Uint32Array(new Uint8Array(binary.subarray(0, 24)).buffer); - var magicNumberFound = int32View[0] == 1836278016; - failIf(!magicNumberFound, "need to see wasm magic number"); - failIf(binary[8] !== 0, "need the dylink section to be first"); - offset = 9; - var section_size = getLEB(); - end = offset + section_size; - name = getString() - } - var customSection = { - neededDynlibs: [], - tlsExports: new Set, - weakImports: new Set - }; - if (name == "dylink") { - customSection.memorySize = getLEB(); - customSection.memoryAlign = getLEB(); - customSection.tableSize = getLEB(); - customSection.tableAlign = getLEB(); - var neededDynlibsCount = getLEB(); - for (var i = 0; i < neededDynlibsCount; ++i) { - var libname = getString(); - customSection.neededDynlibs.push(libname) - } - } else { - failIf(name !== "dylink.0"); - var WASM_DYLINK_MEM_INFO = 1; - var WASM_DYLINK_NEEDED = 2; - var WASM_DYLINK_EXPORT_INFO = 3; - var WASM_DYLINK_IMPORT_INFO = 4; - var WASM_SYMBOL_TLS = 256; - var WASM_SYMBOL_BINDING_MASK = 3; - var WASM_SYMBOL_BINDING_WEAK = 1; - while (offset < end) { - var subsectionType = getU8(); - var subsectionSize = getLEB(); - if (subsectionType === WASM_DYLINK_MEM_INFO) { - customSection.memorySize = getLEB(); - customSection.memoryAlign = getLEB(); - customSection.tableSize = getLEB(); - customSection.tableAlign = getLEB() - } else if (subsectionType === WASM_DYLINK_NEEDED) { - var neededDynlibsCount = getLEB(); - for (var i = 0; i < neededDynlibsCount; ++i) { - libname = getString(); - customSection.neededDynlibs.push(libname) - } - } else if (subsectionType === WASM_DYLINK_EXPORT_INFO) { - var count = getLEB(); - while (count--) { - var symname = getString(); - var flags = getLEB(); - if (flags & WASM_SYMBOL_TLS) { - customSection.tlsExports.add(symname) - } - } - } else if (subsectionType === WASM_DYLINK_IMPORT_INFO) { - var count = getLEB(); - while (count--) { - var modname = getString(); - var symname = getString(); - var flags = getLEB(); - if ((flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) { - customSection.weakImports.add(symname) - } - } - } else { - offset += subsectionSize - } - } - } - return customSection - } - - function getValue(ptr, type = "i8") { - if (type.endsWith("*")) type = "*"; - switch (type) { - case "i1": - return HEAP8[ptr >> 0]; - case "i8": - return HEAP8[ptr >> 0]; - case "i16": - return HEAP16[ptr >> 1]; - case "i32": - return HEAP32[ptr >> 2]; - case "i64": - return HEAP32[ptr >> 2]; - case "float": - return HEAPF32[ptr >> 2]; - case "double": - return HEAPF64[ptr >> 3]; - case "*": - return HEAPU32[ptr >> 2]; - default: - abort("invalid type for getValue: " + type) - } - } - - function newDSO(name, handle, syms) { - var dso = { - refcount: Infinity, - name: name, - exports: syms, - global: true - }; - LDSO.loadedLibsByName[name] = dso; - if (handle != undefined) { - LDSO.loadedLibsByHandle[handle] = dso - } - return dso - } - var LDSO = { - loadedLibsByName: {}, - loadedLibsByHandle: {}, - init: () => newDSO("__main__", 0, wasmImports) - }; - var ___heap_base = 78160; - - function zeroMemory(address, size) { - HEAPU8.fill(0, address, address + size); - return address - } - - function getMemory(size) { - if (runtimeInitialized) { - return zeroMemory(_malloc(size), size) - } - var ret = ___heap_base; - var end = ret + size + 15 & -16; - ___heap_base = end; - GOT["__heap_base"].value = end; - return ret - } - - function isInternalSym(symName) { - return ["__cpp_exception", "__c_longjmp", "__wasm_apply_data_relocs", "__dso_handle", "__tls_size", "__tls_align", "__set_stack_limits", "_emscripten_tls_init", "__wasm_init_tls", "__wasm_call_ctors", "__start_em_asm", "__stop_em_asm"].includes(symName) - } - - function uleb128Encode(n, target) { - if (n < 128) { - target.push(n) - } else { - target.push(n % 128 | 128, n >> 7) - } - } - - function sigToWasmTypes(sig) { - var typeNames = { - "i": "i32", - "j": "i32", - "f": "f32", - "d": "f64", - "p": "i32" - }; - var type = { - parameters: [], - results: sig[0] == "v" ? [] : [typeNames[sig[0]]] - }; - for (var i = 1; i < sig.length; ++i) { - type.parameters.push(typeNames[sig[i]]); - if (sig[i] === "j") { - type.parameters.push("i32") - } - } - return type - } - - function generateFuncType(sig, target) { - var sigRet = sig.slice(0, 1); - var sigParam = sig.slice(1); - var typeCodes = { - "i": 127, - "p": 127, - "j": 126, - "f": 125, - "d": 124 - }; - target.push(96); - uleb128Encode(sigParam.length, target); - for (var i = 0; i < sigParam.length; ++i) { - target.push(typeCodes[sigParam[i]]) - } - if (sigRet == "v") { - target.push(0) - } else { - target.push(1, typeCodes[sigRet]) - } - } - - function convertJsFunctionToWasm(func, sig) { - if (typeof WebAssembly.Function == "function") { - return new WebAssembly.Function(sigToWasmTypes(sig), func) - } - var typeSectionBody = [1]; - generateFuncType(sig, typeSectionBody); - var bytes = [0, 97, 115, 109, 1, 0, 0, 0, 1]; - uleb128Encode(typeSectionBody.length, bytes); - bytes.push.apply(bytes, typeSectionBody); - bytes.push(2, 7, 1, 1, 101, 1, 102, 0, 0, 7, 5, 1, 1, 102, 0, 0); - var module = new WebAssembly.Module(new Uint8Array(bytes)); - var instance = new WebAssembly.Instance(module, { - "e": { - "f": func - } - }); - var wrappedFunc = instance.exports["f"]; - return wrappedFunc - } - var wasmTableMirror = []; - - function getWasmTableEntry(funcPtr) { - var func = wasmTableMirror[funcPtr]; - if (!func) { - if (funcPtr >= wasmTableMirror.length) wasmTableMirror.length = funcPtr + 1; - wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr) - } - return func - } - - function updateTableMap(offset, count) { - if (functionsInTableMap) { - for (var i = offset; i < offset + count; i++) { - var item = getWasmTableEntry(i); - if (item) { - functionsInTableMap.set(item, i) - } - } - } - } - var functionsInTableMap = undefined; - - function getFunctionAddress(func) { - if (!functionsInTableMap) { - functionsInTableMap = new WeakMap; - updateTableMap(0, wasmTable.length) - } - return functionsInTableMap.get(func) || 0 - } - var freeTableIndexes = []; - - function getEmptyTableSlot() { - if (freeTableIndexes.length) { - return freeTableIndexes.pop() - } - try { - wasmTable.grow(1) - } catch (err) { - if (!(err instanceof RangeError)) { - throw err - } - throw "Unable to grow wasm table. Set ALLOW_TABLE_GROWTH." - } - return wasmTable.length - 1 - } - - function setWasmTableEntry(idx, func) { - wasmTable.set(idx, func); - wasmTableMirror[idx] = wasmTable.get(idx) - } - - function addFunction(func, sig) { - var rtn = getFunctionAddress(func); - if (rtn) { - return rtn - } - var ret = getEmptyTableSlot(); - try { - setWasmTableEntry(ret, func) - } catch (err) { - if (!(err instanceof TypeError)) { - throw err - } - var wrapped = convertJsFunctionToWasm(func, sig); - setWasmTableEntry(ret, wrapped) - } - functionsInTableMap.set(func, ret); - return ret - } - - function updateGOT(exports, replace) { - for (var symName in exports) { - if (isInternalSym(symName)) { - continue - } - var value = exports[symName]; - if (symName.startsWith("orig$")) { - symName = symName.split("$")[1]; - replace = true - } - if (!GOT[symName]) { - GOT[symName] = new WebAssembly.Global({ - "value": "i32", - "mutable": true - }) - } - if (replace || GOT[symName].value == 0) { - if (typeof value == "function") { - GOT[symName].value = addFunction(value) - } else if (typeof value == "number") { - GOT[symName].value = value - } else { - err("unhandled export type for `" + symName + "`: " + typeof value) - } - } - } - } - - function relocateExports(exports, memoryBase, replace) { - var relocated = {}; - for (var e in exports) { - var value = exports[e]; - if (typeof value == "object") { - value = value.value - } - if (typeof value == "number") { - value += memoryBase - } - relocated[e] = value - } - updateGOT(relocated, replace); - return relocated - } - - function isSymbolDefined(symName) { - var existing = wasmImports[symName]; - if (!existing || existing.stub) { - return false - } - return true - } - - function resolveGlobalSymbol(symName, direct = false) { - var sym; - if (direct && "orig$" + symName in wasmImports) { - symName = "orig$" + symName - } - if (isSymbolDefined(symName)) { - sym = wasmImports[symName] - } else if (symName.startsWith("invoke_")) { - sym = wasmImports[symName] = createInvokeFunction(symName.split("_")[1]) - } - return { - sym: sym, - name: symName - } - } - - function alignMemory(size, alignment) { - return Math.ceil(size / alignment) * alignment - } - - function dynCallLegacy(sig, ptr, args) { - var f = Module["dynCall_" + sig]; - return args && args.length ? f.apply(null, [ptr].concat(args)) : f.call(null, ptr) - } - - function dynCall(sig, ptr, args) { - if (sig.includes("j")) { - return dynCallLegacy(sig, ptr, args) - } - var rtn = getWasmTableEntry(ptr).apply(null, args); - return rtn - } - - function createInvokeFunction(sig) { - return function() { - var sp = stackSave(); - try { - return dynCall(sig, arguments[0], Array.prototype.slice.call(arguments, 1)) - } catch (e) { - stackRestore(sp); - if (e !== e + 0) throw e; - _setThrew(1, 0) - } - } - } - - function UTF8ToString(ptr, maxBytesToRead) { - return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "" - } - - function loadWebAssemblyModule(binary, flags, localScope, handle) { - var metadata = getDylinkMetadata(binary); - currentModuleWeakSymbols = metadata.weakImports; - - function loadModule() { - var firstLoad = !handle || !HEAP8[handle + 8 >> 0]; - if (firstLoad) { - var memAlign = Math.pow(2, metadata.memoryAlign); - memAlign = Math.max(memAlign, 16); - var memoryBase = metadata.memorySize ? alignMemory(getMemory(metadata.memorySize + memAlign), memAlign) : 0; - var tableBase = metadata.tableSize ? wasmTable.length : 0; - if (handle) { - HEAP8[handle + 8 >> 0] = 1; - HEAPU32[handle + 12 >> 2] = memoryBase; - HEAP32[handle + 16 >> 2] = metadata.memorySize; - HEAPU32[handle + 20 >> 2] = tableBase; - HEAP32[handle + 24 >> 2] = metadata.tableSize - } - } else { - memoryBase = HEAPU32[handle + 12 >> 2]; - tableBase = HEAPU32[handle + 20 >> 2] - } - var tableGrowthNeeded = tableBase + metadata.tableSize - wasmTable.length; - if (tableGrowthNeeded > 0) { - wasmTable.grow(tableGrowthNeeded) - } - var moduleExports; - - function resolveSymbol(sym) { - var resolved = resolveGlobalSymbol(sym).sym; - if (!resolved && localScope) { - resolved = localScope[sym] - } - if (!resolved) { - resolved = moduleExports[sym] - } - return resolved - } - var proxyHandler = { - "get": function(stubs, prop) { - switch (prop) { - case "__memory_base": - return memoryBase; - case "__table_base": - return tableBase - } - if (prop in wasmImports && !wasmImports[prop].stub) { - return wasmImports[prop] - } - if (!(prop in stubs)) { - var resolved; - stubs[prop] = function() { - if (!resolved) resolved = resolveSymbol(prop); - checkForAsmVersion(prop); - return resolved.apply(null, arguments) - } - } - return stubs[prop] - } - }; - var proxy = new Proxy({}, proxyHandler); - var info = { - "GOT.mem": new Proxy({}, GOTHandler), - "GOT.func": new Proxy({}, GOTHandler), - "env": proxy, - wasi_snapshot_preview1: proxy - }; - - function postInstantiation(instance) { - updateTableMap(tableBase, metadata.tableSize); - moduleExports = relocateExports(instance.exports, memoryBase); - if (!flags.allowUndefined) { - reportUndefinedSymbols() - } - - function addEmAsm(addr, body) { - var args = []; - var arity = 0; - for (; arity < 16; arity++) { - if (body.indexOf("$" + arity) != -1) { - args.push("$" + arity) - } else { - break - } - } - args = args.join(","); - var func = "(" + args + " ) => { " + body + "};"; - ASM_CONSTS[start] = eval(func) - } - if ("__start_em_asm" in moduleExports) { - var start = moduleExports["__start_em_asm"]; - var stop = moduleExports["__stop_em_asm"]; - while (start < stop) { - var jsString = UTF8ToString(start); - addEmAsm(start, jsString); - start = HEAPU8.indexOf(0, start) + 1 - } - } - var applyRelocs = moduleExports["__wasm_apply_data_relocs"]; - if (applyRelocs) { - if (runtimeInitialized) { - applyRelocs() - } else { - __RELOC_FUNCS__.push(applyRelocs) - } - } - var init = moduleExports["__wasm_call_ctors"]; - if (init) { - if (runtimeInitialized) { - init() - } else { - __ATINIT__.push(init) - } - } - return moduleExports - } - if (flags.loadAsync) { - if (binary instanceof WebAssembly.Module) { - var instance = new WebAssembly.Instance(binary, info); - return Promise.resolve(postInstantiation(instance)) - } - return WebAssembly.instantiate(binary, info).then(result => postInstantiation(result.instance)) - } - var module = binary instanceof WebAssembly.Module ? binary : new WebAssembly.Module(binary); - var instance = new WebAssembly.Instance(module, info); - return postInstantiation(instance) - } - if (flags.loadAsync) { - return metadata.neededDynlibs.reduce((chain, dynNeeded) => { - return chain.then(() => { - return loadDynamicLibrary(dynNeeded, flags) - }) - }, Promise.resolve()).then(loadModule) - } - metadata.neededDynlibs.forEach(needed => loadDynamicLibrary(needed, flags, localScope)); - return loadModule() - } - - function mergeLibSymbols(exports, libName) { - for (var sym in exports) { - if (!exports.hasOwnProperty(sym)) { - continue - } - const setImport = target => { - if (!isSymbolDefined(target)) { - wasmImports[target] = exports[sym] - } - }; - setImport(sym); - const main_alias = "__main_argc_argv"; - if (sym == "main") { - setImport(main_alias) - } - if (sym == main_alias) { - setImport("main") - } - if (sym.startsWith("dynCall_") && !Module.hasOwnProperty(sym)) { - Module[sym] = exports[sym] - } - } - } - - function asyncLoad(url, onload, onerror, noRunDep) { - var dep = !noRunDep ? getUniqueRunDependency("al " + url) : ""; - readAsync(url, arrayBuffer => { - assert(arrayBuffer, 'Loading data file "' + url + '" failed (no arrayBuffer).'); - onload(new Uint8Array(arrayBuffer)); - if (dep) removeRunDependency(dep) - }, event => { - if (onerror) { - onerror() - } else { - throw 'Loading data file "' + url + '" failed.' - } - }); - if (dep) addRunDependency(dep) - } - - function loadDynamicLibrary(libName, flags = { - global: true, - nodelete: true - }, localScope, handle) { - var dso = LDSO.loadedLibsByName[libName]; - if (dso) { - if (flags.global && !dso.global) { - dso.global = true; - if (dso.exports !== "loading") { - mergeLibSymbols(dso.exports, libName) - } - } - if (flags.nodelete && dso.refcount !== Infinity) { - dso.refcount = Infinity - } - dso.refcount++; - if (handle) { - LDSO.loadedLibsByHandle[handle] = dso - } - return flags.loadAsync ? Promise.resolve(true) : true - } - dso = newDSO(libName, handle, "loading"); - dso.refcount = flags.nodelete ? Infinity : 1; - dso.global = flags.global; - - function loadLibData() { - if (flags.fs && flags.fs.findObject(libName)) { - var libData = flags.fs.readFile(libName, { - encoding: "binary" - }); - if (!(libData instanceof Uint8Array)) { - libData = new Uint8Array(libData) - } - return flags.loadAsync ? Promise.resolve(libData) : libData - } - var libFile = locateFile(libName); - if (flags.loadAsync) { - return new Promise(function(resolve, reject) { - asyncLoad(libFile, data => resolve(data), reject) - }) - } - if (!readBinary) { - throw new Error(libFile + ": file not found, and synchronous loading of external files is not available") - } - return readBinary(libFile) - } - - function getExports() { - if (typeof preloadedWasm != "undefined" && preloadedWasm[libName]) { - var libModule = preloadedWasm[libName]; - return flags.loadAsync ? Promise.resolve(libModule) : libModule - } - if (flags.loadAsync) { - return loadLibData().then(libData => loadWebAssemblyModule(libData, flags, localScope, handle)) - } - return loadWebAssemblyModule(loadLibData(), flags, localScope, handle) - } - - function moduleLoaded(exports) { - if (dso.global) { - mergeLibSymbols(exports, libName) - } else if (localScope) { - Object.assign(localScope, exports) - } - dso.exports = exports - } - if (flags.loadAsync) { - return getExports().then(exports => { - moduleLoaded(exports); - return true - }) - } - moduleLoaded(getExports()); - return true - } - - function reportUndefinedSymbols() { - for (var symName in GOT) { - if (GOT[symName].value == 0) { - var value = resolveGlobalSymbol(symName, true).sym; - if (!value && !GOT[symName].required) { - continue - } - if (typeof value == "function") { - GOT[symName].value = addFunction(value, value.sig) - } else if (typeof value == "number") { - GOT[symName].value = value - } else { - throw new Error("bad export type for `" + symName + "`: " + typeof value) - } - } - } - } - - function loadDylibs() { - if (!dynamicLibraries.length) { - reportUndefinedSymbols(); - return - } - addRunDependency("loadDylibs"); - dynamicLibraries.reduce((chain, lib) => { - return chain.then(() => { - return loadDynamicLibrary(lib, { - loadAsync: true, - global: true, - nodelete: true, - allowUndefined: true - }) - }) - }, Promise.resolve()).then(() => { - reportUndefinedSymbols(); - removeRunDependency("loadDylibs") - }) - } - - function setValue(ptr, value, type = "i8") { - if (type.endsWith("*")) type = "*"; - switch (type) { - case "i1": - HEAP8[ptr >> 0] = value; - break; - case "i8": - HEAP8[ptr >> 0] = value; - break; - case "i16": - HEAP16[ptr >> 1] = value; - break; - case "i32": - HEAP32[ptr >> 2] = value; - break; - case "i64": - tempI64 = [value >>> 0, (tempDouble = value, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1]; - break; - case "float": - HEAPF32[ptr >> 2] = value; - break; - case "double": - HEAPF64[ptr >> 3] = value; - break; - case "*": - HEAPU32[ptr >> 2] = value; - break; - default: - abort("invalid type for setValue: " + type) - } - } - var ___memory_base = new WebAssembly.Global({ - "value": "i32", - "mutable": false - }, 1024); - var ___stack_pointer = new WebAssembly.Global({ - "value": "i32", - "mutable": true - }, 78160); - var ___table_base = new WebAssembly.Global({ - "value": "i32", - "mutable": false - }, 1); - var nowIsMonotonic = true; - - function __emscripten_get_now_is_monotonic() { - return nowIsMonotonic - } - __emscripten_get_now_is_monotonic.sig = "i"; - - function _abort() { - abort("") - } - Module["_abort"] = _abort; - _abort.sig = "v"; - - function _emscripten_date_now() { - return Date.now() - } - _emscripten_date_now.sig = "d"; - var _emscripten_get_now; - if (ENVIRONMENT_IS_NODE) { - _emscripten_get_now = () => { - var t = process.hrtime(); - return t[0] * 1e3 + t[1] / 1e6 - } - } else _emscripten_get_now = () => performance.now(); - _emscripten_get_now.sig = "d"; - - function _emscripten_memcpy_big(dest, src, num) { - HEAPU8.copyWithin(dest, src, src + num) - } - _emscripten_memcpy_big.sig = "vppp"; - - function getHeapMax() { - return 2147483648 - } - - function emscripten_realloc_buffer(size) { - var b = wasmMemory.buffer; - try { - wasmMemory.grow(size - b.byteLength + 65535 >>> 16); - updateMemoryViews(); - return 1 - } catch (e) {} - } - - function _emscripten_resize_heap(requestedSize) { - var oldSize = HEAPU8.length; - requestedSize = requestedSize >>> 0; - var maxHeapSize = getHeapMax(); - if (requestedSize > maxHeapSize) { - return false - } - let alignUp = (x, multiple) => x + (multiple - x % multiple) % multiple; - for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { - var overGrownHeapSize = oldSize * (1 + .2 / cutDown); - overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296); - var newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536)); - var replacement = emscripten_realloc_buffer(newSize); - if (replacement) { - return true - } - } - return false - } - _emscripten_resize_heap.sig = "ip"; - var SYSCALLS = { - DEFAULT_POLLMASK: 5, - calculateAt: function(dirfd, path, allowEmpty) { - if (PATH.isAbs(path)) { - return path - } - var dir; - if (dirfd === -100) { - dir = FS.cwd() - } else { - var dirstream = SYSCALLS.getStreamFromFD(dirfd); - dir = dirstream.path - } - if (path.length == 0) { - if (!allowEmpty) { - throw new FS.ErrnoError(44) - } - return dir - } - return PATH.join2(dir, path) - }, - doStat: function(func, path, buf) { - try { - var stat = func(path) - } catch (e) { - if (e && e.node && PATH.normalize(path) !== PATH.normalize(FS.getPath(e.node))) { - return -54 - } - throw e - } - HEAP32[buf >> 2] = stat.dev; - HEAP32[buf + 8 >> 2] = stat.ino; - HEAP32[buf + 12 >> 2] = stat.mode; - HEAPU32[buf + 16 >> 2] = stat.nlink; - HEAP32[buf + 20 >> 2] = stat.uid; - HEAP32[buf + 24 >> 2] = stat.gid; - HEAP32[buf + 28 >> 2] = stat.rdev; - tempI64 = [stat.size >>> 0, (tempDouble = stat.size, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 40 >> 2] = tempI64[0], HEAP32[buf + 44 >> 2] = tempI64[1]; - HEAP32[buf + 48 >> 2] = 4096; - HEAP32[buf + 52 >> 2] = stat.blocks; - var atime = stat.atime.getTime(); - var mtime = stat.mtime.getTime(); - var ctime = stat.ctime.getTime(); - tempI64 = [Math.floor(atime / 1e3) >>> 0, (tempDouble = Math.floor(atime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 56 >> 2] = tempI64[0], HEAP32[buf + 60 >> 2] = tempI64[1]; - HEAPU32[buf + 64 >> 2] = atime % 1e3 * 1e3; - tempI64 = [Math.floor(mtime / 1e3) >>> 0, (tempDouble = Math.floor(mtime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 72 >> 2] = tempI64[0], HEAP32[buf + 76 >> 2] = tempI64[1]; - HEAPU32[buf + 80 >> 2] = mtime % 1e3 * 1e3; - tempI64 = [Math.floor(ctime / 1e3) >>> 0, (tempDouble = Math.floor(ctime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 88 >> 2] = tempI64[0], HEAP32[buf + 92 >> 2] = tempI64[1]; - HEAPU32[buf + 96 >> 2] = ctime % 1e3 * 1e3; - tempI64 = [stat.ino >>> 0, (tempDouble = stat.ino, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 104 >> 2] = tempI64[0], HEAP32[buf + 108 >> 2] = tempI64[1]; - return 0 - }, - doMsync: function(addr, stream, len, flags, offset) { - if (!FS.isFile(stream.node.mode)) { - throw new FS.ErrnoError(43) - } - if (flags & 2) { - return 0 - } - var buffer = HEAPU8.slice(addr, addr + len); - FS.msync(stream, buffer, offset, len, flags) - }, - varargs: undefined, - get: function() { - SYSCALLS.varargs += 4; - var ret = HEAP32[SYSCALLS.varargs - 4 >> 2]; - return ret - }, - getStr: function(ptr) { - var ret = UTF8ToString(ptr); - return ret - }, - getStreamFromFD: function(fd) { - var stream = FS.getStream(fd); - if (!stream) throw new FS.ErrnoError(8); - return stream - } - }; - - function _fd_close(fd) { - try { - var stream = SYSCALLS.getStreamFromFD(fd); - FS.close(stream); - return 0 - } catch (e) { - if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; - return e.errno - } - } - _fd_close.sig = "ii"; - - function convertI32PairToI53Checked(lo, hi) { - return hi + 2097152 >>> 0 < 4194305 - !!lo ? (lo >>> 0) + hi * 4294967296 : NaN - } - - function _fd_seek(fd, offset_low, offset_high, whence, newOffset) { - try { - var offset = convertI32PairToI53Checked(offset_low, offset_high); - if (isNaN(offset)) return 61; - var stream = SYSCALLS.getStreamFromFD(fd); - FS.llseek(stream, offset, whence); - tempI64 = [stream.position >>> 0, (tempDouble = stream.position, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[newOffset >> 2] = tempI64[0], HEAP32[newOffset + 4 >> 2] = tempI64[1]; - if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null; - return 0 - } catch (e) { - if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; - return e.errno - } - } - _fd_seek.sig = "iijip"; - - function doWritev(stream, iov, iovcnt, offset) { - var ret = 0; - for (var i = 0; i < iovcnt; i++) { - var ptr = HEAPU32[iov >> 2]; - var len = HEAPU32[iov + 4 >> 2]; - iov += 8; - var curr = FS.write(stream, HEAP8, ptr, len, offset); - if (curr < 0) return -1; - ret += curr; - if (typeof offset !== "undefined") { - offset += curr - } - } - return ret - } - - function _fd_write(fd, iov, iovcnt, pnum) { - try { - var stream = SYSCALLS.getStreamFromFD(fd); - var num = doWritev(stream, iov, iovcnt); - HEAPU32[pnum >> 2] = num; - return 0 - } catch (e) { - if (typeof FS == "undefined" || !(e.name === "ErrnoError")) throw e; - return e.errno - } - } - _fd_write.sig = "iippp"; - - function _tree_sitter_log_callback(isLexMessage, messageAddress) { - if (currentLogCallback) { - const message = UTF8ToString(messageAddress); - currentLogCallback(message, isLexMessage !== 0) - } - } - - function _tree_sitter_parse_callback(inputBufferAddress, index, row, column, lengthAddress) { - var INPUT_BUFFER_SIZE = 10 * 1024; - var string = currentParseCallback(index, { - row: row, - column: column - }); - if (typeof string === "string") { - setValue(lengthAddress, string.length, "i32"); - stringToUTF16(string, inputBufferAddress, INPUT_BUFFER_SIZE) - } else { - setValue(lengthAddress, 0, "i32") - } - } - - function _proc_exit(code) { - EXITSTATUS = code; - if (!keepRuntimeAlive()) { - if (Module["onExit"]) Module["onExit"](code); - ABORT = true - } - quit_(code, new ExitStatus(code)) - } - _proc_exit.sig = "vi"; - - function exitJS(status, implicit) { - EXITSTATUS = status; - _proc_exit(status) - } - - function handleException(e) { - if (e instanceof ExitStatus || e == "unwind") { - return EXITSTATUS - } - quit_(1, e) - } - - function lengthBytesUTF8(str) { - var len = 0; - for (var i = 0; i < str.length; ++i) { - var c = str.charCodeAt(i); - if (c <= 127) { - len++ - } else if (c <= 2047) { - len += 2 - } else if (c >= 55296 && c <= 57343) { - len += 4; - ++i - } else { - len += 3 - } - } - return len - } - - function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) { - if (!(maxBytesToWrite > 0)) return 0; - var startIdx = outIdx; - var endIdx = outIdx + maxBytesToWrite - 1; - for (var i = 0; i < str.length; ++i) { - var u = str.charCodeAt(i); - if (u >= 55296 && u <= 57343) { - var u1 = str.charCodeAt(++i); - u = 65536 + ((u & 1023) << 10) | u1 & 1023 - } - if (u <= 127) { - if (outIdx >= endIdx) break; - heap[outIdx++] = u - } else if (u <= 2047) { - if (outIdx + 1 >= endIdx) break; - heap[outIdx++] = 192 | u >> 6; - heap[outIdx++] = 128 | u & 63 - } else if (u <= 65535) { - if (outIdx + 2 >= endIdx) break; - heap[outIdx++] = 224 | u >> 12; - heap[outIdx++] = 128 | u >> 6 & 63; - heap[outIdx++] = 128 | u & 63 - } else { - if (outIdx + 3 >= endIdx) break; - heap[outIdx++] = 240 | u >> 18; - heap[outIdx++] = 128 | u >> 12 & 63; - heap[outIdx++] = 128 | u >> 6 & 63; - heap[outIdx++] = 128 | u & 63 - } - } - heap[outIdx] = 0; - return outIdx - startIdx - } - - function stringToUTF8(str, outPtr, maxBytesToWrite) { - return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite) - } - - function stringToUTF8OnStack(str) { - var size = lengthBytesUTF8(str) + 1; - var ret = stackAlloc(size); - stringToUTF8(str, ret, size); - return ret - } - - function stringToUTF16(str, outPtr, maxBytesToWrite) { - if (maxBytesToWrite === undefined) { - maxBytesToWrite = 2147483647 - } - if (maxBytesToWrite < 2) return 0; - maxBytesToWrite -= 2; - var startPtr = outPtr; - var numCharsToWrite = maxBytesToWrite < str.length * 2 ? maxBytesToWrite / 2 : str.length; - for (var i = 0; i < numCharsToWrite; ++i) { - var codeUnit = str.charCodeAt(i); - HEAP16[outPtr >> 1] = codeUnit; - outPtr += 2 - } - HEAP16[outPtr >> 1] = 0; - return outPtr - startPtr - } - - function AsciiToString(ptr) { - var str = ""; - while (1) { - var ch = HEAPU8[ptr++ >> 0]; - if (!ch) return str; - str += String.fromCharCode(ch) - } - } - var wasmImports = { - "__heap_base": ___heap_base, - "__indirect_function_table": wasmTable, - "__memory_base": ___memory_base, - "__stack_pointer": ___stack_pointer, - "__table_base": ___table_base, - "_emscripten_get_now_is_monotonic": __emscripten_get_now_is_monotonic, - "abort": _abort, - "emscripten_get_now": _emscripten_get_now, - "emscripten_memcpy_big": _emscripten_memcpy_big, - "emscripten_resize_heap": _emscripten_resize_heap, - "fd_close": _fd_close, - "fd_seek": _fd_seek, - "fd_write": _fd_write, - "memory": wasmMemory, - "tree_sitter_log_callback": _tree_sitter_log_callback, - "tree_sitter_parse_callback": _tree_sitter_parse_callback - }; - var asm = createWasm(); - var ___wasm_call_ctors = function() { - return (___wasm_call_ctors = Module["asm"]["__wasm_call_ctors"]).apply(null, arguments) - }; - var ___wasm_apply_data_relocs = Module["___wasm_apply_data_relocs"] = function() { - return (___wasm_apply_data_relocs = Module["___wasm_apply_data_relocs"] = Module["asm"]["__wasm_apply_data_relocs"]).apply(null, arguments) - }; - var _malloc = Module["_malloc"] = function() { - return (_malloc = Module["_malloc"] = Module["asm"]["malloc"]).apply(null, arguments) - }; - var _calloc = Module["_calloc"] = function() { - return (_calloc = Module["_calloc"] = Module["asm"]["calloc"]).apply(null, arguments) - }; - var _realloc = Module["_realloc"] = function() { - return (_realloc = Module["_realloc"] = Module["asm"]["realloc"]).apply(null, arguments) - }; - var _free = Module["_free"] = function() { - return (_free = Module["_free"] = Module["asm"]["free"]).apply(null, arguments) - }; - var _ts_language_symbol_count = Module["_ts_language_symbol_count"] = function() { - return (_ts_language_symbol_count = Module["_ts_language_symbol_count"] = Module["asm"]["ts_language_symbol_count"]).apply(null, arguments) - }; - var _ts_language_state_count = Module["_ts_language_state_count"] = function() { - return (_ts_language_state_count = Module["_ts_language_state_count"] = Module["asm"]["ts_language_state_count"]).apply(null, arguments) - }; - var _ts_language_version = Module["_ts_language_version"] = function() { - return (_ts_language_version = Module["_ts_language_version"] = Module["asm"]["ts_language_version"]).apply(null, arguments) - }; - var _ts_language_field_count = Module["_ts_language_field_count"] = function() { - return (_ts_language_field_count = Module["_ts_language_field_count"] = Module["asm"]["ts_language_field_count"]).apply(null, arguments) - }; - var _ts_language_next_state = Module["_ts_language_next_state"] = function() { - return (_ts_language_next_state = Module["_ts_language_next_state"] = Module["asm"]["ts_language_next_state"]).apply(null, arguments) - }; - var _ts_language_symbol_name = Module["_ts_language_symbol_name"] = function() { - return (_ts_language_symbol_name = Module["_ts_language_symbol_name"] = Module["asm"]["ts_language_symbol_name"]).apply(null, arguments) - }; - var _ts_language_symbol_for_name = Module["_ts_language_symbol_for_name"] = function() { - return (_ts_language_symbol_for_name = Module["_ts_language_symbol_for_name"] = Module["asm"]["ts_language_symbol_for_name"]).apply(null, arguments) - }; - var _strncmp = Module["_strncmp"] = function() { - return (_strncmp = Module["_strncmp"] = Module["asm"]["strncmp"]).apply(null, arguments) - }; - var _ts_language_symbol_type = Module["_ts_language_symbol_type"] = function() { - return (_ts_language_symbol_type = Module["_ts_language_symbol_type"] = Module["asm"]["ts_language_symbol_type"]).apply(null, arguments) - }; - var _ts_language_field_name_for_id = Module["_ts_language_field_name_for_id"] = function() { - return (_ts_language_field_name_for_id = Module["_ts_language_field_name_for_id"] = Module["asm"]["ts_language_field_name_for_id"]).apply(null, arguments) - }; - var _ts_lookahead_iterator_new = Module["_ts_lookahead_iterator_new"] = function() { - return (_ts_lookahead_iterator_new = Module["_ts_lookahead_iterator_new"] = Module["asm"]["ts_lookahead_iterator_new"]).apply(null, arguments) - }; - var _ts_lookahead_iterator_delete = Module["_ts_lookahead_iterator_delete"] = function() { - return (_ts_lookahead_iterator_delete = Module["_ts_lookahead_iterator_delete"] = Module["asm"]["ts_lookahead_iterator_delete"]).apply(null, arguments) - }; - var _ts_lookahead_iterator_reset_state = Module["_ts_lookahead_iterator_reset_state"] = function() { - return (_ts_lookahead_iterator_reset_state = Module["_ts_lookahead_iterator_reset_state"] = Module["asm"]["ts_lookahead_iterator_reset_state"]).apply(null, arguments) - }; - var _ts_lookahead_iterator_reset = Module["_ts_lookahead_iterator_reset"] = function() { - return (_ts_lookahead_iterator_reset = Module["_ts_lookahead_iterator_reset"] = Module["asm"]["ts_lookahead_iterator_reset"]).apply(null, arguments) - }; - var _ts_lookahead_iterator_next = Module["_ts_lookahead_iterator_next"] = function() { - return (_ts_lookahead_iterator_next = Module["_ts_lookahead_iterator_next"] = Module["asm"]["ts_lookahead_iterator_next"]).apply(null, arguments) - }; - var _ts_lookahead_iterator_current_symbol = Module["_ts_lookahead_iterator_current_symbol"] = function() { - return (_ts_lookahead_iterator_current_symbol = Module["_ts_lookahead_iterator_current_symbol"] = Module["asm"]["ts_lookahead_iterator_current_symbol"]).apply(null, arguments) - }; - var _memset = Module["_memset"] = function() { - return (_memset = Module["_memset"] = Module["asm"]["memset"]).apply(null, arguments) - }; - var _memcpy = Module["_memcpy"] = function() { - return (_memcpy = Module["_memcpy"] = Module["asm"]["memcpy"]).apply(null, arguments) - }; - var _ts_parser_delete = Module["_ts_parser_delete"] = function() { - return (_ts_parser_delete = Module["_ts_parser_delete"] = Module["asm"]["ts_parser_delete"]).apply(null, arguments) - }; - var _ts_parser_reset = Module["_ts_parser_reset"] = function() { - return (_ts_parser_reset = Module["_ts_parser_reset"] = Module["asm"]["ts_parser_reset"]).apply(null, arguments) - }; - var _ts_parser_set_language = Module["_ts_parser_set_language"] = function() { - return (_ts_parser_set_language = Module["_ts_parser_set_language"] = Module["asm"]["ts_parser_set_language"]).apply(null, arguments) - }; - var _ts_parser_timeout_micros = Module["_ts_parser_timeout_micros"] = function() { - return (_ts_parser_timeout_micros = Module["_ts_parser_timeout_micros"] = Module["asm"]["ts_parser_timeout_micros"]).apply(null, arguments) - }; - var _ts_parser_set_timeout_micros = Module["_ts_parser_set_timeout_micros"] = function() { - return (_ts_parser_set_timeout_micros = Module["_ts_parser_set_timeout_micros"] = Module["asm"]["ts_parser_set_timeout_micros"]).apply(null, arguments) - }; - var _memmove = Module["_memmove"] = function() { - return (_memmove = Module["_memmove"] = Module["asm"]["memmove"]).apply(null, arguments) - }; - var _memcmp = Module["_memcmp"] = function() { - return (_memcmp = Module["_memcmp"] = Module["asm"]["memcmp"]).apply(null, arguments) - }; - var _ts_query_new = Module["_ts_query_new"] = function() { - return (_ts_query_new = Module["_ts_query_new"] = Module["asm"]["ts_query_new"]).apply(null, arguments) - }; - var _ts_query_delete = Module["_ts_query_delete"] = function() { - return (_ts_query_delete = Module["_ts_query_delete"] = Module["asm"]["ts_query_delete"]).apply(null, arguments) - }; - var _iswspace = Module["_iswspace"] = function() { - return (_iswspace = Module["_iswspace"] = Module["asm"]["iswspace"]).apply(null, arguments) - }; - var _iswalnum = Module["_iswalnum"] = function() { - return (_iswalnum = Module["_iswalnum"] = Module["asm"]["iswalnum"]).apply(null, arguments) - }; - var _ts_query_pattern_count = Module["_ts_query_pattern_count"] = function() { - return (_ts_query_pattern_count = Module["_ts_query_pattern_count"] = Module["asm"]["ts_query_pattern_count"]).apply(null, arguments) - }; - var _ts_query_capture_count = Module["_ts_query_capture_count"] = function() { - return (_ts_query_capture_count = Module["_ts_query_capture_count"] = Module["asm"]["ts_query_capture_count"]).apply(null, arguments) - }; - var _ts_query_string_count = Module["_ts_query_string_count"] = function() { - return (_ts_query_string_count = Module["_ts_query_string_count"] = Module["asm"]["ts_query_string_count"]).apply(null, arguments) - }; - var _ts_query_capture_name_for_id = Module["_ts_query_capture_name_for_id"] = function() { - return (_ts_query_capture_name_for_id = Module["_ts_query_capture_name_for_id"] = Module["asm"]["ts_query_capture_name_for_id"]).apply(null, arguments) - }; - var _ts_query_string_value_for_id = Module["_ts_query_string_value_for_id"] = function() { - return (_ts_query_string_value_for_id = Module["_ts_query_string_value_for_id"] = Module["asm"]["ts_query_string_value_for_id"]).apply(null, arguments) - }; - var _ts_query_predicates_for_pattern = Module["_ts_query_predicates_for_pattern"] = function() { - return (_ts_query_predicates_for_pattern = Module["_ts_query_predicates_for_pattern"] = Module["asm"]["ts_query_predicates_for_pattern"]).apply(null, arguments) - }; - var _ts_tree_copy = Module["_ts_tree_copy"] = function() { - return (_ts_tree_copy = Module["_ts_tree_copy"] = Module["asm"]["ts_tree_copy"]).apply(null, arguments) - }; - var _ts_tree_delete = Module["_ts_tree_delete"] = function() { - return (_ts_tree_delete = Module["_ts_tree_delete"] = Module["asm"]["ts_tree_delete"]).apply(null, arguments) - }; - var _ts_init = Module["_ts_init"] = function() { - return (_ts_init = Module["_ts_init"] = Module["asm"]["ts_init"]).apply(null, arguments) - }; - var _ts_parser_new_wasm = Module["_ts_parser_new_wasm"] = function() { - return (_ts_parser_new_wasm = Module["_ts_parser_new_wasm"] = Module["asm"]["ts_parser_new_wasm"]).apply(null, arguments) - }; - var _ts_parser_enable_logger_wasm = Module["_ts_parser_enable_logger_wasm"] = function() { - return (_ts_parser_enable_logger_wasm = Module["_ts_parser_enable_logger_wasm"] = Module["asm"]["ts_parser_enable_logger_wasm"]).apply(null, arguments) - }; - var _ts_parser_parse_wasm = Module["_ts_parser_parse_wasm"] = function() { - return (_ts_parser_parse_wasm = Module["_ts_parser_parse_wasm"] = Module["asm"]["ts_parser_parse_wasm"]).apply(null, arguments) - }; - var _ts_language_type_is_named_wasm = Module["_ts_language_type_is_named_wasm"] = function() { - return (_ts_language_type_is_named_wasm = Module["_ts_language_type_is_named_wasm"] = Module["asm"]["ts_language_type_is_named_wasm"]).apply(null, arguments) - }; - var _ts_language_type_is_visible_wasm = Module["_ts_language_type_is_visible_wasm"] = function() { - return (_ts_language_type_is_visible_wasm = Module["_ts_language_type_is_visible_wasm"] = Module["asm"]["ts_language_type_is_visible_wasm"]).apply(null, arguments) - }; - var _ts_tree_root_node_wasm = Module["_ts_tree_root_node_wasm"] = function() { - return (_ts_tree_root_node_wasm = Module["_ts_tree_root_node_wasm"] = Module["asm"]["ts_tree_root_node_wasm"]).apply(null, arguments) - }; - var _ts_tree_edit_wasm = Module["_ts_tree_edit_wasm"] = function() { - return (_ts_tree_edit_wasm = Module["_ts_tree_edit_wasm"] = Module["asm"]["ts_tree_edit_wasm"]).apply(null, arguments) - }; - var _ts_tree_get_changed_ranges_wasm = Module["_ts_tree_get_changed_ranges_wasm"] = function() { - return (_ts_tree_get_changed_ranges_wasm = Module["_ts_tree_get_changed_ranges_wasm"] = Module["asm"]["ts_tree_get_changed_ranges_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_new_wasm = Module["_ts_tree_cursor_new_wasm"] = function() { - return (_ts_tree_cursor_new_wasm = Module["_ts_tree_cursor_new_wasm"] = Module["asm"]["ts_tree_cursor_new_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_delete_wasm = Module["_ts_tree_cursor_delete_wasm"] = function() { - return (_ts_tree_cursor_delete_wasm = Module["_ts_tree_cursor_delete_wasm"] = Module["asm"]["ts_tree_cursor_delete_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_reset_wasm = Module["_ts_tree_cursor_reset_wasm"] = function() { - return (_ts_tree_cursor_reset_wasm = Module["_ts_tree_cursor_reset_wasm"] = Module["asm"]["ts_tree_cursor_reset_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_reset_to_wasm = Module["_ts_tree_cursor_reset_to_wasm"] = function() { - return (_ts_tree_cursor_reset_to_wasm = Module["_ts_tree_cursor_reset_to_wasm"] = Module["asm"]["ts_tree_cursor_reset_to_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_goto_first_child_wasm = Module["_ts_tree_cursor_goto_first_child_wasm"] = function() { - return (_ts_tree_cursor_goto_first_child_wasm = Module["_ts_tree_cursor_goto_first_child_wasm"] = Module["asm"]["ts_tree_cursor_goto_first_child_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_goto_last_child_wasm = Module["_ts_tree_cursor_goto_last_child_wasm"] = function() { - return (_ts_tree_cursor_goto_last_child_wasm = Module["_ts_tree_cursor_goto_last_child_wasm"] = Module["asm"]["ts_tree_cursor_goto_last_child_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_goto_next_sibling_wasm = Module["_ts_tree_cursor_goto_next_sibling_wasm"] = function() { - return (_ts_tree_cursor_goto_next_sibling_wasm = Module["_ts_tree_cursor_goto_next_sibling_wasm"] = Module["asm"]["ts_tree_cursor_goto_next_sibling_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_goto_previous_sibling_wasm = Module["_ts_tree_cursor_goto_previous_sibling_wasm"] = function() { - return (_ts_tree_cursor_goto_previous_sibling_wasm = Module["_ts_tree_cursor_goto_previous_sibling_wasm"] = Module["asm"]["ts_tree_cursor_goto_previous_sibling_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_goto_parent_wasm = Module["_ts_tree_cursor_goto_parent_wasm"] = function() { - return (_ts_tree_cursor_goto_parent_wasm = Module["_ts_tree_cursor_goto_parent_wasm"] = Module["asm"]["ts_tree_cursor_goto_parent_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_current_node_type_id_wasm = Module["_ts_tree_cursor_current_node_type_id_wasm"] = function() { - return (_ts_tree_cursor_current_node_type_id_wasm = Module["_ts_tree_cursor_current_node_type_id_wasm"] = Module["asm"]["ts_tree_cursor_current_node_type_id_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_current_node_state_id_wasm = Module["_ts_tree_cursor_current_node_state_id_wasm"] = function() { - return (_ts_tree_cursor_current_node_state_id_wasm = Module["_ts_tree_cursor_current_node_state_id_wasm"] = Module["asm"]["ts_tree_cursor_current_node_state_id_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_current_node_is_named_wasm = Module["_ts_tree_cursor_current_node_is_named_wasm"] = function() { - return (_ts_tree_cursor_current_node_is_named_wasm = Module["_ts_tree_cursor_current_node_is_named_wasm"] = Module["asm"]["ts_tree_cursor_current_node_is_named_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_current_node_is_missing_wasm = Module["_ts_tree_cursor_current_node_is_missing_wasm"] = function() { - return (_ts_tree_cursor_current_node_is_missing_wasm = Module["_ts_tree_cursor_current_node_is_missing_wasm"] = Module["asm"]["ts_tree_cursor_current_node_is_missing_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_current_node_id_wasm = Module["_ts_tree_cursor_current_node_id_wasm"] = function() { - return (_ts_tree_cursor_current_node_id_wasm = Module["_ts_tree_cursor_current_node_id_wasm"] = Module["asm"]["ts_tree_cursor_current_node_id_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_start_position_wasm = Module["_ts_tree_cursor_start_position_wasm"] = function() { - return (_ts_tree_cursor_start_position_wasm = Module["_ts_tree_cursor_start_position_wasm"] = Module["asm"]["ts_tree_cursor_start_position_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_end_position_wasm = Module["_ts_tree_cursor_end_position_wasm"] = function() { - return (_ts_tree_cursor_end_position_wasm = Module["_ts_tree_cursor_end_position_wasm"] = Module["asm"]["ts_tree_cursor_end_position_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_start_index_wasm = Module["_ts_tree_cursor_start_index_wasm"] = function() { - return (_ts_tree_cursor_start_index_wasm = Module["_ts_tree_cursor_start_index_wasm"] = Module["asm"]["ts_tree_cursor_start_index_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_end_index_wasm = Module["_ts_tree_cursor_end_index_wasm"] = function() { - return (_ts_tree_cursor_end_index_wasm = Module["_ts_tree_cursor_end_index_wasm"] = Module["asm"]["ts_tree_cursor_end_index_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_current_field_id_wasm = Module["_ts_tree_cursor_current_field_id_wasm"] = function() { - return (_ts_tree_cursor_current_field_id_wasm = Module["_ts_tree_cursor_current_field_id_wasm"] = Module["asm"]["ts_tree_cursor_current_field_id_wasm"]).apply(null, arguments) - }; - var _ts_tree_cursor_current_node_wasm = Module["_ts_tree_cursor_current_node_wasm"] = function() { - return (_ts_tree_cursor_current_node_wasm = Module["_ts_tree_cursor_current_node_wasm"] = Module["asm"]["ts_tree_cursor_current_node_wasm"]).apply(null, arguments) - }; - var _ts_node_symbol_wasm = Module["_ts_node_symbol_wasm"] = function() { - return (_ts_node_symbol_wasm = Module["_ts_node_symbol_wasm"] = Module["asm"]["ts_node_symbol_wasm"]).apply(null, arguments) - }; - var _ts_node_field_name_for_child_wasm = Module["_ts_node_field_name_for_child_wasm"] = function() { - return (_ts_node_field_name_for_child_wasm = Module["_ts_node_field_name_for_child_wasm"] = Module["asm"]["ts_node_field_name_for_child_wasm"]).apply(null, arguments) - }; - var _ts_node_grammar_symbol_wasm = Module["_ts_node_grammar_symbol_wasm"] = function() { - return (_ts_node_grammar_symbol_wasm = Module["_ts_node_grammar_symbol_wasm"] = Module["asm"]["ts_node_grammar_symbol_wasm"]).apply(null, arguments) - }; - var _ts_node_child_count_wasm = Module["_ts_node_child_count_wasm"] = function() { - return (_ts_node_child_count_wasm = Module["_ts_node_child_count_wasm"] = Module["asm"]["ts_node_child_count_wasm"]).apply(null, arguments) - }; - var _ts_node_named_child_count_wasm = Module["_ts_node_named_child_count_wasm"] = function() { - return (_ts_node_named_child_count_wasm = Module["_ts_node_named_child_count_wasm"] = Module["asm"]["ts_node_named_child_count_wasm"]).apply(null, arguments) - }; - var _ts_node_child_wasm = Module["_ts_node_child_wasm"] = function() { - return (_ts_node_child_wasm = Module["_ts_node_child_wasm"] = Module["asm"]["ts_node_child_wasm"]).apply(null, arguments) - }; - var _ts_node_named_child_wasm = Module["_ts_node_named_child_wasm"] = function() { - return (_ts_node_named_child_wasm = Module["_ts_node_named_child_wasm"] = Module["asm"]["ts_node_named_child_wasm"]).apply(null, arguments) - }; - var _ts_node_child_by_field_id_wasm = Module["_ts_node_child_by_field_id_wasm"] = function() { - return (_ts_node_child_by_field_id_wasm = Module["_ts_node_child_by_field_id_wasm"] = Module["asm"]["ts_node_child_by_field_id_wasm"]).apply(null, arguments) - }; - var _ts_node_next_sibling_wasm = Module["_ts_node_next_sibling_wasm"] = function() { - return (_ts_node_next_sibling_wasm = Module["_ts_node_next_sibling_wasm"] = Module["asm"]["ts_node_next_sibling_wasm"]).apply(null, arguments) - }; - var _ts_node_prev_sibling_wasm = Module["_ts_node_prev_sibling_wasm"] = function() { - return (_ts_node_prev_sibling_wasm = Module["_ts_node_prev_sibling_wasm"] = Module["asm"]["ts_node_prev_sibling_wasm"]).apply(null, arguments) - }; - var _ts_node_next_named_sibling_wasm = Module["_ts_node_next_named_sibling_wasm"] = function() { - return (_ts_node_next_named_sibling_wasm = Module["_ts_node_next_named_sibling_wasm"] = Module["asm"]["ts_node_next_named_sibling_wasm"]).apply(null, arguments) - }; - var _ts_node_prev_named_sibling_wasm = Module["_ts_node_prev_named_sibling_wasm"] = function() { - return (_ts_node_prev_named_sibling_wasm = Module["_ts_node_prev_named_sibling_wasm"] = Module["asm"]["ts_node_prev_named_sibling_wasm"]).apply(null, arguments) - }; - var _ts_node_parent_wasm = Module["_ts_node_parent_wasm"] = function() { - return (_ts_node_parent_wasm = Module["_ts_node_parent_wasm"] = Module["asm"]["ts_node_parent_wasm"]).apply(null, arguments) - }; - var _ts_node_descendant_for_index_wasm = Module["_ts_node_descendant_for_index_wasm"] = function() { - return (_ts_node_descendant_for_index_wasm = Module["_ts_node_descendant_for_index_wasm"] = Module["asm"]["ts_node_descendant_for_index_wasm"]).apply(null, arguments) - }; - var _ts_node_named_descendant_for_index_wasm = Module["_ts_node_named_descendant_for_index_wasm"] = function() { - return (_ts_node_named_descendant_for_index_wasm = Module["_ts_node_named_descendant_for_index_wasm"] = Module["asm"]["ts_node_named_descendant_for_index_wasm"]).apply(null, arguments) - }; - var _ts_node_descendant_for_position_wasm = Module["_ts_node_descendant_for_position_wasm"] = function() { - return (_ts_node_descendant_for_position_wasm = Module["_ts_node_descendant_for_position_wasm"] = Module["asm"]["ts_node_descendant_for_position_wasm"]).apply(null, arguments) - }; - var _ts_node_named_descendant_for_position_wasm = Module["_ts_node_named_descendant_for_position_wasm"] = function() { - return (_ts_node_named_descendant_for_position_wasm = Module["_ts_node_named_descendant_for_position_wasm"] = Module["asm"]["ts_node_named_descendant_for_position_wasm"]).apply(null, arguments) - }; - var _ts_node_start_point_wasm = Module["_ts_node_start_point_wasm"] = function() { - return (_ts_node_start_point_wasm = Module["_ts_node_start_point_wasm"] = Module["asm"]["ts_node_start_point_wasm"]).apply(null, arguments) - }; - var _ts_node_end_point_wasm = Module["_ts_node_end_point_wasm"] = function() { - return (_ts_node_end_point_wasm = Module["_ts_node_end_point_wasm"] = Module["asm"]["ts_node_end_point_wasm"]).apply(null, arguments) - }; - var _ts_node_start_index_wasm = Module["_ts_node_start_index_wasm"] = function() { - return (_ts_node_start_index_wasm = Module["_ts_node_start_index_wasm"] = Module["asm"]["ts_node_start_index_wasm"]).apply(null, arguments) - }; - var _ts_node_end_index_wasm = Module["_ts_node_end_index_wasm"] = function() { - return (_ts_node_end_index_wasm = Module["_ts_node_end_index_wasm"] = Module["asm"]["ts_node_end_index_wasm"]).apply(null, arguments) - }; - var _ts_node_to_string_wasm = Module["_ts_node_to_string_wasm"] = function() { - return (_ts_node_to_string_wasm = Module["_ts_node_to_string_wasm"] = Module["asm"]["ts_node_to_string_wasm"]).apply(null, arguments) - }; - var _ts_node_children_wasm = Module["_ts_node_children_wasm"] = function() { - return (_ts_node_children_wasm = Module["_ts_node_children_wasm"] = Module["asm"]["ts_node_children_wasm"]).apply(null, arguments) - }; - var _ts_node_named_children_wasm = Module["_ts_node_named_children_wasm"] = function() { - return (_ts_node_named_children_wasm = Module["_ts_node_named_children_wasm"] = Module["asm"]["ts_node_named_children_wasm"]).apply(null, arguments) - }; - var _ts_node_descendants_of_type_wasm = Module["_ts_node_descendants_of_type_wasm"] = function() { - return (_ts_node_descendants_of_type_wasm = Module["_ts_node_descendants_of_type_wasm"] = Module["asm"]["ts_node_descendants_of_type_wasm"]).apply(null, arguments) - }; - var _ts_node_is_named_wasm = Module["_ts_node_is_named_wasm"] = function() { - return (_ts_node_is_named_wasm = Module["_ts_node_is_named_wasm"] = Module["asm"]["ts_node_is_named_wasm"]).apply(null, arguments) - }; - var _ts_node_has_changes_wasm = Module["_ts_node_has_changes_wasm"] = function() { - return (_ts_node_has_changes_wasm = Module["_ts_node_has_changes_wasm"] = Module["asm"]["ts_node_has_changes_wasm"]).apply(null, arguments) - }; - var _ts_node_has_error_wasm = Module["_ts_node_has_error_wasm"] = function() { - return (_ts_node_has_error_wasm = Module["_ts_node_has_error_wasm"] = Module["asm"]["ts_node_has_error_wasm"]).apply(null, arguments) - }; - var _ts_node_is_error_wasm = Module["_ts_node_is_error_wasm"] = function() { - return (_ts_node_is_error_wasm = Module["_ts_node_is_error_wasm"] = Module["asm"]["ts_node_is_error_wasm"]).apply(null, arguments) - }; - var _ts_node_is_missing_wasm = Module["_ts_node_is_missing_wasm"] = function() { - return (_ts_node_is_missing_wasm = Module["_ts_node_is_missing_wasm"] = Module["asm"]["ts_node_is_missing_wasm"]).apply(null, arguments) - }; - var _ts_node_parse_state_wasm = Module["_ts_node_parse_state_wasm"] = function() { - return (_ts_node_parse_state_wasm = Module["_ts_node_parse_state_wasm"] = Module["asm"]["ts_node_parse_state_wasm"]).apply(null, arguments) - }; - var _ts_node_next_parse_state_wasm = Module["_ts_node_next_parse_state_wasm"] = function() { - return (_ts_node_next_parse_state_wasm = Module["_ts_node_next_parse_state_wasm"] = Module["asm"]["ts_node_next_parse_state_wasm"]).apply(null, arguments) - }; - var _ts_query_matches_wasm = Module["_ts_query_matches_wasm"] = function() { - return (_ts_query_matches_wasm = Module["_ts_query_matches_wasm"] = Module["asm"]["ts_query_matches_wasm"]).apply(null, arguments) - }; - var _ts_query_captures_wasm = Module["_ts_query_captures_wasm"] = function() { - return (_ts_query_captures_wasm = Module["_ts_query_captures_wasm"] = Module["asm"]["ts_query_captures_wasm"]).apply(null, arguments) - }; - var ___cxa_atexit = Module["___cxa_atexit"] = function() { - return (___cxa_atexit = Module["___cxa_atexit"] = Module["asm"]["__cxa_atexit"]).apply(null, arguments) - }; - var ___errno_location = function() { - return (___errno_location = Module["asm"]["__errno_location"]).apply(null, arguments) - }; - var _isalnum = Module["_isalnum"] = function() { - return (_isalnum = Module["_isalnum"] = Module["asm"]["isalnum"]).apply(null, arguments) - }; - var _isalpha = Module["_isalpha"] = function() { - return (_isalpha = Module["_isalpha"] = Module["asm"]["isalpha"]).apply(null, arguments) - }; - var _isspace = Module["_isspace"] = function() { - return (_isspace = Module["_isspace"] = Module["asm"]["isspace"]).apply(null, arguments) - }; - var _iswdigit = Module["_iswdigit"] = function() { - return (_iswdigit = Module["_iswdigit"] = Module["asm"]["iswdigit"]).apply(null, arguments) - }; - var _iswalpha = Module["_iswalpha"] = function() { - return (_iswalpha = Module["_iswalpha"] = Module["asm"]["iswalpha"]).apply(null, arguments) - }; - var _iswblank = Module["_iswblank"] = function() { - return (_iswblank = Module["_iswblank"] = Module["asm"]["iswblank"]).apply(null, arguments) - }; - var _iswlower = Module["_iswlower"] = function() { - return (_iswlower = Module["_iswlower"] = Module["asm"]["iswlower"]).apply(null, arguments) - }; - var _iswupper = Module["_iswupper"] = function() { - return (_iswupper = Module["_iswupper"] = Module["asm"]["iswupper"]).apply(null, arguments) - }; - var _iswxdigit = Module["_iswxdigit"] = function() { - return (_iswxdigit = Module["_iswxdigit"] = Module["asm"]["iswxdigit"]).apply(null, arguments) - }; - var _isxdigit = Module["_isxdigit"] = function() { - return (_isxdigit = Module["_isxdigit"] = Module["asm"]["isxdigit"]).apply(null, arguments) - }; - var _memchr = Module["_memchr"] = function() { - return (_memchr = Module["_memchr"] = Module["asm"]["memchr"]).apply(null, arguments) - }; - var _strlen = Module["_strlen"] = function() { - return (_strlen = Module["_strlen"] = Module["asm"]["strlen"]).apply(null, arguments) - }; - var _strcmp = Module["_strcmp"] = function() { - return (_strcmp = Module["_strcmp"] = Module["asm"]["strcmp"]).apply(null, arguments) - }; - var _strncpy = Module["_strncpy"] = function() { - return (_strncpy = Module["_strncpy"] = Module["asm"]["strncpy"]).apply(null, arguments) - }; - var _tolower = Module["_tolower"] = function() { - return (_tolower = Module["_tolower"] = Module["asm"]["tolower"]).apply(null, arguments) - }; - var _towlower = Module["_towlower"] = function() { - return (_towlower = Module["_towlower"] = Module["asm"]["towlower"]).apply(null, arguments) - }; - var _towupper = Module["_towupper"] = function() { - return (_towupper = Module["_towupper"] = Module["asm"]["towupper"]).apply(null, arguments) - }; - var _setThrew = function() { - return (_setThrew = Module["asm"]["setThrew"]).apply(null, arguments) - }; - var stackSave = function() { - return (stackSave = Module["asm"]["stackSave"]).apply(null, arguments) - }; - var stackRestore = function() { - return (stackRestore = Module["asm"]["stackRestore"]).apply(null, arguments) - }; - var stackAlloc = function() { - return (stackAlloc = Module["asm"]["stackAlloc"]).apply(null, arguments) - }; - var __Znwm = Module["__Znwm"] = function() { - return (__Znwm = Module["__Znwm"] = Module["asm"]["_Znwm"]).apply(null, arguments) - }; - var __ZdlPv = Module["__ZdlPv"] = function() { - return (__ZdlPv = Module["__ZdlPv"] = Module["asm"]["_ZdlPv"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev"] = function() { - return (__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev"] = Module["asm"]["_ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEED2Ev"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9__grow_byEmmmmmm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9__grow_byEmmmmmm"] = function() { - return (__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9__grow_byEmmmmmm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9__grow_byEmmmmmm"] = Module["asm"]["_ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9__grow_byEmmmmmm"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm"] = function() { - return (__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm"] = Module["asm"]["_ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcm"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE25__init_copy_ctor_externalEPKcm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE25__init_copy_ctor_externalEPKcm"] = function() { - return (__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE25__init_copy_ctor_externalEPKcm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE25__init_copy_ctor_externalEPKcm"] = Module["asm"]["_ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE25__init_copy_ctor_externalEPKcm"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm"] = function() { - return (__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm"] = Module["asm"]["_ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE7reserveEm"]).apply(null, arguments) - }; - var __ZNKSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4copyEPcmm = Module["__ZNKSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4copyEPcmm"] = function() { - return (__ZNKSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4copyEPcmm = Module["__ZNKSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4copyEPcmm"] = Module["asm"]["_ZNKSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4copyEPcmm"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE17__assign_no_aliasILb1EEERS5_PKcm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE17__assign_no_aliasILb1EEERS5_PKcm"] = function() { - return (__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE17__assign_no_aliasILb1EEERS5_PKcm = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE17__assign_no_aliasILb1EEERS5_PKcm"] = Module["asm"]["_ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE17__assign_no_aliasILb1EEERS5_PKcm"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc"] = function() { - return (__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc = Module["__ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc"] = Module["asm"]["_ZNSt3__212basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE9push_backEc"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEED2Ev = Module["__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEED2Ev"] = function() { - return (__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEED2Ev = Module["__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEED2Ev"] = Module["asm"]["_ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEED2Ev"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE9push_backEw = Module["__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE9push_backEw"] = function() { - return (__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE9push_backEw = Module["__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE9push_backEw"] = Module["asm"]["_ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE9push_backEw"]).apply(null, arguments) - }; - var __ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6resizeEmw = Module["__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6resizeEmw"] = function() { - return (__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6resizeEmw = Module["__ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6resizeEmw"] = Module["asm"]["_ZNSt3__212basic_stringIwNS_11char_traitsIwEENS_9allocatorIwEEE6resizeEmw"]).apply(null, arguments) - }; - var dynCall_jiji = Module["dynCall_jiji"] = function() { - return (dynCall_jiji = Module["dynCall_jiji"] = Module["asm"]["dynCall_jiji"]).apply(null, arguments) - }; - var _orig$ts_parser_timeout_micros = Module["_orig$ts_parser_timeout_micros"] = function() { - return (_orig$ts_parser_timeout_micros = Module["_orig$ts_parser_timeout_micros"] = Module["asm"]["orig$ts_parser_timeout_micros"]).apply(null, arguments) - }; - var _orig$ts_parser_set_timeout_micros = Module["_orig$ts_parser_set_timeout_micros"] = function() { - return (_orig$ts_parser_set_timeout_micros = Module["_orig$ts_parser_set_timeout_micros"] = Module["asm"]["orig$ts_parser_set_timeout_micros"]).apply(null, arguments) - }; - var _stderr = Module["_stderr"] = 11824; - Module["AsciiToString"] = AsciiToString; - Module["stringToUTF16"] = stringToUTF16; - var calledRun; - dependenciesFulfilled = function runCaller() { - if (!calledRun) run(); - if (!calledRun) dependenciesFulfilled = runCaller - }; - - function callMain(args = []) { - var entryFunction = resolveGlobalSymbol("main").sym; - if (!entryFunction) return; - args.unshift(thisProgram); - var argc = args.length; - var argv = stackAlloc((argc + 1) * 4); - var argv_ptr = argv >> 2; - args.forEach(arg => { - HEAP32[argv_ptr++] = stringToUTF8OnStack(arg) - }); - HEAP32[argv_ptr] = 0; - try { - var ret = entryFunction(argc, argv); - exitJS(ret, true); - return ret - } catch (e) { - return handleException(e) - } - } - var dylibsLoaded = false; - LDSO.init(); - - function run(args = arguments_) { - if (runDependencies > 0) { - return - } - if (!dylibsLoaded) { - loadDylibs(); - dylibsLoaded = true; - if (runDependencies > 0) { - return - } - } - preRun(); - if (runDependencies > 0) { - return - } - - function doRun() { - if (calledRun) return; - calledRun = true; - Module["calledRun"] = true; - if (ABORT) return; - initRuntime(); - preMain(); - if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"](); - if (shouldRunNow) callMain(args); - postRun() - } - if (Module["setStatus"]) { - Module["setStatus"]("Running..."); - setTimeout(function() { - setTimeout(function() { - Module["setStatus"]("") - }, 1); - doRun() - }, 1) - } else { - doRun() - } - } - if (Module["preInit"]) { - if (typeof Module["preInit"] == "function") Module["preInit"] = [Module["preInit"]]; - while (Module["preInit"].length > 0) { - Module["preInit"].pop()() - } - } - var shouldRunNow = true; - if (Module["noInitialRun"]) shouldRunNow = false; - run(); - const C = Module; - const INTERNAL = {}; - const SIZE_OF_INT = 4; - const SIZE_OF_CURSOR = 3 * SIZE_OF_INT; - const SIZE_OF_NODE = 5 * SIZE_OF_INT; - const SIZE_OF_POINT = 2 * SIZE_OF_INT; - const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT; - const ZERO_POINT = { - row: 0, - column: 0 - }; - const QUERY_WORD_REGEX = /[\w-.]*/g; - const PREDICATE_STEP_TYPE_CAPTURE = 1; - const PREDICATE_STEP_TYPE_STRING = 2; - const LANGUAGE_FUNCTION_REGEX = /^_?tree_sitter_\w+/; - var VERSION; - var MIN_COMPATIBLE_VERSION; - var TRANSFER_BUFFER; - var currentParseCallback; - var currentLogCallback; - class ParserImpl { - static init() { - TRANSFER_BUFFER = C._ts_init(); - VERSION = getValue(TRANSFER_BUFFER, "i32"); - MIN_COMPATIBLE_VERSION = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32") - } - initialize() { - C._ts_parser_new_wasm(); - this[0] = getValue(TRANSFER_BUFFER, "i32"); - this[1] = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32") - } - delete() { - C._ts_parser_delete(this[0]); - C._free(this[1]); - this[0] = 0; - this[1] = 0 - } - setLanguage(language) { - let address; - if (!language) { - address = 0; - language = null - } else if (language.constructor === Language) { - address = language[0]; - const version = C._ts_language_version(address); - if (version < MIN_COMPATIBLE_VERSION || VERSION < version) { - throw new Error(`Incompatible language version ${version}. ` + `Compatibility range ${MIN_COMPATIBLE_VERSION} through ${VERSION}.`) - } - } else { - throw new Error("Argument must be a Language") - } - this.language = language; - C._ts_parser_set_language(this[0], address); - return this - } - getLanguage() { - return this.language - } - parse(callback, oldTree, options) { - if (typeof callback === "string") { - currentParseCallback = (index, _, endIndex) => callback.slice(index, endIndex) - } else if (typeof callback === "function") { - currentParseCallback = callback - } else { - throw new Error("Argument must be a string or a function") - } - if (this.logCallback) { - currentLogCallback = this.logCallback; - C._ts_parser_enable_logger_wasm(this[0], 1) - } else { - currentLogCallback = null; - C._ts_parser_enable_logger_wasm(this[0], 0) - } - let rangeCount = 0; - let rangeAddress = 0; - if (options && options.includedRanges) { - rangeCount = options.includedRanges.length; - rangeAddress = C._calloc(rangeCount, SIZE_OF_RANGE); - let address = rangeAddress; - for (let i = 0; i < rangeCount; i++) { - marshalRange(address, options.includedRanges[i]); - address += SIZE_OF_RANGE - } - } - const treeAddress = C._ts_parser_parse_wasm(this[0], this[1], oldTree ? oldTree[0] : 0, rangeAddress, rangeCount); - if (!treeAddress) { - currentParseCallback = null; - currentLogCallback = null; - throw new Error("Parsing failed") - } - const result = new Tree(INTERNAL, treeAddress, this.language, currentParseCallback); - currentParseCallback = null; - currentLogCallback = null; - return result - } - reset() { - C._ts_parser_reset(this[0]) - } - setTimeoutMicros(timeout) { - C._ts_parser_set_timeout_micros(this[0], timeout) - } - getTimeoutMicros() { - return C._ts_parser_timeout_micros(this[0]) - } - setLogger(callback) { - if (!callback) { - callback = null - } else if (typeof callback !== "function") { - throw new Error("Logger callback must be a function") - } - this.logCallback = callback; - return this - } - getLogger() { - return this.logCallback - } - } - class Tree { - constructor(internal, address, language, textCallback) { - assertInternal(internal); - this[0] = address; - this.language = language; - this.textCallback = textCallback - } - copy() { - const address = C._ts_tree_copy(this[0]); - return new Tree(INTERNAL, address, this.language, this.textCallback) - } - delete() { - C._ts_tree_delete(this[0]); - this[0] = 0 - } - edit(edit) { - marshalEdit(edit); - C._ts_tree_edit_wasm(this[0]) - } - get rootNode() { - C._ts_tree_root_node_wasm(this[0]); - return unmarshalNode(this) - } - getLanguage() { - return this.language - } - walk() { - return this.rootNode.walk() - } - getChangedRanges(other) { - if (other.constructor !== Tree) { - throw new TypeError("Argument must be a Tree") - } - C._ts_tree_get_changed_ranges_wasm(this[0], other[0]); - const count = getValue(TRANSFER_BUFFER, "i32"); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); - const result = new Array(count); - if (count > 0) { - let address = buffer; - for (let i = 0; i < count; i++) { - result[i] = unmarshalRange(address); - address += SIZE_OF_RANGE - } - C._free(buffer) - } - return result - } - } - class Node { - constructor(internal, tree) { - assertInternal(internal); - this.tree = tree - } - get typeId() { - marshalNode(this); - return C._ts_node_symbol_wasm(this.tree[0]) - } - get grammarId() { - marshalNode(this); - return C._ts_node_grammar_symbol_wasm(this.tree[0]) - } - get type() { - return this.tree.language.types[this.typeId] || "ERROR" - } - get grammarType() { - return this.tree.language.types[this.grammarId] || "ERROR" - } - get endPosition() { - marshalNode(this); - C._ts_node_end_point_wasm(this.tree[0]); - return unmarshalPoint(TRANSFER_BUFFER) - } - get endIndex() { - marshalNode(this); - return C._ts_node_end_index_wasm(this.tree[0]) - } - get text() { - return getText(this.tree, this.startIndex, this.endIndex) - } - get parseState() { - marshalNode(this); - return C._ts_node_parse_state_wasm(this.tree[0]) - } - get nextParseState() { - marshalNode(this); - return C._ts_node_next_parse_state_wasm(this.tree[0]) - } - isNamed() { - marshalNode(this); - return C._ts_node_is_named_wasm(this.tree[0]) === 1 - } - hasError() { - marshalNode(this); - return C._ts_node_has_error_wasm(this.tree[0]) === 1 - } - hasChanges() { - marshalNode(this); - return C._ts_node_has_changes_wasm(this.tree[0]) === 1 - } - isError() { - marshalNode(this); - return C._ts_node_is_error_wasm(this.tree[0]) === 1 - } - isMissing() { - marshalNode(this); - return C._ts_node_is_missing_wasm(this.tree[0]) === 1 - } - equals(other) { - return this.id === other.id - } - child(index) { - marshalNode(this); - C._ts_node_child_wasm(this.tree[0], index); - return unmarshalNode(this.tree) - } - fieldNameForChild(index) { - marshalNode(this); - const address = C._ts_node_field_name_for_child_wasm(this.tree[0], index); - if (!address) { - return null - } - const result = AsciiToString(address); - return result - } - namedChild(index) { - marshalNode(this); - C._ts_node_named_child_wasm(this.tree[0], index); - return unmarshalNode(this.tree) - } - childForFieldId(fieldId) { - marshalNode(this); - C._ts_node_child_by_field_id_wasm(this.tree[0], fieldId); - return unmarshalNode(this.tree) - } - childForFieldName(fieldName) { - const fieldId = this.tree.language.fields.indexOf(fieldName); - if (fieldId !== -1) return this.childForFieldId(fieldId) - } - get childCount() { - marshalNode(this); - return C._ts_node_child_count_wasm(this.tree[0]) - } - get namedChildCount() { - marshalNode(this); - return C._ts_node_named_child_count_wasm(this.tree[0]) - } - get firstChild() { - return this.child(0) - } - get firstNamedChild() { - return this.namedChild(0) - } - get lastChild() { - return this.child(this.childCount - 1) - } - get lastNamedChild() { - return this.namedChild(this.namedChildCount - 1) - } - get children() { - if (!this._children) { - marshalNode(this); - C._ts_node_children_wasm(this.tree[0]); - const count = getValue(TRANSFER_BUFFER, "i32"); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); - this._children = new Array(count); - if (count > 0) { - let address = buffer; - for (let i = 0; i < count; i++) { - this._children[i] = unmarshalNode(this.tree, address); - address += SIZE_OF_NODE - } - C._free(buffer) - } - } - return this._children - } - get namedChildren() { - if (!this._namedChildren) { - marshalNode(this); - C._ts_node_named_children_wasm(this.tree[0]); - const count = getValue(TRANSFER_BUFFER, "i32"); - const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); - this._namedChildren = new Array(count); - if (count > 0) { - let address = buffer; - for (let i = 0; i < count; i++) { - this._namedChildren[i] = unmarshalNode(this.tree, address); - address += SIZE_OF_NODE - } - C._free(buffer) - } - } - return this._namedChildren - } - descendantsOfType(types, startPosition, endPosition) { - if (!Array.isArray(types)) types = [types]; - if (!startPosition) startPosition = ZERO_POINT; - if (!endPosition) endPosition = ZERO_POINT; - const symbols = []; - const typesBySymbol = this.tree.language.types; - for (let i = 0, n = typesBySymbol.length; i < n; i++) { - if (types.includes(typesBySymbol[i])) { - symbols.push(i) - } - } - const symbolsAddress = C._malloc(SIZE_OF_INT * symbols.length); - for (let i = 0, n = symbols.length; i < n; i++) { - setValue(symbolsAddress + i * SIZE_OF_INT, symbols[i], "i32") - } - marshalNode(this); - C._ts_node_descendants_of_type_wasm(this.tree[0], symbolsAddress, symbols.length, startPosition.row, startPosition.column, endPosition.row, endPosition.column); - const descendantCount = getValue(TRANSFER_BUFFER, "i32"); - const descendantAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); - const result = new Array(descendantCount); - if (descendantCount > 0) { - let address = descendantAddress; - for (let i = 0; i < descendantCount; i++) { - result[i] = unmarshalNode(this.tree, address); - address += SIZE_OF_NODE - } - } - C._free(descendantAddress); - C._free(symbolsAddress); - return result - } - get nextSibling() { - marshalNode(this); - C._ts_node_next_sibling_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - get previousSibling() { - marshalNode(this); - C._ts_node_prev_sibling_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - get nextNamedSibling() { - marshalNode(this); - C._ts_node_next_named_sibling_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - get previousNamedSibling() { - marshalNode(this); - C._ts_node_prev_named_sibling_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - get parent() { - marshalNode(this); - C._ts_node_parent_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - descendantForIndex(start, end = start) { - if (typeof start !== "number" || typeof end !== "number") { - throw new Error("Arguments must be numbers") - } - marshalNode(this); - let address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, start, "i32"); - setValue(address + SIZE_OF_INT, end, "i32"); - C._ts_node_descendant_for_index_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - namedDescendantForIndex(start, end = start) { - if (typeof start !== "number" || typeof end !== "number") { - throw new Error("Arguments must be numbers") - } - marshalNode(this); - let address = TRANSFER_BUFFER + SIZE_OF_NODE; - setValue(address, start, "i32"); - setValue(address + SIZE_OF_INT, end, "i32"); - C._ts_node_named_descendant_for_index_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - descendantForPosition(start, end = start) { - if (!isPoint(start) || !isPoint(end)) { - throw new Error("Arguments must be {row, column} objects") - } - marshalNode(this); - let address = TRANSFER_BUFFER + SIZE_OF_NODE; - marshalPoint(address, start); - marshalPoint(address + SIZE_OF_POINT, end); - C._ts_node_descendant_for_position_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - namedDescendantForPosition(start, end = start) { - if (!isPoint(start) || !isPoint(end)) { - throw new Error("Arguments must be {row, column} objects") - } - marshalNode(this); - let address = TRANSFER_BUFFER + SIZE_OF_NODE; - marshalPoint(address, start); - marshalPoint(address + SIZE_OF_POINT, end); - C._ts_node_named_descendant_for_position_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - walk() { - marshalNode(this); - C._ts_tree_cursor_new_wasm(this.tree[0]); - return new TreeCursor(INTERNAL, this.tree) - } - toString() { - marshalNode(this); - const address = C._ts_node_to_string_wasm(this.tree[0]); - const result = AsciiToString(address); - C._free(address); - return result - } - } - class TreeCursor { - constructor(internal, tree) { - assertInternal(internal); - this.tree = tree; - unmarshalTreeCursor(this) - } - delete() { - marshalTreeCursor(this); - C._ts_tree_cursor_delete_wasm(this.tree[0]); - this[0] = this[1] = this[2] = 0 - } - reset(node) { - marshalNode(node); - marshalTreeCursor(this, TRANSFER_BUFFER + SIZE_OF_NODE); - C._ts_tree_cursor_reset_wasm(this.tree[0]); - unmarshalTreeCursor(this) - } - resetTo(cursor) { - marshalTreeCursor(this, TRANSFER_BUFFER); - marshalTreeCursor(cursor, TRANSFER_BUFFER + SIZE_OF_CURSOR); - C._ts_tree_cursor_reset_to_wasm(this.tree[0], cursor.tree[0]); - unmarshalTreeCursor(this) - } - get nodeType() { - return this.tree.language.types[this.nodeTypeId] || "ERROR" - } - get nodeTypeId() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_type_id_wasm(this.tree[0]) - } - get nodeStateId() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_state_id_wasm(this.tree[0]) - } - get nodeId() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_id_wasm(this.tree[0]) - } - get nodeIsNamed() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_is_named_wasm(this.tree[0]) === 1 - } - get nodeIsMissing() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_node_is_missing_wasm(this.tree[0]) === 1 - } - get nodeText() { - marshalTreeCursor(this); - const startIndex = C._ts_tree_cursor_start_index_wasm(this.tree[0]); - const endIndex = C._ts_tree_cursor_end_index_wasm(this.tree[0]); - return getText(this.tree, startIndex, endIndex) - } - get startPosition() { - marshalTreeCursor(this); - C._ts_tree_cursor_start_position_wasm(this.tree[0]); - return unmarshalPoint(TRANSFER_BUFFER) - } - get endPosition() { - marshalTreeCursor(this); - C._ts_tree_cursor_end_position_wasm(this.tree[0]); - return unmarshalPoint(TRANSFER_BUFFER) - } - get startIndex() { - marshalTreeCursor(this); - return C._ts_tree_cursor_start_index_wasm(this.tree[0]) - } - get endIndex() { - marshalTreeCursor(this); - return C._ts_tree_cursor_end_index_wasm(this.tree[0]) - } - currentNode() { - marshalTreeCursor(this); - C._ts_tree_cursor_current_node_wasm(this.tree[0]); - return unmarshalNode(this.tree) - } - currentFieldId() { - marshalTreeCursor(this); - return C._ts_tree_cursor_current_field_id_wasm(this.tree[0]) - } - currentFieldName() { - return this.tree.language.fields[this.currentFieldId()] - } - gotoFirstChild() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_first_child_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1 - } - gotoLastChild() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_last_child_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1 - } - gotoNextSibling() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_next_sibling_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1 - } - gotoPreviousSibling() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_previous_sibling_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1 - } - gotoParent() { - marshalTreeCursor(this); - const result = C._ts_tree_cursor_goto_parent_wasm(this.tree[0]); - unmarshalTreeCursor(this); - return result === 1 - } - } - class Language { - constructor(internal, address) { - assertInternal(internal); - this[0] = address; - this.types = new Array(C._ts_language_symbol_count(this[0])); - for (let i = 0, n = this.types.length; i < n; i++) { - if (C._ts_language_symbol_type(this[0], i) < 2) { - this.types[i] = UTF8ToString(C._ts_language_symbol_name(this[0], i)) - } - } - this.fields = new Array(C._ts_language_field_count(this[0]) + 1); - for (let i = 0, n = this.fields.length; i < n; i++) { - const fieldName = C._ts_language_field_name_for_id(this[0], i); - if (fieldName !== 0) { - this.fields[i] = UTF8ToString(fieldName) - } else { - this.fields[i] = null - } - } - } - get version() { - return C._ts_language_version(this[0]) - } - get fieldCount() { - return this.fields.length - 1 - } - get stateCount() { - return C._ts_language_state_count(this[0]) - } - fieldIdForName(fieldName) { - const result = this.fields.indexOf(fieldName); - if (result !== -1) { - return result - } else { - return null - } - } - fieldNameForId(fieldId) { - return this.fields[fieldId] || null - } - idForNodeType(type, named) { - const typeLength = lengthBytesUTF8(type); - const typeAddress = C._malloc(typeLength + 1); - stringToUTF8(type, typeAddress, typeLength + 1); - const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength, named); - C._free(typeAddress); - return result || null - } - get nodeTypeCount() { - return C._ts_language_symbol_count(this[0]) - } - nodeTypeForId(typeId) { - const name = C._ts_language_symbol_name(this[0], typeId); - return name ? UTF8ToString(name) : null - } - nodeTypeIsNamed(typeId) { - return C._ts_language_type_is_named_wasm(this[0], typeId) ? true : false - } - nodeTypeIsVisible(typeId) { - return C._ts_language_type_is_visible_wasm(this[0], typeId) ? true : false - } - nextState(stateId, typeId) { - return C._ts_language_next_state(this[0], stateId, typeId) - } - lookaheadIterator(stateId) { - const address = C._ts_lookahead_iterator_new(this[0], stateId); - if (address) return new LookaheadIterable(INTERNAL, address, this) - } - query(source) { - const sourceLength = lengthBytesUTF8(source); - const sourceAddress = C._malloc(sourceLength + 1); - stringToUTF8(source, sourceAddress, sourceLength + 1); - const address = C._ts_query_new(this[0], sourceAddress, sourceLength, TRANSFER_BUFFER, TRANSFER_BUFFER + SIZE_OF_INT); - if (!address) { - const errorId = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); - const errorByte = getValue(TRANSFER_BUFFER, "i32"); - const errorIndex = UTF8ToString(sourceAddress, errorByte).length; - const suffix = source.substr(errorIndex, 100).split("\n")[0]; - let word = suffix.match(QUERY_WORD_REGEX)[0]; - let error; - switch (errorId) { - case 2: - error = new RangeError(`Bad node name '${word}'`); - break; - case 3: - error = new RangeError(`Bad field name '${word}'`); - break; - case 4: - error = new RangeError(`Bad capture name @${word}`); - break; - case 5: - error = new TypeError(`Bad pattern structure at offset ${errorIndex}: '${suffix}'...`); - word = ""; - break; - default: - error = new SyntaxError(`Bad syntax at offset ${errorIndex}: '${suffix}'...`); - word = ""; - break - } - error.index = errorIndex; - error.length = word.length; - C._free(sourceAddress); - throw error - } - const stringCount = C._ts_query_string_count(address); - const captureCount = C._ts_query_capture_count(address); - const patternCount = C._ts_query_pattern_count(address); - const captureNames = new Array(captureCount); - const stringValues = new Array(stringCount); - for (let i = 0; i < captureCount; i++) { - const nameAddress = C._ts_query_capture_name_for_id(address, i, TRANSFER_BUFFER); - const nameLength = getValue(TRANSFER_BUFFER, "i32"); - captureNames[i] = UTF8ToString(nameAddress, nameLength) - } - for (let i = 0; i < stringCount; i++) { - const valueAddress = C._ts_query_string_value_for_id(address, i, TRANSFER_BUFFER); - const nameLength = getValue(TRANSFER_BUFFER, "i32"); - stringValues[i] = UTF8ToString(valueAddress, nameLength) - } - const setProperties = new Array(patternCount); - const assertedProperties = new Array(patternCount); - const refutedProperties = new Array(patternCount); - const predicates = new Array(patternCount); - const textPredicates = new Array(patternCount); - for (let i = 0; i < patternCount; i++) { - const predicatesAddress = C._ts_query_predicates_for_pattern(address, i, TRANSFER_BUFFER); - const stepCount = getValue(TRANSFER_BUFFER, "i32"); - predicates[i] = []; - textPredicates[i] = []; - const steps = []; - let stepAddress = predicatesAddress; - for (let j = 0; j < stepCount; j++) { - const stepType = getValue(stepAddress, "i32"); - stepAddress += SIZE_OF_INT; - const stepValueId = getValue(stepAddress, "i32"); - stepAddress += SIZE_OF_INT; - if (stepType === PREDICATE_STEP_TYPE_CAPTURE) { - steps.push({ - type: "capture", - name: captureNames[stepValueId] - }) - } else if (stepType === PREDICATE_STEP_TYPE_STRING) { - steps.push({ - type: "string", - value: stringValues[stepValueId] - }) - } else if (steps.length > 0) { - if (steps[0].type !== "string") { - throw new Error("Predicates must begin with a literal value") - } - const operator = steps[0].value; - let isPositive = true; - let matchAll = true; - let captureName; - switch (operator) { - case "any-not-eq?": - case "not-eq?": - isPositive = false; - case "any-eq?": - case "eq?": - if (steps.length !== 3) throw new Error(`Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length-1}`); - if (steps[1].type !== "capture") throw new Error(`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}"`); - matchAll = !operator.startsWith("any-"); - if (steps[2].type === "capture") { - const captureName1 = steps[1].name; - const captureName2 = steps[2].name; - textPredicates[i].push(function(captures) { - let nodes_1 = []; - let nodes_2 = []; - for (const c of captures) { - if (c.name === captureName1) nodes_1.push(c.node); - if (c.name === captureName2) nodes_2.push(c.node) - } - let compare = (n1, n2, positive) => { - return positive ? n1.text === n2.text : n1.text !== n2.text - }; - return matchAll ? nodes_1.every(n1 => nodes_2.some(n2 => compare(n1, n2, isPositive))) : nodes_1.some(n1 => nodes_2.some(n2 => compare(n1, n2, isPositive))) - }) - } else { - captureName = steps[1].name; - const stringValue = steps[2].value; - let matches = n => n.text === stringValue; - let doesNotMatch = n => n.text !== stringValue; - textPredicates[i].push(function(captures) { - let nodes = []; - for (const c of captures) { - if (c.name === captureName) nodes.push(c.node) - } - let test = isPositive ? matches : doesNotMatch; - return matchAll ? nodes.every(test) : nodes.some(test) - }) - } - break; - case "any-not-match?": - case "not-match?": - isPositive = false; - case "any-match?": - case "match?": - if (steps.length !== 3) throw new Error(`Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length-1}.`); - if (steps[1].type !== "capture") throw new Error(`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`); - if (steps[2].type !== "string") throw new Error(`Second argument of \`#${operator}\` predicate must be a string. Got @${steps[2].value}.`); - captureName = steps[1].name; - const regex = new RegExp(steps[2].value); - matchAll = !operator.startsWith("any-"); - textPredicates[i].push(function(captures) { - const nodes = []; - for (const c of captures) { - if (c.name === captureName) nodes.push(c.node.text) - } - let test = (text, positive) => { - return positive ? regex.test(text) : !regex.test(text) - }; - if (nodes.length === 0) return !isPositive; - return matchAll ? nodes.every(text => test(text, isPositive)) : nodes.some(text => test(text, isPositive)) - }); - break; - case "set!": - if (steps.length < 2 || steps.length > 3) throw new Error(`Wrong number of arguments to \`#set!\` predicate. Expected 1 or 2. Got ${steps.length-1}.`); - if (steps.some(s => s.type !== "string")) throw new Error(`Arguments to \`#set!\` predicate must be a strings.".`); - if (!setProperties[i]) setProperties[i] = {}; - setProperties[i][steps[1].value] = steps[2] ? steps[2].value : null; - break; - case "is?": - case "is-not?": - if (steps.length < 2 || steps.length > 3) throw new Error(`Wrong number of arguments to \`#${operator}\` predicate. Expected 1 or 2. Got ${steps.length-1}.`); - if (steps.some(s => s.type !== "string")) throw new Error(`Arguments to \`#${operator}\` predicate must be a strings.".`); - const properties = operator === "is?" ? assertedProperties : refutedProperties; - if (!properties[i]) properties[i] = {}; - properties[i][steps[1].value] = steps[2] ? steps[2].value : null; - break; - case "not-any-of?": - isPositive = false; - case "any-of?": - if (steps.length < 2) throw new Error(`Wrong number of arguments to \`#${operator}\` predicate. Expected at least 1. Got ${steps.length-1}.`); - if (steps[1].type !== "capture") throw new Error(`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`); - for (let i = 2; i < steps.length; i++) { - if (steps[i].type !== "string") throw new Error(`Arguments to \`#${operator}\` predicate must be a strings.".`) - } - captureName = steps[1].name; - const values = steps.slice(2).map(s => s.value); - textPredicates[i].push(function(captures) { - const nodes = []; - for (const c of captures) { - if (c.name === captureName) nodes.push(c.node.text) - } - if (nodes.length === 0) return !isPositive; - return nodes.every(text => values.includes(text)) === isPositive - }); - break; - default: - predicates[i].push({ - operator: operator, - operands: steps.slice(1) - }) - } - steps.length = 0 - } - } - Object.freeze(setProperties[i]); - Object.freeze(assertedProperties[i]); - Object.freeze(refutedProperties[i]) - } - C._free(sourceAddress); - return new Query(INTERNAL, address, captureNames, textPredicates, predicates, Object.freeze(setProperties), Object.freeze(assertedProperties), Object.freeze(refutedProperties)) - } - static load(input) { - let bytes; - if (input instanceof Uint8Array) { - bytes = Promise.resolve(input) - } else { - const url = input; - if (typeof process !== "undefined" && process.versions && process.versions.node) { - const fs = require("fs"); - bytes = Promise.resolve(fs.readFileSync(url)) - } else { - bytes = fetch(url).then(response => response.arrayBuffer().then(buffer => { - if (response.ok) { - return new Uint8Array(buffer) - } else { - const body = new TextDecoder("utf-8").decode(buffer); - throw new Error(`Language.load failed with status ${response.status}.\n\n${body}`) - } - })) - } - } - return bytes.then(bytes => loadWebAssemblyModule(bytes, { - loadAsync: true - })).then(mod => { - const symbolNames = Object.keys(mod); - const functionName = symbolNames.find(key => LANGUAGE_FUNCTION_REGEX.test(key) && !key.includes("external_scanner_")); - if (!functionName) { - console.log(`Couldn't find language function in WASM file. Symbols:\n${JSON.stringify(symbolNames,null,2)}`) - } - const languageAddress = mod[functionName](); - return new Language(INTERNAL, languageAddress) - }) - } - } - class LookaheadIterable { - constructor(internal, address, language) { - assertInternal(internal); - this[0] = address; - this.language = language - } - get currentTypeId() { - return C._ts_lookahead_iterator_current_symbol(this[0]) - } - get currentType() { - return this.language.types[this.currentTypeId] || "ERROR" - } - delete() { - C._ts_lookahead_iterator_delete(this[0]); - this[0] = 0 - } - resetState(stateId) { - return C._ts_lookahead_iterator_reset_state(this[0], stateId) - } - reset(language, stateId) { - if (C._ts_lookahead_iterator_reset(this[0], language[0], stateId)) { - this.language = language; - return true - } - return false - } [Symbol.iterator]() { - const self = this; - return { - next() { - if (C._ts_lookahead_iterator_next(self[0])) { - return { - done: false, - value: self.currentType - } - } - return { - done: true, - value: "" - } - } - } - } - } - class Query { - constructor(internal, address, captureNames, textPredicates, predicates, setProperties, assertedProperties, refutedProperties) { - assertInternal(internal); - this[0] = address; - this.captureNames = captureNames; - this.textPredicates = textPredicates; - this.predicates = predicates; - this.setProperties = setProperties; - this.assertedProperties = assertedProperties; - this.refutedProperties = refutedProperties; - this.exceededMatchLimit = false - } - delete() { - C._ts_query_delete(this[0]); - this[0] = 0 - } - matches(node, startPosition, endPosition, options) { - if (!startPosition) startPosition = ZERO_POINT; - if (!endPosition) endPosition = ZERO_POINT; - if (!options) options = {}; - let matchLimit = options.matchLimit; - if (typeof matchLimit === "undefined") { - matchLimit = 0 - } else if (typeof matchLimit !== "number") { - throw new Error("Arguments must be numbers") - } - marshalNode(node); - C._ts_query_matches_wasm(this[0], node.tree[0], startPosition.row, startPosition.column, endPosition.row, endPosition.column, matchLimit); - const rawCount = getValue(TRANSFER_BUFFER, "i32"); - const startAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); - const didExceedMatchLimit = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, "i32"); - const result = new Array(rawCount); - this.exceededMatchLimit = !!didExceedMatchLimit; - let filteredCount = 0; - let address = startAddress; - for (let i = 0; i < rawCount; i++) { - const pattern = getValue(address, "i32"); - address += SIZE_OF_INT; - const captureCount = getValue(address, "i32"); - address += SIZE_OF_INT; - const captures = new Array(captureCount); - address = unmarshalCaptures(this, node.tree, address, captures); - if (this.textPredicates[pattern].every(p => p(captures))) { - result[filteredCount++] = { - pattern: pattern, - captures: captures - }; - const setProperties = this.setProperties[pattern]; - if (setProperties) result[i].setProperties = setProperties; - const assertedProperties = this.assertedProperties[pattern]; - if (assertedProperties) result[i].assertedProperties = assertedProperties; - const refutedProperties = this.refutedProperties[pattern]; - if (refutedProperties) result[i].refutedProperties = refutedProperties - } - } - result.length = filteredCount; - C._free(startAddress); - return result - } - captures(node, startPosition, endPosition, options) { - if (!startPosition) startPosition = ZERO_POINT; - if (!endPosition) endPosition = ZERO_POINT; - if (!options) options = {}; - let matchLimit = options.matchLimit; - if (typeof matchLimit === "undefined") { - matchLimit = 0 - } else if (typeof matchLimit !== "number") { - throw new Error("Arguments must be numbers") - } - marshalNode(node); - C._ts_query_captures_wasm(this[0], node.tree[0], startPosition.row, startPosition.column, endPosition.row, endPosition.column, matchLimit); - const count = getValue(TRANSFER_BUFFER, "i32"); - const startAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); - const didExceedMatchLimit = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, "i32"); - const result = []; - this.exceededMatchLimit = !!didExceedMatchLimit; - const captures = []; - let address = startAddress; - for (let i = 0; i < count; i++) { - const pattern = getValue(address, "i32"); - address += SIZE_OF_INT; - const captureCount = getValue(address, "i32"); - address += SIZE_OF_INT; - const captureIndex = getValue(address, "i32"); - address += SIZE_OF_INT; - captures.length = captureCount; - address = unmarshalCaptures(this, node.tree, address, captures); - if (this.textPredicates[pattern].every(p => p(captures))) { - const capture = captures[captureIndex]; - const setProperties = this.setProperties[pattern]; - if (setProperties) capture.setProperties = setProperties; - const assertedProperties = this.assertedProperties[pattern]; - if (assertedProperties) capture.assertedProperties = assertedProperties; - const refutedProperties = this.refutedProperties[pattern]; - if (refutedProperties) capture.refutedProperties = refutedProperties; - result.push(capture) - } - } - C._free(startAddress); - return result - } - predicatesForPattern(patternIndex) { - return this.predicates[patternIndex] - } - didExceedMatchLimit() { - return this.exceededMatchLimit - } - } - - function getText(tree, startIndex, endIndex) { - const length = endIndex - startIndex; - let result = tree.textCallback(startIndex, null, endIndex); - startIndex += result.length; - while (startIndex < endIndex) { - const string = tree.textCallback(startIndex, null, endIndex); - if (string && string.length > 0) { - startIndex += string.length; - result += string - } else { - break - } - } - if (startIndex > endIndex) { - result = result.slice(0, length) - } - return result - } - - function unmarshalCaptures(query, tree, address, result) { - for (let i = 0, n = result.length; i < n; i++) { - const captureIndex = getValue(address, "i32"); - address += SIZE_OF_INT; - const node = unmarshalNode(tree, address); - address += SIZE_OF_NODE; - result[i] = { - name: query.captureNames[captureIndex], - node: node - } - } - return address - } - function assertInternal(x) { - if (x !== INTERNAL) throw new Error("Illegal constructor") - } - - function isPoint(point) { - return point && typeof point.row === "number" && typeof point.column === "number" - } - - function marshalNode(node) { - let address = TRANSFER_BUFFER; - setValue(address, node.id, "i32"); - address += SIZE_OF_INT; - setValue(address, node.startIndex, "i32"); - address += SIZE_OF_INT; - setValue(address, node.startPosition.row, "i32"); - address += SIZE_OF_INT; - setValue(address, node.startPosition.column, "i32"); - address += SIZE_OF_INT; - setValue(address, node[0], "i32") - } - - function unmarshalNode(tree, address = TRANSFER_BUFFER) { - const id = getValue(address, "i32"); - address += SIZE_OF_INT; - if (id === 0) return null; - const index = getValue(address, "i32"); - address += SIZE_OF_INT; - const row = getValue(address, "i32"); - address += SIZE_OF_INT; - const column = getValue(address, "i32"); - address += SIZE_OF_INT; - const other = getValue(address, "i32"); - const result = new Node(INTERNAL, tree); - result.id = id; - result.startIndex = index; - result.startPosition = { - row: row, - column: column - }; - result[0] = other; - return result - } - - function marshalTreeCursor(cursor, address = TRANSFER_BUFFER) { - setValue(address + 0 * SIZE_OF_INT, cursor[0], "i32"), setValue(address + 1 * SIZE_OF_INT, cursor[1], "i32"), setValue(address + 2 * SIZE_OF_INT, cursor[2], "i32") - } - - function unmarshalTreeCursor(cursor) { - cursor[0] = getValue(TRANSFER_BUFFER + 0 * SIZE_OF_INT, "i32"), cursor[1] = getValue(TRANSFER_BUFFER + 1 * SIZE_OF_INT, "i32"), cursor[2] = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, "i32") - } +// Determine the runtime environment we are in. You can customize this by +// setting the ENVIRONMENT setting at compile time (see settings.js). +// Attempt to auto-detect the environment +var ENVIRONMENT_IS_WEB = typeof window == "object"; - function marshalPoint(address, point) { - setValue(address, point.row, "i32"); - setValue(address + SIZE_OF_INT, point.column, "i32") - } +var ENVIRONMENT_IS_WORKER = typeof importScripts == "function"; - function unmarshalPoint(address) { - return { - row: getValue(address, "i32"), - column: getValue(address + SIZE_OF_INT, "i32") - } - } +// N.b. Electron.js environment is simultaneously a NODE-environment, but +// also a web environment. +var ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; - function marshalRange(address, range) { - marshalPoint(address, range.startPosition); - address += SIZE_OF_POINT; - marshalPoint(address, range.endPosition); - address += SIZE_OF_POINT; - setValue(address, range.startIndex, "i32"); - address += SIZE_OF_INT; - setValue(address, range.endIndex, "i32"); - address += SIZE_OF_INT - } +if (ENVIRONMENT_IS_NODE) {} - function unmarshalRange(address) { - const result = {}; - result.startPosition = unmarshalPoint(address); - address += SIZE_OF_POINT; - result.endPosition = unmarshalPoint(address); - address += SIZE_OF_POINT; - result.startIndex = getValue(address, "i32"); - address += SIZE_OF_INT; - result.endIndex = getValue(address, "i32"); - return result +// --pre-jses are emitted after the Module integration code, so that they can +// refer to Module (if they choose; they can also define Module) +// include: /Users/andrew/Code/tree-sitter/tree-sitter/lib/binding_web/prefix.js +var TreeSitter = function() { + var initPromise; + var document = typeof window == "object" ? { + currentScript: window.document.currentScript + } : null; + class Parser { + constructor() { + this.initialize(); + } + initialize() { + throw new Error("cannot construct a Parser before calling `init()`"); + } + static init(moduleOptions) { + if (initPromise) return initPromise; + Module = Object.assign({}, Module, moduleOptions); + return initPromise = new Promise(resolveInitPromise => { + // end include: /Users/andrew/Code/tree-sitter/tree-sitter/lib/binding_web/prefix.js + // Sometimes an existing Module object exists with properties + // meant to overwrite the default module functionality. Here + // we collect those properties and reapply _after_ we configure + // the current environment's defaults to avoid having to be so + // defensive during initialization. + var moduleOverrides = Object.assign({}, Module); + var arguments_ = []; + var thisProgram = "./this.program"; + var quit_ = (status, toThrow) => { + throw toThrow; + }; + // `/` should be present at the end if `scriptDirectory` is not empty + var scriptDirectory = ""; + function locateFile(path) { + if (Module["locateFile"]) { + return Module["locateFile"](path, scriptDirectory); + } + return scriptDirectory + path; + } + // Hooks that are implemented differently in different runtime environments. + var readAsync, readBinary; + if (ENVIRONMENT_IS_NODE) { + // These modules will usually be used on Node.js. Load them eagerly to avoid + // the complexity of lazy-loading. + var fs = require("fs"); + var nodePath = require("path"); + scriptDirectory = __dirname + "/"; + // include: node_shell_read.js + readBinary = filename => { + // We need to re-wrap `file://` strings to URLs. Normalizing isn't + // necessary in that case, the path should already be absolute. + filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); + var ret = fs.readFileSync(filename); + return ret; + }; + readAsync = (filename, binary = true) => { + // See the comment in the `readBinary` function. + filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename); + return new Promise((resolve, reject) => { + fs.readFile(filename, binary ? undefined : "utf8", (err, data) => { + if (err) reject(err); else resolve(binary ? data.buffer : data); + }); + }); + }; + // end include: node_shell_read.js + if (!Module["thisProgram"] && process.argv.length > 1) { + thisProgram = process.argv[1].replace(/\\/g, "/"); + } + arguments_ = process.argv.slice(2); + if (typeof module != "undefined") { + module["exports"] = Module; + } + quit_ = (status, toThrow) => { + process.exitCode = status; + throw toThrow; + }; + } else // Note that this includes Node.js workers when relevant (pthreads is enabled). + // Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and + // ENVIRONMENT_IS_NODE. + if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + if (ENVIRONMENT_IS_WORKER) { + // Check worker, not web, since window could be polyfilled + scriptDirectory = self.location.href; + } else if (typeof document != "undefined" && document.currentScript) { + // web + scriptDirectory = document.currentScript.src; + } + // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them. + // otherwise, slice off the final part of the url to find the script directory. + // if scriptDirectory does not contain a slash, lastIndexOf will return -1, + // and scriptDirectory will correctly be replaced with an empty string. + // If scriptDirectory contains a query (starting with ?) or a fragment (starting with #), + // they are removed because they could contain a slash. + if (scriptDirectory.startsWith("blob:")) { + scriptDirectory = ""; + } else { + scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1); + } + { + // include: web_or_worker_shell_read.js + if (ENVIRONMENT_IS_WORKER) { + readBinary = url => { + var xhr = new XMLHttpRequest; + xhr.open("GET", url, false); + xhr.responseType = "arraybuffer"; + xhr.send(null); + return new Uint8Array(/** @type{!ArrayBuffer} */ (xhr.response)); + }; + } + readAsync = url => { + // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url. + // See https://github.com/github/fetch/pull/92#issuecomment-140665932 + // Cordova or Electron apps are typically loaded from a file:// url. + // So use XHR on webview if URL is a file URL. + if (isFileURI(url)) { + return new Promise((reject, resolve) => { + var xhr = new XMLHttpRequest; + xhr.open("GET", url, true); + xhr.responseType = "arraybuffer"; + xhr.onload = () => { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { + // file URLs can return 0 + resolve(xhr.response); + } + reject(xhr.status); + }; + xhr.onerror = reject; + xhr.send(null); + }); + } + return fetch(url, { + credentials: "same-origin" + }).then(response => { + if (response.ok) { + return response.arrayBuffer(); + } + return Promise.reject(new Error(response.status + " : " + response.url)); + }); + }; + } + } else // end include: web_or_worker_shell_read.js + {} + var out = Module["print"] || console.log.bind(console); + var err = Module["printErr"] || console.error.bind(console); + // Merge back in the overrides + Object.assign(Module, moduleOverrides); + // Free the object hierarchy contained in the overrides, this lets the GC + // reclaim data used. + moduleOverrides = null; + // Emit code to handle expected values on the Module object. This applies Module.x + // to the proper local x. This has two benefits: first, we only emit it if it is + // expected to arrive, and second, by using a local everywhere else that can be + // minified. + if (Module["arguments"]) arguments_ = Module["arguments"]; + if (Module["thisProgram"]) thisProgram = Module["thisProgram"]; + if (Module["quit"]) quit_ = Module["quit"]; + // perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message + // end include: shell.js + // include: preamble.js + // === Preamble library stuff === + // Documentation for the public APIs defined in this file must be updated in: + // site/source/docs/api_reference/preamble.js.rst + // A prebuilt local version of the documentation is available at: + // site/build/text/docs/api_reference/preamble.js.txt + // You can also build docs locally as HTML or other formats in site/ + // An online HTML version (which may be of a different version of Emscripten) + // is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html + var dynamicLibraries = Module["dynamicLibraries"] || []; + var wasmBinary; + if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"]; + // Wasm globals + var wasmMemory; + //======================================== + // Runtime essentials + //======================================== + // whether we are quitting the application. no code should run after this. + // set in exit() and abort() + var ABORT = false; + // set by exit() and abort(). Passed to 'onExit' handler. + // NOTE: This is also used as the process return code code in shell environments + // but only when noExitRuntime is false. + var EXITSTATUS; + // Memory management + var /** @type {!Int8Array} */ HEAP8, /** @type {!Uint8Array} */ HEAPU8, /** @type {!Int16Array} */ HEAP16, /** @type {!Uint16Array} */ HEAPU16, /** @type {!Int32Array} */ HEAP32, /** @type {!Uint32Array} */ HEAPU32, /** @type {!Float32Array} */ HEAPF32, /** @type {!Float64Array} */ HEAPF64; + var HEAP_DATA_VIEW; + // include: runtime_shared.js + function updateMemoryViews() { + var b = wasmMemory.buffer; + Module["HEAP_DATA_VIEW"] = HEAP_DATA_VIEW = new DataView(b); + Module["HEAP8"] = HEAP8 = new Int8Array(b); + Module["HEAP16"] = HEAP16 = new Int16Array(b); + Module["HEAPU8"] = HEAPU8 = new Uint8Array(b); + Module["HEAPU16"] = HEAPU16 = new Uint16Array(b); + Module["HEAP32"] = HEAP32 = new Int32Array(b); + Module["HEAPU32"] = HEAPU32 = new Uint32Array(b); + Module["HEAPF32"] = HEAPF32 = new Float32Array(b); + Module["HEAPF64"] = HEAPF64 = new Float64Array(b); + } + // end include: runtime_shared.js + // In non-standalone/normal mode, we create the memory here. + // include: runtime_init_memory.js + // Create the wasm memory. (Note: this only applies if IMPORTED_MEMORY is defined) + // check for full engine support (use string 'subarray' to avoid closure compiler confusion) + if (Module["wasmMemory"]) { + wasmMemory = Module["wasmMemory"]; + } else { + var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 33554432; + wasmMemory = new WebAssembly.Memory({ + "initial": INITIAL_MEMORY / 65536, + // In theory we should not need to emit the maximum if we want "unlimited" + // or 4GB of memory, but VMs error on that atm, see + // https://github.com/emscripten-core/emscripten/issues/14130 + // And in the pthreads case we definitely need to emit a maximum. So + // always emit one. + "maximum": 2147483648 / 65536 + }); + } + updateMemoryViews(); + // end include: runtime_init_memory.js + // include: runtime_stack_check.js + // end include: runtime_stack_check.js + // include: runtime_assertions.js + // end include: runtime_assertions.js + var __ATPRERUN__ = []; + // functions called before the runtime is initialized + var __ATINIT__ = []; + // functions called during startup + var __ATMAIN__ = []; + // functions called during shutdown + var __ATPOSTRUN__ = []; + // functions called after the main() is called + var __RELOC_FUNCS__ = []; + var runtimeInitialized = false; + function preRun() { + if (Module["preRun"]) { + if (typeof Module["preRun"] == "function") Module["preRun"] = [ Module["preRun"] ]; + while (Module["preRun"].length) { + addOnPreRun(Module["preRun"].shift()); + } + } + callRuntimeCallbacks(__ATPRERUN__); + } + function initRuntime() { + runtimeInitialized = true; + callRuntimeCallbacks(__RELOC_FUNCS__); + callRuntimeCallbacks(__ATINIT__); + } + function preMain() { + callRuntimeCallbacks(__ATMAIN__); + } + function postRun() { + if (Module["postRun"]) { + if (typeof Module["postRun"] == "function") Module["postRun"] = [ Module["postRun"] ]; + while (Module["postRun"].length) { + addOnPostRun(Module["postRun"].shift()); + } + } + callRuntimeCallbacks(__ATPOSTRUN__); + } + function addOnPreRun(cb) { + __ATPRERUN__.unshift(cb); + } + function addOnInit(cb) { + __ATINIT__.unshift(cb); + } + function addOnPostRun(cb) { + __ATPOSTRUN__.unshift(cb); + } + // include: runtime_math.js + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc + // end include: runtime_math.js + // A counter of dependencies for calling run(). If we need to + // do asynchronous work before running, increment this and + // decrement it. Incrementing must happen in a place like + // Module.preRun (used by emcc to add file preloading). + // Note that you can add dependencies in preRun, even though + // it happens right before run - run will be postponed until + // the dependencies are met. + var runDependencies = 0; + var runDependencyWatcher = null; + var dependenciesFulfilled = null; + // overridden to take different actions when all run dependencies are fulfilled + function getUniqueRunDependency(id) { + return id; + } + function addRunDependency(id) { + runDependencies++; + Module["monitorRunDependencies"]?.(runDependencies); + } + function removeRunDependency(id) { + runDependencies--; + Module["monitorRunDependencies"]?.(runDependencies); + if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); + } + } + } + /** @param {string|number=} what */ function abort(what) { + Module["onAbort"]?.(what); + what = "Aborted(" + what + ")"; + // TODO(sbc): Should we remove printing and leave it up to whoever + // catches the exception? + err(what); + ABORT = true; + EXITSTATUS = 1; + what += ". Build with -sASSERTIONS for more info."; + // Use a wasm runtime error, because a JS error might be seen as a foreign + // exception, which means we'd run destructors on it. We need the error to + // simply make the program stop. + // FIXME This approach does not work in Wasm EH because it currently does not assume + // all RuntimeErrors are from traps; it decides whether a RuntimeError is from + // a trap or not based on a hidden field within the object. So at the moment + // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that + // allows this in the wasm spec. + // Suppress closure compiler warning here. Closure compiler's builtin extern + // definition for WebAssembly.RuntimeError claims it takes no arguments even + // though it can. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. + /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what); + // Throw the error whether or not MODULARIZE is set because abort is used + // in code paths apart from instantiation where an exception is expected + // to be thrown when abort is called. + throw e; + } + // include: memoryprofiler.js + // end include: memoryprofiler.js + // include: URIUtils.js + // Prefix of data URIs emitted by SINGLE_FILE and related options. + var dataURIPrefix = "data:application/octet-stream;base64,"; + /** + * Indicates whether filename is a base64 data URI. + * @noinline + */ var isDataURI = filename => filename.startsWith(dataURIPrefix); + /** + * Indicates whether filename is delivered via file protocol (as opposed to http/https) + * @noinline + */ var isFileURI = filename => filename.startsWith("file://"); + // end include: URIUtils.js + // include: runtime_exceptions.js + // end include: runtime_exceptions.js + function findWasmBinary() { + var f = "tree-sitter.wasm"; + if (!isDataURI(f)) { + return locateFile(f); + } + return f; + } + var wasmBinaryFile; + function getBinarySync(file) { + if (file == wasmBinaryFile && wasmBinary) { + return new Uint8Array(wasmBinary); + } + if (readBinary) { + return readBinary(file); + } + throw "both async and sync fetching of the wasm failed"; + } + function getBinaryPromise(binaryFile) { + // If we don't have the binary yet, load it asynchronously using readAsync. + if (!wasmBinary) { + // Fetch the binary using readAsync + return readAsync(binaryFile).then(response => new Uint8Array(/** @type{!ArrayBuffer} */ (response)), // Fall back to getBinarySync if readAsync fails + () => getBinarySync(binaryFile)); + } + // Otherwise, getBinarySync should be able to get it synchronously + return Promise.resolve().then(() => getBinarySync(binaryFile)); + } + function instantiateArrayBuffer(binaryFile, imports, receiver) { + return getBinaryPromise(binaryFile).then(binary => WebAssembly.instantiate(binary, imports)).then(receiver, reason => { + err(`failed to asynchronously prepare wasm: ${reason}`); + abort(reason); + }); + } + function instantiateAsync(binary, binaryFile, imports, callback) { + if (!binary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(binaryFile) && // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously. + !isFileURI(binaryFile) && // Avoid instantiateStreaming() on Node.js environment for now, as while + // Node.js v18.1.0 implements it, it does not have a full fetch() + // implementation yet. + // Reference: + // https://github.com/emscripten-core/emscripten/pull/16917 + !ENVIRONMENT_IS_NODE && typeof fetch == "function") { + return fetch(binaryFile, { + credentials: "same-origin" + }).then(response => { + // Suppress closure warning here since the upstream definition for + // instantiateStreaming only allows Promise rather than + // an actual Response. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed. + /** @suppress {checkTypes} */ var result = WebAssembly.instantiateStreaming(response, imports); + return result.then(callback, function(reason) { + // We expect the most common failure cause to be a bad MIME type for the binary, + // in which case falling back to ArrayBuffer instantiation should work. + err(`wasm streaming compile failed: ${reason}`); + err("falling back to ArrayBuffer instantiation"); + return instantiateArrayBuffer(binaryFile, imports, callback); + }); + }); + } + return instantiateArrayBuffer(binaryFile, imports, callback); + } + function getWasmImports() { + // prepare imports + return { + "env": wasmImports, + "wasi_snapshot_preview1": wasmImports, + "GOT.mem": new Proxy(wasmImports, GOTHandler), + "GOT.func": new Proxy(wasmImports, GOTHandler) + }; + } + // Create the wasm instance. + // Receives the wasm imports, returns the exports. + function createWasm() { + var info = getWasmImports(); + // Load the wasm module and create an instance of using native support in the JS engine. + // handle a generated wasm instance, receiving its exports and + // performing other necessary setup + /** @param {WebAssembly.Module=} module*/ function receiveInstance(instance, module) { + wasmExports = instance.exports; + wasmExports = relocateExports(wasmExports, 1024); + var metadata = getDylinkMetadata(module); + if (metadata.neededDynlibs) { + dynamicLibraries = metadata.neededDynlibs.concat(dynamicLibraries); + } + mergeLibSymbols(wasmExports, "main"); + LDSO.init(); + loadDylibs(); + addOnInit(wasmExports["__wasm_call_ctors"]); + __RELOC_FUNCS__.push(wasmExports["__wasm_apply_data_relocs"]); + removeRunDependency("wasm-instantiate"); + return wasmExports; + } + // wait for the pthread pool (if any) + addRunDependency("wasm-instantiate"); + // Prefer streaming instantiation if available. + function receiveInstantiationResult(result) { + // 'result' is a ResultObject object which has both the module and instance. + // receiveInstance() will swap in the exports (to Module.asm) so they can be called + receiveInstance(result["instance"], result["module"]); + } + // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback + // to manually instantiate the Wasm module themselves. This allows pages to + // run the instantiation parallel to any other async startup actions they are + // performing. + // Also pthreads and wasm workers initialize the wasm instance through this + // path. + if (Module["instantiateWasm"]) { + try { + return Module["instantiateWasm"](info, receiveInstance); + } catch (e) { + err(`Module.instantiateWasm callback failed with error: ${e}`); + return false; + } + } + if (!wasmBinaryFile) wasmBinaryFile = findWasmBinary(); + instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult); + return {}; + } + // include: runtime_debug.js + // end include: runtime_debug.js + // === Body === + var ASM_CONSTS = {}; + // end include: preamble.js + /** @constructor */ function ExitStatus(status) { + this.name = "ExitStatus"; + this.message = `Program terminated with exit(${status})`; + this.status = status; + } + var GOT = {}; + var currentModuleWeakSymbols = new Set([]); + var GOTHandler = { + get(obj, symName) { + var rtn = GOT[symName]; + if (!rtn) { + rtn = GOT[symName] = new WebAssembly.Global({ + "value": "i32", + "mutable": true + }); + } + if (!currentModuleWeakSymbols.has(symName)) { + // Any non-weak reference to a symbol marks it as `required`, which + // enabled `reportUndefinedSymbols` to report undefeind symbol errors + // correctly. + rtn.required = true; + } + return rtn; + } + }; + var LE_HEAP_LOAD_F32 = byteOffset => HEAP_DATA_VIEW.getFloat32(byteOffset, true); + var LE_HEAP_LOAD_F64 = byteOffset => HEAP_DATA_VIEW.getFloat64(byteOffset, true); + var LE_HEAP_LOAD_I16 = byteOffset => HEAP_DATA_VIEW.getInt16(byteOffset, true); + var LE_HEAP_LOAD_I32 = byteOffset => HEAP_DATA_VIEW.getInt32(byteOffset, true); + var LE_HEAP_LOAD_U32 = byteOffset => HEAP_DATA_VIEW.getUint32(byteOffset, true); + var LE_HEAP_STORE_F32 = (byteOffset, value) => HEAP_DATA_VIEW.setFloat32(byteOffset, value, true); + var LE_HEAP_STORE_F64 = (byteOffset, value) => HEAP_DATA_VIEW.setFloat64(byteOffset, value, true); + var LE_HEAP_STORE_I16 = (byteOffset, value) => HEAP_DATA_VIEW.setInt16(byteOffset, value, true); + var LE_HEAP_STORE_I32 = (byteOffset, value) => HEAP_DATA_VIEW.setInt32(byteOffset, value, true); + var LE_HEAP_STORE_U32 = (byteOffset, value) => HEAP_DATA_VIEW.setUint32(byteOffset, value, true); + var callRuntimeCallbacks = callbacks => { + while (callbacks.length > 0) { + // Pass the module as the first argument. + callbacks.shift()(Module); + } + }; + var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder : undefined; + /** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number} idx + * @param {number=} maxBytesToRead + * @return {string} + */ var UTF8ArrayToString = (heapOrArray, idx, maxBytesToRead) => { + var endIdx = idx + maxBytesToRead; + var endPtr = idx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. Also, use the length info to avoid running tiny + // strings through TextDecoder, since .subarray() allocates garbage. + // (As a tiny code save trick, compare endPtr against endIdx using a negation, + // so that undefined means Infinity) + while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ""; + // If building with TextDecoder, we have already computed the string length + // above, so test loop end condition against that + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 128)) { + str += String.fromCharCode(u0); + continue; + } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 224) == 192) { + str += String.fromCharCode(((u0 & 31) << 6) | u1); + continue; + } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 240) == 224) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); + } + if (u0 < 65536) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 65536; + str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023)); + } + } + return str; + }; + var getDylinkMetadata = binary => { + var offset = 0; + var end = 0; + function getU8() { + return binary[offset++]; + } + function getLEB() { + var ret = 0; + var mul = 1; + while (1) { + var byte = binary[offset++]; + ret += ((byte & 127) * mul); + mul *= 128; + if (!(byte & 128)) break; + } + return ret; + } + function getString() { + var len = getLEB(); + offset += len; + return UTF8ArrayToString(binary, offset - len, len); + } + /** @param {string=} message */ function failIf(condition, message) { + if (condition) throw new Error(message); + } + var name = "dylink.0"; + if (binary instanceof WebAssembly.Module) { + var dylinkSection = WebAssembly.Module.customSections(binary, name); + if (dylinkSection.length === 0) { + name = "dylink"; + dylinkSection = WebAssembly.Module.customSections(binary, name); + } + failIf(dylinkSection.length === 0, "need dylink section"); + binary = new Uint8Array(dylinkSection[0]); + end = binary.length; + } else { + var int32View = new Uint32Array(new Uint8Array(binary.subarray(0, 24)).buffer); + var magicNumberFound = int32View[0] == 1836278016 || int32View[0] == 6386541; + failIf(!magicNumberFound, "need to see wasm magic number"); + // \0asm + // we should see the dylink custom section right after the magic number and wasm version + failIf(binary[8] !== 0, "need the dylink section to be first"); + offset = 9; + var section_size = getLEB(); + //section size + end = offset + section_size; + name = getString(); + } + var customSection = { + neededDynlibs: [], + tlsExports: new Set, + weakImports: new Set + }; + if (name == "dylink") { + customSection.memorySize = getLEB(); + customSection.memoryAlign = getLEB(); + customSection.tableSize = getLEB(); + customSection.tableAlign = getLEB(); + // shared libraries this module needs. We need to load them first, so that + // current module could resolve its imports. (see tools/shared.py + // WebAssembly.make_shared_library() for "dylink" section extension format) + var neededDynlibsCount = getLEB(); + for (var i = 0; i < neededDynlibsCount; ++i) { + var libname = getString(); + customSection.neededDynlibs.push(libname); + } + } else { + failIf(name !== "dylink.0"); + var WASM_DYLINK_MEM_INFO = 1; + var WASM_DYLINK_NEEDED = 2; + var WASM_DYLINK_EXPORT_INFO = 3; + var WASM_DYLINK_IMPORT_INFO = 4; + var WASM_SYMBOL_TLS = 256; + var WASM_SYMBOL_BINDING_MASK = 3; + var WASM_SYMBOL_BINDING_WEAK = 1; + while (offset < end) { + var subsectionType = getU8(); + var subsectionSize = getLEB(); + if (subsectionType === WASM_DYLINK_MEM_INFO) { + customSection.memorySize = getLEB(); + customSection.memoryAlign = getLEB(); + customSection.tableSize = getLEB(); + customSection.tableAlign = getLEB(); + } else if (subsectionType === WASM_DYLINK_NEEDED) { + var neededDynlibsCount = getLEB(); + for (var i = 0; i < neededDynlibsCount; ++i) { + libname = getString(); + customSection.neededDynlibs.push(libname); + } + } else if (subsectionType === WASM_DYLINK_EXPORT_INFO) { + var count = getLEB(); + while (count--) { + var symname = getString(); + var flags = getLEB(); + if (flags & WASM_SYMBOL_TLS) { + customSection.tlsExports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_IMPORT_INFO) { + var count = getLEB(); + while (count--) { + var modname = getString(); + var symname = getString(); + var flags = getLEB(); + if ((flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) { + customSection.weakImports.add(symname); + } + } + } else { + // unknown subsection + offset += subsectionSize; + } + } + } + return customSection; + }; + /** + * @param {number} ptr + * @param {string} type + */ function getValue(ptr, type = "i8") { + if (type.endsWith("*")) type = "*"; + switch (type) { + case "i1": + return HEAP8[ptr]; + + case "i8": + return HEAP8[ptr]; + + case "i16": + return LE_HEAP_LOAD_I16(((ptr) >> 1) * 2); + + case "i32": + return LE_HEAP_LOAD_I32(((ptr) >> 2) * 4); + + case "i64": + abort("to do getValue(i64) use WASM_BIGINT"); + + case "float": + return LE_HEAP_LOAD_F32(((ptr) >> 2) * 4); + + case "double": + return LE_HEAP_LOAD_F64(((ptr) >> 3) * 8); + + case "*": + return LE_HEAP_LOAD_U32(((ptr) >> 2) * 4); + + default: + abort(`invalid type for getValue: ${type}`); + } + } + var newDSO = (name, handle, syms) => { + var dso = { + refcount: Infinity, + name: name, + exports: syms, + global: true + }; + LDSO.loadedLibsByName[name] = dso; + if (handle != undefined) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return dso; + }; + var LDSO = { + loadedLibsByName: {}, + loadedLibsByHandle: {}, + init() { + newDSO("__main__", 0, wasmImports); + } + }; + var ___heap_base = 78112; + var zeroMemory = (address, size) => { + HEAPU8.fill(0, address, address + size); + return address; + }; + var alignMemory = (size, alignment) => Math.ceil(size / alignment) * alignment; + var getMemory = size => { + // After the runtime is initialized, we must only use sbrk() normally. + if (runtimeInitialized) { + // Currently we don't support freeing of static data when modules are + // unloaded via dlclose. This function is tagged as `noleakcheck` to + // avoid having this reported as leak. + return zeroMemory(_malloc(size), size); + } + var ret = ___heap_base; + // Keep __heap_base stack aligned. + var end = ret + alignMemory(size, 16); + ___heap_base = end; + GOT["__heap_base"].value = end; + return ret; + }; + var isInternalSym = symName => [ "__cpp_exception", "__c_longjmp", "__wasm_apply_data_relocs", "__dso_handle", "__tls_size", "__tls_align", "__set_stack_limits", "_emscripten_tls_init", "__wasm_init_tls", "__wasm_call_ctors", "__start_em_asm", "__stop_em_asm", "__start_em_js", "__stop_em_js" ].includes(symName) || symName.startsWith("__em_js__"); + var uleb128Encode = (n, target) => { + if (n < 128) { + target.push(n); + } else { + target.push((n % 128) | 128, n >> 7); + } + }; + var sigToWasmTypes = sig => { + var typeNames = { + "i": "i32", + "j": "i64", + "f": "f32", + "d": "f64", + "e": "externref", + "p": "i32" + }; + var type = { + parameters: [], + results: sig[0] == "v" ? [] : [ typeNames[sig[0]] ] + }; + for (var i = 1; i < sig.length; ++i) { + type.parameters.push(typeNames[sig[i]]); + } + return type; + }; + var generateFuncType = (sig, target) => { + var sigRet = sig.slice(0, 1); + var sigParam = sig.slice(1); + var typeCodes = { + "i": 127, + // i32 + "p": 127, + // i32 + "j": 126, + // i64 + "f": 125, + // f32 + "d": 124, + // f64 + "e": 111 + }; + // Parameters, length + signatures + target.push(96); + /* form: func */ uleb128Encode(sigParam.length, target); + for (var i = 0; i < sigParam.length; ++i) { + target.push(typeCodes[sigParam[i]]); + } + // Return values, length + signatures + // With no multi-return in MVP, either 0 (void) or 1 (anything else) + if (sigRet == "v") { + target.push(0); + } else { + target.push(1, typeCodes[sigRet]); + } + }; + var convertJsFunctionToWasm = (func, sig) => { + // If the type reflection proposal is available, use the new + // "WebAssembly.Function" constructor. + // Otherwise, construct a minimal wasm module importing the JS function and + // re-exporting it. + if (typeof WebAssembly.Function == "function") { + return new WebAssembly.Function(sigToWasmTypes(sig), func); + } + // The module is static, with the exception of the type section, which is + // generated based on the signature passed in. + var typeSectionBody = [ 1 ]; + // count: 1 + generateFuncType(sig, typeSectionBody); + // Rest of the module is static + var bytes = [ 0, 97, 115, 109, // magic ("\0asm") + 1, 0, 0, 0, // version: 1 + 1 ]; + // Write the overall length of the type section followed by the body + uleb128Encode(typeSectionBody.length, bytes); + bytes.push(...typeSectionBody); + // The rest of the module is static + bytes.push(2, 7, // import section + // (import "e" "f" (func 0 (type 0))) + 1, 1, 101, 1, 102, 0, 0, 7, 5, // export section + // (export "f" (func 0 (type 0))) + 1, 1, 102, 0, 0); + // We can compile this wasm module synchronously because it is very small. + // This accepts an import (at "e.f"), that it reroutes to an export (at "f") + var module = new WebAssembly.Module(new Uint8Array(bytes)); + var instance = new WebAssembly.Instance(module, { + "e": { + "f": func + } + }); + var wrappedFunc = instance.exports["f"]; + return wrappedFunc; + }; + var wasmTableMirror = []; + /** @type {WebAssembly.Table} */ var wasmTable = new WebAssembly.Table({ + "initial": 28, + "element": "anyfunc" + }); + var getWasmTableEntry = funcPtr => { + var func = wasmTableMirror[funcPtr]; + if (!func) { + if (funcPtr >= wasmTableMirror.length) wasmTableMirror.length = funcPtr + 1; + wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr); + } + return func; + }; + var updateTableMap = (offset, count) => { + if (functionsInTableMap) { + for (var i = offset; i < offset + count; i++) { + var item = getWasmTableEntry(i); + // Ignore null values. + if (item) { + functionsInTableMap.set(item, i); + } + } + } + }; + var functionsInTableMap; + var getFunctionAddress = func => { + // First, create the map if this is the first use. + if (!functionsInTableMap) { + functionsInTableMap = new WeakMap; + updateTableMap(0, wasmTable.length); + } + return functionsInTableMap.get(func) || 0; + }; + var freeTableIndexes = []; + var getEmptyTableSlot = () => { + // Reuse a free index if there is one, otherwise grow. + if (freeTableIndexes.length) { + return freeTableIndexes.pop(); + } + // Grow the table + try { + wasmTable.grow(1); + } catch (err) { + if (!(err instanceof RangeError)) { + throw err; + } + throw "Unable to grow wasm table. Set ALLOW_TABLE_GROWTH."; + } + return wasmTable.length - 1; + }; + var setWasmTableEntry = (idx, func) => { + wasmTable.set(idx, func); + // With ABORT_ON_WASM_EXCEPTIONS wasmTable.get is overridden to return wrapped + // functions so we need to call it here to retrieve the potential wrapper correctly + // instead of just storing 'func' directly into wasmTableMirror + wasmTableMirror[idx] = wasmTable.get(idx); + }; + /** @param {string=} sig */ var addFunction = (func, sig) => { + // Check if the function is already in the table, to ensure each function + // gets a unique index. + var rtn = getFunctionAddress(func); + if (rtn) { + return rtn; + } + // It's not in the table, add it now. + var ret = getEmptyTableSlot(); + // Set the new value. + try { + // Attempting to call this with JS function will cause of table.set() to fail + setWasmTableEntry(ret, func); + } catch (err) { + if (!(err instanceof TypeError)) { + throw err; + } + var wrapped = convertJsFunctionToWasm(func, sig); + setWasmTableEntry(ret, wrapped); + } + functionsInTableMap.set(func, ret); + return ret; + }; + var updateGOT = (exports, replace) => { + for (var symName in exports) { + if (isInternalSym(symName)) { + continue; + } + var value = exports[symName]; + if (symName.startsWith("orig$")) { + symName = symName.split("$")[1]; + replace = true; + } + GOT[symName] ||= new WebAssembly.Global({ + "value": "i32", + "mutable": true + }); + if (replace || GOT[symName].value == 0) { + if (typeof value == "function") { + GOT[symName].value = addFunction(value); + } else if (typeof value == "number") { + GOT[symName].value = value; + } else { + err(`unhandled export type for '${symName}': ${typeof value}`); + } + } + } + }; + /** @param {boolean=} replace */ var relocateExports = (exports, memoryBase, replace) => { + var relocated = {}; + for (var e in exports) { + var value = exports[e]; + if (typeof value == "object") { + // a breaking change in the wasm spec, globals are now objects + // https://github.com/WebAssembly/mutable-global/issues/1 + value = value.value; + } + if (typeof value == "number") { + value += memoryBase; + } + relocated[e] = value; + } + updateGOT(relocated, replace); + return relocated; + }; + var isSymbolDefined = symName => { + // Ignore 'stub' symbols that are auto-generated as part of the original + // `wasmImports` used to instantiate the main module. + var existing = wasmImports[symName]; + if (!existing || existing.stub) { + return false; + } + return true; + }; + var dynCallLegacy = (sig, ptr, args) => { + sig = sig.replace(/p/g, "i"); + var f = Module["dynCall_" + sig]; + return f(ptr, ...args); + }; + var dynCall = (sig, ptr, args = []) => { + // Without WASM_BIGINT support we cannot directly call function with i64 as + // part of their signature, so we rely on the dynCall functions generated by + // wasm-emscripten-finalize + if (sig.includes("j")) { + return dynCallLegacy(sig, ptr, args); + } + var rtn = getWasmTableEntry(ptr)(...args); + return rtn; + }; + var stackSave = () => _emscripten_stack_get_current(); + var stackRestore = val => __emscripten_stack_restore(val); + var createInvokeFunction = sig => (ptr, ...args) => { + var sp = stackSave(); + try { + return dynCall(sig, ptr, args); + } catch (e) { + stackRestore(sp); + // Create a try-catch guard that rethrows the Emscripten EH exception. + // Exceptions thrown from C++ will be a pointer (number) and longjmp + // will throw the number Infinity. Use the compact and fast "e !== e+0" + // test to check if e was not a Number. + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + }; + var resolveGlobalSymbol = (symName, direct = false) => { + var sym; + // First look for the orig$ symbol which is the symbol without i64 + // legalization performed. + if (direct && ("orig$" + symName in wasmImports)) { + symName = "orig$" + symName; + } + if (isSymbolDefined(symName)) { + sym = wasmImports[symName]; + } else // Asm.js-style exception handling: invoke wrapper generation + if (symName.startsWith("invoke_")) { + // Create (and cache) new invoke_ functions on demand. + sym = wasmImports[symName] = createInvokeFunction(symName.split("_")[1]); + } + return { + sym: sym, + name: symName + }; + }; + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ var UTF8ToString = (ptr, maxBytesToRead) => ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ""; + /** + * @param {string=} libName + * @param {Object=} localScope + * @param {number=} handle + */ var loadWebAssemblyModule = (binary, flags, libName, localScope, handle) => { + var metadata = getDylinkMetadata(binary); + currentModuleWeakSymbols = metadata.weakImports; + // loadModule loads the wasm module after all its dependencies have been loaded. + // can be called both sync/async. + function loadModule() { + // The first thread to load a given module needs to allocate the static + // table and memory regions. Later threads re-use the same table region + // and can ignore the memory region (since memory is shared between + // threads already). + // If `handle` is specified than it is assumed that the calling thread has + // exclusive access to it for the duration of this function. See the + // locking in `dynlink.c`. + var firstLoad = !handle || !HEAP8[(handle) + (8)]; + if (firstLoad) { + // alignments are powers of 2 + var memAlign = Math.pow(2, metadata.memoryAlign); + // prepare memory + var memoryBase = metadata.memorySize ? alignMemory(getMemory(metadata.memorySize + memAlign), memAlign) : 0; + // TODO: add to cleanups + var tableBase = metadata.tableSize ? wasmTable.length : 0; + if (handle) { + HEAP8[(handle) + (8)] = 1; + LE_HEAP_STORE_U32((((handle) + (12)) >> 2) * 4, memoryBase); + LE_HEAP_STORE_I32((((handle) + (16)) >> 2) * 4, metadata.memorySize); + LE_HEAP_STORE_U32((((handle) + (20)) >> 2) * 4, tableBase); + LE_HEAP_STORE_I32((((handle) + (24)) >> 2) * 4, metadata.tableSize); + } + } else { + memoryBase = LE_HEAP_LOAD_U32((((handle) + (12)) >> 2) * 4); + tableBase = LE_HEAP_LOAD_U32((((handle) + (20)) >> 2) * 4); + } + var tableGrowthNeeded = tableBase + metadata.tableSize - wasmTable.length; + if (tableGrowthNeeded > 0) { + wasmTable.grow(tableGrowthNeeded); + } + // This is the export map that we ultimately return. We declare it here + // so it can be used within resolveSymbol. We resolve symbols against + // this local symbol map in the case there they are not present on the + // global Module object. We need this fallback because Modules sometime + // need to import their own symbols + var moduleExports; + function resolveSymbol(sym) { + var resolved = resolveGlobalSymbol(sym).sym; + if (!resolved && localScope) { + resolved = localScope[sym]; + } + if (!resolved) { + resolved = moduleExports[sym]; + } + if (!resolved) { + console.warn(`Warning: parser wants to call function ${sym}, but it is not defined. If parsing fails, this is probably the reason why. Please report this to the Pulsar team so that this parser can be supported properly.`); + } + return resolved; + } + // TODO kill ↓↓↓ (except "symbols local to this module", it will likely be + // not needed if we require that if A wants symbols from B it has to link + // to B explicitly: similarly to -Wl,--no-undefined) + // wasm dynamic libraries are pure wasm, so they cannot assist in + // their own loading. When side module A wants to import something + // provided by a side module B that is loaded later, we need to + // add a layer of indirection, but worse, we can't even tell what + // to add the indirection for, without inspecting what A's imports + // are. To do that here, we use a JS proxy (another option would + // be to inspect the binary directly). + var proxyHandler = { + get(stubs, prop) { + // symbols that should be local to this module + switch (prop) { + case "__memory_base": + return memoryBase; + + case "__table_base": + return tableBase; + } + if (prop in wasmImports && !wasmImports[prop].stub) { + // No stub needed, symbol already exists in symbol table + return wasmImports[prop]; + } + // Return a stub function that will resolve the symbol + // when first called. + if (!(prop in stubs)) { + var resolved; + stubs[prop] = (...args) => { + resolved ||= resolveSymbol(prop); + return resolved(...args); + }; + } + return stubs[prop]; + } + }; + var proxy = new Proxy({}, proxyHandler); + var info = { + "GOT.mem": new Proxy({}, GOTHandler), + "GOT.func": new Proxy({}, GOTHandler), + "env": proxy, + "wasi_snapshot_preview1": proxy + }; + function postInstantiation(module, instance) { + // add new entries to functionsInTableMap + updateTableMap(tableBase, metadata.tableSize); + moduleExports = relocateExports(instance.exports, memoryBase); + if (!flags.allowUndefined) { + reportUndefinedSymbols(); + } + function addEmAsm(addr, body) { + var args = []; + var arity = 0; + for (;arity < 16; arity++) { + if (body.indexOf("$" + arity) != -1) { + args.push("$" + arity); + } else { + break; + } + } + args = args.join(","); + var func = `(${args}) => { ${body} };`; + ASM_CONSTS[start] = eval(func); + } + // Add any EM_ASM function that exist in the side module + if ("__start_em_asm" in moduleExports) { + var start = moduleExports["__start_em_asm"]; + var stop = moduleExports["__stop_em_asm"]; + while (start < stop) { + var jsString = UTF8ToString(start); + addEmAsm(start, jsString); + start = HEAPU8.indexOf(0, start) + 1; + } + } + function addEmJs(name, cSig, body) { + // The signature here is a C signature (e.g. "(int foo, char* bar)"). + // See `create_em_js` in emcc.py` for the build-time version of this + // code. + var jsArgs = []; + cSig = cSig.slice(1, -1); + if (cSig != "void") { + cSig = cSig.split(","); + for (var i in cSig) { + var jsArg = cSig[i].split(" ").pop(); + jsArgs.push(jsArg.replace("*", "")); + } + } + var func = `(${jsArgs}) => ${body};`; + moduleExports[name] = eval(func); + } + for (var name in moduleExports) { + if (name.startsWith("__em_js__")) { + var start = moduleExports[name]; + var jsString = UTF8ToString(start); + // EM_JS strings are stored in the data section in the form + // SIG<::>BODY. + var parts = jsString.split("<::>"); + addEmJs(name.replace("__em_js__", ""), parts[0], parts[1]); + delete moduleExports[name]; + } + } + // initialize the module + var applyRelocs = moduleExports["__wasm_apply_data_relocs"]; + if (applyRelocs) { + if (runtimeInitialized) { + applyRelocs(); + } else { + __RELOC_FUNCS__.push(applyRelocs); } + } + var init = moduleExports["__wasm_call_ctors"]; + if (init) { + if (runtimeInitialized) { + init(); + } else { + // we aren't ready to run compiled code yet + __ATINIT__.push(init); + } + } + return moduleExports; + } + if (flags.loadAsync) { + if (binary instanceof WebAssembly.Module) { + var instance = new WebAssembly.Instance(binary, info); + return Promise.resolve(postInstantiation(binary, instance)); + } + return WebAssembly.instantiate(binary, info).then(result => postInstantiation(result.module, result.instance)); + } + var module = binary instanceof WebAssembly.Module ? binary : new WebAssembly.Module(binary); + var instance = new WebAssembly.Instance(module, info); + return postInstantiation(module, instance); + } + // now load needed libraries and the module itself. + if (flags.loadAsync) { + return metadata.neededDynlibs.reduce((chain, dynNeeded) => chain.then(() => loadDynamicLibrary(dynNeeded, flags, localScope)), Promise.resolve()).then(loadModule); + } + metadata.neededDynlibs.forEach(needed => loadDynamicLibrary(needed, flags, localScope)); + return loadModule(); + }; + var mergeLibSymbols = (exports, libName) => { + // add symbols into global namespace TODO: weak linking etc. + for (var [sym, exp] of Object.entries(exports)) { + // When RTLD_GLOBAL is enabled, the symbols defined by this shared object + // will be made available for symbol resolution of subsequently loaded + // shared objects. + // We should copy the symbols (which include methods and variables) from + // SIDE_MODULE to MAIN_MODULE. + const setImport = target => { + if (!isSymbolDefined(target)) { + wasmImports[target] = exp; + } + }; + setImport(sym); + // Special case for handling of main symbol: If a side module exports + // `main` that also acts a definition for `__main_argc_argv` and vice + // versa. + const main_alias = "__main_argc_argv"; + if (sym == "main") { + setImport(main_alias); + } + if (sym == main_alias) { + setImport("main"); + } + if (sym.startsWith("dynCall_") && !Module.hasOwnProperty(sym)) { + Module[sym] = exp; + } + } + }; + /** @param {boolean=} noRunDep */ var asyncLoad = (url, onload, onerror, noRunDep) => { + var dep = !noRunDep ? getUniqueRunDependency(`al ${url}`) : ""; + readAsync(url).then(arrayBuffer => { + onload(new Uint8Array(arrayBuffer)); + if (dep) removeRunDependency(dep); + }, err => { + if (onerror) { + onerror(); + } else { + throw `Loading data file "${url}" failed.`; + } + }); + if (dep) addRunDependency(dep); + }; + /** + * @param {number=} handle + * @param {Object=} localScope + */ function loadDynamicLibrary(libName, flags = { + global: true, + nodelete: true + }, localScope, handle) { + // when loadDynamicLibrary did not have flags, libraries were loaded + // globally & permanently + var dso = LDSO.loadedLibsByName[libName]; + if (dso) { + // the library is being loaded or has been loaded already. + if (!flags.global) { + if (localScope) { + Object.assign(localScope, dso.exports); + } + } else if (!dso.global) { + // The library was previously loaded only locally but not + // we have a request with global=true. + dso.global = true; + mergeLibSymbols(dso.exports, libName); + } + // same for "nodelete" + if (flags.nodelete && dso.refcount !== Infinity) { + dso.refcount = Infinity; + } + dso.refcount++; + if (handle) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return flags.loadAsync ? Promise.resolve(true) : true; + } + // allocate new DSO + dso = newDSO(libName, handle, "loading"); + dso.refcount = flags.nodelete ? Infinity : 1; + dso.global = flags.global; + // libName -> libData + function loadLibData() { + // for wasm, we can use fetch for async, but for fs mode we can only imitate it + if (handle) { + var data = LE_HEAP_LOAD_U32((((handle) + (28)) >> 2) * 4); + var dataSize = LE_HEAP_LOAD_U32((((handle) + (32)) >> 2) * 4); + if (data && dataSize) { + var libData = HEAP8.slice(data, data + dataSize); + return flags.loadAsync ? Promise.resolve(libData) : libData; + } + } + var libFile = locateFile(libName); + if (flags.loadAsync) { + return new Promise(function(resolve, reject) { + asyncLoad(libFile, resolve, reject); + }); + } + // load the binary synchronously + if (!readBinary) { + throw new Error(`${libFile}: file not found, and synchronous loading of external files is not available`); + } + return readBinary(libFile); + } + // libName -> exports + function getExports() { + // module not preloaded - load lib data and create new module from it + if (flags.loadAsync) { + return loadLibData().then(libData => loadWebAssemblyModule(libData, flags, libName, localScope, handle)); + } + return loadWebAssemblyModule(loadLibData(), flags, libName, localScope, handle); + } + // module for lib is loaded - update the dso & global namespace + function moduleLoaded(exports) { + if (dso.global) { + mergeLibSymbols(exports, libName); + } else if (localScope) { + Object.assign(localScope, exports); + } + dso.exports = exports; + } + if (flags.loadAsync) { + return getExports().then(exports => { + moduleLoaded(exports); + return true; + }); + } + moduleLoaded(getExports()); + return true; + } + var reportUndefinedSymbols = () => { + for (var [symName, entry] of Object.entries(GOT)) { + if (entry.value == 0) { + var value = resolveGlobalSymbol(symName, true).sym; + if (!value && !entry.required) { + // Ignore undefined symbols that are imported as weak. + continue; + } + if (typeof value == "function") { + /** @suppress {checkTypes} */ entry.value = addFunction(value, value.sig); + } else if (typeof value == "number") { + entry.value = value; + } else { + throw new Error(`bad export type for '${symName}': ${typeof value}`); + } + } + } + }; + var loadDylibs = () => { + if (!dynamicLibraries.length) { + reportUndefinedSymbols(); + return; + } + // Load binaries asynchronously + addRunDependency("loadDylibs"); + dynamicLibraries.reduce((chain, lib) => chain.then(() => loadDynamicLibrary(lib, { + loadAsync: true, + global: true, + nodelete: true, + allowUndefined: true + })), Promise.resolve()).then(() => { + // we got them all, wonderful + reportUndefinedSymbols(); + removeRunDependency("loadDylibs"); + }); + }; + var noExitRuntime = Module["noExitRuntime"] || true; + /** + * @param {number} ptr + * @param {number} value + * @param {string} type + */ function setValue(ptr, value, type = "i8") { + if (type.endsWith("*")) type = "*"; + switch (type) { + case "i1": + HEAP8[ptr] = value; + break; + + case "i8": + HEAP8[ptr] = value; + break; + + case "i16": + LE_HEAP_STORE_I16(((ptr) >> 1) * 2, value); + break; + + case "i32": + LE_HEAP_STORE_I32(((ptr) >> 2) * 4, value); + break; + + case "i64": + abort("to do setValue(i64) use WASM_BIGINT"); + + case "float": + LE_HEAP_STORE_F32(((ptr) >> 2) * 4, value); + break; + + case "double": + LE_HEAP_STORE_F64(((ptr) >> 3) * 8, value); + break; + + case "*": + LE_HEAP_STORE_U32(((ptr) >> 2) * 4, value); + break; + + default: + abort(`invalid type for setValue: ${type}`); + } + } + var ___memory_base = new WebAssembly.Global({ + "value": "i32", + "mutable": false + }, 1024); + var ___stack_pointer = new WebAssembly.Global({ + "value": "i32", + "mutable": true + }, 78112); + var ___table_base = new WebAssembly.Global({ + "value": "i32", + "mutable": false + }, 1); + var __abort_js = () => { + abort(""); + }; + __abort_js.sig = "v"; + var nowIsMonotonic = 1; + var __emscripten_get_now_is_monotonic = () => nowIsMonotonic; + __emscripten_get_now_is_monotonic.sig = "i"; + var __emscripten_memcpy_js = (dest, src, num) => HEAPU8.copyWithin(dest, src, src + num); + __emscripten_memcpy_js.sig = "vppp"; + var _emscripten_date_now = () => Date.now(); + _emscripten_date_now.sig = "d"; + var _emscripten_get_now; + // Modern environment where performance.now() is supported: + // N.B. a shorter form "_emscripten_get_now = performance.now;" is + // unfortunately not allowed even in current browsers (e.g. FF Nightly 75). + _emscripten_get_now = () => performance.now(); + _emscripten_get_now.sig = "d"; + var getHeapMax = () => // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate + // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side + // for any code that deals with heap sizes, which would require special + // casing all heap size related code to treat 0 specially. + 2147483648; + var growMemory = size => { + var b = wasmMemory.buffer; + var pages = (size - b.byteLength + 65535) / 65536; + try { + // round size grow request up to wasm page size (fixed 64KB per spec) + wasmMemory.grow(pages); + // .grow() takes a delta compared to the previous size + updateMemoryViews(); + return 1; + } /*success*/ catch (e) {} + }; + // implicit 0 return to save code size (caller will cast "undefined" into 0 + // anyhow) + var _emscripten_resize_heap = requestedSize => { + var oldSize = HEAPU8.length; + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + requestedSize >>>= 0; + // With multithreaded builds, races can happen (another thread might increase the size + // in between), so return a failure, and let the caller retry. + // Memory resize rules: + // 1. Always increase heap size to at least the requested size, rounded up + // to next page multiple. + // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap + // geometrically: increase the heap size according to + // MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most + // overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB). + // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap + // linearly: increase the heap size by at least + // MEMORY_GROWTH_LINEAR_STEP bytes. + // 3. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by + // MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest + // 4. If we were unable to allocate as much memory, it may be due to + // over-eager decision to excessively reserve due to (3) above. + // Hence if an allocation fails, cut down on the amount of excess + // growth, in an attempt to succeed to perform a smaller allocation. + // A limit is set for how much we can grow. We should not exceed that + // (the wasm binary specifies it, so if we tried, we'd fail anyhow). + var maxHeapSize = getHeapMax(); + if (requestedSize > maxHeapSize) { + return false; + } + var alignUp = (x, multiple) => x + (multiple - x % multiple) % multiple; + // Loop through potential heap size increases. If we attempt a too eager + // reservation that fails, cut down on the attempted size and reserve a + // smaller bump instead. (max 3 times, chosen somewhat arbitrarily) + for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { + var overGrownHeapSize = oldSize * (1 + .2 / cutDown); + // ensure geometric growth + // but limit overreserving (default to capping at +96MB overgrowth at most) + overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296); + var newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536)); + var replacement = growMemory(newSize); + if (replacement) { + return true; + } + } + return false; + }; + _emscripten_resize_heap.sig = "ip"; + var _fd_close = fd => 52; + _fd_close.sig = "ii"; + var convertI32PairToI53Checked = (lo, hi) => ((hi + 2097152) >>> 0 < 4194305 - !!lo) ? (lo >>> 0) + hi * 4294967296 : NaN; + function _fd_seek(fd, offset_low, offset_high, whence, newOffset) { + var offset = convertI32PairToI53Checked(offset_low, offset_high); + return 70; + } + _fd_seek.sig = "iiiiip"; + var printCharBuffers = [ null, [], [] ]; + var printChar = (stream, curr) => { + var buffer = printCharBuffers[stream]; + if (curr === 0 || curr === 10) { + (stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0)); + buffer.length = 0; + } else { + buffer.push(curr); + } + }; + var _fd_write = (fd, iov, iovcnt, pnum) => { + // hack to support printf in SYSCALLS_REQUIRE_FILESYSTEM=0 + var num = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = LE_HEAP_LOAD_U32(((iov) >> 2) * 4); + var len = LE_HEAP_LOAD_U32((((iov) + (4)) >> 2) * 4); + iov += 8; + for (var j = 0; j < len; j++) { + printChar(fd, HEAPU8[ptr + j]); + } + num += len; + } + LE_HEAP_STORE_U32(((pnum) >> 2) * 4, num); + return 0; + }; + _fd_write.sig = "iippp"; + function _tree_sitter_log_callback(isLexMessage, messageAddress) { + if (currentLogCallback) { + const message = UTF8ToString(messageAddress); + currentLogCallback(message, isLexMessage !== 0); + } + } + function _tree_sitter_parse_callback(inputBufferAddress, index, row, column, lengthAddress) { + const INPUT_BUFFER_SIZE = 10 * 1024; + const string = currentParseCallback(index, { + row: row, + column: column + }); + if (typeof string === "string") { + setValue(lengthAddress, string.length, "i32"); + stringToUTF16(string, inputBufferAddress, INPUT_BUFFER_SIZE); + } else { + setValue(lengthAddress, 0, "i32"); + } + } + var runtimeKeepaliveCounter = 0; + var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; + var _proc_exit = code => { + EXITSTATUS = code; + if (!keepRuntimeAlive()) { + Module["onExit"]?.(code); + ABORT = true; + } + quit_(code, new ExitStatus(code)); + }; + _proc_exit.sig = "vi"; + /** @param {boolean|number=} implicit */ var exitJS = (status, implicit) => { + EXITSTATUS = status; + _proc_exit(status); + }; + var handleException = e => { + // Certain exception types we do not treat as errors since they are used for + // internal control flow. + // 1. ExitStatus, which is thrown by exit() + // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others + // that wish to return to JS event loop. + if (e instanceof ExitStatus || e == "unwind") { + return EXITSTATUS; + } + quit_(1, e); + }; + var lengthBytesUTF8 = str => { + var len = 0; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var c = str.charCodeAt(i); + // possibly a lead surrogate + if (c <= 127) { + len++; + } else if (c <= 2047) { + len += 2; + } else if (c >= 55296 && c <= 57343) { + len += 4; + ++i; + } else { + len += 3; + } + } + return len; + }; + var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + // Parameter maxBytesToWrite is not optional. Negative values, 0, null, + // undefined and false each don't write out any bytes. + if (!(maxBytesToWrite > 0)) return 0; + var startIdx = outIdx; + var endIdx = outIdx + maxBytesToWrite - 1; + // -1 for string null terminator. + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description + // and https://www.ietf.org/rfc/rfc2279.txt + // and https://tools.ietf.org/html/rfc3629 + var u = str.charCodeAt(i); + // possibly a lead surrogate + if (u >= 55296 && u <= 57343) { + var u1 = str.charCodeAt(++i); + u = 65536 + ((u & 1023) << 10) | (u1 & 1023); + } + if (u <= 127) { + if (outIdx >= endIdx) break; + heap[outIdx++] = u; + } else if (u <= 2047) { + if (outIdx + 1 >= endIdx) break; + heap[outIdx++] = 192 | (u >> 6); + heap[outIdx++] = 128 | (u & 63); + } else if (u <= 65535) { + if (outIdx + 2 >= endIdx) break; + heap[outIdx++] = 224 | (u >> 12); + heap[outIdx++] = 128 | ((u >> 6) & 63); + heap[outIdx++] = 128 | (u & 63); + } else { + if (outIdx + 3 >= endIdx) break; + heap[outIdx++] = 240 | (u >> 18); + heap[outIdx++] = 128 | ((u >> 12) & 63); + heap[outIdx++] = 128 | ((u >> 6) & 63); + heap[outIdx++] = 128 | (u & 63); + } + } + // Null-terminate the pointer to the buffer. + heap[outIdx] = 0; + return outIdx - startIdx; + }; + var stringToUTF8 = (str, outPtr, maxBytesToWrite) => stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); + var stackAlloc = sz => __emscripten_stack_alloc(sz); + var stringToUTF8OnStack = str => { + var size = lengthBytesUTF8(str) + 1; + var ret = stackAlloc(size); + stringToUTF8(str, ret, size); + return ret; + }; + var stringToUTF16 = (str, outPtr, maxBytesToWrite) => { + // Backwards compatibility: if max bytes is not specified, assume unsafe unbounded write is allowed. + maxBytesToWrite ??= 2147483647; + if (maxBytesToWrite < 2) return 0; + maxBytesToWrite -= 2; + // Null terminator. + var startPtr = outPtr; + var numCharsToWrite = (maxBytesToWrite < str.length * 2) ? (maxBytesToWrite / 2) : str.length; + for (var i = 0; i < numCharsToWrite; ++i) { + // charCodeAt returns a UTF-16 encoded code unit, so it can be directly written to the HEAP. + var codeUnit = str.charCodeAt(i); + // possibly a lead surrogate + LE_HEAP_STORE_I16(((outPtr) >> 1) * 2, codeUnit); + outPtr += 2; + } + // Null-terminate the pointer to the HEAP. + LE_HEAP_STORE_I16(((outPtr) >> 1) * 2, 0); + return outPtr - startPtr; + }; + var AsciiToString = ptr => { + var str = ""; + while (1) { + var ch = HEAPU8[ptr++]; + if (!ch) return str; + str += String.fromCharCode(ch); + } + }; + var wasmImports = { + /** @export */ __heap_base: ___heap_base, + /** @export */ __indirect_function_table: wasmTable, + /** @export */ __memory_base: ___memory_base, + /** @export */ __stack_pointer: ___stack_pointer, + /** @export */ __table_base: ___table_base, + /** @export */ _abort_js: __abort_js, + /** @export */ _emscripten_get_now_is_monotonic: __emscripten_get_now_is_monotonic, + /** @export */ _emscripten_memcpy_js: __emscripten_memcpy_js, + /** @export */ emscripten_get_now: _emscripten_get_now, + /** @export */ emscripten_resize_heap: _emscripten_resize_heap, + /** @export */ fd_close: _fd_close, + /** @export */ fd_seek: _fd_seek, + /** @export */ fd_write: _fd_write, + /** @export */ memory: wasmMemory, + /** @export */ tree_sitter_log_callback: _tree_sitter_log_callback, + /** @export */ tree_sitter_parse_callback: _tree_sitter_parse_callback + }; + var wasmExports = createWasm(); + var ___wasm_call_ctors = () => (___wasm_call_ctors = wasmExports["__wasm_call_ctors"])(); + var ___wasm_apply_data_relocs = () => (___wasm_apply_data_relocs = wasmExports["__wasm_apply_data_relocs"])(); + var _malloc = Module["_malloc"] = a0 => (_malloc = Module["_malloc"] = wasmExports["malloc"])(a0); + var _calloc = Module["_calloc"] = (a0, a1) => (_calloc = Module["_calloc"] = wasmExports["calloc"])(a0, a1); + var _realloc = Module["_realloc"] = (a0, a1) => (_realloc = Module["_realloc"] = wasmExports["realloc"])(a0, a1); + var _free = Module["_free"] = a0 => (_free = Module["_free"] = wasmExports["free"])(a0); + var _ts_language_symbol_count = Module["_ts_language_symbol_count"] = a0 => (_ts_language_symbol_count = Module["_ts_language_symbol_count"] = wasmExports["ts_language_symbol_count"])(a0); + var _ts_language_state_count = Module["_ts_language_state_count"] = a0 => (_ts_language_state_count = Module["_ts_language_state_count"] = wasmExports["ts_language_state_count"])(a0); + var _ts_language_version = Module["_ts_language_version"] = a0 => (_ts_language_version = Module["_ts_language_version"] = wasmExports["ts_language_version"])(a0); + var _ts_language_field_count = Module["_ts_language_field_count"] = a0 => (_ts_language_field_count = Module["_ts_language_field_count"] = wasmExports["ts_language_field_count"])(a0); + var _ts_language_next_state = Module["_ts_language_next_state"] = (a0, a1, a2) => (_ts_language_next_state = Module["_ts_language_next_state"] = wasmExports["ts_language_next_state"])(a0, a1, a2); + var _ts_language_symbol_name = Module["_ts_language_symbol_name"] = (a0, a1) => (_ts_language_symbol_name = Module["_ts_language_symbol_name"] = wasmExports["ts_language_symbol_name"])(a0, a1); + var _ts_language_symbol_for_name = Module["_ts_language_symbol_for_name"] = (a0, a1, a2, a3) => (_ts_language_symbol_for_name = Module["_ts_language_symbol_for_name"] = wasmExports["ts_language_symbol_for_name"])(a0, a1, a2, a3); + var _strncmp = Module["_strncmp"] = (a0, a1, a2) => (_strncmp = Module["_strncmp"] = wasmExports["strncmp"])(a0, a1, a2); + var _ts_language_symbol_type = Module["_ts_language_symbol_type"] = (a0, a1) => (_ts_language_symbol_type = Module["_ts_language_symbol_type"] = wasmExports["ts_language_symbol_type"])(a0, a1); + var _ts_language_field_name_for_id = Module["_ts_language_field_name_for_id"] = (a0, a1) => (_ts_language_field_name_for_id = Module["_ts_language_field_name_for_id"] = wasmExports["ts_language_field_name_for_id"])(a0, a1); + var _ts_lookahead_iterator_new = Module["_ts_lookahead_iterator_new"] = (a0, a1) => (_ts_lookahead_iterator_new = Module["_ts_lookahead_iterator_new"] = wasmExports["ts_lookahead_iterator_new"])(a0, a1); + var _ts_lookahead_iterator_delete = Module["_ts_lookahead_iterator_delete"] = a0 => (_ts_lookahead_iterator_delete = Module["_ts_lookahead_iterator_delete"] = wasmExports["ts_lookahead_iterator_delete"])(a0); + var _ts_lookahead_iterator_reset_state = Module["_ts_lookahead_iterator_reset_state"] = (a0, a1) => (_ts_lookahead_iterator_reset_state = Module["_ts_lookahead_iterator_reset_state"] = wasmExports["ts_lookahead_iterator_reset_state"])(a0, a1); + var _ts_lookahead_iterator_reset = Module["_ts_lookahead_iterator_reset"] = (a0, a1, a2) => (_ts_lookahead_iterator_reset = Module["_ts_lookahead_iterator_reset"] = wasmExports["ts_lookahead_iterator_reset"])(a0, a1, a2); + var _ts_lookahead_iterator_next = Module["_ts_lookahead_iterator_next"] = a0 => (_ts_lookahead_iterator_next = Module["_ts_lookahead_iterator_next"] = wasmExports["ts_lookahead_iterator_next"])(a0); + var _ts_lookahead_iterator_current_symbol = Module["_ts_lookahead_iterator_current_symbol"] = a0 => (_ts_lookahead_iterator_current_symbol = Module["_ts_lookahead_iterator_current_symbol"] = wasmExports["ts_lookahead_iterator_current_symbol"])(a0); + var _memset = Module["_memset"] = (a0, a1, a2) => (_memset = Module["_memset"] = wasmExports["memset"])(a0, a1, a2); + var _memcpy = Module["_memcpy"] = (a0, a1, a2) => (_memcpy = Module["_memcpy"] = wasmExports["memcpy"])(a0, a1, a2); + var _ts_parser_delete = Module["_ts_parser_delete"] = a0 => (_ts_parser_delete = Module["_ts_parser_delete"] = wasmExports["ts_parser_delete"])(a0); + var _ts_parser_reset = Module["_ts_parser_reset"] = a0 => (_ts_parser_reset = Module["_ts_parser_reset"] = wasmExports["ts_parser_reset"])(a0); + var _ts_parser_set_language = Module["_ts_parser_set_language"] = (a0, a1) => (_ts_parser_set_language = Module["_ts_parser_set_language"] = wasmExports["ts_parser_set_language"])(a0, a1); + var _ts_parser_timeout_micros = Module["_ts_parser_timeout_micros"] = a0 => (_ts_parser_timeout_micros = Module["_ts_parser_timeout_micros"] = wasmExports["ts_parser_timeout_micros"])(a0); + var _ts_parser_set_timeout_micros = Module["_ts_parser_set_timeout_micros"] = (a0, a1, a2) => (_ts_parser_set_timeout_micros = Module["_ts_parser_set_timeout_micros"] = wasmExports["ts_parser_set_timeout_micros"])(a0, a1, a2); + var _ts_parser_set_included_ranges = Module["_ts_parser_set_included_ranges"] = (a0, a1, a2) => (_ts_parser_set_included_ranges = Module["_ts_parser_set_included_ranges"] = wasmExports["ts_parser_set_included_ranges"])(a0, a1, a2); + var _memmove = Module["_memmove"] = (a0, a1, a2) => (_memmove = Module["_memmove"] = wasmExports["memmove"])(a0, a1, a2); + var _memcmp = Module["_memcmp"] = (a0, a1, a2) => (_memcmp = Module["_memcmp"] = wasmExports["memcmp"])(a0, a1, a2); + var _ts_query_new = Module["_ts_query_new"] = (a0, a1, a2, a3, a4) => (_ts_query_new = Module["_ts_query_new"] = wasmExports["ts_query_new"])(a0, a1, a2, a3, a4); + var _ts_query_delete = Module["_ts_query_delete"] = a0 => (_ts_query_delete = Module["_ts_query_delete"] = wasmExports["ts_query_delete"])(a0); + var _iswspace = Module["_iswspace"] = a0 => (_iswspace = Module["_iswspace"] = wasmExports["iswspace"])(a0); + var _iswalnum = Module["_iswalnum"] = a0 => (_iswalnum = Module["_iswalnum"] = wasmExports["iswalnum"])(a0); + var _ts_query_pattern_count = Module["_ts_query_pattern_count"] = a0 => (_ts_query_pattern_count = Module["_ts_query_pattern_count"] = wasmExports["ts_query_pattern_count"])(a0); + var _ts_query_capture_count = Module["_ts_query_capture_count"] = a0 => (_ts_query_capture_count = Module["_ts_query_capture_count"] = wasmExports["ts_query_capture_count"])(a0); + var _ts_query_string_count = Module["_ts_query_string_count"] = a0 => (_ts_query_string_count = Module["_ts_query_string_count"] = wasmExports["ts_query_string_count"])(a0); + var _ts_query_capture_name_for_id = Module["_ts_query_capture_name_for_id"] = (a0, a1, a2) => (_ts_query_capture_name_for_id = Module["_ts_query_capture_name_for_id"] = wasmExports["ts_query_capture_name_for_id"])(a0, a1, a2); + var _ts_query_string_value_for_id = Module["_ts_query_string_value_for_id"] = (a0, a1, a2) => (_ts_query_string_value_for_id = Module["_ts_query_string_value_for_id"] = wasmExports["ts_query_string_value_for_id"])(a0, a1, a2); + var _ts_query_predicates_for_pattern = Module["_ts_query_predicates_for_pattern"] = (a0, a1, a2) => (_ts_query_predicates_for_pattern = Module["_ts_query_predicates_for_pattern"] = wasmExports["ts_query_predicates_for_pattern"])(a0, a1, a2); + var _ts_query_disable_capture = Module["_ts_query_disable_capture"] = (a0, a1, a2) => (_ts_query_disable_capture = Module["_ts_query_disable_capture"] = wasmExports["ts_query_disable_capture"])(a0, a1, a2); + var _ts_tree_copy = Module["_ts_tree_copy"] = a0 => (_ts_tree_copy = Module["_ts_tree_copy"] = wasmExports["ts_tree_copy"])(a0); + var _ts_tree_delete = Module["_ts_tree_delete"] = a0 => (_ts_tree_delete = Module["_ts_tree_delete"] = wasmExports["ts_tree_delete"])(a0); + var _ts_init = Module["_ts_init"] = () => (_ts_init = Module["_ts_init"] = wasmExports["ts_init"])(); + var _ts_parser_new_wasm = Module["_ts_parser_new_wasm"] = () => (_ts_parser_new_wasm = Module["_ts_parser_new_wasm"] = wasmExports["ts_parser_new_wasm"])(); + var _ts_parser_enable_logger_wasm = Module["_ts_parser_enable_logger_wasm"] = (a0, a1) => (_ts_parser_enable_logger_wasm = Module["_ts_parser_enable_logger_wasm"] = wasmExports["ts_parser_enable_logger_wasm"])(a0, a1); + var _ts_parser_parse_wasm = Module["_ts_parser_parse_wasm"] = (a0, a1, a2, a3, a4) => (_ts_parser_parse_wasm = Module["_ts_parser_parse_wasm"] = wasmExports["ts_parser_parse_wasm"])(a0, a1, a2, a3, a4); + var _ts_parser_included_ranges_wasm = Module["_ts_parser_included_ranges_wasm"] = a0 => (_ts_parser_included_ranges_wasm = Module["_ts_parser_included_ranges_wasm"] = wasmExports["ts_parser_included_ranges_wasm"])(a0); + var _ts_language_type_is_named_wasm = Module["_ts_language_type_is_named_wasm"] = (a0, a1) => (_ts_language_type_is_named_wasm = Module["_ts_language_type_is_named_wasm"] = wasmExports["ts_language_type_is_named_wasm"])(a0, a1); + var _ts_language_type_is_visible_wasm = Module["_ts_language_type_is_visible_wasm"] = (a0, a1) => (_ts_language_type_is_visible_wasm = Module["_ts_language_type_is_visible_wasm"] = wasmExports["ts_language_type_is_visible_wasm"])(a0, a1); + var _ts_tree_root_node_wasm = Module["_ts_tree_root_node_wasm"] = a0 => (_ts_tree_root_node_wasm = Module["_ts_tree_root_node_wasm"] = wasmExports["ts_tree_root_node_wasm"])(a0); + var _ts_tree_root_node_with_offset_wasm = Module["_ts_tree_root_node_with_offset_wasm"] = a0 => (_ts_tree_root_node_with_offset_wasm = Module["_ts_tree_root_node_with_offset_wasm"] = wasmExports["ts_tree_root_node_with_offset_wasm"])(a0); + var _ts_tree_edit_wasm = Module["_ts_tree_edit_wasm"] = a0 => (_ts_tree_edit_wasm = Module["_ts_tree_edit_wasm"] = wasmExports["ts_tree_edit_wasm"])(a0); + var _ts_tree_included_ranges_wasm = Module["_ts_tree_included_ranges_wasm"] = a0 => (_ts_tree_included_ranges_wasm = Module["_ts_tree_included_ranges_wasm"] = wasmExports["ts_tree_included_ranges_wasm"])(a0); + var _ts_tree_get_changed_ranges_wasm = Module["_ts_tree_get_changed_ranges_wasm"] = (a0, a1) => (_ts_tree_get_changed_ranges_wasm = Module["_ts_tree_get_changed_ranges_wasm"] = wasmExports["ts_tree_get_changed_ranges_wasm"])(a0, a1); + var _ts_tree_cursor_new_wasm = Module["_ts_tree_cursor_new_wasm"] = a0 => (_ts_tree_cursor_new_wasm = Module["_ts_tree_cursor_new_wasm"] = wasmExports["ts_tree_cursor_new_wasm"])(a0); + var _ts_tree_cursor_delete_wasm = Module["_ts_tree_cursor_delete_wasm"] = a0 => (_ts_tree_cursor_delete_wasm = Module["_ts_tree_cursor_delete_wasm"] = wasmExports["ts_tree_cursor_delete_wasm"])(a0); + var _ts_tree_cursor_reset_wasm = Module["_ts_tree_cursor_reset_wasm"] = a0 => (_ts_tree_cursor_reset_wasm = Module["_ts_tree_cursor_reset_wasm"] = wasmExports["ts_tree_cursor_reset_wasm"])(a0); + var _ts_tree_cursor_reset_to_wasm = Module["_ts_tree_cursor_reset_to_wasm"] = (a0, a1) => (_ts_tree_cursor_reset_to_wasm = Module["_ts_tree_cursor_reset_to_wasm"] = wasmExports["ts_tree_cursor_reset_to_wasm"])(a0, a1); + var _ts_tree_cursor_goto_first_child_wasm = Module["_ts_tree_cursor_goto_first_child_wasm"] = a0 => (_ts_tree_cursor_goto_first_child_wasm = Module["_ts_tree_cursor_goto_first_child_wasm"] = wasmExports["ts_tree_cursor_goto_first_child_wasm"])(a0); + var _ts_tree_cursor_goto_last_child_wasm = Module["_ts_tree_cursor_goto_last_child_wasm"] = a0 => (_ts_tree_cursor_goto_last_child_wasm = Module["_ts_tree_cursor_goto_last_child_wasm"] = wasmExports["ts_tree_cursor_goto_last_child_wasm"])(a0); + var _ts_tree_cursor_goto_first_child_for_index_wasm = Module["_ts_tree_cursor_goto_first_child_for_index_wasm"] = a0 => (_ts_tree_cursor_goto_first_child_for_index_wasm = Module["_ts_tree_cursor_goto_first_child_for_index_wasm"] = wasmExports["ts_tree_cursor_goto_first_child_for_index_wasm"])(a0); + var _ts_tree_cursor_goto_first_child_for_position_wasm = Module["_ts_tree_cursor_goto_first_child_for_position_wasm"] = a0 => (_ts_tree_cursor_goto_first_child_for_position_wasm = Module["_ts_tree_cursor_goto_first_child_for_position_wasm"] = wasmExports["ts_tree_cursor_goto_first_child_for_position_wasm"])(a0); + var _ts_tree_cursor_goto_next_sibling_wasm = Module["_ts_tree_cursor_goto_next_sibling_wasm"] = a0 => (_ts_tree_cursor_goto_next_sibling_wasm = Module["_ts_tree_cursor_goto_next_sibling_wasm"] = wasmExports["ts_tree_cursor_goto_next_sibling_wasm"])(a0); + var _ts_tree_cursor_goto_previous_sibling_wasm = Module["_ts_tree_cursor_goto_previous_sibling_wasm"] = a0 => (_ts_tree_cursor_goto_previous_sibling_wasm = Module["_ts_tree_cursor_goto_previous_sibling_wasm"] = wasmExports["ts_tree_cursor_goto_previous_sibling_wasm"])(a0); + var _ts_tree_cursor_goto_descendant_wasm = Module["_ts_tree_cursor_goto_descendant_wasm"] = (a0, a1) => (_ts_tree_cursor_goto_descendant_wasm = Module["_ts_tree_cursor_goto_descendant_wasm"] = wasmExports["ts_tree_cursor_goto_descendant_wasm"])(a0, a1); + var _ts_tree_cursor_goto_parent_wasm = Module["_ts_tree_cursor_goto_parent_wasm"] = a0 => (_ts_tree_cursor_goto_parent_wasm = Module["_ts_tree_cursor_goto_parent_wasm"] = wasmExports["ts_tree_cursor_goto_parent_wasm"])(a0); + var _ts_tree_cursor_current_node_type_id_wasm = Module["_ts_tree_cursor_current_node_type_id_wasm"] = a0 => (_ts_tree_cursor_current_node_type_id_wasm = Module["_ts_tree_cursor_current_node_type_id_wasm"] = wasmExports["ts_tree_cursor_current_node_type_id_wasm"])(a0); + var _ts_tree_cursor_current_node_state_id_wasm = Module["_ts_tree_cursor_current_node_state_id_wasm"] = a0 => (_ts_tree_cursor_current_node_state_id_wasm = Module["_ts_tree_cursor_current_node_state_id_wasm"] = wasmExports["ts_tree_cursor_current_node_state_id_wasm"])(a0); + var _ts_tree_cursor_current_node_is_named_wasm = Module["_ts_tree_cursor_current_node_is_named_wasm"] = a0 => (_ts_tree_cursor_current_node_is_named_wasm = Module["_ts_tree_cursor_current_node_is_named_wasm"] = wasmExports["ts_tree_cursor_current_node_is_named_wasm"])(a0); + var _ts_tree_cursor_current_node_is_missing_wasm = Module["_ts_tree_cursor_current_node_is_missing_wasm"] = a0 => (_ts_tree_cursor_current_node_is_missing_wasm = Module["_ts_tree_cursor_current_node_is_missing_wasm"] = wasmExports["ts_tree_cursor_current_node_is_missing_wasm"])(a0); + var _ts_tree_cursor_current_node_id_wasm = Module["_ts_tree_cursor_current_node_id_wasm"] = a0 => (_ts_tree_cursor_current_node_id_wasm = Module["_ts_tree_cursor_current_node_id_wasm"] = wasmExports["ts_tree_cursor_current_node_id_wasm"])(a0); + var _ts_tree_cursor_start_position_wasm = Module["_ts_tree_cursor_start_position_wasm"] = a0 => (_ts_tree_cursor_start_position_wasm = Module["_ts_tree_cursor_start_position_wasm"] = wasmExports["ts_tree_cursor_start_position_wasm"])(a0); + var _ts_tree_cursor_end_position_wasm = Module["_ts_tree_cursor_end_position_wasm"] = a0 => (_ts_tree_cursor_end_position_wasm = Module["_ts_tree_cursor_end_position_wasm"] = wasmExports["ts_tree_cursor_end_position_wasm"])(a0); + var _ts_tree_cursor_start_index_wasm = Module["_ts_tree_cursor_start_index_wasm"] = a0 => (_ts_tree_cursor_start_index_wasm = Module["_ts_tree_cursor_start_index_wasm"] = wasmExports["ts_tree_cursor_start_index_wasm"])(a0); + var _ts_tree_cursor_end_index_wasm = Module["_ts_tree_cursor_end_index_wasm"] = a0 => (_ts_tree_cursor_end_index_wasm = Module["_ts_tree_cursor_end_index_wasm"] = wasmExports["ts_tree_cursor_end_index_wasm"])(a0); + var _ts_tree_cursor_current_field_id_wasm = Module["_ts_tree_cursor_current_field_id_wasm"] = a0 => (_ts_tree_cursor_current_field_id_wasm = Module["_ts_tree_cursor_current_field_id_wasm"] = wasmExports["ts_tree_cursor_current_field_id_wasm"])(a0); + var _ts_tree_cursor_current_depth_wasm = Module["_ts_tree_cursor_current_depth_wasm"] = a0 => (_ts_tree_cursor_current_depth_wasm = Module["_ts_tree_cursor_current_depth_wasm"] = wasmExports["ts_tree_cursor_current_depth_wasm"])(a0); + var _ts_tree_cursor_current_descendant_index_wasm = Module["_ts_tree_cursor_current_descendant_index_wasm"] = a0 => (_ts_tree_cursor_current_descendant_index_wasm = Module["_ts_tree_cursor_current_descendant_index_wasm"] = wasmExports["ts_tree_cursor_current_descendant_index_wasm"])(a0); + var _ts_tree_cursor_current_node_wasm = Module["_ts_tree_cursor_current_node_wasm"] = a0 => (_ts_tree_cursor_current_node_wasm = Module["_ts_tree_cursor_current_node_wasm"] = wasmExports["ts_tree_cursor_current_node_wasm"])(a0); + var _ts_node_symbol_wasm = Module["_ts_node_symbol_wasm"] = a0 => (_ts_node_symbol_wasm = Module["_ts_node_symbol_wasm"] = wasmExports["ts_node_symbol_wasm"])(a0); + var _ts_node_field_name_for_child_wasm = Module["_ts_node_field_name_for_child_wasm"] = (a0, a1) => (_ts_node_field_name_for_child_wasm = Module["_ts_node_field_name_for_child_wasm"] = wasmExports["ts_node_field_name_for_child_wasm"])(a0, a1); + var _ts_node_children_by_field_id_wasm = Module["_ts_node_children_by_field_id_wasm"] = (a0, a1) => (_ts_node_children_by_field_id_wasm = Module["_ts_node_children_by_field_id_wasm"] = wasmExports["ts_node_children_by_field_id_wasm"])(a0, a1); + var _ts_node_first_child_for_byte_wasm = Module["_ts_node_first_child_for_byte_wasm"] = a0 => (_ts_node_first_child_for_byte_wasm = Module["_ts_node_first_child_for_byte_wasm"] = wasmExports["ts_node_first_child_for_byte_wasm"])(a0); + var _ts_node_first_named_child_for_byte_wasm = Module["_ts_node_first_named_child_for_byte_wasm"] = a0 => (_ts_node_first_named_child_for_byte_wasm = Module["_ts_node_first_named_child_for_byte_wasm"] = wasmExports["ts_node_first_named_child_for_byte_wasm"])(a0); + var _ts_node_grammar_symbol_wasm = Module["_ts_node_grammar_symbol_wasm"] = a0 => (_ts_node_grammar_symbol_wasm = Module["_ts_node_grammar_symbol_wasm"] = wasmExports["ts_node_grammar_symbol_wasm"])(a0); + var _ts_node_child_count_wasm = Module["_ts_node_child_count_wasm"] = a0 => (_ts_node_child_count_wasm = Module["_ts_node_child_count_wasm"] = wasmExports["ts_node_child_count_wasm"])(a0); + var _ts_node_named_child_count_wasm = Module["_ts_node_named_child_count_wasm"] = a0 => (_ts_node_named_child_count_wasm = Module["_ts_node_named_child_count_wasm"] = wasmExports["ts_node_named_child_count_wasm"])(a0); + var _ts_node_child_wasm = Module["_ts_node_child_wasm"] = (a0, a1) => (_ts_node_child_wasm = Module["_ts_node_child_wasm"] = wasmExports["ts_node_child_wasm"])(a0, a1); + var _ts_node_named_child_wasm = Module["_ts_node_named_child_wasm"] = (a0, a1) => (_ts_node_named_child_wasm = Module["_ts_node_named_child_wasm"] = wasmExports["ts_node_named_child_wasm"])(a0, a1); + var _ts_node_child_by_field_id_wasm = Module["_ts_node_child_by_field_id_wasm"] = (a0, a1) => (_ts_node_child_by_field_id_wasm = Module["_ts_node_child_by_field_id_wasm"] = wasmExports["ts_node_child_by_field_id_wasm"])(a0, a1); + var _ts_node_next_sibling_wasm = Module["_ts_node_next_sibling_wasm"] = a0 => (_ts_node_next_sibling_wasm = Module["_ts_node_next_sibling_wasm"] = wasmExports["ts_node_next_sibling_wasm"])(a0); + var _ts_node_prev_sibling_wasm = Module["_ts_node_prev_sibling_wasm"] = a0 => (_ts_node_prev_sibling_wasm = Module["_ts_node_prev_sibling_wasm"] = wasmExports["ts_node_prev_sibling_wasm"])(a0); + var _ts_node_next_named_sibling_wasm = Module["_ts_node_next_named_sibling_wasm"] = a0 => (_ts_node_next_named_sibling_wasm = Module["_ts_node_next_named_sibling_wasm"] = wasmExports["ts_node_next_named_sibling_wasm"])(a0); + var _ts_node_prev_named_sibling_wasm = Module["_ts_node_prev_named_sibling_wasm"] = a0 => (_ts_node_prev_named_sibling_wasm = Module["_ts_node_prev_named_sibling_wasm"] = wasmExports["ts_node_prev_named_sibling_wasm"])(a0); + var _ts_node_descendant_count_wasm = Module["_ts_node_descendant_count_wasm"] = a0 => (_ts_node_descendant_count_wasm = Module["_ts_node_descendant_count_wasm"] = wasmExports["ts_node_descendant_count_wasm"])(a0); + var _ts_node_parent_wasm = Module["_ts_node_parent_wasm"] = a0 => (_ts_node_parent_wasm = Module["_ts_node_parent_wasm"] = wasmExports["ts_node_parent_wasm"])(a0); + var _ts_node_descendant_for_index_wasm = Module["_ts_node_descendant_for_index_wasm"] = a0 => (_ts_node_descendant_for_index_wasm = Module["_ts_node_descendant_for_index_wasm"] = wasmExports["ts_node_descendant_for_index_wasm"])(a0); + var _ts_node_named_descendant_for_index_wasm = Module["_ts_node_named_descendant_for_index_wasm"] = a0 => (_ts_node_named_descendant_for_index_wasm = Module["_ts_node_named_descendant_for_index_wasm"] = wasmExports["ts_node_named_descendant_for_index_wasm"])(a0); + var _ts_node_descendant_for_position_wasm = Module["_ts_node_descendant_for_position_wasm"] = a0 => (_ts_node_descendant_for_position_wasm = Module["_ts_node_descendant_for_position_wasm"] = wasmExports["ts_node_descendant_for_position_wasm"])(a0); + var _ts_node_named_descendant_for_position_wasm = Module["_ts_node_named_descendant_for_position_wasm"] = a0 => (_ts_node_named_descendant_for_position_wasm = Module["_ts_node_named_descendant_for_position_wasm"] = wasmExports["ts_node_named_descendant_for_position_wasm"])(a0); + var _ts_node_start_point_wasm = Module["_ts_node_start_point_wasm"] = a0 => (_ts_node_start_point_wasm = Module["_ts_node_start_point_wasm"] = wasmExports["ts_node_start_point_wasm"])(a0); + var _ts_node_end_point_wasm = Module["_ts_node_end_point_wasm"] = a0 => (_ts_node_end_point_wasm = Module["_ts_node_end_point_wasm"] = wasmExports["ts_node_end_point_wasm"])(a0); + var _ts_node_start_index_wasm = Module["_ts_node_start_index_wasm"] = a0 => (_ts_node_start_index_wasm = Module["_ts_node_start_index_wasm"] = wasmExports["ts_node_start_index_wasm"])(a0); + var _ts_node_end_index_wasm = Module["_ts_node_end_index_wasm"] = a0 => (_ts_node_end_index_wasm = Module["_ts_node_end_index_wasm"] = wasmExports["ts_node_end_index_wasm"])(a0); + var _ts_node_to_string_wasm = Module["_ts_node_to_string_wasm"] = a0 => (_ts_node_to_string_wasm = Module["_ts_node_to_string_wasm"] = wasmExports["ts_node_to_string_wasm"])(a0); + var _ts_node_children_wasm = Module["_ts_node_children_wasm"] = a0 => (_ts_node_children_wasm = Module["_ts_node_children_wasm"] = wasmExports["ts_node_children_wasm"])(a0); + var _ts_node_named_children_wasm = Module["_ts_node_named_children_wasm"] = a0 => (_ts_node_named_children_wasm = Module["_ts_node_named_children_wasm"] = wasmExports["ts_node_named_children_wasm"])(a0); + var _ts_node_descendants_of_type_wasm = Module["_ts_node_descendants_of_type_wasm"] = (a0, a1, a2, a3, a4, a5, a6) => (_ts_node_descendants_of_type_wasm = Module["_ts_node_descendants_of_type_wasm"] = wasmExports["ts_node_descendants_of_type_wasm"])(a0, a1, a2, a3, a4, a5, a6); + var _ts_node_is_named_wasm = Module["_ts_node_is_named_wasm"] = a0 => (_ts_node_is_named_wasm = Module["_ts_node_is_named_wasm"] = wasmExports["ts_node_is_named_wasm"])(a0); + var _ts_node_has_changes_wasm = Module["_ts_node_has_changes_wasm"] = a0 => (_ts_node_has_changes_wasm = Module["_ts_node_has_changes_wasm"] = wasmExports["ts_node_has_changes_wasm"])(a0); + var _ts_node_has_error_wasm = Module["_ts_node_has_error_wasm"] = a0 => (_ts_node_has_error_wasm = Module["_ts_node_has_error_wasm"] = wasmExports["ts_node_has_error_wasm"])(a0); + var _ts_node_is_error_wasm = Module["_ts_node_is_error_wasm"] = a0 => (_ts_node_is_error_wasm = Module["_ts_node_is_error_wasm"] = wasmExports["ts_node_is_error_wasm"])(a0); + var _ts_node_is_missing_wasm = Module["_ts_node_is_missing_wasm"] = a0 => (_ts_node_is_missing_wasm = Module["_ts_node_is_missing_wasm"] = wasmExports["ts_node_is_missing_wasm"])(a0); + var _ts_node_is_extra_wasm = Module["_ts_node_is_extra_wasm"] = a0 => (_ts_node_is_extra_wasm = Module["_ts_node_is_extra_wasm"] = wasmExports["ts_node_is_extra_wasm"])(a0); + var _ts_node_parse_state_wasm = Module["_ts_node_parse_state_wasm"] = a0 => (_ts_node_parse_state_wasm = Module["_ts_node_parse_state_wasm"] = wasmExports["ts_node_parse_state_wasm"])(a0); + var _ts_node_next_parse_state_wasm = Module["_ts_node_next_parse_state_wasm"] = a0 => (_ts_node_next_parse_state_wasm = Module["_ts_node_next_parse_state_wasm"] = wasmExports["ts_node_next_parse_state_wasm"])(a0); + var _ts_query_matches_wasm = Module["_ts_query_matches_wasm"] = (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) => (_ts_query_matches_wasm = Module["_ts_query_matches_wasm"] = wasmExports["ts_query_matches_wasm"])(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); + var _ts_query_captures_wasm = Module["_ts_query_captures_wasm"] = (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) => (_ts_query_captures_wasm = Module["_ts_query_captures_wasm"] = wasmExports["ts_query_captures_wasm"])(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); + var _isalnum = Module["_isalnum"] = a0 => (_isalnum = Module["_isalnum"] = wasmExports["isalnum"])(a0); + var _iswalpha = Module["_iswalpha"] = a0 => (_iswalpha = Module["_iswalpha"] = wasmExports["iswalpha"])(a0); + var _iswblank = Module["_iswblank"] = a0 => (_iswblank = Module["_iswblank"] = wasmExports["iswblank"])(a0); + var _iswdigit = Module["_iswdigit"] = a0 => (_iswdigit = Module["_iswdigit"] = wasmExports["iswdigit"])(a0); + var _iswlower = Module["_iswlower"] = a0 => (_iswlower = Module["_iswlower"] = wasmExports["iswlower"])(a0); + var _iswupper = Module["_iswupper"] = a0 => (_iswupper = Module["_iswupper"] = wasmExports["iswupper"])(a0); + var _iswxdigit = Module["_iswxdigit"] = a0 => (_iswxdigit = Module["_iswxdigit"] = wasmExports["iswxdigit"])(a0); + var _isxdigit = Module["_isxdigit"] = a0 => (_isxdigit = Module["_isxdigit"] = wasmExports["isxdigit"])(a0); + var _memchr = Module["_memchr"] = (a0, a1, a2) => (_memchr = Module["_memchr"] = wasmExports["memchr"])(a0, a1, a2); + var _strlen = Module["_strlen"] = a0 => (_strlen = Module["_strlen"] = wasmExports["strlen"])(a0); + var _strcmp = Module["_strcmp"] = (a0, a1) => (_strcmp = Module["_strcmp"] = wasmExports["strcmp"])(a0, a1); + var _strncat = Module["_strncat"] = (a0, a1, a2) => (_strncat = Module["_strncat"] = wasmExports["strncat"])(a0, a1, a2); + var _strncpy = Module["_strncpy"] = (a0, a1, a2) => (_strncpy = Module["_strncpy"] = wasmExports["strncpy"])(a0, a1, a2); + var _towlower = Module["_towlower"] = a0 => (_towlower = Module["_towlower"] = wasmExports["towlower"])(a0); + var _towupper = Module["_towupper"] = a0 => (_towupper = Module["_towupper"] = wasmExports["towupper"])(a0); + var _setThrew = (a0, a1) => (_setThrew = wasmExports["setThrew"])(a0, a1); + var __emscripten_stack_restore = a0 => (__emscripten_stack_restore = wasmExports["_emscripten_stack_restore"])(a0); + var __emscripten_stack_alloc = a0 => (__emscripten_stack_alloc = wasmExports["_emscripten_stack_alloc"])(a0); + var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports["emscripten_stack_get_current"])(); + var dynCall_jiji = Module["dynCall_jiji"] = (a0, a1, a2, a3, a4) => (dynCall_jiji = Module["dynCall_jiji"] = wasmExports["dynCall_jiji"])(a0, a1, a2, a3, a4); + var _orig$ts_parser_timeout_micros = Module["_orig$ts_parser_timeout_micros"] = a0 => (_orig$ts_parser_timeout_micros = Module["_orig$ts_parser_timeout_micros"] = wasmExports["orig$ts_parser_timeout_micros"])(a0); + var _orig$ts_parser_set_timeout_micros = Module["_orig$ts_parser_set_timeout_micros"] = (a0, a1) => (_orig$ts_parser_set_timeout_micros = Module["_orig$ts_parser_set_timeout_micros"] = wasmExports["orig$ts_parser_set_timeout_micros"])(a0, a1); + // include: postamble.js + // === Auto-generated postamble setup entry stuff === + Module["AsciiToString"] = AsciiToString; + Module["stringToUTF16"] = stringToUTF16; + var calledRun; + dependenciesFulfilled = function runCaller() { + // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false) + if (!calledRun) run(); + if (!calledRun) dependenciesFulfilled = runCaller; + }; + // try this again later, after new deps are fulfilled + function callMain(args = []) { + var entryFunction = resolveGlobalSymbol("main").sym; + // Main modules can't tell if they have main() at compile time, since it may + // arrive from a dynamic library. + if (!entryFunction) return; + args.unshift(thisProgram); + var argc = args.length; + var argv = stackAlloc((argc + 1) * 4); + var argv_ptr = argv; + args.forEach(arg => { + LE_HEAP_STORE_U32(((argv_ptr) >> 2) * 4, stringToUTF8OnStack(arg)); + argv_ptr += 4; + }); + LE_HEAP_STORE_U32(((argv_ptr) >> 2) * 4, 0); + try { + var ret = entryFunction(argc, argv); + // if we're not running an evented main loop, it's time to exit + exitJS(ret, /* implicit = */ true); + return ret; + } catch (e) { + return handleException(e); + } + } + function run(args = arguments_) { + if (runDependencies > 0) { + return; + } + preRun(); + // a preRun added a dependency, run will be called later + if (runDependencies > 0) { + return; + } + function doRun() { + // run may have just been called through dependencies being fulfilled just in this very frame, + // or while the async setStatus time below was happening + if (calledRun) return; + calledRun = true; + Module["calledRun"] = true; + if (ABORT) return; + initRuntime(); + preMain(); + Module["onRuntimeInitialized"]?.(); + if (shouldRunNow) callMain(args); + postRun(); + } + if (Module["setStatus"]) { + Module["setStatus"]("Running..."); + setTimeout(function() { + setTimeout(function() { + Module["setStatus"](""); + }, 1); + doRun(); + }, 1); + } else { + doRun(); + } + } + if (Module["preInit"]) { + if (typeof Module["preInit"] == "function") Module["preInit"] = [ Module["preInit"] ]; + while (Module["preInit"].length > 0) { + Module["preInit"].pop()(); + } + } + // shouldRunNow refers to calling main(), not run(). + var shouldRunNow = true; + if (Module["noInitialRun"]) shouldRunNow = false; + run(); + // end include: postamble.js + // include: /Users/andrew/Code/tree-sitter/tree-sitter/lib/binding_web/binding.js + /* eslint-disable-next-line spaced-comment */ /// + /* eslint-disable-next-line spaced-comment */ /// + const C = Module; + const INTERNAL = {}; + const SIZE_OF_INT = 4; + const SIZE_OF_CURSOR = 4 * SIZE_OF_INT; + const SIZE_OF_NODE = 5 * SIZE_OF_INT; + const SIZE_OF_POINT = 2 * SIZE_OF_INT; + const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT; + const ZERO_POINT = { + row: 0, + column: 0 + }; + const QUERY_WORD_REGEX = /[\w-.]*/g; + const PREDICATE_STEP_TYPE_CAPTURE = 1; + const PREDICATE_STEP_TYPE_STRING = 2; + const LANGUAGE_FUNCTION_REGEX = /^_?tree_sitter_\w+/; + let VERSION; + let MIN_COMPATIBLE_VERSION; + let TRANSFER_BUFFER; + let currentParseCallback; + // eslint-disable-next-line no-unused-vars + let currentLogCallback; + // eslint-disable-next-line no-unused-vars + class ParserImpl { + static init() { + TRANSFER_BUFFER = C._ts_init(); + VERSION = getValue(TRANSFER_BUFFER, "i32"); + MIN_COMPATIBLE_VERSION = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + } + initialize() { + C._ts_parser_new_wasm(); + this[0] = getValue(TRANSFER_BUFFER, "i32"); + this[1] = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + } + delete() { + C._ts_parser_delete(this[0]); + C._free(this[1]); + this[0] = 0; + this[1] = 0; + } + setLanguage(language) { + let address; + if (!language) { + address = 0; + language = null; + } else if (language.constructor === Language) { + address = language[0]; + const version = C._ts_language_version(address); + if (version < MIN_COMPATIBLE_VERSION || VERSION < version) { + throw new Error(`Incompatible language version ${version}. ` + `Compatibility range ${MIN_COMPATIBLE_VERSION} through ${VERSION}.`); + } + } else { + throw new Error("Argument must be a Language"); + } + this.language = language; + C._ts_parser_set_language(this[0], address); + return this; + } + getLanguage() { + return this.language; + } + parse(callback, oldTree, options) { + if (typeof callback === "string") { + currentParseCallback = (index, _) => callback.slice(index); + } else if (typeof callback === "function") { + currentParseCallback = callback; + } else { + throw new Error("Argument must be a string or a function"); + } + if (this.logCallback) { + currentLogCallback = this.logCallback; + C._ts_parser_enable_logger_wasm(this[0], 1); + } else { + currentLogCallback = null; + C._ts_parser_enable_logger_wasm(this[0], 0); + } + let rangeCount = 0; + let rangeAddress = 0; + if (options?.includedRanges) { + rangeCount = options.includedRanges.length; + rangeAddress = C._calloc(rangeCount, SIZE_OF_RANGE); + let address = rangeAddress; + for (let i = 0; i < rangeCount; i++) { + marshalRange(address, options.includedRanges[i]); + address += SIZE_OF_RANGE; + } + } + const treeAddress = C._ts_parser_parse_wasm(this[0], this[1], oldTree ? oldTree[0] : 0, rangeAddress, rangeCount); + if (!treeAddress) { + currentParseCallback = null; + currentLogCallback = null; + throw new Error("Parsing failed"); + } + const result = new Tree(INTERNAL, treeAddress, this.language, currentParseCallback); + currentParseCallback = null; + currentLogCallback = null; + return result; + } + reset() { + C._ts_parser_reset(this[0]); + } + getIncludedRanges() { + C._ts_parser_included_ranges_wasm(this[0]); + const count = getValue(TRANSFER_BUFFER, "i32"); + const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + const result = new Array(count); + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = unmarshalRange(address); + address += SIZE_OF_RANGE; + } + C._free(buffer); + } + return result; + } + getTimeoutMicros() { + return C._ts_parser_timeout_micros(this[0]); + } + setTimeoutMicros(timeout) { + C._ts_parser_set_timeout_micros(this[0], timeout); + } + setLogger(callback) { + if (!callback) { + callback = null; + } else if (typeof callback !== "function") { + throw new Error("Logger callback must be a function"); + } + this.logCallback = callback; + return this; + } + getLogger() { + return this.logCallback; + } + } + class Tree { + constructor(internal, address, language, textCallback) { + assertInternal(internal); + this[0] = address; + this.language = language; + this.textCallback = textCallback; + } + copy() { + const address = C._ts_tree_copy(this[0]); + return new Tree(INTERNAL, address, this.language, this.textCallback); + } + delete() { + C._ts_tree_delete(this[0]); + this[0] = 0; + } + edit(edit) { + marshalEdit(edit); + C._ts_tree_edit_wasm(this[0]); + } + get rootNode() { + C._ts_tree_root_node_wasm(this[0]); + return unmarshalNode(this); + } + rootNodeWithOffset(offsetBytes, offsetExtent) { + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, offsetBytes, "i32"); + marshalPoint(address + SIZE_OF_INT, offsetExtent); + C._ts_tree_root_node_with_offset_wasm(this[0]); + return unmarshalNode(this); + } + getLanguage() { + return this.language; + } + walk() { + return this.rootNode.walk(); + } + getChangedRanges(other) { + if (other.constructor !== Tree) { + throw new TypeError("Argument must be a Tree"); + } + C._ts_tree_get_changed_ranges_wasm(this[0], other[0]); + const count = getValue(TRANSFER_BUFFER, "i32"); + const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + const result = new Array(count); + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = unmarshalRange(address); + address += SIZE_OF_RANGE; + } + C._free(buffer); + } + return result; + } + getIncludedRanges() { + C._ts_tree_included_ranges_wasm(this[0]); + const count = getValue(TRANSFER_BUFFER, "i32"); + const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + const result = new Array(count); + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = unmarshalRange(address); + address += SIZE_OF_RANGE; + } + C._free(buffer); + } + return result; + } + } + class Node { + constructor(internal, tree) { + assertInternal(internal); + this.tree = tree; + } + get typeId() { + marshalNode(this); + return C._ts_node_symbol_wasm(this.tree[0]); + } + get grammarId() { + marshalNode(this); + return C._ts_node_grammar_symbol_wasm(this.tree[0]); + } + get type() { + return this.tree.language.types[this.typeId] || "ERROR"; + } + get grammarType() { + return this.tree.language.types[this.grammarId] || "ERROR"; + } + get endPosition() { + marshalNode(this); + C._ts_node_end_point_wasm(this.tree[0]); + return unmarshalPoint(TRANSFER_BUFFER); + } + get endIndex() { + marshalNode(this); + return C._ts_node_end_index_wasm(this.tree[0]); + } + get text() { + return getText(this.tree, this.startIndex, this.endIndex); + } + get parseState() { + marshalNode(this); + return C._ts_node_parse_state_wasm(this.tree[0]); + } + get nextParseState() { + marshalNode(this); + return C._ts_node_next_parse_state_wasm(this.tree[0]); + } + get isNamed() { + marshalNode(this); + return C._ts_node_is_named_wasm(this.tree[0]) === 1; + } + get hasError() { + marshalNode(this); + return C._ts_node_has_error_wasm(this.tree[0]) === 1; + } + get hasChanges() { + marshalNode(this); + return C._ts_node_has_changes_wasm(this.tree[0]) === 1; + } + get isError() { + marshalNode(this); + return C._ts_node_is_error_wasm(this.tree[0]) === 1; + } + get isMissing() { + marshalNode(this); + return C._ts_node_is_missing_wasm(this.tree[0]) === 1; + } + get isExtra() { + marshalNode(this); + return C._ts_node_is_extra_wasm(this.tree[0]) === 1; + } + equals(other) { + return this.id === other.id; + } + child(index) { + marshalNode(this); + C._ts_node_child_wasm(this.tree[0], index); + return unmarshalNode(this.tree); + } + namedChild(index) { + marshalNode(this); + C._ts_node_named_child_wasm(this.tree[0], index); + return unmarshalNode(this.tree); + } + childForFieldId(fieldId) { + marshalNode(this); + C._ts_node_child_by_field_id_wasm(this.tree[0], fieldId); + return unmarshalNode(this.tree); + } + childForFieldName(fieldName) { + const fieldId = this.tree.language.fields.indexOf(fieldName); + if (fieldId !== -1) return this.childForFieldId(fieldId); + return null; + } + fieldNameForChild(index) { + marshalNode(this); + const address = C._ts_node_field_name_for_child_wasm(this.tree[0], index); + if (!address) { + return null; + } + const result = AsciiToString(address); + // must not free, the string memory is owned by the language + return result; + } + childrenForFieldName(fieldName) { + const fieldId = this.tree.language.fields.indexOf(fieldName); + if (fieldId !== -1 && fieldId !== 0) return this.childrenForFieldId(fieldId); + return []; + } + childrenForFieldId(fieldId) { + marshalNode(this); + C._ts_node_children_by_field_id_wasm(this.tree[0], fieldId); + const count = getValue(TRANSFER_BUFFER, "i32"); + const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + const result = new Array(count); + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + result[i] = unmarshalNode(this.tree, address); + address += SIZE_OF_NODE; + } + C._free(buffer); + } + return result; + } + firstChildForIndex(index) { + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, index, "i32"); + C._ts_node_first_child_for_byte_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + firstNamedChildForIndex(index) { + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, index, "i32"); + C._ts_node_first_named_child_for_byte_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + get childCount() { + marshalNode(this); + return C._ts_node_child_count_wasm(this.tree[0]); + } + get namedChildCount() { + marshalNode(this); + return C._ts_node_named_child_count_wasm(this.tree[0]); + } + get firstChild() { + return this.child(0); + } + get firstNamedChild() { + return this.namedChild(0); + } + get lastChild() { + return this.child(this.childCount - 1); + } + get lastNamedChild() { + return this.namedChild(this.namedChildCount - 1); + } + get children() { + if (!this._children) { + marshalNode(this); + C._ts_node_children_wasm(this.tree[0]); + const count = getValue(TRANSFER_BUFFER, "i32"); + const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + this._children = new Array(count); + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + this._children[i] = unmarshalNode(this.tree, address); + address += SIZE_OF_NODE; + } + C._free(buffer); + } + } + return this._children; + } + get namedChildren() { + if (!this._namedChildren) { + marshalNode(this); + C._ts_node_named_children_wasm(this.tree[0]); + const count = getValue(TRANSFER_BUFFER, "i32"); + const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + this._namedChildren = new Array(count); + if (count > 0) { + let address = buffer; + for (let i = 0; i < count; i++) { + this._namedChildren[i] = unmarshalNode(this.tree, address); + address += SIZE_OF_NODE; + } + C._free(buffer); + } + } + return this._namedChildren; + } + descendantsOfType(types, startPosition, endPosition) { + if (!Array.isArray(types)) types = [ types ]; + if (!startPosition) startPosition = ZERO_POINT; + if (!endPosition) endPosition = ZERO_POINT; + // Convert the type strings to numeric type symbols. + const symbols = []; + const typesBySymbol = this.tree.language.types; + for (let i = 0, n = typesBySymbol.length; i < n; i++) { + if (types.includes(typesBySymbol[i])) { + symbols.push(i); + } + } + // Copy the array of symbols to the WASM heap. + const symbolsAddress = C._malloc(SIZE_OF_INT * symbols.length); + for (let i = 0, n = symbols.length; i < n; i++) { + setValue(symbolsAddress + i * SIZE_OF_INT, symbols[i], "i32"); + } + // Call the C API to compute the descendants. + marshalNode(this); + C._ts_node_descendants_of_type_wasm(this.tree[0], symbolsAddress, symbols.length, startPosition.row, startPosition.column, endPosition.row, endPosition.column); + // Instantiate the nodes based on the data returned. + const descendantCount = getValue(TRANSFER_BUFFER, "i32"); + const descendantAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + const result = new Array(descendantCount); + if (descendantCount > 0) { + let address = descendantAddress; + for (let i = 0; i < descendantCount; i++) { + result[i] = unmarshalNode(this.tree, address); + address += SIZE_OF_NODE; + } + } + // Free the intermediate buffers + C._free(descendantAddress); + C._free(symbolsAddress); + return result; + } + get nextSibling() { + marshalNode(this); + C._ts_node_next_sibling_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + get previousSibling() { + marshalNode(this); + C._ts_node_prev_sibling_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + get nextNamedSibling() { + marshalNode(this); + C._ts_node_next_named_sibling_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + get previousNamedSibling() { + marshalNode(this); + C._ts_node_prev_named_sibling_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + get descendantCount() { + marshalNode(this); + return C._ts_node_descendant_count_wasm(this.tree[0]); + } + get parent() { + marshalNode(this); + C._ts_node_parent_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + descendantForIndex(start, end = start) { + if (typeof start !== "number" || typeof end !== "number") { + throw new Error("Arguments must be numbers"); + } + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, start, "i32"); + setValue(address + SIZE_OF_INT, end, "i32"); + C._ts_node_descendant_for_index_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + namedDescendantForIndex(start, end = start) { + if (typeof start !== "number" || typeof end !== "number") { + throw new Error("Arguments must be numbers"); + } + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + setValue(address, start, "i32"); + setValue(address + SIZE_OF_INT, end, "i32"); + C._ts_node_named_descendant_for_index_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + descendantForPosition(start, end = start) { + if (!isPoint(start) || !isPoint(end)) { + throw new Error("Arguments must be {row, column} objects"); + } + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + marshalPoint(address, start); + marshalPoint(address + SIZE_OF_POINT, end); + C._ts_node_descendant_for_position_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + namedDescendantForPosition(start, end = start) { + if (!isPoint(start) || !isPoint(end)) { + throw new Error("Arguments must be {row, column} objects"); + } + marshalNode(this); + const address = TRANSFER_BUFFER + SIZE_OF_NODE; + marshalPoint(address, start); + marshalPoint(address + SIZE_OF_POINT, end); + C._ts_node_named_descendant_for_position_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + walk() { + marshalNode(this); + C._ts_tree_cursor_new_wasm(this.tree[0]); + return new TreeCursor(INTERNAL, this.tree); + } + toString() { + marshalNode(this); + const address = C._ts_node_to_string_wasm(this.tree[0]); + const result = AsciiToString(address); + C._free(address); + return result; + } + } + class TreeCursor { + constructor(internal, tree) { + assertInternal(internal); + this.tree = tree; + unmarshalTreeCursor(this); + } + delete() { + marshalTreeCursor(this); + C._ts_tree_cursor_delete_wasm(this.tree[0]); + this[0] = this[1] = this[2] = 0; + } + reset(node) { + marshalNode(node); + marshalTreeCursor(this, TRANSFER_BUFFER + SIZE_OF_NODE); + C._ts_tree_cursor_reset_wasm(this.tree[0]); + unmarshalTreeCursor(this); + } + resetTo(cursor) { + marshalTreeCursor(this, TRANSFER_BUFFER); + marshalTreeCursor(cursor, TRANSFER_BUFFER + SIZE_OF_CURSOR); + C._ts_tree_cursor_reset_to_wasm(this.tree[0], cursor.tree[0]); + unmarshalTreeCursor(this); + } + get nodeType() { + return this.tree.language.types[this.nodeTypeId] || "ERROR"; + } + get nodeTypeId() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_type_id_wasm(this.tree[0]); + } + get nodeStateId() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_state_id_wasm(this.tree[0]); + } + get nodeId() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_id_wasm(this.tree[0]); + } + get nodeIsNamed() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_is_named_wasm(this.tree[0]) === 1; + } + get nodeIsMissing() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_node_is_missing_wasm(this.tree[0]) === 1; + } + get nodeText() { + marshalTreeCursor(this); + const startIndex = C._ts_tree_cursor_start_index_wasm(this.tree[0]); + const endIndex = C._ts_tree_cursor_end_index_wasm(this.tree[0]); + return getText(this.tree, startIndex, endIndex); + } + get startPosition() { + marshalTreeCursor(this); + C._ts_tree_cursor_start_position_wasm(this.tree[0]); + return unmarshalPoint(TRANSFER_BUFFER); + } + get endPosition() { + marshalTreeCursor(this); + C._ts_tree_cursor_end_position_wasm(this.tree[0]); + return unmarshalPoint(TRANSFER_BUFFER); + } + get startIndex() { + marshalTreeCursor(this); + return C._ts_tree_cursor_start_index_wasm(this.tree[0]); + } + get endIndex() { + marshalTreeCursor(this); + return C._ts_tree_cursor_end_index_wasm(this.tree[0]); + } + get currentNode() { + marshalTreeCursor(this); + C._ts_tree_cursor_current_node_wasm(this.tree[0]); + return unmarshalNode(this.tree); + } + get currentFieldId() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_field_id_wasm(this.tree[0]); + } + get currentFieldName() { + return this.tree.language.fields[this.currentFieldId]; + } + get currentDepth() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_depth_wasm(this.tree[0]); + } + get currentDescendantIndex() { + marshalTreeCursor(this); + return C._ts_tree_cursor_current_descendant_index_wasm(this.tree[0]); + } + gotoFirstChild() { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_first_child_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + gotoLastChild() { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_last_child_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + gotoFirstChildForIndex(goalIndex) { + marshalTreeCursor(this); + setValue(TRANSFER_BUFFER + SIZE_OF_CURSOR, goalIndex, "i32"); + const result = C._ts_tree_cursor_goto_first_child_for_index_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + gotoFirstChildForPosition(goalPosition) { + marshalTreeCursor(this); + marshalPoint(TRANSFER_BUFFER + SIZE_OF_CURSOR, goalPosition); + const result = C._ts_tree_cursor_goto_first_child_for_position_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + gotoNextSibling() { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_next_sibling_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + gotoPreviousSibling() { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_previous_sibling_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + gotoDescendant(goalDescendantindex) { + marshalTreeCursor(this); + C._ts_tree_cursor_goto_descendant_wasm(this.tree[0], goalDescendantindex); + unmarshalTreeCursor(this); + } + gotoParent() { + marshalTreeCursor(this); + const result = C._ts_tree_cursor_goto_parent_wasm(this.tree[0]); + unmarshalTreeCursor(this); + return result === 1; + } + } + class Language { + constructor(internal, address) { + assertInternal(internal); + this[0] = address; + this.types = new Array(C._ts_language_symbol_count(this[0])); + for (let i = 0, n = this.types.length; i < n; i++) { + if (C._ts_language_symbol_type(this[0], i) < 2) { + this.types[i] = UTF8ToString(C._ts_language_symbol_name(this[0], i)); + } + } + this.fields = new Array(C._ts_language_field_count(this[0]) + 1); + for (let i = 0, n = this.fields.length; i < n; i++) { + const fieldName = C._ts_language_field_name_for_id(this[0], i); + if (fieldName !== 0) { + this.fields[i] = UTF8ToString(fieldName); + } else { + this.fields[i] = null; + } + } + } + get version() { + return C._ts_language_version(this[0]); + } + get fieldCount() { + return this.fields.length - 1; + } + get stateCount() { + return C._ts_language_state_count(this[0]); + } + fieldIdForName(fieldName) { + const result = this.fields.indexOf(fieldName); + if (result !== -1) { + return result; + } else { + return null; + } + } + fieldNameForId(fieldId) { + return this.fields[fieldId] || null; + } + idForNodeType(type, named) { + const typeLength = lengthBytesUTF8(type); + const typeAddress = C._malloc(typeLength + 1); + stringToUTF8(type, typeAddress, typeLength + 1); + const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength, named); + C._free(typeAddress); + return result || null; + } + get nodeTypeCount() { + return C._ts_language_symbol_count(this[0]); + } + nodeTypeForId(typeId) { + const name = C._ts_language_symbol_name(this[0], typeId); + return name ? UTF8ToString(name) : null; + } + nodeTypeIsNamed(typeId) { + return C._ts_language_type_is_named_wasm(this[0], typeId) ? true : false; + } + nodeTypeIsVisible(typeId) { + return C._ts_language_type_is_visible_wasm(this[0], typeId) ? true : false; + } + nextState(stateId, typeId) { + return C._ts_language_next_state(this[0], stateId, typeId); + } + lookaheadIterator(stateId) { + const address = C._ts_lookahead_iterator_new(this[0], stateId); + if (address) return new LookaheadIterable(INTERNAL, address, this); + return null; + } + query(source) { + const sourceLength = lengthBytesUTF8(source); + const sourceAddress = C._malloc(sourceLength + 1); + stringToUTF8(source, sourceAddress, sourceLength + 1); + const address = C._ts_query_new(this[0], sourceAddress, sourceLength, TRANSFER_BUFFER, TRANSFER_BUFFER + SIZE_OF_INT); + if (!address) { + const errorId = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + const errorByte = getValue(TRANSFER_BUFFER, "i32"); + const errorIndex = UTF8ToString(sourceAddress, errorByte).length; + const suffix = source.substr(errorIndex, 100).split("\n")[0]; + let word = suffix.match(QUERY_WORD_REGEX)[0]; + let error; + switch (errorId) { + case 2: + error = new RangeError(`Bad node name '${word}'`); + break; + + case 3: + error = new RangeError(`Bad field name '${word}'`); + break; + + case 4: + error = new RangeError(`Bad capture name @${word}`); + break; + + case 5: + error = new TypeError(`Bad pattern structure at offset ${errorIndex}: '${suffix}'...`); + word = ""; + break; + + default: + error = new SyntaxError(`Bad syntax at offset ${errorIndex}: '${suffix}'...`); + word = ""; + break; + } + error.index = errorIndex; + error.length = word.length; + C._free(sourceAddress); + throw error; + } + const stringCount = C._ts_query_string_count(address); + const captureCount = C._ts_query_capture_count(address); + const patternCount = C._ts_query_pattern_count(address); + const captureNames = new Array(captureCount); + const stringValues = new Array(stringCount); + for (let i = 0; i < captureCount; i++) { + const nameAddress = C._ts_query_capture_name_for_id(address, i, TRANSFER_BUFFER); + const nameLength = getValue(TRANSFER_BUFFER, "i32"); + captureNames[i] = UTF8ToString(nameAddress, nameLength); + } + for (let i = 0; i < stringCount; i++) { + const valueAddress = C._ts_query_string_value_for_id(address, i, TRANSFER_BUFFER); + const nameLength = getValue(TRANSFER_BUFFER, "i32"); + stringValues[i] = UTF8ToString(valueAddress, nameLength); + } + const setProperties = new Array(patternCount); + const assertedProperties = new Array(patternCount); + const refutedProperties = new Array(patternCount); + const predicates = new Array(patternCount); + const textPredicates = new Array(patternCount); + for (let i = 0; i < patternCount; i++) { + const predicatesAddress = C._ts_query_predicates_for_pattern(address, i, TRANSFER_BUFFER); + const stepCount = getValue(TRANSFER_BUFFER, "i32"); + predicates[i] = []; + textPredicates[i] = []; + const steps = []; + let stepAddress = predicatesAddress; + for (let j = 0; j < stepCount; j++) { + const stepType = getValue(stepAddress, "i32"); + stepAddress += SIZE_OF_INT; + const stepValueId = getValue(stepAddress, "i32"); + stepAddress += SIZE_OF_INT; + if (stepType === PREDICATE_STEP_TYPE_CAPTURE) { + steps.push({ + type: "capture", + name: captureNames[stepValueId] + }); + } else if (stepType === PREDICATE_STEP_TYPE_STRING) { + steps.push({ + type: "string", + value: stringValues[stepValueId] + }); + } else if (steps.length > 0) { + if (steps[0].type !== "string") { + throw new Error("Predicates must begin with a literal value"); + } + const operator = steps[0].value; + let isPositive = true; + let matchAll = true; + let captureName; + switch (operator) { + case "any-not-eq?": + case "not-eq?": + isPositive = false; + + case "any-eq?": + case "eq?": + if (steps.length !== 3) { + throw new Error(`Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}`); + } + if (steps[1].type !== "capture") { + throw new Error(`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}"`); + } + matchAll = !operator.startsWith("any-"); + if (steps[2].type === "capture") { + const captureName1 = steps[1].name; + const captureName2 = steps[2].name; + textPredicates[i].push(captures => { + const nodes1 = []; + const nodes2 = []; + for (const c of captures) { + if (c.name === captureName1) nodes1.push(c.node); + if (c.name === captureName2) nodes2.push(c.node); + } + const compare = (n1, n2, positive) => positive ? n1.text === n2.text : n1.text !== n2.text; + return matchAll ? nodes1.every(n1 => nodes2.some(n2 => compare(n1, n2, isPositive))) : nodes1.some(n1 => nodes2.some(n2 => compare(n1, n2, isPositive))); + }); + } else { + captureName = steps[1].name; + const stringValue = steps[2].value; + const matches = n => n.text === stringValue; + const doesNotMatch = n => n.text !== stringValue; + textPredicates[i].push(captures => { + const nodes = []; + for (const c of captures) { + if (c.name === captureName) nodes.push(c.node); + } + const test = isPositive ? matches : doesNotMatch; + return matchAll ? nodes.every(test) : nodes.some(test); + }); + } + break; + + case "any-not-match?": + case "not-match?": + isPositive = false; + + case "any-match?": + case "match?": + if (steps.length !== 3) { + throw new Error(`Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}.`); + } + if (steps[1].type !== "capture") { + throw new Error(`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`); + } + if (steps[2].type !== "string") { + throw new Error(`Second argument of \`#${operator}\` predicate must be a string. Got @${steps[2].value}.`); + } + captureName = steps[1].name; + const regex = new RegExp(steps[2].value); + matchAll = !operator.startsWith("any-"); + textPredicates[i].push(captures => { + const nodes = []; + for (const c of captures) { + if (c.name === captureName) nodes.push(c.node.text); + } + const test = (text, positive) => positive ? regex.test(text) : !regex.test(text); + if (nodes.length === 0) return !isPositive; + return matchAll ? nodes.every(text => test(text, isPositive)) : nodes.some(text => test(text, isPositive)); + }); + break; + + case "set!": + if (steps.length < 2 || steps.length > 3) { + throw new Error(`Wrong number of arguments to \`#set!\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`); + } + if (steps.some(s => s.type !== "string")) { + throw new Error(`Arguments to \`#set!\` predicate must be a strings.".`); + } + if (!setProperties[i]) setProperties[i] = {}; + setProperties[i][steps[1].value] = steps[2] ? steps[2].value : null; + break; + + case "is?": + case "is-not?": + if (steps.length < 2 || steps.length > 3) { + throw new Error(`Wrong number of arguments to \`#${operator}\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`); + } + if (steps.some(s => s.type !== "string")) { + throw new Error(`Arguments to \`#${operator}\` predicate must be a strings.".`); + } + const properties = operator === "is?" ? assertedProperties : refutedProperties; + if (!properties[i]) properties[i] = {}; + properties[i][steps[1].value] = steps[2] ? steps[2].value : null; + break; + + case "not-any-of?": + isPositive = false; + + case "any-of?": + if (steps.length < 2) { + throw new Error(`Wrong number of arguments to \`#${operator}\` predicate. Expected at least 1. Got ${steps.length - 1}.`); + } + if (steps[1].type !== "capture") { + throw new Error(`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`); + } + for (let i = 2; i < steps.length; i++) { + if (steps[i].type !== "string") { + throw new Error(`Arguments to \`#${operator}\` predicate must be a strings.".`); + } + } + captureName = steps[1].name; + const values = steps.slice(2).map(s => s.value); + textPredicates[i].push(captures => { + const nodes = []; + for (const c of captures) { + if (c.name === captureName) nodes.push(c.node.text); + } + if (nodes.length === 0) return !isPositive; + return nodes.every(text => values.includes(text)) === isPositive; + }); + break; - function marshalEdit(edit) { - let address = TRANSFER_BUFFER; - marshalPoint(address, edit.startPosition); - address += SIZE_OF_POINT; - marshalPoint(address, edit.oldEndPosition); - address += SIZE_OF_POINT; - marshalPoint(address, edit.newEndPosition); - address += SIZE_OF_POINT; - setValue(address, edit.startIndex, "i32"); - address += SIZE_OF_INT; - setValue(address, edit.oldEndIndex, "i32"); - address += SIZE_OF_INT; - setValue(address, edit.newEndIndex, "i32"); - address += SIZE_OF_INT - } - for (const name of Object.getOwnPropertyNames(ParserImpl.prototype)) { - Object.defineProperty(Parser.prototype, name, { - value: ParserImpl.prototype[name], - enumerable: false, - writable: false - }) - } - Parser.Language = Language; - Module.onRuntimeInitialized = () => { - ParserImpl.init(); - resolveInitPromise() - } - }) + default: + predicates[i].push({ + operator: operator, + operands: steps.slice(1) + }); + } + steps.length = 0; + } + } + Object.freeze(setProperties[i]); + Object.freeze(assertedProperties[i]); + Object.freeze(refutedProperties[i]); + } + C._free(sourceAddress); + return new Query(INTERNAL, address, captureNames, textPredicates, predicates, Object.freeze(setProperties), Object.freeze(assertedProperties), Object.freeze(refutedProperties)); + } + static load(input) { + let bytes; + if (input instanceof Uint8Array) { + bytes = Promise.resolve(input); + } else { + const url = input; + if (typeof process !== "undefined" && process.versions && process.versions.node) { + const fs = require("fs"); + bytes = Promise.resolve(fs.readFileSync(url)); + } else { + bytes = fetch(url).then(response => response.arrayBuffer().then(buffer => { + if (response.ok) { + return new Uint8Array(buffer); + } else { + const body = new TextDecoder("utf-8").decode(buffer); + throw new Error(`Language.load failed with status ${response.status}.\n\n${body}`); + } + })); + } + } + return bytes.then(bytes => loadWebAssemblyModule(bytes, { + loadAsync: true + })).then(mod => { + const symbolNames = Object.keys(mod); + const functionName = symbolNames.find(key => LANGUAGE_FUNCTION_REGEX.test(key) && !key.includes("external_scanner_")); + if (!functionName) { + console.log(`Couldn't find language function in WASM file. Symbols:\n${JSON.stringify(symbolNames, null, 2)}`); + } + const languageAddress = mod[functionName](); + return new Language(INTERNAL, languageAddress); + }); + } + } + class LookaheadIterable { + constructor(internal, address, language) { + assertInternal(internal); + this[0] = address; + this.language = language; + } + get currentTypeId() { + return C._ts_lookahead_iterator_current_symbol(this[0]); + } + get currentType() { + return this.language.types[this.currentTypeId] || "ERROR"; + } + delete() { + C._ts_lookahead_iterator_delete(this[0]); + this[0] = 0; + } + resetState(stateId) { + return C._ts_lookahead_iterator_reset_state(this[0], stateId); + } + reset(language, stateId) { + if (C._ts_lookahead_iterator_reset(this[0], language[0], stateId)) { + this.language = language; + return true; + } + return false; + } + [Symbol.iterator]() { + const self = this; + return { + next() { + if (C._ts_lookahead_iterator_next(self[0])) { + return { + done: false, + value: self.currentType + }; + } + return { + done: true, + value: "" + }; + } + }; + } } + class Query { + constructor(internal, address, captureNames, textPredicates, predicates, setProperties, assertedProperties, refutedProperties) { + assertInternal(internal); + this[0] = address; + this.captureNames = captureNames; + this.textPredicates = textPredicates; + this.predicates = predicates; + this.setProperties = setProperties; + this.assertedProperties = assertedProperties; + this.refutedProperties = refutedProperties; + this.exceededMatchLimit = false; + } + delete() { + C._ts_query_delete(this[0]); + this[0] = 0; + } + matches(node, {startPosition: startPosition = ZERO_POINT, endPosition: endPosition = ZERO_POINT, startIndex: startIndex = 0, endIndex: endIndex = 0, matchLimit: matchLimit = 4294967295, maxStartDepth: maxStartDepth = 4294967295} = {}) { + if (typeof matchLimit !== "number") { + throw new Error("Arguments must be numbers"); + } + marshalNode(node); + C._ts_query_matches_wasm(this[0], node.tree[0], startPosition.row, startPosition.column, endPosition.row, endPosition.column, startIndex, endIndex, matchLimit, maxStartDepth); + const rawCount = getValue(TRANSFER_BUFFER, "i32"); + const startAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + const didExceedMatchLimit = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, "i32"); + const result = new Array(rawCount); + this.exceededMatchLimit = Boolean(didExceedMatchLimit); + let filteredCount = 0; + let address = startAddress; + for (let i = 0; i < rawCount; i++) { + const pattern = getValue(address, "i32"); + address += SIZE_OF_INT; + const captureCount = getValue(address, "i32"); + address += SIZE_OF_INT; + const captures = new Array(captureCount); + address = unmarshalCaptures(this, node.tree, address, captures); + if (this.textPredicates[pattern].every(p => p(captures))) { + result[filteredCount] = { + pattern: pattern, + captures: captures + }; + const setProperties = this.setProperties[pattern]; + if (setProperties) result[filteredCount].setProperties = setProperties; + const assertedProperties = this.assertedProperties[pattern]; + if (assertedProperties) result[filteredCount].assertedProperties = assertedProperties; + const refutedProperties = this.refutedProperties[pattern]; + if (refutedProperties) result[filteredCount].refutedProperties = refutedProperties; + filteredCount++; + } + } + result.length = filteredCount; + C._free(startAddress); + return result; + } + captures(node, {startPosition: startPosition = ZERO_POINT, endPosition: endPosition = ZERO_POINT, startIndex: startIndex = 0, endIndex: endIndex = 0, matchLimit: matchLimit = 4294967295, maxStartDepth: maxStartDepth = 4294967295} = {}) { + if (typeof matchLimit !== "number") { + throw new Error("Arguments must be numbers"); + } + marshalNode(node); + C._ts_query_captures_wasm(this[0], node.tree[0], startPosition.row, startPosition.column, endPosition.row, endPosition.column, startIndex, endIndex, matchLimit, maxStartDepth); + const count = getValue(TRANSFER_BUFFER, "i32"); + const startAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, "i32"); + const didExceedMatchLimit = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, "i32"); + const result = []; + this.exceededMatchLimit = Boolean(didExceedMatchLimit); + const captures = []; + let address = startAddress; + for (let i = 0; i < count; i++) { + const pattern = getValue(address, "i32"); + address += SIZE_OF_INT; + const captureCount = getValue(address, "i32"); + address += SIZE_OF_INT; + const captureIndex = getValue(address, "i32"); + address += SIZE_OF_INT; + captures.length = captureCount; + address = unmarshalCaptures(this, node.tree, address, captures); + if (this.textPredicates[pattern].every(p => p(captures))) { + const capture = captures[captureIndex]; + const setProperties = this.setProperties[pattern]; + if (setProperties) capture.setProperties = setProperties; + const assertedProperties = this.assertedProperties[pattern]; + if (assertedProperties) capture.assertedProperties = assertedProperties; + const refutedProperties = this.refutedProperties[pattern]; + if (refutedProperties) capture.refutedProperties = refutedProperties; + result.push(capture); + } + } + C._free(startAddress); + return result; + } + predicatesForPattern(patternIndex) { + return this.predicates[patternIndex]; + } + disableCapture(captureName) { + const captureNameLength = lengthBytesUTF8(captureName); + const captureNameAddress = C._malloc(captureNameLength + 1); + stringToUTF8(captureName, captureNameAddress, captureNameLength + 1); + C._ts_query_disable_capture(this[0], captureNameAddress, captureNameLength); + C._free(captureNameAddress); + } + didExceedMatchLimit() { + return this.exceededMatchLimit; + } + } + function getText(tree, startIndex, endIndex) { + const length = endIndex - startIndex; + let result = tree.textCallback(startIndex, null, endIndex); + startIndex += result.length; + while (startIndex < endIndex) { + const string = tree.textCallback(startIndex, null, endIndex); + if (string && string.length > 0) { + startIndex += string.length; + result += string; + } else { + break; + } + } + if (startIndex > endIndex) { + result = result.slice(0, length); + } + return result; + } + function unmarshalCaptures(query, tree, address, result) { + for (let i = 0, n = result.length; i < n; i++) { + const captureIndex = getValue(address, "i32"); + address += SIZE_OF_INT; + const node = unmarshalNode(tree, address); + address += SIZE_OF_NODE; + result[i] = { + name: query.captureNames[captureIndex], + node: node + }; + } + return address; + } + function assertInternal(x) { + if (x !== INTERNAL) throw new Error("Illegal constructor"); + } + function isPoint(point) { + return (point && typeof point.row === "number" && typeof point.column === "number"); + } + function marshalNode(node) { + let address = TRANSFER_BUFFER; + setValue(address, node.id, "i32"); + address += SIZE_OF_INT; + setValue(address, node.startIndex, "i32"); + address += SIZE_OF_INT; + setValue(address, node.startPosition.row, "i32"); + address += SIZE_OF_INT; + setValue(address, node.startPosition.column, "i32"); + address += SIZE_OF_INT; + setValue(address, node[0], "i32"); + } + function unmarshalNode(tree, address = TRANSFER_BUFFER) { + const id = getValue(address, "i32"); + address += SIZE_OF_INT; + if (id === 0) return null; + const index = getValue(address, "i32"); + address += SIZE_OF_INT; + const row = getValue(address, "i32"); + address += SIZE_OF_INT; + const column = getValue(address, "i32"); + address += SIZE_OF_INT; + const other = getValue(address, "i32"); + const result = new Node(INTERNAL, tree); + result.id = id; + result.startIndex = index; + result.startPosition = { + row: row, + column: column + }; + result[0] = other; + return result; + } + function marshalTreeCursor(cursor, address = TRANSFER_BUFFER) { + setValue(address + 0 * SIZE_OF_INT, cursor[0], "i32"); + setValue(address + 1 * SIZE_OF_INT, cursor[1], "i32"); + setValue(address + 2 * SIZE_OF_INT, cursor[2], "i32"); + setValue(address + 3 * SIZE_OF_INT, cursor[3], "i32"); + } + function unmarshalTreeCursor(cursor) { + cursor[0] = getValue(TRANSFER_BUFFER + 0 * SIZE_OF_INT, "i32"); + cursor[1] = getValue(TRANSFER_BUFFER + 1 * SIZE_OF_INT, "i32"); + cursor[2] = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, "i32"); + cursor[3] = getValue(TRANSFER_BUFFER + 3 * SIZE_OF_INT, "i32"); + } + function marshalPoint(address, point) { + setValue(address, point.row, "i32"); + setValue(address + SIZE_OF_INT, point.column, "i32"); + } + function unmarshalPoint(address) { + const result = { + row: getValue(address, "i32") >>> 0, + column: getValue(address + SIZE_OF_INT, "i32") >>> 0 + }; + return result; + } + function marshalRange(address, range) { + marshalPoint(address, range.startPosition); + address += SIZE_OF_POINT; + marshalPoint(address, range.endPosition); + address += SIZE_OF_POINT; + setValue(address, range.startIndex, "i32"); + address += SIZE_OF_INT; + setValue(address, range.endIndex, "i32"); + address += SIZE_OF_INT; + } + function unmarshalRange(address) { + const result = {}; + result.startPosition = unmarshalPoint(address); + address += SIZE_OF_POINT; + result.endPosition = unmarshalPoint(address); + address += SIZE_OF_POINT; + result.startIndex = getValue(address, "i32") >>> 0; + address += SIZE_OF_INT; + result.endIndex = getValue(address, "i32") >>> 0; + return result; + } + function marshalEdit(edit) { + let address = TRANSFER_BUFFER; + marshalPoint(address, edit.startPosition); + address += SIZE_OF_POINT; + marshalPoint(address, edit.oldEndPosition); + address += SIZE_OF_POINT; + marshalPoint(address, edit.newEndPosition); + address += SIZE_OF_POINT; + setValue(address, edit.startIndex, "i32"); + address += SIZE_OF_INT; + setValue(address, edit.oldEndIndex, "i32"); + address += SIZE_OF_INT; + setValue(address, edit.newEndIndex, "i32"); + address += SIZE_OF_INT; + } + // end include: /Users/andrew/Code/tree-sitter/tree-sitter/lib/binding_web/binding.js + // include: /Users/andrew/Code/tree-sitter/tree-sitter/lib/binding_web/suffix.js + for (const name of Object.getOwnPropertyNames(ParserImpl.prototype)) { + Object.defineProperty(Parser.prototype, name, { + value: ParserImpl.prototype[name], + enumerable: false, + writable: false + }); + } + Parser.Language = Language; + Module.onRuntimeInitialized = () => { + ParserImpl.init(); + resolveInitPromise(); + }; + }); } - return Parser + } + return Parser; }(); + if (typeof exports === "object") { - module.exports = TreeSitter + module.exports = TreeSitter; } diff --git a/vendor/web-tree-sitter/tree-sitter.wasm b/vendor/web-tree-sitter/tree-sitter.wasm index d81c219e08..d5ac842884 100755 Binary files a/vendor/web-tree-sitter/tree-sitter.wasm and b/vendor/web-tree-sitter/tree-sitter.wasm differ