From 7e59cfc3a6b9067c2561d8629e6b85780fff0f44 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 15 Aug 2023 16:33:04 -0700 Subject: [PATCH] migrate Go keys and values to treesitter This doesn't handle all uses of key, value, and name. Missing still are migrate Go key and value scope to treesitter This doesn't handle all uses of key and value. Missing still are assignment statements (name, value), function signatures (name, value), and possibly other places (perhaps type declarations?). It does handle all existing uses of key, value, and name, though, so we can start here. The handling of multiple return values is complicated, particularly around the handling of comments and removal ranges. It would be nice to find a simpler approach. --- data/playground/go/values.go | 55 ++++++++++++ packages/common/src/types/Position.ts | 12 +++ packages/common/src/types/Range.ts | 12 +++ packages/common/src/types/Selection.ts | 12 +++ .../TreeSitterQuery/checkCaptureStartEnd.ts | 6 +- .../queryPredicateOperators.ts | 17 ++++ .../cursorless-engine/src/languages/go.ts | 5 -- queries/go.scm | 84 +++++++++++++++++++ 8 files changed, 196 insertions(+), 7 deletions(-) create mode 100644 data/playground/go/values.go diff --git a/data/playground/go/values.go b/data/playground/go/values.go new file mode 100644 index 0000000000..8e0257c2ce --- /dev/null +++ b/data/playground/go/values.go @@ -0,0 +1,55 @@ +package p + +func returnZero() { + return +} + +func returnOne() string { + return "lorem" +} + +func returnOneWithCommentBefore() string { + return /* comment */ "lorem" +} + +func returnOneWithCommentAfter() string { + return "lorem" /* comment */ +} + + +func returnTwo() (string, string) { + return "lorem", "ipsum" +} + +func returnTwoWithCommentInside() (string, string) { + return "lorem" /* comment */, "ipsum" +} + +func returnTwoWithCommentBefore() (string, string) { + return /* comment */ "lorem", "ipsum" +} + +func returnTwoWithCommentAfter() (string, string) { + return "lorem", "ipsum" /* comment */ +} + + +func returnThree() (string, string, string) { + return "lorem", "ipsum", "blah" +} + +func returnThreeWithCommentInside() (string, string, string) { + return "lorem" /* comment */, "ipsum", "blah" +} + +func returnThreeWithCommentInside2() (string, string, string) { + return "lorem", "ipsum" /* comment */, "blah" +} + +func returnThreeWithCommentBefore() (string, string, string) { + return /* comment */ "lorem", "ipsum", "blah" +} + +func returnThreeWithCommentAfter() (string, string, string) { + return "lorem", "ipsum", "blah" /* comment */ +} diff --git a/packages/common/src/types/Position.ts b/packages/common/src/types/Position.ts index 79f4d9063b..e4b423218b 100644 --- a/packages/common/src/types/Position.ts +++ b/packages/common/src/types/Position.ts @@ -138,4 +138,16 @@ export class Position { public toEmptyRange(): Range { return new Range(this, this); } + + /** + * Return a concise string representation of the position. + * @returns concise representation + **/ + public concise(): string { + return `${this.line}:${this.character}`; + } + + public toString(): string { + return this.concise(); + } } diff --git a/packages/common/src/types/Range.ts b/packages/common/src/types/Range.ts index 55ea3b4029..b0ffe057f3 100644 --- a/packages/common/src/types/Range.ts +++ b/packages/common/src/types/Range.ts @@ -146,4 +146,16 @@ export class Range { ? new Selection(this.end, this.start) : new Selection(this.start, this.end); } + + /** + * Return a concise string representation of the range + * @returns concise representation + **/ + public concise(): string { + return `${this.start.concise()}-${this.end.concise()}`; + } + + public toString(): string { + return this.concise(); + } } diff --git a/packages/common/src/types/Selection.ts b/packages/common/src/types/Selection.ts index f54ac06787..a202c47251 100644 --- a/packages/common/src/types/Selection.ts +++ b/packages/common/src/types/Selection.ts @@ -72,4 +72,16 @@ export class Selection extends Range { this.anchor.isEqual(other.anchor) && this.active.isEqual(other.active) ); } + + /** + * Return a concise string representation of the selection + * @returns concise representation + **/ + public concise(): string { + return `${this.anchor.concise()}->${this.active.concise()}`; + } + + public toString(): string { + return this.concise(); + } } diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.ts index e9b13d22b2..d254f5bd04 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.ts @@ -60,7 +60,9 @@ export function checkCaptureStartEnd( showError( messages, "TreeSitterQuery.checkCaptures.mixRegularStartEnd", - `Please do not mix regular captures and start/end captures: ${captures}`, + `Please do not mix regular captures and start/end captures: ${captures.map( + ({ name, range }) => name + "@" + range.toString(), + )}`, ); shownError = true; } @@ -71,7 +73,7 @@ export function checkCaptureStartEnd( messages, "TreeSitterQuery.checkCaptures.duplicate", `A capture with the same name may only appear once in a single pattern: ${captures.map( - ({ name }) => name, + ({ name, range }) => name + "@" + range.toString(), )}`, ); shownError = true; diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts index e1647f5f1e..921a9ae469 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts @@ -73,6 +73,22 @@ class HasMultipleChildrenOfType extends QueryPredicateOperator { + name = "has-multiple-children-not-of-type?" as const; + schema = z.tuple([q.node, q.string]); + + run({ node }: MutableQueryCapture, type: string) { + const count = node.children.filter((n) => n.type !== type).length; + return count > 1; + } +} + class ChildRange extends QueryPredicateOperator { name = "child-range!" as const; schema = z.union([ @@ -187,4 +203,5 @@ export const queryPredicateOperators = [ new AllowMultiple(), new InsertionDelimiter(), new HasMultipleChildrenOfType(), + new HasMultipleChildrenNotOfType(), ]; diff --git a/packages/cursorless-engine/src/languages/go.ts b/packages/cursorless-engine/src/languages/go.ts index 208d16e222..302747b8c1 100644 --- a/packages/cursorless-engine/src/languages/go.ts +++ b/packages/cursorless-engine/src/languages/go.ts @@ -27,11 +27,6 @@ const nodeMatchers: Partial< patternMatcher("parameter_declaration"), patternMatcher("argument_declaration"), ), - collectionKey: "keyed_element[0]", - value: cascadingMatcher( - patternMatcher("keyed_element[1]"), - patternMatcher("return_statement.expression_list!"), - ), }; export default createPatternMatchers(nodeMatchers); diff --git a/queries/go.scm b/queries/go.scm index 1f71bde6f5..e002793f17 100644 --- a/queries/go.scm +++ b/queries/go.scm @@ -222,3 +222,87 @@ . ) ) @anonymousFunction @namedFunction + +;; keys in maps +(literal_value + "{" @collectionKey.iteration.start.endOf + (keyed_element + (_) @collectionKey @collectionKey.trailing.start.endOf + ":" + (_) @collectionKey.trailing.end.startOf + ) @collectionKey.domain + "}" @collectionKey.iteration.end.startOf +) + +;; values in maps +(literal_value + "{" @value.iteration.start.endOf + (keyed_element + (_) @value.leading.start.endOf + ":" + (_) @value @value.leading.end.startOf + ) @value.domain + "}" @value.iteration.end.startOf +) + +;; values in return statements + +;; one value within a return statement +(return_statement + (expression_list + . + (_) + . + ) @value + (#insertion-delimiter! @value ", ") +) @value.domain @value.iteration + +;; multiple values within a return statement + +;; NB: gofmt puts the comma after block comments in lists of things +;; +;; Like this: +;; return "lorem" /* comment */, "ipsum" +;; Not like this: +;; return "lorem", /* comment */ "ipsum" +;; +;; It's really hard to deal with commas both before and after, +;; and they're rare anyway, so assume gofmt for now. +;; Non-gofmt commas will mess up removal ranges. + +;; the first value + +;; BUG: in this code: +;; return "lorem" /* comment */ , "ipsum" +;; the comment is included in the removal range of "lorem". +;; This is too hard to fix now, because it would require +;; disjoint removal ranges. And it is rare anyway. + +;; the first of many return values... +(return_statement + (expression_list + . + (_) @value @value.trailing.start.endOf + (#insertion-delimiter! @value ", ") + . + (comment)* @value.trailing.omit + . + "," + . + (_) @value.trailing.end.startOf + ) @_exprlist @value.leading.start.startOf + (#not-type? @value comment) + (#has-multiple-children-not-of-type? @_exprlist comment) +) @value.iteration + +;; ...and the rest of the values +(return_statement + (expression_list + "," @value.leading.start.startOf + . + (_) @value @value.leading.start.startOf + (#insertion-delimiter! @value ", ") + ) @_exprlist + (#not-type? @value comment) + (#has-multiple-children-not-of-type? @_exprlist comment) +) @value.iteration