diff --git a/data/fixtures/recorded/languages/ruby/chuckArg3.yml b/data/fixtures/recorded/languages/ruby/chuckArg3.yml index f1b4d93708..e4c68c0598 100644 --- a/data/fixtures/recorded/languages/ruby/chuckArg3.yml +++ b/data/fixtures/recorded/languages/ruby/chuckArg3.yml @@ -19,7 +19,7 @@ initialState: marks: {} finalState: documentContents: | - [1, 2, 3].each_with_the_next { |n, | } + [1, 2, 3].each_with_the_next { |n| } selections: - - anchor: {line: 0, character: 35} - active: {line: 0, character: 35} + - anchor: {line: 0, character: 33} + active: {line: 0, character: 33} diff --git a/packages/cursorless-engine/src/languages/LegacyLanguageId.ts b/packages/cursorless-engine/src/languages/LegacyLanguageId.ts index 48930a0b82..f4252da4e5 100644 --- a/packages/cursorless-engine/src/languages/LegacyLanguageId.ts +++ b/packages/cursorless-engine/src/languages/LegacyLanguageId.ts @@ -2,6 +2,6 @@ * The language IDs that we have full tree-sitter support for using our legacy * modifiers. */ -export const legacyLanguageIds = ["clojure", "latex", "ruby", "rust"] as const; +export const legacyLanguageIds = ["clojure", "latex", "rust"] as const; export type LegacyLanguageId = (typeof legacyLanguageIds)[number]; diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts index 73c2f1a104..00985321fb 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts @@ -7,10 +7,24 @@ import { QueryPredicateOperator } from "./QueryPredicateOperator"; import { q } from "./operatorArgumentSchemaTypes"; /** - * A predicate operator that returns true if the node is not of the given type. - * For example, `(not-type? @foo string)` will reject the match if the `@foo` + * A predicate operator that returns true if the node is of the given type. + * For example, `(#type? @foo string)` will accept the match if the `@foo` * capture is a `string` node. It is acceptable to pass in multiple types, e.g. - * `(not-type? @foo string comment)`. + * `(#type? @foo string comment)`. + */ +class Type extends QueryPredicateOperator { + name = "type?" as const; + schema = z.tuple([q.node, q.string]).rest(q.string); + run({ node }: MutableQueryCapture, ...types: string[]) { + return types.includes(node.type); + } +} + +/** + * A predicate operator that returns true if the node is NOT of the given type. + * For example, `(#not-type? @foo string)` will reject the match if the `@foo` + * capture is a `string` node. It is acceptable to pass in multiple types, e.g. + * `(#not-type? @foo string comment)`. */ class NotType extends QueryPredicateOperator { name = "not-type?" as const; @@ -22,9 +36,9 @@ class NotType extends QueryPredicateOperator { /** * A predicate operator that returns true if the node's parent is not of the - * given type. For example, `(not-parent-type? @foo string)` will reject the + * given type. For example, `(#not-parent-type? @foo string)` will reject the * match if the `@foo` capture is a child of a `string` node. It is acceptable - * to pass in multiple types, e.g. `(not-parent-type? @foo string comment)`. + * to pass in multiple types, e.g. `(#not-parent-type? @foo string comment)`. */ class NotParentType extends QueryPredicateOperator { name = "not-parent-type?" as const; @@ -36,7 +50,7 @@ class NotParentType extends QueryPredicateOperator { /** * A predicate operator that returns true if the node is the nth child of its - * parent. For example, `(is-nth-child? @foo 0)` will reject the match if the + * parent. For example, `(#is-nth-child? @foo 0)` will reject the match if the * `@foo` capture is not the first child of its parent. */ class IsNthChild extends QueryPredicateOperator { @@ -49,7 +63,7 @@ class IsNthChild extends QueryPredicateOperator { /** * A predicate operator that returns true if the node has more than 1 child of - * type {@link type} (inclusive). For example, `(has-multiple-children-of-type? + * type {@link type} (inclusive). For example, `(#has-multiple-children-of-type? * @foo bar)` will accept the match if the `@foo` capture has 2 or more children * of type `bar`. */ @@ -374,6 +388,7 @@ class EmptySingleMultiDelimiter extends QueryPredicateOperator = { clojure, latex, - ruby, rust, }; diff --git a/packages/cursorless-engine/src/languages/ruby.ts b/packages/cursorless-engine/src/languages/ruby.ts deleted file mode 100644 index 0e0e166b64..0000000000 --- a/packages/cursorless-engine/src/languages/ruby.ts +++ /dev/null @@ -1,188 +0,0 @@ -import type { SimpleScopeTypeType } from "@cursorless/common"; -import type { Node } from "web-tree-sitter"; -import type { NodeMatcherAlternative } from "../typings/Types"; -import { patternFinder } from "../util/nodeFinders"; -import { - ancestorChainNodeMatcher, - argumentMatcher, - cascadingMatcher, - conditionMatcher, - createPatternMatchers, - leadingMatcher, - matcher, - patternMatcher, - trailingMatcher, -} from "../util/nodeMatchers"; - -// Generated by the following command: -// curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-ruby/1ebfdb288842dae5a9233e2509a135949023dd82/src/node-types.json \ -// | jq '[.[] | select((.type == "_statement" or .type == "_simple_statement") and .type != "_expression") | .subtypes[] | select(.type != "_expression") | .type ]' -const STATEMENT_TYPES = [ - "alias", - "begin_block", - "end_block", - "if_modifier", - "rescue_modifier", - "undef", - "unless_modifier", - "until_modifier", - "while_modifier", -]; - -// Generated by the following command: -// > curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-ruby/1ebfdb288842dae5a9233e2509a135949023dd82/src/node-types.json \ -// | jq '[.[] | select(.type == _expression or .type == _arg or .type == _primary or .type == _lhs or .type == _simple_numeric or .type == _variable or .type == _nonlocal_variable) | .subtypes[].type | select(startswith(_) | not)] | sort | unique' -const EXPRESSION_TYPES = [ - "array", - "assignment", - "begin", - "binary", - "break", - "call", - "case", - "case_match", - "chained_string", - "character", - "class", - "class_variable", - "complex", - "conditional", - "constant", - "delimited_symbol", - "element_reference", - "false", - "float", - "for", - "global_variable", - "hash", - "heredoc_beginning", - "identifier", - "if", - "instance_variable", - "integer", - "lambda", - "method", - "module", - "next", - "nil", - "operator_assignment", - "parenthesized_statements", - "range", - "rational", - "redo", - "regex", - "retry", - "return", - "scope_resolution", - "self", - "simple_symbol", - "singleton_class", - "singleton_method", - "string", - "string_array", - "subshell", - "super", - "symbol_array", - "true", - "unary", - "unless", - "until", - "while", - "yield", -]; - -const EXPRESSION_STATEMENT_PARENT_TYPES = [ - "begin_block", - "begin", - "block_body", - "block", - "body_statement", - "do_block", - "do", - "else", - "end_block", - "ensure", - "heredoc_beginning", - "interpolation", - "lambda", - "method", - "parenthesized_statements", - "program", - "singleton_class", - "singleton_method", - "then", -]; - -const assignmentOperators = [ - "=", - "+=", - "-=", - "*=", - "**=", - "/=", - "||=", - "|=", - "&&=", - "&=", - "%=", - ">>=", - "<<=", - "^=", -]; -const mapKeyValueSeparators = [":", "=>"]; - -function blockFinder(node: Node) { - if (node.type !== "call") { - return null; - } - - const receiver = node.childForFieldName("receiver"); - const method = node.childForFieldName("method"); - const block = node.childForFieldName("block"); - - if ( - (receiver?.text === "Proc" && method?.text === "new") || - (receiver == null && method?.text === "lambda") - ) { - return node; - } - - return block; -} - -const nodeMatchers: Partial< - Record -> = { - statement: cascadingMatcher( - patternMatcher(...STATEMENT_TYPES), - ancestorChainNodeMatcher( - [ - patternFinder(...EXPRESSION_STATEMENT_PARENT_TYPES), - patternFinder(...EXPRESSION_TYPES), - ], - 1, - ), - ), - anonymousFunction: cascadingMatcher( - patternMatcher("lambda", "do_block"), - matcher(blockFinder), - ), - condition: conditionMatcher("*[condition]"), - argumentOrParameter: argumentMatcher( - "lambda_parameters", - "method_parameters", - "block_parameters", - "argument_list", - ), - collectionKey: trailingMatcher(["pair[key]"], [":"]), - value: leadingMatcher( - [ - "pair[value]", - "assignment[right]", - "operator_assignment[right]", - "return.argument_list!", - ], - assignmentOperators.concat(mapKeyValueSeparators), - ), -}; -export const patternMatchers = createPatternMatchers(nodeMatchers); diff --git a/queries/ruby.scm b/queries/ruby.scm index 473a701a53..ce5aac6030 100644 --- a/queries/ruby.scm +++ b/queries/ruby.scm @@ -1,5 +1,108 @@ ;; https://github.com/tree-sitter/tree-sitter-ruby/blob/master/src/grammar.json +;; Generated by the following command: +;; curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-ruby/1ebfdb288842dae5a9233e2509a135949023dd82/src/node-types.json \ +;; | jq '[.[] | select((.type == "_statement" or .type == "_simple_statement") and .type != "_expression") | .subtypes[] | select(.type != "_expression") | .type ]' +[ + (alias) + (begin_block) + (end_block) + (if_modifier) + (rescue_modifier) + (undef) + (unless_modifier) + (until_modifier) + (while_modifier) +] @statement + +;; Generated by the following command: +;; > curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-ruby/1ebfdb288842dae5a9233e2509a135949023dd82/src/node-types.json \ +;; | jq '[.[] | select(.type == _expression or .type == _arg or .type == _primary or .type == _lhs or .type == _simple_numeric or .type == _variable or .type == _nonlocal_variable) | .subtypes[].type | select(startswith(_) | not)] | sort | unique' +( + (_ + [ + (array) + (assignment) + (begin) + (binary) + (break) + (call) + (case) + (case_match) + (chained_string) + (character) + (class) + (class_variable) + (complex) + (conditional) + (constant) + (delimited_symbol) + (element_reference) + (false) + (float) + (for) + (global_variable) + (hash) + (heredoc_beginning) + (identifier) + (if) + (instance_variable) + (integer) + (lambda) + (method) + (module) + (next) + (nil) + (operator_assignment) + (parenthesized_statements) + (range) + (rational) + (redo) + (regex) + (retry) + (return) + (scope_resolution) + (self) + (simple_symbol) + (singleton_class) + (singleton_method) + (string) + (string_array) + (subshell) + (super) + (symbol_array) + (true) + (unary) + (unless) + (until) + (while) + (yield) + ] @statement + ) @_dummy + (#type? + @_dummy + begin_block + begin + block_body + block + body_statement + do_block + do + else + end_block + ensure + heredoc_beginning + interpolation + lambda + method + parenthesized_statements + program + singleton_class + singleton_method + then + ) +) + (comment) @comment @textFragment (hash) @map (regex) @regularExpression @@ -95,3 +198,146 @@ operator: [ ) (#insertion-delimiter! @collectionItem " ") ) + +;;!! if true +;;! ^^^^ +(_ + condition: (_) @condition +) @_.domain + +;;!! hi = -> { puts "Hi!" } +;;! ^^^^^^^^^^^^^^^^^ +(lambda) @anonymousFunction + +;;!! [1,2,3].each do |i| end +;;! ^^^^^^^^^^ +(do_block) @anonymousFunction + +(call + receiver: (_)? @_dummy_receiver + method: (_)? @_dummy_method + block: (_) @anonymousFunction + (#not-eq? @_dummy_receiver Proc) + (#not-eq? @_dummy_method lambda) + (#not-type? @anonymousFunction do_block) +) + +;;!! Proc.new { puts "hi" } +;;! ^^^^^^^^^^^^^^^^^^^^^^ +(call + receiver: (_) @_dummy_receiver + method: (_) @_dummy_method + (#eq? @_dummy_receiver Proc) + (#eq? @_dummy_method new) +) @anonymousFunction + +;;!! lambda { puts "hi" } +;;! ^^^^^^^^^^^^^^^^^^^^ +(call + method: (_) @_dummy + (#eq? @_dummy lambda) +) @anonymousFunction + +;;!! {"1" => "one"} +;;! ^^^ +;;! ^^^^^ +(pair + key: (_) @collectionKey @value.leading.endOf + value: (_) @value @collectionKey.trailing.startOf +) @_.domain + +;;!! {"1" => "one", "2" => "two"} +;;! ^^^^^^^^^^^^^^^^^^^^^^^^^^ +(hash + "{" @collectionKey.iteration.start.endOf @value.iteration.start.endOf + "}" @collectionKey.iteration.end.startOf @value.iteration.end.startOf +) + +;;!! return 10 +;;! ^^ +(return + (argument_list) @value +) @_.domain + +;;!! a = 10 +;;! ^^ +(assignment + left: (_) @_.leading.endOf + right: (_) @value +) @_.domain + +;;!! a += 10 +;;! ^^ +(operator_assignment + left: (_) @_.leading.endOf + right: (_) @value +) @_.domain + +;;!! def foo(aaa, bbb) +;;! ^^^ ^^^ +( + (method_parameters + (_)? @_.leading.endOf + . + (_) @argumentOrParameter + . + (_)? @_.trailing.startOf + ) @_dummy + (#single-or-multi-line-delimiter! @argumentOrParameter @_dummy ", " ",\n") +) + +;;!! foo(aaa, bbb) +;;! ^^^ ^^^ +( + (argument_list + (_)? @_.leading.endOf + . + (_) @argumentOrParameter + . + (_)? @_.trailing.startOf + ) @_dummy + (#single-or-multi-line-delimiter! @argumentOrParameter @_dummy ", " ",\n") +) + +;;!! { |aaa, bbb| } +;;! ^^^ ^^^ +( + (block_parameters + (_)? @_.leading.endOf + . + (_) @argumentOrParameter + . + (_)? @_.trailing.startOf + ) @_dummy + (#single-or-multi-line-delimiter! @argumentOrParameter @_dummy ", " ",\n") +) + +;;!! def foo(aaa, bbb) +;;! ^^^^^^^^ +(_ + (method_parameters + "(" @argumentList.start.endOf @argumentOrParameter.iteration.start.endOf + ")" @argumentList.end.startOf @argumentOrParameter.iteration.end.startOf + ) @_dummy + (#empty-single-multi-delimiter! @argumentList.start.endOf @_dummy "" ", " ",\n") +) @argumentList.domain @argumentOrParameter.iteration.domain + +;;!! foo(aaa, bbb) +;;! ^^^^^^^^ +(_ + (argument_list + "(" @argumentList.start.endOf @argumentOrParameter.iteration.start.endOf + ")" @argumentList.end.startOf @argumentOrParameter.iteration.end.startOf + ) @_dummy + (#empty-single-multi-delimiter! @argumentList.start.endOf @_dummy "" ", " ",\n") +) @argumentList.domain @argumentOrParameter.iteration.domain + +;;!! { |aaa, bbb| } +;;! ^^^^^^^^ +(_ + (block_parameters + "|" @argumentList.start.endOf @argumentOrParameter.iteration.start.endOf + "|" @argumentList.end.startOf @argumentOrParameter.iteration.end.startOf + ) @_dummy + (#empty-single-multi-delimiter! @argumentList.start.endOf @_dummy "" ", " ",\n") +) @argumentList.domain @argumentOrParameter.iteration.domain