Skip to content

Commit

Permalink
Add surrounding pair modifier (cursorless-dev#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
maciejklimek committed Aug 6, 2021
1 parent 7b3db14 commit 150cc84
Show file tree
Hide file tree
Showing 24 changed files with 650 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
node_modules
.vscode-test/
*.vsix
package-lock.json
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,4 @@
"immutability-helper": "^3.1.1",
"lodash": "^4.17.21"
}
}
}
5 changes: 4 additions & 1 deletion src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type Delimiter =
| "parentheses"
| "singleQuotes"
| "doubleQuotes";

export type ScopeType =
| "argumentOrParameter"
| "arrowFunction"
Expand Down Expand Up @@ -95,9 +96,11 @@ export type ScopeType =
| "xmlStartTag";
export type PieceType = "word" | "character";

export type SurroundingPairModifierSubtype = "matchingSubtype" | "boundSubtype";
export interface SurroundingPairModifier {
type: "surroundingPair";
delimiter: Delimiter;
subtype: SurroundingPairModifierSubtype;
delimiter: Delimiter | null;
}
export interface ContainingScopeModifier {
type: "containingScope";
Expand Down
7 changes: 5 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export async function activate(context: vscode.ExtensionContext) {
console.debug(JSON.stringify(partialTargets, null, 3));
console.debug(`extraArgs:`);
console.debug(JSON.stringify(extraArgs, null, 3));

console.log("action name", actionName);
const action = graph.actions[actionName];

const selectionContents =
Expand All @@ -144,7 +144,10 @@ export async function activate(context: vscode.ExtensionContext) {
isPaste,
clipboardContents,
};

console.log(
"kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk"
);
console.log(action);
const targets = inferFullTargets(
inferenceContext,
partialTargets,
Expand Down
10 changes: 5 additions & 5 deletions src/languages/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { SyntaxNode } from "web-tree-sitter";
import { notSupported } from "../nodeMatchers";
import { selectionWithEditorFromRange } from "../selectionUtils";
import {
NodeMatcher,
NodeMatcherValue,
ScopeType,
SelectionWithEditor,
} from "../Types";
import csharp from "./csharp";
import java from "./java";
import json from "./json";
import python from "./python";
import typescript from "./typescript";
import csharp from "./csharp";
import java from "./java";
import { notSupported } from "../nodeMatchers";
import { SyntaxNode } from "web-tree-sitter";
import { selectionWithEditorFromRange } from "../selectionUtils";

const languageMatchers: Record<string, Record<ScopeType, NodeMatcher>> = {
csharp: csharp,
Expand Down
154 changes: 154 additions & 0 deletions src/languages/surroundingPair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Position, TextEditor, Selection } from "vscode";
import { Point, SyntaxNode } from "web-tree-sitter";
import {
Delimiter,
NodeMatcher,
NodeMatcherValue,
SelectionWithContext,
SelectionWithEditor,
SurroundingPairModifierSubtype,
} from "../Types";

function positionFromPoint(point: Point): Position {
return new Position(point.row, point.column);
}

const delimiterToText: Record<Delimiter, String[]> = {
squareBrackets: ["[", "]"],
curlyBrackets: ["{", "}"],
angleBrackets: ["<", ">"],
parentheses: ["(", ")"],
singleQuotes: ["'", "'"],
doubleQuotes: ['"', '"'],
};
function isSyntaxNodeLeftPartOfMatching(
node: SyntaxNode,
delimiter: Delimiter
): boolean {
return node.type === delimiterToText[delimiter][0];
}
function isSyntaxNodeRightPartOfMatching(
node: SyntaxNode,
delimiter: Delimiter
): boolean {
return node.type === delimiterToText[delimiter][1];
}

export function createSurroundingPairMatcher(
delimiter: Delimiter | null,
matchingSubtype: SurroundingPairModifierSubtype
): NodeMatcher {
return function nodeMatcher(
selection: SelectionWithEditor,
node: SyntaxNode
) {
let delimetersToCheck: Delimiter[];
if (delimiter != null) {
delimetersToCheck = [delimiter];
} else {
delimetersToCheck = [
"squareBrackets",
"curlyBrackets",
"angleBrackets",
"parentheses",
"singleQuotes",
"doubleQuotes",
];
}

// This is a special case.
let nodeLeftOfSelection: SyntaxNode | null = null;
let nodeRightOfSelection: SyntaxNode | null = null;
for (const child of node.children) {
// We iterate from the so we take the **last** node that is good
if (
positionFromPoint(child.endPosition).isBeforeOrEqual(
selection.selection.start
)
) {
nodeLeftOfSelection = child;
}
// We iterate from the so we take the **first** node that is good
if (
nodeRightOfSelection == null &&
selection.selection.start.isBeforeOrEqual(
positionFromPoint(child.startPosition)
)
) {
nodeRightOfSelection = child;
}
}
if (nodeLeftOfSelection != null && nodeRightOfSelection != null) {
let result = doOutwardScan(
nodeLeftOfSelection,
nodeRightOfSelection,
delimetersToCheck,
matchingSubtype
);
if (result != null) {
return result;
}
}

if (node.parent == null) {
return null;
}
// We don't take the next sibling here, because if current node is a
// closing element of the pair we want to take it.
return doOutwardScan(
node.previousSibling,
node,
delimetersToCheck,
matchingSubtype
);
};
}

function doOutwardScan(
scanLeftStartNode: SyntaxNode | null,
scanRightStartNode: SyntaxNode | null,
delimetersToCheck: Delimiter[],
matchingSubtype: SurroundingPairModifierSubtype
): NodeMatcherValue[] | null {
for (const delimiter of delimetersToCheck) {
let left = scanLeftStartNode;
while (left != null) {
if (isSyntaxNodeLeftPartOfMatching(left, delimiter)) {
break;
}
left = left.previousSibling;
}
let right = scanRightStartNode;
while (right != null) {
if (isSyntaxNodeRightPartOfMatching(right, delimiter)) {
break;
}
right = right.nextSibling;
}
if (left != null && right != null) {
// We have found the matching pair
if (matchingSubtype === "matchingSubtype") {
return [
{
node: left,
selection: {
selection: new Selection(
positionFromPoint(left.endPosition),
positionFromPoint(right.startPosition)
),
context: {
outerSelection: new Selection(
positionFromPoint(left.startPosition),
positionFromPoint(right.endPosition)
),
},
},
},
];
} else {
throw new Error("Not implemented");
}
}
}
return null;
}
91 changes: 66 additions & 25 deletions src/processTargets.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { concat, range, zip } from "lodash";
import update from "immutability-helper";
import { concat, range, zip } from "lodash";
import { start } from "repl";
import * as vscode from "vscode";
import { Location, Position, Range, Selection, TextDocument } from "vscode";
import { SyntaxNode } from "web-tree-sitter";
import { SUBWORD_MATCHER } from "./constants";
import { getNodeMatcher } from "./languages";
import { Selection, Range, Position, Location, TextDocument } from "vscode";
import { createSurroundingPairMatcher } from "./languages/surroundingPair";
import { performInsideOutsideAdjustment } from "./performInsideOutsideAdjustment";
import {
selectionWithEditorFromPositions,
selectionWithEditorFromRange,
} from "./selectionUtils";
import {
LineNumberPosition,
Mark,
Modifier,
NodeMatcher,
PrimitiveTarget,
ProcessedTargetsContext,
RangeTarget,
SelectionContext,
SelectionWithEditor,
Target,
TypedSelection,
Modifier,
LineNumberPosition,
} from "./Types";
import { performInsideOutsideAdjustment } from "./performInsideOutsideAdjustment";
import { SUBWORD_MATCHER } from "./constants";
import {
selectionWithEditorFromPositions,
selectionWithEditorFromRange,
} from "./selectionUtils";

export default function processTargets(
context: ProcessedTargetsContext,
Expand Down Expand Up @@ -250,6 +254,31 @@ function getSelectionsFromMark(
}
}

function findFirstMatchingNode(
startNode: SyntaxNode,
nodeMatcher: NodeMatcher,
selection: SelectionWithEditor
) {
let node: SyntaxNode | null = startNode;
while (node != null) {
const matches = nodeMatcher(selection, node);
if (matches != null) {
return matches
.map((match) => match.selection)
.map((matchedSelection) => ({
selection: selectionWithEditorFromRange(
selection,
matchedSelection.selection
),
context: matchedSelection.context,
}));
}
node = node.parent;
}

return null;
}

function transformSelection(
context: ProcessedTargetsContext,
target: PrimitiveTarget,
Expand All @@ -271,21 +300,10 @@ function transformSelection(
modifier.scopeType,
modifier.includeSiblings ?? false
);
let result = findFirstMatchingNode(node, nodeMatcher, selection);

while (node != null) {
const matches = nodeMatcher(selection, node);
if (matches != null) {
return matches
.map((match) => match.selection)
.map((matchedSelection) => ({
selection: selectionWithEditorFromRange(
selection,
matchedSelection.selection
),
context: matchedSelection.context,
}));
}
node = node.parent;
if (result != null) {
return result;
}

throw new Error(`Couldn't find containing ${modifier.scopeType}`);
Expand Down Expand Up @@ -391,8 +409,31 @@ function transformSelection(
}

case "matchingPairSymbol":
case "surroundingPair":
throw new Error("Not implemented");

case "surroundingPair":
{
if (modifier.subtype === "boundSubtype") {
throw new Error("Not implemented");
}
let node: SyntaxNode | null = context.getNodeAtLocation(
new vscode.Location(
selection.editor.document.uri,
selection.selection
)
);

const nodeMatcher = createSurroundingPairMatcher(
modifier.delimiter,
modifier.subtype
);
let result = findFirstMatchingNode(node, nodeMatcher, selection);
if (result != null) {
return result;
}
}

throw new Error(`Couldn't find containing `);
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/test/suite/fixtures/recorded/surroundingPair/clearMatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
spokenForm: clear match
languageId: python
command:
actionName: clear
partialTargets:
- type: primitive
modifier: {type: surroundingPair, subtype: matchingSubtype, delimiter: null}
extraArgs: []
marks: {}
initialState:
documentContents: |
"fdsfads"
selections:
- anchor: {line: 0, character: 5}
active: {line: 0, character: 5}
finalState:
documentContents: |
""
selections:
- anchor: {line: 0, character: 1}
active: {line: 0, character: 1}
thatMark:
- anchor: {line: 0, character: 1}
active: {line: 0, character: 1}
fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, modifier: {type: surroundingPair, subtype: matchingSubtype, delimiter: null}, insideOutsideType: inside}]
Loading

0 comments on commit 150cc84

Please sign in to comment.