Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { parsePredicates } from "./parsePredicates";
import { predicateToString } from "./predicateToString";
import { groupBy, uniq } from "lodash";
import { checkCaptureStartEnd } from "./checkCaptureStartEnd";
import { rewriteStartOfEndOf } from "./rewriteStartOfEndOf";

/**
* Wrapper around a tree-sitter query that provides a more convenient API, and
Expand Down Expand Up @@ -95,6 +96,7 @@ export class TreeSitterQuery {
const captures: QueryCapture[] = Object.entries(
groupBy(match.captures, ({ name }) => normalizeCaptureName(name)),
).map(([name, captures]) => {
captures = rewriteStartOfEndOf(captures);
const capturesAreValid = checkCaptureStartEnd(
captures,
ide().messages,
Expand Down Expand Up @@ -123,7 +125,7 @@ export class TreeSitterQuery {
}

function normalizeCaptureName(name: string): string {
return name.replace(/\.(start|end)$/, "");
return name.replace(/(\.(start|end))?(\.(startOf|endOf))?$/, "");
}

function positionToPoint(start: Position): Point {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ export function checkCaptureStartEnd(
showError(
messages,
"TreeSitterQuery.checkCaptures.duplicate",
`A capture with the same name may only appear once in a single pattern: ${captures}`,
`A capture with the same name may only appear once in a single pattern: ${captures.map(
({ name }) => name,
)}`,
);
shownError = true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Range } from "@cursorless/common";
import z from "zod";
import { makeRangeFromPositions } from "../../util/nodeSelectors";
import { MutableQueryCapture } from "./QueryCapture";
Expand Down Expand Up @@ -46,40 +45,6 @@ class IsNthChild extends QueryPredicateOperator<IsNthChild> {
}
}

/**
* A predicate operator that modifies the range of the match to be a zero-width
* range at the start of the node. For example, `(#start-position! @foo)` will
* modify the range of the `@foo` capture to be a zero-width range at the start
* of the `@foo` node.
*/
class StartPosition extends QueryPredicateOperator<StartPosition> {
name = "start-position!" as const;
schema = z.tuple([q.node]);

run(nodeInfo: MutableQueryCapture) {
nodeInfo.range = new Range(nodeInfo.range.start, nodeInfo.range.start);

return true;
}
}

/**
* A predicate operator that modifies the range of the match to be a zero-width
* range at the end of the node. For example, `(#end-position! @foo)` will
* modify the range of the `@foo` capture to be a zero-width range at the end of
* the `@foo` node.
*/
class EndPosition extends QueryPredicateOperator<EndPosition> {
name = "end-position!" as const;
schema = z.tuple([q.node]);

run(nodeInfo: MutableQueryCapture) {
nodeInfo.range = new Range(nodeInfo.range.end, nodeInfo.range.end);

return true;
}
}

class ChildRange extends QueryPredicateOperator<ChildRange> {
name = "child-range!" as const;
schema = z.union([
Expand Down Expand Up @@ -131,8 +96,6 @@ export const queryPredicateOperators = [
new NotType(),
new NotParentType(),
new IsNthChild(),
new StartPosition(),
new EndPosition(),
new ChildRange(),
new AllowMultiple(),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Range } from "@cursorless/common";
import { MutableQueryCapture } from "./QueryCapture";
import { SyntaxNode } from "web-tree-sitter";
import { rewriteStartOfEndOf } from "./rewriteStartOfEndOf";
import assert = require("assert");

type NameRange = Pick<MutableQueryCapture, "name" | "range">;

interface TestCase {
name: string;
captures: NameRange[];
expected: NameRange[];
}

const testCases: TestCase[] = [
{
name: "should rewrite startOf to start of range",
captures: [
{ name: "@value.iteration.start.startOf", range: new Range(1, 2, 1, 3) },
{ name: "@collectionKey.startOf", range: new Range(1, 2, 1, 3) },
],
expected: [
{ name: "@value.iteration.start", range: new Range(1, 2, 1, 2) },
{ name: "@collectionKey", range: new Range(1, 2, 1, 2) },
],
},

{
name: "should rewrite endOf to start of range",
captures: [
{ name: "@value.iteration.start.endOf", range: new Range(1, 2, 1, 3) },
{ name: "@collectionKey.endOf", range: new Range(1, 2, 1, 3) },
],
expected: [
{ name: "@value.iteration.start", range: new Range(1, 3, 1, 3) },
{ name: "@collectionKey", range: new Range(1, 3, 1, 3) },
],
},

{
name: "should leave other captures alone",
captures: [
{ name: "@value.iteration.start", range: new Range(1, 2, 1, 3) },
{ name: "@collectionKey", range: new Range(1, 2, 1, 3) },
],
expected: [
{ name: "@value.iteration.start", range: new Range(1, 2, 1, 3) },
{ name: "@collectionKey", range: new Range(1, 2, 1, 3) },
],
},
];

suite("rewriteStartOfEndOf", () => {
for (const testCase of testCases) {
test(testCase.name, () => {
const actual = rewriteStartOfEndOf(
testCase.captures.map((capture) => ({
...capture,
allowMultiple: false,
node: null as unknown as SyntaxNode,
})),
);
assert.deepStrictEqual(
actual,
testCase.expected.map((capture) => ({
...capture,
allowMultiple: false,
node: null as unknown as SyntaxNode,
})),
);
});
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MutableQueryCapture } from "./QueryCapture";

/**
* Rewrite captures, absorbing .startOf and .endOf into ranges.
*
* @param captures A list of captures
* @returns rewritten captures, with .startOf and .endOf removed
*/
export function rewriteStartOfEndOf(
captures: MutableQueryCapture[],
): MutableQueryCapture[] {
return captures.map((capture) => {
// Remove trailing .startOf and .endOf, adjusting ranges.
if (capture.name.endsWith(".startOf")) {
return {
...capture,
name: capture.name.replace(/\.startOf$/, ""),
range: capture.range.start.toEmptyRange(),
};
}
if (capture.name.endsWith(".endOf")) {
return {
...capture,
name: capture.name.replace(/\.endOf$/, ""),
range: capture.range.end.toEmptyRange(),
};
}
return capture;
});
}
16 changes: 6 additions & 10 deletions queries/javascript.core.scm
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,11 @@
;; Treat interior of all bodies as iteration scopes for `name`, eg
;;!! function foo() { }
;;! ***
(
(_
body: (_
.
"{" @name.iteration.start
"}" @name.iteration.end
.
)
(_
body: (_
.
"{" @name.iteration.start.endOf
"}" @name.iteration.end.startOf
.
)
(#end-position! @name.iteration.start)
(#start-position! @name.iteration.end)
)
9 changes: 3 additions & 6 deletions queries/javascript.jsx.scm
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,7 @@
;;!! <>foo</>
;;! {} {}
;;! -- ---
(
(jsx_fragment
"<" @_.domain.start
">" @name @_.domain.end
)
(#start-position! @name)
(jsx_fragment
"<" @_.domain.start
">" @name.startOf @_.domain.end
)