From 5f35c86e9a2bb332f0caefa152db6ab4a67c1e32 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sun, 12 Mar 2023 01:17:15 +0000
Subject: [PATCH 01/26] Initial Perl support and some tests

---
 .../src/languages/constants.ts                |   1 +
 .../src/languages/getNodeMatcher.ts           |   2 +
 .../src/languages/getTextFragmentExtractor.ts |   5 +
 .../cursorless-engine/src/languages/perl.ts   | 132 ++++++++++++++++++
 .../recorded/languages/perl/changeArg.yml     |  22 +++
 .../recorded/languages/perl/changeCall.yml    |  28 ++++
 .../recorded/languages/perl/changeCall2.yml   |  28 ++++
 .../recorded/languages/perl/changeCall3.yml   |  28 ++++
 .../recorded/languages/perl/changeCall4.yml   |  28 ++++
 .../languages/perl/changeClassName.yml        |  22 +++
 .../recorded/languages/perl/changeComment.yml |  28 ++++
 .../languages/perl/changeCondition.yml        |  40 ++++++
 .../recorded/languages/perl/changeFunk.yml    |  25 ++++
 .../recorded/languages/perl/changeFunk2.yml   |  25 ++++
 .../recorded/languages/perl/changeIfState.yml |  22 +++
 .../recorded/languages/perl/changeItem.yml    |  30 ++++
 .../recorded/languages/perl/changeKey.yml     |  30 ++++
 .../recorded/languages/perl/changeLambda.yml  |  22 +++
 .../recorded/languages/perl/changeList.yml    |  22 +++
 .../recorded/languages/perl/changeMap.yml     |  26 ++++
 .../recorded/languages/perl/changeRegex.yml   |  22 +++
 .../recorded/languages/perl/changeRegex2.yml  |  22 +++
 .../recorded/languages/perl/changeString.yml  |  28 ++++
 .../recorded/languages/perl/changeString2.yml |  28 ++++
 24 files changed, 666 insertions(+)
 create mode 100644 packages/cursorless-engine/src/languages/perl.ts
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml

diff --git a/packages/cursorless-engine/src/languages/constants.ts b/packages/cursorless-engine/src/languages/constants.ts
index d19faa0c5c..035dbb1512 100644
--- a/packages/cursorless-engine/src/languages/constants.ts
+++ b/packages/cursorless-engine/src/languages/constants.ts
@@ -14,6 +14,7 @@ export const supportedLanguageIds = [
   "jsonc",
   "latex",
   "markdown",
+  "perl",
   "php",
   "python",
   "ruby",
diff --git a/packages/cursorless-engine/src/languages/getNodeMatcher.ts b/packages/cursorless-engine/src/languages/getNodeMatcher.ts
index f0d0012141..7c3863f92c 100644
--- a/packages/cursorless-engine/src/languages/getNodeMatcher.ts
+++ b/packages/cursorless-engine/src/languages/getNodeMatcher.ts
@@ -18,6 +18,7 @@ import java from "./java";
 import { patternMatchers as json } from "./json";
 import latex from "./latex";
 import markdown from "./markdown";
+import { patternMatchers as perl } from "./perl";
 import php from "./php";
 import python from "./python";
 import { patternMatchers as ruby } from "./ruby";
@@ -68,6 +69,7 @@ const languageMatchers: Record<
   jsonc: json,
   latex,
   markdown,
+  perl,
   php,
   python,
   ruby,
diff --git a/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts b/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts
index b126c56c4f..25446e431d 100644
--- a/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts
+++ b/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts
@@ -7,6 +7,7 @@ import { SupportedLanguageId } from "./constants";
 import { getNodeMatcher } from "./getNodeMatcher";
 import { stringTextFragmentExtractor as htmlStringTextFragmentExtractor } from "./html";
 import { stringTextFragmentExtractor as jsonStringTextFragmentExtractor } from "./json";
+import { stringTextFragmentExtractor as perlStringTextFragmentExtractor } from "./perl";
 import { stringTextFragmentExtractor as phpStringTextFragmentExtractor } from "./php";
 import { stringTextFragmentExtractor as rubyStringTextFragmentExtractor } from "./ruby";
 import { stringTextFragmentExtractor as scssStringTextFragmentExtractor } from "./scss";
@@ -162,6 +163,10 @@ const textFragmentExtractors: Record<
   ),
   latex: fullDocumentTextFragmentExtractor,
   markdown: fullDocumentTextFragmentExtractor,
+  perl: constructDefaultTextFragmentExtractor(
+    "perl",
+    perlStringTextFragmentExtractor,
+  ),
   php: constructDefaultTextFragmentExtractor(
     "php",
     phpStringTextFragmentExtractor,
diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
new file mode 100644
index 0000000000..ce37b9062f
--- /dev/null
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -0,0 +1,132 @@
+import {
+  createPatternMatchers,
+  matcher,
+} from "../util/nodeMatchers";
+import { NodeMatcherAlternative, SelectionWithEditor } from "../typings/Types";
+import { SimpleScopeTypeType } from "@cursorless/common";
+import { SyntaxNode } from "web-tree-sitter";
+import { getNodeRange, unwrapSelectionExtractor } from "../util/nodeSelectors";
+import { patternFinder } from "../util/nodeFinders";
+
+// Generated by the following command:
+// curl https://raw.githubusercontent.com/ganezdragon/tree-sitter-perl/ee1001210af5f32ba14d2ced834636548e1b6485/src/node-types.json \
+// | jq '[.[] | select(.type|match("_statement")) | .type ]'
+const STATEMENT_TYPES = [
+  "ellipsis_statement",
+  "for_simple_statement",
+  "for_statement_1",
+  "for_statement_2",
+  "foreach_simple_statement",
+  "foreach_statement",
+  "if_simple_statement",
+  "if_statement",
+  "loop_control_statement",
+  "loop_control_statement",
+  "named_block_statement",
+  "package_statement",
+  "pod_statement",
+  "require_statement",
+  "single_line_statement",
+  "unless_simple_statement",
+  "unless_statement",
+  "until_simple_statement",
+  "until_statement",
+  "use_constant_statement",
+  "use_no_feature_statement",
+  "use_no_if_statement",
+  "use_no_statement",
+  "use_no_subs_statement",
+  "use_parent_statement",
+  "when_simple_statement",
+  "while_simple_statement",
+  "while_statement",
+];
+
+const EXPRESSION_TYPES = [
+  "array",
+  "assignment",
+  "begin",
+  // MANY MORE TODO ...
+];
+
+const EXPRESSION_STATEMENT_PARENT_TYPES = [
+  "begin",
+  "block",
+  "do",
+  "do_block",
+  "else",
+  "lambda",
+  "method",
+  "then",
+];
+
+const assignmentOperators = [
+  "=",
+  "+=",
+  "-=",
+  "*=",
+  "/=",
+  "||=",
+  "//=",
+  "|=",
+  "&&=",
+  "&=",
+  "%=",
+  ">>=",
+  "<<=",
+  "^=",
+];
+
+const mapKeyValueSeparators = [",", "=>"];
+
+const nodeMatchers: Partial<
+  Record<SimpleScopeTypeType, NodeMatcherAlternative>
+> = {
+  map: "hash",
+  list: "array",
+  condition: matcher(
+    patternFinder("while_statement[condition]"),
+    unwrapSelectionExtractor,
+  ),
+  string: [
+    "string_single_quoted",
+    "string_double_quoted",
+    "string_q_quoted",
+    "string_qq_quoted",
+  ],
+  ifStatement: "if_statement",
+  functionCall: [
+    "call_expression",
+    "call_expression_with_just_name",
+    "method_invocation",
+  ],
+  comment: "comments",
+  namedFunction: ["function_definition"],
+  anonymousFunction: "anonymous_function",
+  regularExpression: ["regex_pattern", "regex_pattern_qr"],
+  collectionKey: "*[key]", // TODO: child of "value: hash?"
+  collectionItem: "*[value]", // TODO: child of "value: hash?"
+  argumentOrParameter: [
+    "empty_parenthesized_argument",
+    "parenthesized_argument",
+    "argument",
+  ],
+  className: "package_name",
+};
+
+export const patternMatchers = createPatternMatchers(nodeMatchers);
+
+export function stringTextFragmentExtractor(
+  node: SyntaxNode,
+  _selection: SelectionWithEditor,
+) {
+  // heredoc_content does not seem to supported by tree-sitter-perl,
+  // leaving it anyway since it won't hurt
+  if (node.type === "string_content" || node.type === "heredoc_content") {
+    return getNodeRange(node);
+  }
+
+  return null;
+}
+
+// EOF
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
new file mode 100644
index 0000000000..d650a654db
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change arg
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: argumentOrParameter}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: some_funky_func( "and", "three", "args" )
+  selections:
+    - anchor: {line: 0, character: 36}
+      active: {line: 0, character: 36}
+  marks: {}
+finalState:
+  documentContents: some_funky_func(  )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml
new file mode 100644
index 0000000000..7ec30f134f
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml
@@ -0,0 +1,28 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change call
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCall}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    my $var = 1;
+    $var = func();
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 12}
+      active: {line: 1, character: 12}
+  marks: {}
+finalState:
+  documentContents: |-
+    my $var = 1;
+    $var = ;
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 7}
+      active: {line: 1, character: 7}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml
new file mode 100644
index 0000000000..cee0884671
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml
@@ -0,0 +1,28 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change call
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCall}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    my $var = 1;
+    $var = func_with_params( $var );
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 27}
+      active: {line: 1, character: 27}
+  marks: {}
+finalState:
+  documentContents: |-
+    my $var = 1;
+    $var = ;
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 7}
+      active: {line: 1, character: 7}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml
new file mode 100644
index 0000000000..8f07017062
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml
@@ -0,0 +1,28 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change call
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCall}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    my $var = 1;
+    $var = $object->method( );
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 24}
+      active: {line: 1, character: 24}
+  marks: {}
+finalState:
+  documentContents: |-
+    my $var = 1;
+    $var = ;
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 7}
+      active: {line: 1, character: 7}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml
new file mode 100644
index 0000000000..41910dc0d5
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml
@@ -0,0 +1,28 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change call
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCall}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    my $var = 1;
+    $var = Other::Package::func();
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 24}
+      active: {line: 1, character: 24}
+  marks: {}
+finalState:
+  documentContents: |-
+    my $var = 1;
+    $var = ;
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 7}
+      active: {line: 1, character: 7}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml
new file mode 100644
index 0000000000..ad7d1d2ddf
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change class name
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: className}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: $var = Some::Package::func()
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+  marks: {}
+finalState:
+  documentContents: $var = ::func()
+  selections:
+    - anchor: {line: 0, character: 7}
+      active: {line: 0, character: 7}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml
new file mode 100644
index 0000000000..7e953c6dc2
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml
@@ -0,0 +1,28 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change comment
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: comment}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    $var = 1;
+    # An actual comment
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 2}
+      active: {line: 1, character: 2}
+  marks: {}
+finalState:
+  documentContents: |-
+    $var = 1;
+
+    $var = 2;
+  selections:
+    - anchor: {line: 1, character: 0}
+      active: {line: 1, character: 0}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml
new file mode 100644
index 0000000000..5fd07db78c
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml
@@ -0,0 +1,40 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    while ( $a < 0 ) {
+        # ...
+    }
+
+    while ( ( $a < 0 ) ) {
+        # ...
+    }
+  selections:
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}
+    - anchor: {line: 5, character: 4}
+      active: {line: 5, character: 4}
+  marks: {}
+finalState:
+  documentContents: |-
+    while () {
+        # ...
+    }
+
+    while () {
+        # ...
+    }
+  selections:
+    - anchor: {line: 0, character: 7}
+      active: {line: 0, character: 7}
+    - anchor: {line: 4, character: 7}
+      active: {line: 4, character: 7}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml
new file mode 100644
index 0000000000..3d37dd993c
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml
@@ -0,0 +1,25 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change funk
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: namedFunction}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    sub funky {
+        # ...
+    }
+  selections:
+    - anchor: {line: 1, character: 9}
+      active: {line: 1, character: 9}
+  marks: {}
+finalState:
+  documentContents: ""
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml
new file mode 100644
index 0000000000..6ec56caedf
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml
@@ -0,0 +1,25 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change funk
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: namedFunction}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    sub funky_with_prototype ($) {
+        # ...
+    }
+  selections:
+    - anchor: {line: 1, character: 8}
+      active: {line: 1, character: 8}
+  marks: {}
+finalState:
+  documentContents: ""
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml
new file mode 100644
index 0000000000..69607888b3
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change if state
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: ifStatement}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: if ( $this_is_a_test ) { }
+  selections:
+    - anchor: {line: 0, character: 24}
+      active: {line: 0, character: 24}
+  marks: {}
+finalState:
+  documentContents: ""
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
new file mode 100644
index 0000000000..f8bbb72e24
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
@@ -0,0 +1,30 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change item
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: collectionItem}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    %hash = (
+        one => 1,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 1, character: 7}
+      active: {line: 1, character: 7}
+  marks: {}
+finalState:
+  documentContents: |-
+    %hash = (
+        one => ,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 1, character: 11}
+      active: {line: 1, character: 11}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml
new file mode 100644
index 0000000000..54647d41b2
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml
@@ -0,0 +1,30 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change key
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: collectionKey}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    %hash = (
+        one => 1,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 1, character: 0}
+      active: {line: 1, character: 0}
+  marks: {}
+finalState:
+  documentContents: |-
+    %hash = (
+         => 1,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml
new file mode 100644
index 0000000000..3d3a8c43d2
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change lambda
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: anonymousFunction}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: $func = sub { 1 };
+  selections:
+    - anchor: {line: 0, character: 14}
+      active: {line: 0, character: 14}
+  marks: {}
+finalState:
+  documentContents: $func = ;
+  selections:
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml
new file mode 100644
index 0000000000..f514d833db
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change list
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: list}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: "@array = ( \"one\", 2, \"_tres\" )"
+  selections:
+    - anchor: {line: 0, character: 18}
+      active: {line: 0, character: 18}
+  marks: {}
+finalState:
+  documentContents: "@array = "
+  selections:
+    - anchor: {line: 0, character: 9}
+      active: {line: 0, character: 9}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml
new file mode 100644
index 0000000000..54f68606f2
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml
@@ -0,0 +1,26 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change map
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: map}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    %hash = (
+        first => "1st",
+        second => "yup",
+    )
+  selections:
+    - anchor: {line: 1, character: 0}
+      active: {line: 1, character: 0}
+  marks: {}
+finalState:
+  documentContents: "%hash = "
+  selections:
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml
new file mode 100644
index 0000000000..91b0d9ba11
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change regex
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: regularExpression}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: $str =~ /bananabeast/
+  selections:
+    - anchor: {line: 0, character: 14}
+      active: {line: 0, character: 14}
+  marks: {}
+finalState:
+  documentContents: $str =~ //
+  selections:
+    - anchor: {line: 0, character: 9}
+      active: {line: 0, character: 9}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml
new file mode 100644
index 0000000000..a252a92572
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change regex
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: regularExpression}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: $str =~ qr/qr match/
+  selections:
+    - anchor: {line: 0, character: 13}
+      active: {line: 0, character: 13}
+  marks: {}
+finalState:
+  documentContents: "$str =~ "
+  selections:
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml
new file mode 100644
index 0000000000..d4acbe9796
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml
@@ -0,0 +1,28 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change string
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: surroundingPair, delimiter: string}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    @strings = (
+        'single',
+    );
+  selections:
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}
+  marks: {}
+finalState:
+  documentContents: |-
+    @strings = (
+        ,
+    );
+  selections:
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml
new file mode 100644
index 0000000000..6c00098b76
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml
@@ -0,0 +1,28 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change string
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: surroundingPair, delimiter: string}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    @strings = (
+        "double $some_var",
+    );
+  selections:
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}
+  marks: {}
+finalState:
+  documentContents: |-
+    @strings = (
+        ,
+    );
+  selections:
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}

From cbc72ff4f277f1df4223c45f32b1718d6d2acaa0 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Mon, 13 Mar 2023 20:22:48 +0000
Subject: [PATCH 02/26] Initial review notes: improve tests, few new tests

---
 .../cursorless-engine/src/languages/perl.ts   |  2 -
 .../recorded/languages/perl/changeArg.yml     |  2 +-
 .../recorded/languages/perl/changeItem.yml    |  2 +-
 .../recorded/languages/perl/chuckItem.yml     | 29 +++++++++++++
 .../recorded/languages/perl/chuckRound.yml    | 42 +++++++++++++++++++
 5 files changed, 73 insertions(+), 4 deletions(-)
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
 create mode 100644 packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index ce37b9062f..ae8ff91c56 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -128,5 +128,3 @@ export function stringTextFragmentExtractor(
 
   return null;
 }
-
-// EOF
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
index d650a654db..e86433ba57 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
@@ -16,7 +16,7 @@ initialState:
       active: {line: 0, character: 36}
   marks: {}
 finalState:
-  documentContents: some_funky_func(  )
+  documentContents: some_funky_func( "three", "args" )
   selections:
     - anchor: {line: 0, character: 17}
       active: {line: 0, character: 17}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
index f8bbb72e24..288f3afe7f 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
@@ -22,7 +22,7 @@ initialState:
 finalState:
   documentContents: |-
     %hash = (
-        one => ,
+        ,
         two => 2,
     );
   selections:
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
new file mode 100644
index 0000000000..b8e5fc0b32
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
@@ -0,0 +1,29 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: chuck item
+  action: {name: remove}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: collectionItem}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    %hash = (
+        one => 1,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 2, character: 4}
+      active: {line: 2, character: 4}
+  marks: {}
+finalState:
+  documentContents: |-
+    %hash = (
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 2, character: 4}
+      active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
new file mode 100644
index 0000000000..091dfd2cb7
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
@@ -0,0 +1,42 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: chuck round
+  action: {name: remove}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: surroundingPair, delimiter: parentheses}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    "aaa (bbb) ccc"
+    'aaa (bbb) ccc'
+    q(aaa (bbb) ccc)
+    qq(aaa (bbb) ccc)
+  selections:
+    - anchor: {line: 0, character: 7}
+      active: {line: 0, character: 7}
+    - anchor: {line: 1, character: 7}
+      active: {line: 1, character: 7}
+    - anchor: {line: 2, character: 8}
+      active: {line: 2, character: 8}
+    - anchor: {line: 3, character: 9}
+      active: {line: 3, character: 9}
+  marks: {}
+finalState:
+  documentContents: |-
+    "aaa ccc"
+    'aaa ccc'
+    q(aaa ccc)
+    qq(aaa ccc)
+  selections:
+    - anchor: {line: 0, character: 5}
+      active: {line: 0, character: 5}
+    - anchor: {line: 1, character: 5}
+      active: {line: 1, character: 5}
+    - anchor: {line: 2, character: 6}
+      active: {line: 2, character: 6}
+    - anchor: {line: 3, character: 7}
+      active: {line: 3, character: 7}

From 580f55d9ab75ed27d6e7a63118a9afa8c8c2034c Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Mon, 13 Mar 2023 20:24:10 +0000
Subject: [PATCH 03/26] Remove all unused variables

---
 .../cursorless-engine/src/languages/perl.ts   | 71 -------------------
 1 file changed, 71 deletions(-)

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index ae8ff91c56..e1d66d711b 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -8,77 +8,6 @@ import { SyntaxNode } from "web-tree-sitter";
 import { getNodeRange, unwrapSelectionExtractor } from "../util/nodeSelectors";
 import { patternFinder } from "../util/nodeFinders";
 
-// Generated by the following command:
-// curl https://raw.githubusercontent.com/ganezdragon/tree-sitter-perl/ee1001210af5f32ba14d2ced834636548e1b6485/src/node-types.json \
-// | jq '[.[] | select(.type|match("_statement")) | .type ]'
-const STATEMENT_TYPES = [
-  "ellipsis_statement",
-  "for_simple_statement",
-  "for_statement_1",
-  "for_statement_2",
-  "foreach_simple_statement",
-  "foreach_statement",
-  "if_simple_statement",
-  "if_statement",
-  "loop_control_statement",
-  "loop_control_statement",
-  "named_block_statement",
-  "package_statement",
-  "pod_statement",
-  "require_statement",
-  "single_line_statement",
-  "unless_simple_statement",
-  "unless_statement",
-  "until_simple_statement",
-  "until_statement",
-  "use_constant_statement",
-  "use_no_feature_statement",
-  "use_no_if_statement",
-  "use_no_statement",
-  "use_no_subs_statement",
-  "use_parent_statement",
-  "when_simple_statement",
-  "while_simple_statement",
-  "while_statement",
-];
-
-const EXPRESSION_TYPES = [
-  "array",
-  "assignment",
-  "begin",
-  // MANY MORE TODO ...
-];
-
-const EXPRESSION_STATEMENT_PARENT_TYPES = [
-  "begin",
-  "block",
-  "do",
-  "do_block",
-  "else",
-  "lambda",
-  "method",
-  "then",
-];
-
-const assignmentOperators = [
-  "=",
-  "+=",
-  "-=",
-  "*=",
-  "/=",
-  "||=",
-  "//=",
-  "|=",
-  "&&=",
-  "&=",
-  "%=",
-  ">>=",
-  "<<=",
-  "^=",
-];
-
-const mapKeyValueSeparators = [",", "=>"];
-
 const nodeMatchers: Partial<
   Record<SimpleScopeTypeType, NodeMatcherAlternative>
 > = {

From 3175e6b72185a79ad0a2cc78e770c93fff5a873b Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Tue, 14 Mar 2023 20:36:06 +0000
Subject: [PATCH 04/26] Fix item in hash not selecting both key and value

---
 packages/cursorless-engine/src/languages/perl.ts          | 2 +-
 .../suite/fixtures/recorded/languages/perl/changeItem.yml | 4 ++--
 .../suite/fixtures/recorded/languages/perl/chuckItem.yml  | 8 ++++----
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index e1d66d711b..733aafc006 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -34,7 +34,7 @@ const nodeMatchers: Partial<
   anonymousFunction: "anonymous_function",
   regularExpression: ["regex_pattern", "regex_pattern_qr"],
   collectionKey: "*[key]", // TODO: child of "value: hash?"
-  collectionItem: "*[value]", // TODO: child of "value: hash?"
+  collectionItem: "hash[variable]",
   argumentOrParameter: [
     "empty_parenthesized_argument",
     "parenthesized_argument",
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
index 288f3afe7f..a5fd68ebdd 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
@@ -26,5 +26,5 @@ finalState:
         two => 2,
     );
   selections:
-    - anchor: {line: 1, character: 11}
-      active: {line: 1, character: 11}
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
index b8e5fc0b32..37f1c0be51 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
@@ -16,8 +16,8 @@ initialState:
         two => 2,
     );
   selections:
-    - anchor: {line: 2, character: 4}
-      active: {line: 2, character: 4}
+    - anchor: {line: 1, character: 8}
+      active: {line: 1, character: 8}
   marks: {}
 finalState:
   documentContents: |-
@@ -25,5 +25,5 @@ finalState:
         two => 2,
     );
   selections:
-    - anchor: {line: 2, character: 4}
-      active: {line: 2, character: 4}
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}

From c4c6afa0ab6fbe5a2598ebf90cb2eada70258a3c Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Wed, 22 Mar 2023 18:35:21 +0000
Subject: [PATCH 05/26] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 .../recorded/languages/perl/changeArg.yml     | 12 +++----
 .../recorded/languages/perl/changeCall.yml    | 12 +++----
 .../recorded/languages/perl/changeCall2.yml   | 12 +++----
 .../recorded/languages/perl/changeCall3.yml   | 12 +++----
 .../recorded/languages/perl/changeCall4.yml   | 12 +++----
 .../languages/perl/changeClassName.yml        | 12 +++----
 .../recorded/languages/perl/changeComment.yml | 12 +++----
 .../languages/perl/changeCondition.yml        | 20 +++++------
 .../recorded/languages/perl/changeFunk.yml    | 12 +++----
 .../recorded/languages/perl/changeFunk2.yml   | 12 +++----
 .../recorded/languages/perl/changeIfState.yml | 12 +++----
 .../recorded/languages/perl/changeItem.yml    | 12 +++----
 .../recorded/languages/perl/changeKey.yml     | 12 +++----
 .../recorded/languages/perl/changeLambda.yml  | 12 +++----
 .../recorded/languages/perl/changeList.yml    | 14 ++++----
 .../recorded/languages/perl/changeMap.yml     | 12 +++----
 .../recorded/languages/perl/changeRegex.yml   | 12 +++----
 .../recorded/languages/perl/changeRegex2.yml  | 12 +++----
 .../recorded/languages/perl/changeString.yml  | 12 +++----
 .../recorded/languages/perl/changeString2.yml | 12 +++----
 .../recorded/languages/perl/chuckItem.yml     | 12 +++----
 .../recorded/languages/perl/chuckRound.yml    | 36 +++++++++----------
 22 files changed, 149 insertions(+), 149 deletions(-)

diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
index e86433ba57..c794f7f681 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
@@ -2,21 +2,21 @@ languageId: perl
 command:
   version: 4
   spokenForm: change arg
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: argumentOrParameter}
+          scopeType: { type: argumentOrParameter }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: some_funky_func( "and", "three", "args" )
   selections:
-    - anchor: {line: 0, character: 36}
-      active: {line: 0, character: 36}
+    - anchor: { line: 0, character: 36 }
+      active: { line: 0, character: 36 }
   marks: {}
 finalState:
   documentContents: some_funky_func( "three", "args" )
   selections:
-    - anchor: {line: 0, character: 17}
-      active: {line: 0, character: 17}
+    - anchor: { line: 0, character: 17 }
+      active: { line: 0, character: 17 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml
index 7ec30f134f..ee458c0df9 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change call
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: functionCall}
+          scopeType: { type: functionCall }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,8 +15,8 @@ initialState:
     $var = func();
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 12}
-      active: {line: 1, character: 12}
+    - anchor: { line: 1, character: 12 }
+      active: { line: 1, character: 12 }
   marks: {}
 finalState:
   documentContents: |-
@@ -24,5 +24,5 @@ finalState:
     $var = ;
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 7}
-      active: {line: 1, character: 7}
+    - anchor: { line: 1, character: 7 }
+      active: { line: 1, character: 7 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml
index cee0884671..c03cc62a83 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change call
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: functionCall}
+          scopeType: { type: functionCall }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,8 +15,8 @@ initialState:
     $var = func_with_params( $var );
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 27}
-      active: {line: 1, character: 27}
+    - anchor: { line: 1, character: 27 }
+      active: { line: 1, character: 27 }
   marks: {}
 finalState:
   documentContents: |-
@@ -24,5 +24,5 @@ finalState:
     $var = ;
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 7}
-      active: {line: 1, character: 7}
+    - anchor: { line: 1, character: 7 }
+      active: { line: 1, character: 7 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml
index 8f07017062..3b0f16d9fa 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change call
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: functionCall}
+          scopeType: { type: functionCall }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,8 +15,8 @@ initialState:
     $var = $object->method( );
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 24}
-      active: {line: 1, character: 24}
+    - anchor: { line: 1, character: 24 }
+      active: { line: 1, character: 24 }
   marks: {}
 finalState:
   documentContents: |-
@@ -24,5 +24,5 @@ finalState:
     $var = ;
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 7}
-      active: {line: 1, character: 7}
+    - anchor: { line: 1, character: 7 }
+      active: { line: 1, character: 7 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml
index 41910dc0d5..f40999c7f8 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change call
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: functionCall}
+          scopeType: { type: functionCall }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,8 +15,8 @@ initialState:
     $var = Other::Package::func();
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 24}
-      active: {line: 1, character: 24}
+    - anchor: { line: 1, character: 24 }
+      active: { line: 1, character: 24 }
   marks: {}
 finalState:
   documentContents: |-
@@ -24,5 +24,5 @@ finalState:
     $var = ;
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 7}
-      active: {line: 1, character: 7}
+    - anchor: { line: 1, character: 7 }
+      active: { line: 1, character: 7 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml
index ad7d1d2ddf..ad7f5db974 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml
@@ -2,21 +2,21 @@ languageId: perl
 command:
   version: 4
   spokenForm: change class name
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: className}
+          scopeType: { type: className }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: $var = Some::Package::func()
   selections:
-    - anchor: {line: 0, character: 17}
-      active: {line: 0, character: 17}
+    - anchor: { line: 0, character: 17 }
+      active: { line: 0, character: 17 }
   marks: {}
 finalState:
   documentContents: $var = ::func()
   selections:
-    - anchor: {line: 0, character: 7}
-      active: {line: 0, character: 7}
+    - anchor: { line: 0, character: 7 }
+      active: { line: 0, character: 7 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml
index 7e953c6dc2..b1019973d2 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change comment
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: comment}
+          scopeType: { type: comment }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,8 +15,8 @@ initialState:
     # An actual comment
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 2}
-      active: {line: 1, character: 2}
+    - anchor: { line: 1, character: 2 }
+      active: { line: 1, character: 2 }
   marks: {}
 finalState:
   documentContents: |-
@@ -24,5 +24,5 @@ finalState:
 
     $var = 2;
   selections:
-    - anchor: {line: 1, character: 0}
-      active: {line: 1, character: 0}
+    - anchor: { line: 1, character: 0 }
+      active: { line: 1, character: 0 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml
index 5fd07db78c..206616cc86 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change condition
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: condition}
+          scopeType: { type: condition }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -19,10 +19,10 @@ initialState:
         # ...
     }
   selections:
-    - anchor: {line: 1, character: 4}
-      active: {line: 1, character: 4}
-    - anchor: {line: 5, character: 4}
-      active: {line: 5, character: 4}
+    - anchor: { line: 1, character: 4 }
+      active: { line: 1, character: 4 }
+    - anchor: { line: 5, character: 4 }
+      active: { line: 5, character: 4 }
   marks: {}
 finalState:
   documentContents: |-
@@ -34,7 +34,7 @@ finalState:
         # ...
     }
   selections:
-    - anchor: {line: 0, character: 7}
-      active: {line: 0, character: 7}
-    - anchor: {line: 4, character: 7}
-      active: {line: 4, character: 7}
+    - anchor: { line: 0, character: 7 }
+      active: { line: 0, character: 7 }
+    - anchor: { line: 4, character: 7 }
+      active: { line: 4, character: 7 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml
index 3d37dd993c..c25a1d9363 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change funk
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: namedFunction}
+          scopeType: { type: namedFunction }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,11 +15,11 @@ initialState:
         # ...
     }
   selections:
-    - anchor: {line: 1, character: 9}
-      active: {line: 1, character: 9}
+    - anchor: { line: 1, character: 9 }
+      active: { line: 1, character: 9 }
   marks: {}
 finalState:
   documentContents: ""
   selections:
-    - anchor: {line: 0, character: 0}
-      active: {line: 0, character: 0}
+    - anchor: { line: 0, character: 0 }
+      active: { line: 0, character: 0 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml
index 6ec56caedf..2fc0f0ebad 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change funk
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: namedFunction}
+          scopeType: { type: namedFunction }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,11 +15,11 @@ initialState:
         # ...
     }
   selections:
-    - anchor: {line: 1, character: 8}
-      active: {line: 1, character: 8}
+    - anchor: { line: 1, character: 8 }
+      active: { line: 1, character: 8 }
   marks: {}
 finalState:
   documentContents: ""
   selections:
-    - anchor: {line: 0, character: 0}
-      active: {line: 0, character: 0}
+    - anchor: { line: 0, character: 0 }
+      active: { line: 0, character: 0 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml
index 69607888b3..6cf0dd2c58 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml
@@ -2,21 +2,21 @@ languageId: perl
 command:
   version: 4
   spokenForm: change if state
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: ifStatement}
+          scopeType: { type: ifStatement }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: if ( $this_is_a_test ) { }
   selections:
-    - anchor: {line: 0, character: 24}
-      active: {line: 0, character: 24}
+    - anchor: { line: 0, character: 24 }
+      active: { line: 0, character: 24 }
   marks: {}
 finalState:
   documentContents: ""
   selections:
-    - anchor: {line: 0, character: 0}
-      active: {line: 0, character: 0}
+    - anchor: { line: 0, character: 0 }
+      active: { line: 0, character: 0 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
index a5fd68ebdd..7a61da8e3b 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change item
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: collectionItem}
+          scopeType: { type: collectionItem }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -16,8 +16,8 @@ initialState:
         two => 2,
     );
   selections:
-    - anchor: {line: 1, character: 7}
-      active: {line: 1, character: 7}
+    - anchor: { line: 1, character: 7 }
+      active: { line: 1, character: 7 }
   marks: {}
 finalState:
   documentContents: |-
@@ -26,5 +26,5 @@ finalState:
         two => 2,
     );
   selections:
-    - anchor: {line: 1, character: 4}
-      active: {line: 1, character: 4}
+    - anchor: { line: 1, character: 4 }
+      active: { line: 1, character: 4 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml
index 54647d41b2..557b5a00e6 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change key
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: collectionKey}
+          scopeType: { type: collectionKey }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -16,8 +16,8 @@ initialState:
         two => 2,
     );
   selections:
-    - anchor: {line: 1, character: 0}
-      active: {line: 1, character: 0}
+    - anchor: { line: 1, character: 0 }
+      active: { line: 1, character: 0 }
   marks: {}
 finalState:
   documentContents: |-
@@ -26,5 +26,5 @@ finalState:
         two => 2,
     );
   selections:
-    - anchor: {line: 1, character: 4}
-      active: {line: 1, character: 4}
+    - anchor: { line: 1, character: 4 }
+      active: { line: 1, character: 4 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml
index 3d3a8c43d2..23a0516012 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml
@@ -2,21 +2,21 @@ languageId: perl
 command:
   version: 4
   spokenForm: change lambda
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: anonymousFunction}
+          scopeType: { type: anonymousFunction }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: $func = sub { 1 };
   selections:
-    - anchor: {line: 0, character: 14}
-      active: {line: 0, character: 14}
+    - anchor: { line: 0, character: 14 }
+      active: { line: 0, character: 14 }
   marks: {}
 finalState:
   documentContents: $func = ;
   selections:
-    - anchor: {line: 0, character: 8}
-      active: {line: 0, character: 8}
+    - anchor: { line: 0, character: 8 }
+      active: { line: 0, character: 8 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml
index f514d833db..512ed08ed4 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml
@@ -2,21 +2,21 @@ languageId: perl
 command:
   version: 4
   spokenForm: change list
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: list}
+          scopeType: { type: list }
   usePrePhraseSnapshot: true
 initialState:
-  documentContents: "@array = ( \"one\", 2, \"_tres\" )"
+  documentContents: '@array = ( "one", 2, "_tres" )'
   selections:
-    - anchor: {line: 0, character: 18}
-      active: {line: 0, character: 18}
+    - anchor: { line: 0, character: 18 }
+      active: { line: 0, character: 18 }
   marks: {}
 finalState:
   documentContents: "@array = "
   selections:
-    - anchor: {line: 0, character: 9}
-      active: {line: 0, character: 9}
+    - anchor: { line: 0, character: 9 }
+      active: { line: 0, character: 9 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml
index 54f68606f2..627e987394 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change map
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: map}
+          scopeType: { type: map }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -16,11 +16,11 @@ initialState:
         second => "yup",
     )
   selections:
-    - anchor: {line: 1, character: 0}
-      active: {line: 1, character: 0}
+    - anchor: { line: 1, character: 0 }
+      active: { line: 1, character: 0 }
   marks: {}
 finalState:
   documentContents: "%hash = "
   selections:
-    - anchor: {line: 0, character: 8}
-      active: {line: 0, character: 8}
+    - anchor: { line: 0, character: 8 }
+      active: { line: 0, character: 8 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml
index 91b0d9ba11..a2ab17cb92 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml
@@ -2,21 +2,21 @@ languageId: perl
 command:
   version: 4
   spokenForm: change regex
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: regularExpression}
+          scopeType: { type: regularExpression }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: $str =~ /bananabeast/
   selections:
-    - anchor: {line: 0, character: 14}
-      active: {line: 0, character: 14}
+    - anchor: { line: 0, character: 14 }
+      active: { line: 0, character: 14 }
   marks: {}
 finalState:
   documentContents: $str =~ //
   selections:
-    - anchor: {line: 0, character: 9}
-      active: {line: 0, character: 9}
+    - anchor: { line: 0, character: 9 }
+      active: { line: 0, character: 9 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml
index a252a92572..119292d831 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml
@@ -2,21 +2,21 @@ languageId: perl
 command:
   version: 4
   spokenForm: change regex
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: regularExpression}
+          scopeType: { type: regularExpression }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: $str =~ qr/qr match/
   selections:
-    - anchor: {line: 0, character: 13}
-      active: {line: 0, character: 13}
+    - anchor: { line: 0, character: 13 }
+      active: { line: 0, character: 13 }
   marks: {}
 finalState:
   documentContents: "$str =~ "
   selections:
-    - anchor: {line: 0, character: 8}
-      active: {line: 0, character: 8}
+    - anchor: { line: 0, character: 8 }
+      active: { line: 0, character: 8 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml
index d4acbe9796..6a5cb274c8 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change string
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: surroundingPair, delimiter: string}
+          scopeType: { type: surroundingPair, delimiter: string }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,8 +15,8 @@ initialState:
         'single',
     );
   selections:
-    - anchor: {line: 1, character: 4}
-      active: {line: 1, character: 4}
+    - anchor: { line: 1, character: 4 }
+      active: { line: 1, character: 4 }
   marks: {}
 finalState:
   documentContents: |-
@@ -24,5 +24,5 @@ finalState:
         ,
     );
   selections:
-    - anchor: {line: 1, character: 4}
-      active: {line: 1, character: 4}
+    - anchor: { line: 1, character: 4 }
+      active: { line: 1, character: 4 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml
index 6c00098b76..c5f72e166c 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: change string
-  action: {name: clearAndSetSelection}
+  action: { name: clearAndSetSelection }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: surroundingPair, delimiter: string}
+          scopeType: { type: surroundingPair, delimiter: string }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -15,8 +15,8 @@ initialState:
         "double $some_var",
     );
   selections:
-    - anchor: {line: 1, character: 4}
-      active: {line: 1, character: 4}
+    - anchor: { line: 1, character: 4 }
+      active: { line: 1, character: 4 }
   marks: {}
 finalState:
   documentContents: |-
@@ -24,5 +24,5 @@ finalState:
         ,
     );
   selections:
-    - anchor: {line: 1, character: 4}
-      active: {line: 1, character: 4}
+    - anchor: { line: 1, character: 4 }
+      active: { line: 1, character: 4 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
index 37f1c0be51..c4c3fde441 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: chuck item
-  action: {name: remove}
+  action: { name: remove }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: collectionItem}
+          scopeType: { type: collectionItem }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -16,8 +16,8 @@ initialState:
         two => 2,
     );
   selections:
-    - anchor: {line: 1, character: 8}
-      active: {line: 1, character: 8}
+    - anchor: { line: 1, character: 8 }
+      active: { line: 1, character: 8 }
   marks: {}
 finalState:
   documentContents: |-
@@ -25,5 +25,5 @@ finalState:
         two => 2,
     );
   selections:
-    - anchor: {line: 1, character: 4}
-      active: {line: 1, character: 4}
+    - anchor: { line: 1, character: 4 }
+      active: { line: 1, character: 4 }
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
index 091dfd2cb7..8ebc853a56 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: chuck round
-  action: {name: remove}
+  action: { name: remove }
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: {type: surroundingPair, delimiter: parentheses}
+          scopeType: { type: surroundingPair, delimiter: parentheses }
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -16,14 +16,14 @@ initialState:
     q(aaa (bbb) ccc)
     qq(aaa (bbb) ccc)
   selections:
-    - anchor: {line: 0, character: 7}
-      active: {line: 0, character: 7}
-    - anchor: {line: 1, character: 7}
-      active: {line: 1, character: 7}
-    - anchor: {line: 2, character: 8}
-      active: {line: 2, character: 8}
-    - anchor: {line: 3, character: 9}
-      active: {line: 3, character: 9}
+    - anchor: { line: 0, character: 7 }
+      active: { line: 0, character: 7 }
+    - anchor: { line: 1, character: 7 }
+      active: { line: 1, character: 7 }
+    - anchor: { line: 2, character: 8 }
+      active: { line: 2, character: 8 }
+    - anchor: { line: 3, character: 9 }
+      active: { line: 3, character: 9 }
   marks: {}
 finalState:
   documentContents: |-
@@ -32,11 +32,11 @@ finalState:
     q(aaa ccc)
     qq(aaa ccc)
   selections:
-    - anchor: {line: 0, character: 5}
-      active: {line: 0, character: 5}
-    - anchor: {line: 1, character: 5}
-      active: {line: 1, character: 5}
-    - anchor: {line: 2, character: 6}
-      active: {line: 2, character: 6}
-    - anchor: {line: 3, character: 7}
-      active: {line: 3, character: 7}
+    - anchor: { line: 0, character: 5 }
+      active: { line: 0, character: 5 }
+    - anchor: { line: 1, character: 5 }
+      active: { line: 1, character: 5 }
+    - anchor: { line: 2, character: 6 }
+      active: { line: 2, character: 6 }
+    - anchor: { line: 3, character: 7 }
+      active: { line: 3, character: 7 }

From 2abccee2e315d6f49f93c1b977790e43445e0874 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Mon, 13 Mar 2023 20:22:48 +0000
Subject: [PATCH 06/26] Initial review notes: improve tests, few new tests

---
 .../recorded/languages/perl/chuckItem.yml     | 12 +++----
 .../recorded/languages/perl/chuckRound.yml    | 36 +++++++++----------
 2 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
index c4c3fde441..b8e5fc0b32 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: chuck item
-  action: { name: remove }
+  action: {name: remove}
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: { type: collectionItem }
+          scopeType: {type: collectionItem}
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -16,8 +16,8 @@ initialState:
         two => 2,
     );
   selections:
-    - anchor: { line: 1, character: 8 }
-      active: { line: 1, character: 8 }
+    - anchor: {line: 2, character: 4}
+      active: {line: 2, character: 4}
   marks: {}
 finalState:
   documentContents: |-
@@ -25,5 +25,5 @@ finalState:
         two => 2,
     );
   selections:
-    - anchor: { line: 1, character: 4 }
-      active: { line: 1, character: 4 }
+    - anchor: {line: 2, character: 4}
+      active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
index 8ebc853a56..091dfd2cb7 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
@@ -2,12 +2,12 @@ languageId: perl
 command:
   version: 4
   spokenForm: chuck round
-  action: { name: remove }
+  action: {name: remove}
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: { type: surroundingPair, delimiter: parentheses }
+          scopeType: {type: surroundingPair, delimiter: parentheses}
   usePrePhraseSnapshot: true
 initialState:
   documentContents: |-
@@ -16,14 +16,14 @@ initialState:
     q(aaa (bbb) ccc)
     qq(aaa (bbb) ccc)
   selections:
-    - anchor: { line: 0, character: 7 }
-      active: { line: 0, character: 7 }
-    - anchor: { line: 1, character: 7 }
-      active: { line: 1, character: 7 }
-    - anchor: { line: 2, character: 8 }
-      active: { line: 2, character: 8 }
-    - anchor: { line: 3, character: 9 }
-      active: { line: 3, character: 9 }
+    - anchor: {line: 0, character: 7}
+      active: {line: 0, character: 7}
+    - anchor: {line: 1, character: 7}
+      active: {line: 1, character: 7}
+    - anchor: {line: 2, character: 8}
+      active: {line: 2, character: 8}
+    - anchor: {line: 3, character: 9}
+      active: {line: 3, character: 9}
   marks: {}
 finalState:
   documentContents: |-
@@ -32,11 +32,11 @@ finalState:
     q(aaa ccc)
     qq(aaa ccc)
   selections:
-    - anchor: { line: 0, character: 5 }
-      active: { line: 0, character: 5 }
-    - anchor: { line: 1, character: 5 }
-      active: { line: 1, character: 5 }
-    - anchor: { line: 2, character: 6 }
-      active: { line: 2, character: 6 }
-    - anchor: { line: 3, character: 7 }
-      active: { line: 3, character: 7 }
+    - anchor: {line: 0, character: 5}
+      active: {line: 0, character: 5}
+    - anchor: {line: 1, character: 5}
+      active: {line: 1, character: 5}
+    - anchor: {line: 2, character: 6}
+      active: {line: 2, character: 6}
+    - anchor: {line: 3, character: 7}
+      active: {line: 3, character: 7}

From b406a4806855056d92ec04355ed05946b86de975 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Tue, 14 Mar 2023 20:36:06 +0000
Subject: [PATCH 07/26] Fix item in hash not selecting both key and value

---
 .../suite/fixtures/recorded/languages/perl/changeItem.yml | 4 ++--
 .../suite/fixtures/recorded/languages/perl/chuckItem.yml  | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
index 7a61da8e3b..cd1db2e350 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
@@ -26,5 +26,5 @@ finalState:
         two => 2,
     );
   selections:
-    - anchor: { line: 1, character: 4 }
-      active: { line: 1, character: 4 }
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
index b8e5fc0b32..37f1c0be51 100644
--- a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
+++ b/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
@@ -16,8 +16,8 @@ initialState:
         two => 2,
     );
   selections:
-    - anchor: {line: 2, character: 4}
-      active: {line: 2, character: 4}
+    - anchor: {line: 1, character: 8}
+      active: {line: 1, character: 8}
   marks: {}
 finalState:
   documentContents: |-
@@ -25,5 +25,5 @@ finalState:
         two => 2,
     );
   selections:
-    - anchor: {line: 2, character: 4}
-      active: {line: 2, character: 4}
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}

From 2a709599bc67e571ffb7b2aec2a0e407799d65fb Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Thu, 23 Mar 2023 21:43:20 +0000
Subject: [PATCH 08/26] Move perl tests to match pnpm updates

---
 .../suite/fixtures/recorded/languages/perl/changeArg.yml          | 0
 .../suite/fixtures/recorded/languages/perl/changeCall.yml         | 0
 .../suite/fixtures/recorded/languages/perl/changeCall2.yml        | 0
 .../suite/fixtures/recorded/languages/perl/changeCall3.yml        | 0
 .../suite/fixtures/recorded/languages/perl/changeCall4.yml        | 0
 .../suite/fixtures/recorded/languages/perl/changeClassName.yml    | 0
 .../suite/fixtures/recorded/languages/perl/changeComment.yml      | 0
 .../suite/fixtures/recorded/languages/perl/changeCondition.yml    | 0
 .../suite/fixtures/recorded/languages/perl/changeFunk.yml         | 0
 .../suite/fixtures/recorded/languages/perl/changeFunk2.yml        | 0
 .../suite/fixtures/recorded/languages/perl/changeIfState.yml      | 0
 .../suite/fixtures/recorded/languages/perl/changeItem.yml         | 0
 .../suite/fixtures/recorded/languages/perl/changeKey.yml          | 0
 .../suite/fixtures/recorded/languages/perl/changeLambda.yml       | 0
 .../suite/fixtures/recorded/languages/perl/changeList.yml         | 0
 .../suite/fixtures/recorded/languages/perl/changeMap.yml          | 0
 .../suite/fixtures/recorded/languages/perl/changeRegex.yml        | 0
 .../suite/fixtures/recorded/languages/perl/changeRegex2.yml       | 0
 .../suite/fixtures/recorded/languages/perl/changeString.yml       | 0
 .../suite/fixtures/recorded/languages/perl/changeString2.yml      | 0
 .../suite/fixtures/recorded/languages/perl/chuckItem.yml          | 0
 .../suite/fixtures/recorded/languages/perl/chuckRound.yml         | 0
 22 files changed, 0 insertions(+), 0 deletions(-)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeArg.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeCall.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeCall2.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeCall3.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeCall4.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeClassName.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeComment.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeCondition.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeFunk.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeFunk2.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeIfState.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeItem.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeKey.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeLambda.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeList.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeMap.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeRegex.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeRegex2.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeString.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/changeString2.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/chuckItem.yml (100%)
 rename packages/cursorless-vscode-e2e/{ => src}/suite/fixtures/recorded/languages/perl/chuckRound.yml (100%)

diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeArg.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCall.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCall.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCall2.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall2.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCall2.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCall3.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall3.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCall3.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCall4.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCall4.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCall4.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeClassName.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeComment.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeComment.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeComment.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeCondition.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeFunk.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeFunk.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeFunk2.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeFunk2.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeFunk2.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeIfState.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeIfState.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeIfState.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeItem.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeItem.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeItem.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeKey.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeLambda.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeLambda.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeLambda.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeList.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeList.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeList.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeMap.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeMap.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeMap.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex2.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeRegex2.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex2.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeString.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeString.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeString2.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/changeString2.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeString2.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckItem.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckItem.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckItem.yml
diff --git a/packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckRound.yml
similarity index 100%
rename from packages/cursorless-vscode-e2e/suite/fixtures/recorded/languages/perl/chuckRound.yml
rename to packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckRound.yml

From fa494ec0af3892f2d90d276c719292f5d84b878f Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Thu, 23 Mar 2023 23:58:53 +0000
Subject: [PATCH 09/26] Better regular expression handling and more tests

Now supports more regular expression syntaxes, and it captures all of
the regular expressionas as long as there are no whitespace comments
in these regular expressions (not yet supported by the parser).
---
 .../cursorless-engine/src/languages/perl.ts   |  7 +-
 .../recorded/languages/perl/changeRegex.yml   | 14 ++--
 .../recorded/languages/perl/changeRegex3.yml  | 66 +++++++++++++++++++
 .../recorded/languages/perl/changeRegex4.yml  | 66 +++++++++++++++++++
 4 files changed, 145 insertions(+), 8 deletions(-)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex3.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex4.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 733aafc006..fb8040ae38 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -32,7 +32,12 @@ const nodeMatchers: Partial<
   comment: "comments",
   namedFunction: ["function_definition"],
   anonymousFunction: "anonymous_function",
-  regularExpression: ["regex_pattern", "regex_pattern_qr"],
+  regularExpression: [
+    "patter_matcher_m", // Mistype (?) but that is the name in tree-sitter-perl; it must come before pattern_matcher
+    "pattern_matcher",
+    "regex_pattern_qr",
+    "substitution_pattern_s",
+  ],
   collectionKey: "*[key]", // TODO: child of "value: hash?"
   collectionItem: "hash[variable]",
   argumentOrParameter: [
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex.yml
index a2ab17cb92..7db125dd4d 100644
--- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex.yml
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex.yml
@@ -2,21 +2,21 @@ languageId: perl
 command:
   version: 4
   spokenForm: change regex
-  action: { name: clearAndSetSelection }
+  action: {name: clearAndSetSelection}
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: { type: regularExpression }
+          scopeType: {type: regularExpression}
   usePrePhraseSnapshot: true
 initialState:
   documentContents: $str =~ /bananabeast/
   selections:
-    - anchor: { line: 0, character: 14 }
-      active: { line: 0, character: 14 }
+    - anchor: {line: 0, character: 15}
+      active: {line: 0, character: 15}
   marks: {}
 finalState:
-  documentContents: $str =~ //
+  documentContents: "$str =~ "
   selections:
-    - anchor: { line: 0, character: 9 }
-      active: { line: 0, character: 9 }
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex3.yml
new file mode 100644
index 0000000000..933266c7f8
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex3.yml
@@ -0,0 +1,66 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change regex
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: regularExpression}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    $ccc =~ s{cccc}{dddd};
+    $aaa =~ s/aaaa/bbbb/ms;
+    $ddd =~ qr/foooooooo/x;
+    $ddd =~ qr/foooooool/;
+    $eee =~ /foobarbeast/;
+    $fff =~ m/fuballbob/i;
+    $ggg =~ /foobarbeast/msx;
+    $hhh =~ m!/some/path/!;
+  selections:
+    - anchor: {line: 0, character: 12}
+      active: {line: 0, character: 12}
+    - anchor: {line: 1, character: 13}
+      active: {line: 1, character: 13}
+    - anchor: {line: 2, character: 13}
+      active: {line: 2, character: 13}
+    - anchor: {line: 3, character: 13}
+      active: {line: 3, character: 13}
+    - anchor: {line: 4, character: 12}
+      active: {line: 4, character: 12}
+    - anchor: {line: 5, character: 13}
+      active: {line: 5, character: 13}
+    - anchor: {line: 6, character: 12}
+      active: {line: 6, character: 12}
+    - anchor: {line: 7, character: 12}
+      active: {line: 7, character: 12}
+  marks: {}
+finalState:
+  documentContents: |-
+    $ccc =~ ;
+    $aaa =~ ;
+    $ddd =~ ;
+    $ddd =~ ;
+    $eee =~ ;
+    $fff =~ ;
+    $ggg =~ ;
+    $hhh =~ ;
+  selections:
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}
+    - anchor: {line: 1, character: 8}
+      active: {line: 1, character: 8}
+    - anchor: {line: 2, character: 8}
+      active: {line: 2, character: 8}
+    - anchor: {line: 3, character: 8}
+      active: {line: 3, character: 8}
+    - anchor: {line: 4, character: 8}
+      active: {line: 4, character: 8}
+    - anchor: {line: 5, character: 8}
+      active: {line: 5, character: 8}
+    - anchor: {line: 6, character: 8}
+      active: {line: 6, character: 8}
+    - anchor: {line: 7, character: 8}
+      active: {line: 7, character: 8}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex4.yml
new file mode 100644
index 0000000000..47ad4f17b7
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeRegex4.yml
@@ -0,0 +1,66 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change regex
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: regularExpression}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    $ccc =~ s{cccc}{dddd};
+    $aaa =~ s/aaaa/bbbb/ms;
+    $ddd =~ qr/foooooooo/x;
+    $ddd =~ qr/foooooool/;
+    $eee =~ /foobarbeast/;
+    $fff =~ m/fuballbob/i;
+    $ggg =~ /foobarbeast/msx;
+    $hhh =~ m!/some/path/!;
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+    - anchor: {line: 1, character: 18}
+      active: {line: 1, character: 18}
+    - anchor: {line: 2, character: 18}
+      active: {line: 2, character: 18}
+    - anchor: {line: 3, character: 18}
+      active: {line: 3, character: 18}
+    - anchor: {line: 4, character: 17}
+      active: {line: 4, character: 17}
+    - anchor: {line: 5, character: 18}
+      active: {line: 5, character: 18}
+    - anchor: {line: 6, character: 17}
+      active: {line: 6, character: 17}
+    - anchor: {line: 7, character: 17}
+      active: {line: 7, character: 17}
+  marks: {}
+finalState:
+  documentContents: |-
+    $ccc =~ ;
+    $aaa =~ ;
+    $ddd =~ ;
+    $ddd =~ ;
+    $eee =~ ;
+    $fff =~ ;
+    $ggg =~ ;
+    $hhh =~ ;
+  selections:
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}
+    - anchor: {line: 1, character: 8}
+      active: {line: 1, character: 8}
+    - anchor: {line: 2, character: 8}
+      active: {line: 2, character: 8}
+    - anchor: {line: 3, character: 8}
+      active: {line: 3, character: 8}
+    - anchor: {line: 4, character: 8}
+      active: {line: 4, character: 8}
+    - anchor: {line: 5, character: 8}
+      active: {line: 5, character: 8}
+    - anchor: {line: 6, character: 8}
+      active: {line: 6, character: 8}
+    - anchor: {line: 7, character: 8}
+      active: {line: 7, character: 8}

From fe039a6fe15599a4db051aa0e84c539233e83668 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sun, 26 Mar 2023 11:20:43 +0100
Subject: [PATCH 10/26] Fix "class name"

---
 .../cursorless-engine/src/languages/perl.ts   |  2 +-
 .../languages/perl/changeClassName.yml        | 14 ++----
 .../languages/perl/changeClassName2.yml       | 46 +++++++++++++++++++
 .../languages/perl/changeClassName3.yml       | 46 +++++++++++++++++++
 4 files changed, 98 insertions(+), 10 deletions(-)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName2.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName3.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index fb8040ae38..3acc61dca1 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -45,7 +45,7 @@ const nodeMatchers: Partial<
     "parenthesized_argument",
     "argument",
   ],
-  className: "package_name",
+  className: "package_statement.package_name!",
 };
 
 export const patternMatchers = createPatternMatchers(nodeMatchers);
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName.yml
index ad7f5db974..d31262a779 100644
--- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName.yml
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName.yml
@@ -2,21 +2,17 @@ languageId: perl
 command:
   version: 4
   spokenForm: change class name
-  action: { name: clearAndSetSelection }
+  action: {name: clearAndSetSelection}
   targets:
     - type: primitive
       modifiers:
         - type: containingScope
-          scopeType: { type: className }
+          scopeType: {type: className}
   usePrePhraseSnapshot: true
 initialState:
   documentContents: $var = Some::Package::func()
   selections:
-    - anchor: { line: 0, character: 17 }
-      active: { line: 0, character: 17 }
+    - anchor: {line: 0, character: 23}
+      active: {line: 0, character: 23}
   marks: {}
-finalState:
-  documentContents: $var = ::func()
-  selections:
-    - anchor: { line: 0, character: 7 }
-      active: { line: 0, character: 7 }
+thrownError: {name: NoContainingScopeError}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName2.yml
new file mode 100644
index 0000000000..b99e6f531b
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName2.yml
@@ -0,0 +1,46 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change class name
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: className}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 13}
+      active: {line: 0, character: 13}
+    - anchor: {line: 5, character: 19}
+      active: {line: 5, character: 19}
+  marks: {}
+finalState:
+  documentContents: |-
+    package ;
+
+    # A sort of
+    # comment
+
+    package  {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}
+    - anchor: {line: 5, character: 8}
+      active: {line: 5, character: 8}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName3.yml
new file mode 100644
index 0000000000..4281a40471
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName3.yml
@@ -0,0 +1,46 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change class name
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: className}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 5, character: 2}
+      active: {line: 5, character: 2}
+    - anchor: {line: 0, character: 2}
+      active: {line: 0, character: 2}
+  marks: {}
+finalState:
+  documentContents: |-
+    package ;
+
+    # A sort of
+    # comment
+
+    package  {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}
+    - anchor: {line: 5, character: 8}
+      active: {line: 5, character: 8}

From 8de6370cc864b9a9ff4da9c7e818b8e86dde6ff1 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sun, 26 Mar 2023 11:21:52 +0100
Subject: [PATCH 11/26] Remove TODO comment

---
 packages/cursorless-engine/src/languages/perl.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 3acc61dca1..48ec737e8c 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -38,7 +38,7 @@ const nodeMatchers: Partial<
     "regex_pattern_qr",
     "substitution_pattern_s",
   ],
-  collectionKey: "*[key]", // TODO: child of "value: hash?"
+  collectionKey: "*[key]",
   collectionItem: "hash[variable]",
   argumentOrParameter: [
     "empty_parenthesized_argument",

From e8ed2bcd0a14360a35c919940bd8610a8b98cdc0 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Sun, 26 Mar 2023 12:00:36 +0000
Subject: [PATCH 12/26] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 packages/cursorless-engine/src/languages/perl.ts | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 48ec737e8c..8c2ce900cd 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -1,7 +1,4 @@
-import {
-  createPatternMatchers,
-  matcher,
-} from "../util/nodeMatchers";
+import { createPatternMatchers, matcher } from "../util/nodeMatchers";
 import { NodeMatcherAlternative, SelectionWithEditor } from "../typings/Types";
 import { SimpleScopeTypeType } from "@cursorless/common";
 import { SyntaxNode } from "web-tree-sitter";

From 726cab0d59c37787f245ef1f8bbe1a583b8f18c3 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sun, 26 Mar 2023 22:36:24 +0100
Subject: [PATCH 13/26] Fix `arg` and more tests

Was not yet working after the tree-sitter-perl updates, now fixed to
match the behaviour of the python implementation.
---
 .../cursorless-engine/src/languages/perl.ts   | 11 ++--------
 .../recorded/languages/perl/changeArg.yml     |  6 ++---
 .../recorded/languages/perl/changeArg2.yml    | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeArg3.yml    | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeArg4.yml    | 22 +++++++++++++++++++
 5 files changed, 71 insertions(+), 12 deletions(-)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg2.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg3.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg4.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 48ec737e8c..463301f4a0 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -1,7 +1,4 @@
-import {
-  createPatternMatchers,
-  matcher,
-} from "../util/nodeMatchers";
+import { createPatternMatchers, matcher } from "../util/nodeMatchers";
 import { NodeMatcherAlternative, SelectionWithEditor } from "../typings/Types";
 import { SimpleScopeTypeType } from "@cursorless/common";
 import { SyntaxNode } from "web-tree-sitter";
@@ -40,11 +37,7 @@ const nodeMatchers: Partial<
   ],
   collectionKey: "*[key]",
   collectionItem: "hash[variable]",
-  argumentOrParameter: [
-    "empty_parenthesized_argument",
-    "parenthesized_argument",
-    "argument",
-  ],
+  argumentOrParameter: ["argument", "parenthesized_argument.arguments!"],
   className: "package_statement.package_name!",
 };
 
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg.yml
index c794f7f681..abc8fb2894 100644
--- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg.yml
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg.yml
@@ -16,7 +16,7 @@ initialState:
       active: { line: 0, character: 36 }
   marks: {}
 finalState:
-  documentContents: some_funky_func( "three", "args" )
+  documentContents: some_funky_func( "and", "three",  )
   selections:
-    - anchor: { line: 0, character: 17 }
-      active: { line: 0, character: 17 }
+    - anchor: { line: 0, character: 33 }
+      active: { line: 0, character: 33 }
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg2.yml
new file mode 100644
index 0000000000..a2b33ff40e
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg2.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change arg
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: argumentOrParameter}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: some_funky_func( "and", "three", "args" )
+  selections:
+    - anchor: {line: 0, character: 29}
+      active: {line: 0, character: 29}
+  marks: {}
+finalState:
+  documentContents: some_funky_func( "and", , "args" )
+  selections:
+    - anchor: {line: 0, character: 24}
+      active: {line: 0, character: 24}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg3.yml
new file mode 100644
index 0000000000..fe7e8a8c84
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg3.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change arg
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: argumentOrParameter}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: some_funky_func( "and", "three", "args" )
+  selections:
+    - anchor: {line: 0, character: 21}
+      active: {line: 0, character: 21}
+  marks: {}
+finalState:
+  documentContents: some_funky_func( , "three", "args" )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg4.yml
new file mode 100644
index 0000000000..d51e233ab6
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg4.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change arg
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: argumentOrParameter}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: some_funky_func( "and", "three", "args" )
+  selections:
+    - anchor: {line: 0, character: 16}
+      active: {line: 0, character: 16}
+  marks: {}
+finalState:
+  documentContents: some_funky_func(  )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}

From f7a0fcfbe5e5381bb9ffe0c694782d687d605277 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sun, 26 Mar 2023 23:39:20 +0100
Subject: [PATCH 14/26] Fix hash key matching, add more tests

---
 .../cursorless-engine/src/languages/perl.ts   |  2 +-
 .../recorded/languages/perl/changeKey.yml     |  4 +--
 .../recorded/languages/perl/changeKey2.yml    | 30 +++++++++++++++++++
 .../recorded/languages/perl/changeKey3.yml    | 30 +++++++++++++++++++
 .../recorded/languages/perl/changeKey4.yml    | 30 +++++++++++++++++++
 5 files changed, 93 insertions(+), 3 deletions(-)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey2.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey3.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey4.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 463301f4a0..250e89e26e 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -35,7 +35,7 @@ const nodeMatchers: Partial<
     "regex_pattern_qr",
     "substitution_pattern_s",
   ],
-  collectionKey: "*[key]",
+  collectionKey: "key_value_pair[key]",
   collectionItem: "hash[variable]",
   argumentOrParameter: ["argument", "parenthesized_argument.arguments!"],
   className: "package_statement.package_name!",
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey.yml
index 557b5a00e6..6f7b6b9d48 100644
--- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey.yml
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey.yml
@@ -16,8 +16,8 @@ initialState:
         two => 2,
     );
   selections:
-    - anchor: { line: 1, character: 0 }
-      active: { line: 1, character: 0 }
+    - anchor: { line: 1, character: 10 }
+      active: { line: 1, character: 10 }
   marks: {}
 finalState:
   documentContents: |-
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey2.yml
new file mode 100644
index 0000000000..d097898bb1
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey2.yml
@@ -0,0 +1,30 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change key
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: collectionKey}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    %hash = (
+        one => 1,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 2, character: 11}
+      active: {line: 2, character: 11}
+  marks: {}
+finalState:
+  documentContents: |-
+    %hash = (
+        one => 1,
+         => 2,
+    );
+  selections:
+    - anchor: {line: 2, character: 4}
+      active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey3.yml
new file mode 100644
index 0000000000..68f8d169b7
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey3.yml
@@ -0,0 +1,30 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change key
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: collectionKey}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    %hash = (
+        one => 1,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 2, character: 8}
+      active: {line: 2, character: 8}
+  marks: {}
+finalState:
+  documentContents: |-
+    %hash = (
+        one => 1,
+         => 2,
+    );
+  selections:
+    - anchor: {line: 2, character: 4}
+      active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey4.yml
new file mode 100644
index 0000000000..f6ea0528e7
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeKey4.yml
@@ -0,0 +1,30 @@
+languageId: perl
+command:
+  version: 4
+  spokenForm: change key
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: collectionKey}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    %hash = (
+        one => 1,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 1, character: 5}
+      active: {line: 1, character: 5}
+  marks: {}
+finalState:
+  documentContents: |-
+    %hash = (
+         => 1,
+        two => 2,
+    );
+  selections:
+    - anchor: {line: 1, character: 4}
+      active: {line: 1, character: 4}

From 3596f1f20f23b5c3fd335b5889d377a7c6286c34 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Mon, 27 Mar 2023 00:31:33 +0100
Subject: [PATCH 15/26] Simplify string processing; make chuck round work

The implemented string parser wasn't working well enough; back to the
default parser which doesn't handle all Perl strings well either, but
it can at least support `round`. Future improvement is likely to happen.
---
 .../src/languages/getTextFragmentExtractor.ts  |  6 +-----
 .../cursorless-engine/src/languages/perl.ts    | 18 ++----------------
 .../recorded/languages/perl/chuckRound.yml     | 12 ------------
 3 files changed, 3 insertions(+), 33 deletions(-)

diff --git a/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts b/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts
index 25446e431d..c7105deab0 100644
--- a/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts
+++ b/packages/cursorless-engine/src/languages/getTextFragmentExtractor.ts
@@ -7,7 +7,6 @@ import { SupportedLanguageId } from "./constants";
 import { getNodeMatcher } from "./getNodeMatcher";
 import { stringTextFragmentExtractor as htmlStringTextFragmentExtractor } from "./html";
 import { stringTextFragmentExtractor as jsonStringTextFragmentExtractor } from "./json";
-import { stringTextFragmentExtractor as perlStringTextFragmentExtractor } from "./perl";
 import { stringTextFragmentExtractor as phpStringTextFragmentExtractor } from "./php";
 import { stringTextFragmentExtractor as rubyStringTextFragmentExtractor } from "./ruby";
 import { stringTextFragmentExtractor as scssStringTextFragmentExtractor } from "./scss";
@@ -163,10 +162,7 @@ const textFragmentExtractors: Record<
   ),
   latex: fullDocumentTextFragmentExtractor,
   markdown: fullDocumentTextFragmentExtractor,
-  perl: constructDefaultTextFragmentExtractor(
-    "perl",
-    perlStringTextFragmentExtractor,
-  ),
+  perl: constructDefaultTextFragmentExtractor("perl"),
   php: constructDefaultTextFragmentExtractor(
     "php",
     phpStringTextFragmentExtractor,
diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 250e89e26e..7ec3d5dc19 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -1,8 +1,7 @@
 import { createPatternMatchers, matcher } from "../util/nodeMatchers";
-import { NodeMatcherAlternative, SelectionWithEditor } from "../typings/Types";
+import { NodeMatcherAlternative } from "../typings/Types";
 import { SimpleScopeTypeType } from "@cursorless/common";
-import { SyntaxNode } from "web-tree-sitter";
-import { getNodeRange, unwrapSelectionExtractor } from "../util/nodeSelectors";
+import { unwrapSelectionExtractor } from "../util/nodeSelectors";
 import { patternFinder } from "../util/nodeFinders";
 
 const nodeMatchers: Partial<
@@ -42,16 +41,3 @@ const nodeMatchers: Partial<
 };
 
 export const patternMatchers = createPatternMatchers(nodeMatchers);
-
-export function stringTextFragmentExtractor(
-  node: SyntaxNode,
-  _selection: SelectionWithEditor,
-) {
-  // heredoc_content does not seem to supported by tree-sitter-perl,
-  // leaving it anyway since it won't hurt
-  if (node.type === "string_content" || node.type === "heredoc_content") {
-    return getNodeRange(node);
-  }
-
-  return null;
-}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckRound.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckRound.yml
index 091dfd2cb7..c6c08d2ea1 100644
--- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckRound.yml
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckRound.yml
@@ -13,30 +13,18 @@ initialState:
   documentContents: |-
     "aaa (bbb) ccc"
     'aaa (bbb) ccc'
-    q(aaa (bbb) ccc)
-    qq(aaa (bbb) ccc)
   selections:
     - anchor: {line: 0, character: 7}
       active: {line: 0, character: 7}
     - anchor: {line: 1, character: 7}
       active: {line: 1, character: 7}
-    - anchor: {line: 2, character: 8}
-      active: {line: 2, character: 8}
-    - anchor: {line: 3, character: 9}
-      active: {line: 3, character: 9}
   marks: {}
 finalState:
   documentContents: |-
     "aaa ccc"
     'aaa ccc'
-    q(aaa ccc)
-    qq(aaa ccc)
   selections:
     - anchor: {line: 0, character: 5}
       active: {line: 0, character: 5}
     - anchor: {line: 1, character: 5}
       active: {line: 1, character: 5}
-    - anchor: {line: 2, character: 6}
-      active: {line: 2, character: 6}
-    - anchor: {line: 3, character: 7}
-      active: {line: 3, character: 7}

From 521f2e04e633f83e299407b9b5ffed04fecfefde Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Tue, 28 Mar 2023 00:23:06 +0100
Subject: [PATCH 16/26] Implement `callee`

Not perfect yet, some complicated constructs do not work well yet and
I'm not sure yet if it is me or the parser. Here is some exotic,
syntactically valid Perl:

```perl
foo( $bar )->bar();
foo( $bar )->bar( $baz );
```
---
 .../cursorless-engine/src/languages/perl.ts   | 22 +++++++++++++++++--
 .../recorded/languages/perl/changeCallee.yml  | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeCallee2.yml | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeCallee3.yml | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeCallee4.yml | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeCallee5.yml | 22 +++++++++++++++++++
 6 files changed, 130 insertions(+), 2 deletions(-)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee2.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee3.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee4.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee5.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 7ec3d5dc19..9679a0f61d 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -1,7 +1,15 @@
-import { createPatternMatchers, matcher } from "../util/nodeMatchers";
+import {
+  cascadingMatcher,
+  createPatternMatchers,
+  matcher,
+  patternMatcher,
+} from "../util/nodeMatchers";
 import { NodeMatcherAlternative } from "../typings/Types";
 import { SimpleScopeTypeType } from "@cursorless/common";
-import { unwrapSelectionExtractor } from "../util/nodeSelectors";
+import {
+  childRangeSelector,
+  unwrapSelectionExtractor,
+} from "../util/nodeSelectors";
 import { patternFinder } from "../util/nodeFinders";
 
 const nodeMatchers: Partial<
@@ -25,6 +33,16 @@ const nodeMatchers: Partial<
     "call_expression_with_just_name",
     "method_invocation",
   ],
+  functionCallee: cascadingMatcher(
+    patternMatcher("call_expression_with_just_name"),
+    matcher(
+      patternFinder("call_expression", "method_invocation"),
+      childRangeSelector(
+        ["arguments", "argument", "empty_parenthesized_argument"],
+        [],
+      ),
+    ),
+  ),
   comment: "comments",
   namedFunction: ["function_definition"],
   anonymousFunction: "anonymous_function",
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee.yml
new file mode 100644
index 0000000000..d4f923c2c2
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change callee
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCallee}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: foo();
+  selections:
+    - anchor: {line: 0, character: 4}
+      active: {line: 0, character: 4}
+  marks: {}
+finalState:
+  documentContents: ();
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee2.yml
new file mode 100644
index 0000000000..2986292c9f
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee2.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change callee
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCallee}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: $thing->method( $arg );
+  selections:
+    - anchor: {line: 0, character: 19}
+      active: {line: 0, character: 19}
+  marks: {}
+finalState:
+  documentContents: ( $arg );
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee3.yml
new file mode 100644
index 0000000000..8b843b4b64
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee3.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change callee
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCallee}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: $foo->();
+  selections:
+    - anchor: {line: 0, character: 7}
+      active: {line: 0, character: 7}
+  marks: {}
+finalState:
+  documentContents: ();
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee4.yml
new file mode 100644
index 0000000000..3d07eb3ab4
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee4.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change callee
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCallee}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: foo( $bar );
+  selections:
+    - anchor: {line: 0, character: 7}
+      active: {line: 0, character: 7}
+  marks: {}
+finalState:
+  documentContents: ( $bar );
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee5.yml
new file mode 100644
index 0000000000..cbc3a822a5
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCallee5.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change callee
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: functionCallee}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: Package::Foo->new( $thing );
+  selections:
+    - anchor: {line: 0, character: 23}
+      active: {line: 0, character: 23}
+  marks: {}
+finalState:
+  documentContents: ( $thing );
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}

From 12a1a223c4d8dfdb00b9d41f3564e803e9cf8c2b Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Tue, 4 Apr 2023 00:36:45 +0100
Subject: [PATCH 17/26] Add class support

For the file or a file scoped package declaration, takes the entire
file. For a lexically scoped package, takes just that package.

Should ideally take all of the file but not any packages declared within
to be "correct", but that is not yet possible.
---
 .../cursorless-engine/src/languages/perl.ts   |  6 +++
 .../recorded/languages/perl/takeClass.yml     | 44 +++++++++++++++++++
 .../recorded/languages/perl/takeClass2.yml    | 44 +++++++++++++++++++
 .../recorded/languages/perl/takeClass3.yml    | 44 +++++++++++++++++++
 .../recorded/languages/perl/takeClass4.yml    | 44 +++++++++++++++++++
 .../recorded/languages/perl/takeClass5.yml    | 44 +++++++++++++++++++
 .../recorded/languages/perl/takeClass6.yml    | 44 +++++++++++++++++++
 .../recorded/languages/perl/takeClass7.yml    | 44 +++++++++++++++++++
 .../recorded/languages/perl/takeClass8.yml    | 34 ++++++++++++++
 9 files changed, 348 insertions(+)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass2.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass3.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass4.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass5.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass6.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass7.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass8.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 9679a0f61d..7fb189b26c 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -55,6 +55,12 @@ const nodeMatchers: Partial<
   collectionKey: "key_value_pair[key]",
   collectionItem: "hash[variable]",
   argumentOrParameter: ["argument", "parenthesized_argument.arguments!"],
+  class: [
+    "package_statement!.block[body]",
+    "source_file!.package_statement",
+    "package_statement",
+    "source_file",
+  ],
   className: "package_statement.package_name!",
 };
 
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass.yml
new file mode 100644
index 0000000000..e7f7d8581a
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass.yml
@@ -0,0 +1,44 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: take class
+  action: {name: setSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: class}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 3}
+      active: {line: 0, character: 3}
+  marks: {}
+finalState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 10, character: 2}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass2.yml
new file mode 100644
index 0000000000..95620c5fed
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass2.yml
@@ -0,0 +1,44 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: take class
+  action: {name: setSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: class}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+  marks: {}
+finalState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 10, character: 2}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass3.yml
new file mode 100644
index 0000000000..f7ffb36154
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass3.yml
@@ -0,0 +1,44 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: take class
+  action: {name: setSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: class}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 2, character: 6}
+      active: {line: 2, character: 6}
+  marks: {}
+finalState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 10, character: 2}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass4.yml
new file mode 100644
index 0000000000..0bc321c49b
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass4.yml
@@ -0,0 +1,44 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: take class
+  action: {name: setSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: class}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 6, character: 21}
+      active: {line: 6, character: 21}
+  marks: {}
+finalState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 6, character: 0}
+      active: {line: 8, character: 1}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass5.yml
new file mode 100644
index 0000000000..2dd0efe7e2
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass5.yml
@@ -0,0 +1,44 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: take class
+  action: {name: setSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: class}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 10, character: 0}
+      active: {line: 10, character: 0}
+  marks: {}
+finalState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 10, character: 2}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass6.yml
new file mode 100644
index 0000000000..04cba31f0a
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass6.yml
@@ -0,0 +1,44 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: take class
+  action: {name: setSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: class}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 6, character: 4}
+      active: {line: 6, character: 4}
+  marks: {}
+finalState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 6, character: 0}
+      active: {line: 8, character: 1}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass7.yml
new file mode 100644
index 0000000000..18df65a5e2
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass7.yml
@@ -0,0 +1,44 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: take class
+  action: {name: setSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: class}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 6, character: 13}
+      active: {line: 6, character: 13}
+  marks: {}
+finalState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+    $foo = 1;
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 6, character: 0}
+      active: {line: 8, character: 1}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass8.yml
new file mode 100644
index 0000000000..5866db1546
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/takeClass8.yml
@@ -0,0 +1,34 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: take class
+  action: {name: setSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: class}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    use Modern::Perl;
+    use Getopt::Long;
+
+    my $aaa = "bbb";
+
+    exit;
+  selections:
+    - anchor: {line: 4, character: 0}
+      active: {line: 4, character: 0}
+  marks: {}
+finalState:
+  documentContents: |-
+    use Modern::Perl;
+    use Getopt::Long;
+
+    my $aaa = "bbb";
+
+    exit;
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 5, character: 5}

From d287a38994257955020ceae6f32af7972a0521cf Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sat, 8 Apr 2023 15:07:19 +0100
Subject: [PATCH 18/26] Improve arg behaviour

Not perfect yet, I've not been able to yet find a way to get the first
argument when on the opening paren, last argument on the closing paren,
or the arg left of the cursor. But behaviour much improved otherwise
(respects comma's much better now, which was broken before).
---
 .../cursorless-engine/src/languages/perl.ts   |  3 ++-
 .../recorded/languages/perl/changeArg4.yml    |  8 ++----
 .../recorded/languages/perl/chuckArg.yml      | 22 ++++++++++++++++
 .../recorded/languages/perl/chuckEveryArg.yml | 26 +++++++++++++++++++
 .../languages/perl/chuckSecondArg.yml         | 24 +++++++++++++++++
 .../recorded/languages/perl/chuckThirdArg.yml | 24 +++++++++++++++++
 6 files changed, 100 insertions(+), 7 deletions(-)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckArg.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckEveryArg.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckSecondArg.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckThirdArg.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 7fb189b26c..3173ebdcd5 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -1,4 +1,5 @@
 import {
+  argumentMatcher,
   cascadingMatcher,
   createPatternMatchers,
   matcher,
@@ -54,7 +55,7 @@ const nodeMatchers: Partial<
   ],
   collectionKey: "key_value_pair[key]",
   collectionItem: "hash[variable]",
-  argumentOrParameter: ["argument", "parenthesized_argument.arguments!"],
+  argumentOrParameter: argumentMatcher("arguments"),
   class: [
     "package_statement!.block[body]",
     "source_file!.package_statement",
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg4.yml
index d51e233ab6..c0d475fef2 100644
--- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg4.yml
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeArg4.yml
@@ -1,6 +1,6 @@
 languageId: perl
 command:
-  version: 4
+  version: 5
   spokenForm: change arg
   action: {name: clearAndSetSelection}
   targets:
@@ -15,8 +15,4 @@ initialState:
     - anchor: {line: 0, character: 16}
       active: {line: 0, character: 16}
   marks: {}
-finalState:
-  documentContents: some_funky_func(  )
-  selections:
-    - anchor: {line: 0, character: 17}
-      active: {line: 0, character: 17}
+thrownError: {name: NoContainingScopeError}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckArg.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckArg.yml
new file mode 100644
index 0000000000..2937807739
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckArg.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: chuck arg
+  action: {name: remove}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: argumentOrParameter}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: some_funky_func( "three", "args" )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+  marks: {}
+finalState:
+  documentContents: some_funky_func( "args" )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckEveryArg.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckEveryArg.yml
new file mode 100644
index 0000000000..6747c7926d
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckEveryArg.yml
@@ -0,0 +1,26 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: chuck every arg
+  action: {name: remove}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: everyScope
+          scopeType: {type: argumentOrParameter}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: "some_funky_func( \"and\", \"three\", \"args\" );\r\nsome_other_func( $and,  $three,  $args  );\r\n"
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+    - anchor: {line: 1, character: 17}
+      active: {line: 1, character: 17}
+  marks: {}
+finalState:
+  documentContents: "some_funky_func( );\r\nsome_other_func( );\r\n"
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+    - anchor: {line: 1, character: 17}
+      active: {line: 1, character: 17}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckSecondArg.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckSecondArg.yml
new file mode 100644
index 0000000000..3935ce9a42
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckSecondArg.yml
@@ -0,0 +1,24 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: chuck second arg
+  action: {name: remove}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: ordinalScope
+          scopeType: {type: argumentOrParameter}
+          start: 1
+          length: 1
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: some_funky_func( "and", "three", "args" )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+  marks: {}
+finalState:
+  documentContents: some_funky_func( "and", "args" )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckThirdArg.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckThirdArg.yml
new file mode 100644
index 0000000000..c5581b90bf
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckThirdArg.yml
@@ -0,0 +1,24 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: chuck third arg
+  action: {name: remove}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: ordinalScope
+          scopeType: {type: argumentOrParameter}
+          start: 2
+          length: 1
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: some_funky_func( "and", "three", "args" )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+  marks: {}
+finalState:
+  documentContents: some_funky_func( "and", "three" )
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}

From c6cb621aaf8171d058ec7977b49e6b2f61382337 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sat, 8 Apr 2023 15:27:38 +0100
Subject: [PATCH 19/26] Add test for change class name in lexical package

---
 .../languages/perl/changeClassName4.yml       | 42 +++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName4.yml

diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName4.yml
new file mode 100644
index 0000000000..ef35bec3dd
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeClassName4.yml
@@ -0,0 +1,42 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change class name
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: className}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+
+    package Hello::World {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 6, character: 4}
+      active: {line: 6, character: 4}
+  marks: {}
+finalState:
+  documentContents: |-
+    package Foo::Bar::Banana;
+
+    # A sort of
+    # comment
+
+    package  {
+        1;
+    }
+
+    1;
+  selections:
+    - anchor: {line: 5, character: 8}
+      active: {line: 5, character: 8}

From b40ff3daa5ece539b7bb299336ba95505265796b Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sat, 8 Apr 2023 15:35:57 +0100
Subject: [PATCH 20/26] Suggested "item" tests for array assignment

---
 .../languages/perl/changeEveryItem.yml        | 26 +++++++++++++++++++
 .../languages/perl/changeThirdItem.yml        | 24 +++++++++++++++++
 .../languages/perl/chuckFirstItem.yml         | 24 +++++++++++++++++
 .../languages/perl/chuckThirdItem.yml         | 24 +++++++++++++++++
 4 files changed, 98 insertions(+)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeEveryItem.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeThirdItem.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckFirstItem.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckThirdItem.yml

diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeEveryItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeEveryItem.yml
new file mode 100644
index 0000000000..5535afb382
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeEveryItem.yml
@@ -0,0 +1,26 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change every item
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: everyScope
+          scopeType: {type: collectionItem}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: "@array = ( \"one\", 2, \"_tres\" )"
+  selections:
+    - anchor: {line: 0, character: 30}
+      active: {line: 0, character: 30}
+  marks: {}
+finalState:
+  documentContents: "@array = ( , ,  )"
+  selections:
+    - anchor: {line: 0, character: 11}
+      active: {line: 0, character: 11}
+    - anchor: {line: 0, character: 13}
+      active: {line: 0, character: 13}
+    - anchor: {line: 0, character: 15}
+      active: {line: 0, character: 15}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeThirdItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeThirdItem.yml
new file mode 100644
index 0000000000..1ca0ed112a
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeThirdItem.yml
@@ -0,0 +1,24 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change third item
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: ordinalScope
+          scopeType: {type: collectionItem}
+          start: 2
+          length: 1
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: "@array = ( \"one\", 2, \"_tres\" )"
+  selections:
+    - anchor: {line: 0, character: 30}
+      active: {line: 0, character: 30}
+  marks: {}
+finalState:
+  documentContents: "@array = ( \"one\", 2,  )"
+  selections:
+    - anchor: {line: 0, character: 21}
+      active: {line: 0, character: 21}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckFirstItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckFirstItem.yml
new file mode 100644
index 0000000000..e8e28e115f
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckFirstItem.yml
@@ -0,0 +1,24 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: chuck first item
+  action: {name: remove}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: ordinalScope
+          scopeType: {type: collectionItem}
+          start: 0
+          length: 1
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: "@array = ( \"one\", 2, \"_tres\" )"
+  selections:
+    - anchor: {line: 0, character: 30}
+      active: {line: 0, character: 30}
+  marks: {}
+finalState:
+  documentContents: "@array = ( 2, \"_tres\" )"
+  selections:
+    - anchor: {line: 0, character: 23}
+      active: {line: 0, character: 23}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckThirdItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckThirdItem.yml
new file mode 100644
index 0000000000..f9c57cb575
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/chuckThirdItem.yml
@@ -0,0 +1,24 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: chuck third item
+  action: {name: remove}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: ordinalScope
+          scopeType: {type: collectionItem}
+          start: 2
+          length: 1
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: "@array = ( \"one\", 2, \"_tres\" )"
+  selections:
+    - anchor: {line: 0, character: 30}
+      active: {line: 0, character: 30}
+  marks: {}
+finalState:
+  documentContents: "@array = ( \"one\", 2 )"
+  selections:
+    - anchor: {line: 0, character: 21}
+      active: {line: 0, character: 21}

From 70bed590938000bfb2c35c8823c54e22c906895b Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sat, 8 Apr 2023 18:56:31 +0100
Subject: [PATCH 21/26] Improved condition scope with for loop support

Does not yet manage to find the condition for a C-style loop like

```
for ( my $i = 0; $i < 10; $i += 1 ) {}
```

when the cursor is at the keyword at the start of the line.

Also, the tree-sitter-perl fails processing for loops that assign to the
default variable, so can't add all the tests for that yet. Example:

```
for ( 1 .. 10 ) { say "$_" }
```

Taken a note to try and fix that or raise issues for.
---
 .../cursorless-engine/src/languages/perl.ts   |  9 +++++++-
 .../languages/perl/changeCondition2.yml       | 22 +++++++++++++++++++
 .../languages/perl/changeCondition3.yml       | 22 +++++++++++++++++++
 .../languages/perl/changeCondition4.yml       | 22 +++++++++++++++++++
 .../languages/perl/changeCondition5.yml       | 22 +++++++++++++++++++
 .../languages/perl/changeCondition6.yml       | 22 +++++++++++++++++++
 6 files changed, 118 insertions(+), 1 deletion(-)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition2.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition3.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition4.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition5.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition6.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 3173ebdcd5..d269239c65 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -19,7 +19,14 @@ const nodeMatchers: Partial<
   map: "hash",
   list: "array",
   condition: matcher(
-    patternFinder("while_statement[condition]"),
+    patternFinder(
+      "while_statement[condition]",
+      "for_statement_1.binary_expression!",
+      "for_statement_2.binary_expression!",
+      "for_statement_2.array_variable!",
+      "for_simple_statement.binary_expression!",
+      "for_simple_statement.array_variable!",
+    ),
     unwrapSelectionExtractor,
   ),
   string: [
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition2.yml
new file mode 100644
index 0000000000..bdd1f9ea2e
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition2.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: for my $bbb ( 1 .. 10 ) {}
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
+  marks: {}
+finalState:
+  documentContents: for my $bbb (  ) {}
+  selections:
+    - anchor: {line: 0, character: 14}
+      active: {line: 0, character: 14}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition3.yml
new file mode 100644
index 0000000000..f3fb102060
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition3.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: foreach my $aaa ( @bbb ) {}
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
+  marks: {}
+finalState:
+  documentContents: foreach my $aaa (  ) {}
+  selections:
+    - anchor: {line: 0, character: 18}
+      active: {line: 0, character: 18}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition4.yml
new file mode 100644
index 0000000000..44b23a9990
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition4.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: count( $_ ) for ( @each );
+  selections:
+    - anchor: {line: 0, character: 14}
+      active: {line: 0, character: 14}
+  marks: {}
+finalState:
+  documentContents: count( $_ ) for (  );
+  selections:
+    - anchor: {line: 0, character: 18}
+      active: {line: 0, character: 18}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition5.yml
new file mode 100644
index 0000000000..680942ea42
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition5.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: count( $_ ) for ( 1 .. 10 );
+  selections:
+    - anchor: {line: 0, character: 12}
+      active: {line: 0, character: 12}
+  marks: {}
+finalState:
+  documentContents: count( $_ ) for (  );
+  selections:
+    - anchor: {line: 0, character: 18}
+      active: {line: 0, character: 18}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition6.yml
new file mode 100644
index 0000000000..1d8e50f2a8
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition6.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: for ( my $i = 0; $i < 10; $i += 1 ) {}
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}
+  marks: {}
+finalState:
+  documentContents: for ( my $i = 0; ; $i += 1 ) {}
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}

From c0187b2fa451c8a5a4272e6d77dfc8fa5ccbf1d4 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sat, 8 Apr 2023 19:23:56 +0100
Subject: [PATCH 22/26] Fix scope condition for C-style for loops

Now works from the `for` keyword
---
 .../cursorless-engine/src/languages/perl.ts   |  2 +-
 .../languages/perl/changeCondition7.yml       | 22 +++++++++++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition7.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index d269239c65..c4572c1a7a 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -21,7 +21,7 @@ const nodeMatchers: Partial<
   condition: matcher(
     patternFinder(
       "while_statement[condition]",
-      "for_statement_1.binary_expression!",
+      "for_statement_1[condition]",
       "for_statement_2.binary_expression!",
       "for_statement_2.array_variable!",
       "for_simple_statement.binary_expression!",
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition7.yml
new file mode 100644
index 0000000000..6a5363f415
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition7.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: for ( my $i = 0; $i < 10; $i += 1 ) {}
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
+  marks: {}
+finalState:
+  documentContents: for ( my $i = 0; ; $i += 1 ) {}
+  selections:
+    - anchor: {line: 0, character: 17}
+      active: {line: 0, character: 17}

From a0bb761c886f89f73fb992b63f8a736060ba759a Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sat, 8 Apr 2023 19:29:08 +0100
Subject: [PATCH 23/26] Add if and unless support for condition scope

---
 .../cursorless-engine/src/languages/perl.ts   |  2 ++
 .../languages/perl/changeCondition8.yml       | 22 +++++++++++++++++++
 .../languages/perl/changeCondition9.yml       | 22 +++++++++++++++++++
 3 files changed, 46 insertions(+)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition8.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition9.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index c4572c1a7a..a7d0f78a41 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -26,6 +26,8 @@ const nodeMatchers: Partial<
       "for_statement_2.array_variable!",
       "for_simple_statement.binary_expression!",
       "for_simple_statement.array_variable!",
+      "if_statement[condition]",
+      "unless_statement[condition]",
     ),
     unwrapSelectionExtractor,
   ),
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition8.yml
new file mode 100644
index 0000000000..208e567017
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition8.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: if ( $foo > 1 ) {}
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
+  marks: {}
+finalState:
+  documentContents: if () {}
+  selections:
+    - anchor: {line: 0, character: 4}
+      active: {line: 0, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition9.yml
new file mode 100644
index 0000000000..0c4ce76cb0
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeCondition9.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change condition
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: condition}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: unless ( $the_sky_is_falling ) {}
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
+  marks: {}
+finalState:
+  documentContents: unless () {}
+  selections:
+    - anchor: {line: 0, character: 8}
+      active: {line: 0, character: 8}

From 64a989408e9cf1c5fcefac789709caa48ea3df0d Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sat, 8 Apr 2023 20:38:11 +0100
Subject: [PATCH 24/26] Initial name scope support

tree-sitter-perl doesn't yet differentiate between LHS and RHS of
assignments (lvalue and rvalue) so support for taking the name of a
variable is not implemented yet.
---
 .../cursorless-engine/src/languages/perl.ts   |  1 +
 .../recorded/languages/perl/changeName.yml    | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeName2.yml   | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeName3.yml   | 22 +++++++++++++++++++
 .../recorded/languages/perl/changeName4.yml   | 22 +++++++++++++++++++
 5 files changed, 89 insertions(+)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName2.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName3.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName4.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index a7d0f78a41..f3dec38fc4 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -72,6 +72,7 @@ const nodeMatchers: Partial<
     "source_file",
   ],
   className: "package_statement.package_name!",
+  name: ["function_definition[name]", "*[key]"],
 };
 
 export const patternMatchers = createPatternMatchers(nodeMatchers);
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName.yml
new file mode 100644
index 0000000000..cd609832b4
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change name
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: name}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: "%foo = ( bar => \"baz\" );"
+  selections:
+    - anchor: {line: 0, character: 18}
+      active: {line: 0, character: 18}
+  marks: {}
+finalState:
+  documentContents: "%foo = (  => \"baz\" );"
+  selections:
+    - anchor: {line: 0, character: 9}
+      active: {line: 0, character: 9}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName2.yml
new file mode 100644
index 0000000000..f382cd26e5
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName2.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change name
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: name}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: sub foo {}
+  selections:
+    - anchor: {line: 0, character: 9}
+      active: {line: 0, character: 9}
+  marks: {}
+finalState:
+  documentContents: sub  {}
+  selections:
+    - anchor: {line: 0, character: 4}
+      active: {line: 0, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName3.yml
new file mode 100644
index 0000000000..853f86c769
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName3.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change name
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: name}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: sub foo {}
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
+  marks: {}
+finalState:
+  documentContents: sub  {}
+  selections:
+    - anchor: {line: 0, character: 4}
+      active: {line: 0, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName4.yml
new file mode 100644
index 0000000000..8b656f5f54
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeName4.yml
@@ -0,0 +1,22 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change name
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: name}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: foo( bar => "baz" );
+  selections:
+    - anchor: {line: 0, character: 14}
+      active: {line: 0, character: 14}
+  marks: {}
+finalState:
+  documentContents: foo(  => "baz" );
+  selections:
+    - anchor: {line: 0, character: 5}
+      active: {line: 0, character: 5}

From dd0d992f1f903d6fe33144074505d5005b7810d7 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sat, 8 Apr 2023 22:51:17 +0100
Subject: [PATCH 25/26] Initial value scope support

Hash values only; rvalue not yet available in the parse tree, and have
yet to work out how to return just the value node from a return value.
---
 .../cursorless-engine/src/languages/perl.ts   |  1 +
 .../recorded/languages/perl/changeValue.yml   | 24 +++++++++++++++++++
 .../recorded/languages/perl/changeValue2.yml  | 24 +++++++++++++++++++
 3 files changed, 49 insertions(+)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeValue.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeValue2.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index f3dec38fc4..85d0c27f0d 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -73,6 +73,7 @@ const nodeMatchers: Partial<
   ],
   className: "package_statement.package_name!",
   name: ["function_definition[name]", "*[key]"],
+  value: ["*[value]"],
 };
 
 export const patternMatchers = createPatternMatchers(nodeMatchers);
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeValue.yml
new file mode 100644
index 0000000000..7075a4ead5
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeValue.yml
@@ -0,0 +1,24 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change value
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: value}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |
+    %foo = ( bar => "baz" );
+  selections:
+    - anchor: {line: 0, character: 16}
+      active: {line: 0, character: 21}
+  marks: {}
+finalState:
+  documentContents: |
+    %foo = ( bar =>  );
+  selections:
+    - anchor: {line: 0, character: 16}
+      active: {line: 0, character: 16}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeValue2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeValue2.yml
new file mode 100644
index 0000000000..5bcce09d56
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeValue2.yml
@@ -0,0 +1,24 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change value
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: value}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |
+    %foo = ( bar => "baz" );
+  selections:
+    - anchor: {line: 0, character: 9}
+      active: {line: 0, character: 9}
+  marks: {}
+finalState:
+  documentContents: |
+    %foo = ( bar =>  );
+  selections:
+    - anchor: {line: 0, character: 16}
+      active: {line: 0, character: 16}

From 38464185094f828fca783aabfb5ada52e0e6b1c7 Mon Sep 17 00:00:00 2001
From: Lianna Eeftinck <liannaee@gmail.com>
Date: Sun, 9 Apr 2023 00:14:54 +0100
Subject: [PATCH 26/26] Partial branch scope support; ternary not yet done

Can't work out how to get the full branch node selected for ternary
operators, so not implemented this just yet. if/elsif/else behaviour
is like other languages that support it though.
---
 .../cursorless-engine/src/languages/perl.ts   |  4 +++
 .../recorded/languages/perl/changeBranch.yml  | 34 +++++++++++++++++++
 .../recorded/languages/perl/changeBranch2.yml | 34 +++++++++++++++++++
 .../recorded/languages/perl/changeBranch3.yml | 34 +++++++++++++++++++
 4 files changed, 106 insertions(+)
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch2.yml
 create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch3.yml

diff --git a/packages/cursorless-engine/src/languages/perl.ts b/packages/cursorless-engine/src/languages/perl.ts
index 85d0c27f0d..b529da91ce 100644
--- a/packages/cursorless-engine/src/languages/perl.ts
+++ b/packages/cursorless-engine/src/languages/perl.ts
@@ -12,6 +12,7 @@ import {
   unwrapSelectionExtractor,
 } from "../util/nodeSelectors";
 import { patternFinder } from "../util/nodeFinders";
+import { branchMatcher } from "./branchMatcher";
 
 const nodeMatchers: Partial<
   Record<SimpleScopeTypeType, NodeMatcherAlternative>
@@ -74,6 +75,9 @@ const nodeMatchers: Partial<
   className: "package_statement.package_name!",
   name: ["function_definition[name]", "*[key]"],
   value: ["*[value]"],
+  branch: cascadingMatcher(
+    branchMatcher("if_statement", ["else_clause", "elsif_clause"]),
+  ),
 };
 
 export const patternMatchers = createPatternMatchers(nodeMatchers);
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch.yml
new file mode 100644
index 0000000000..19e5335d93
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch.yml
@@ -0,0 +1,34 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change branch
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: branch}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |
+    if ( 1 ) {
+        2;
+    } elsif ( 3 ) {
+        4;
+    } else {
+        5;
+    }
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
+  marks: {}
+finalState:
+  documentContents: |2
+     elsif ( 3 ) {
+        4;
+    } else {
+        5;
+    }
+  selections:
+    - anchor: {line: 0, character: 0}
+      active: {line: 0, character: 0}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch2.yml
new file mode 100644
index 0000000000..8a86c97bf3
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch2.yml
@@ -0,0 +1,34 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change branch
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: branch}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |
+    if ( 1 ) {
+        2;
+    } elsif ( 3 ) {
+        4;
+    } else {
+        5;
+    }
+  selections:
+    - anchor: {line: 2, character: 2}
+      active: {line: 2, character: 2}
+  marks: {}
+finalState:
+  documentContents: |
+    if ( 1 ) {
+        2;
+    }  else {
+        5;
+    }
+  selections:
+    - anchor: {line: 2, character: 2}
+      active: {line: 2, character: 2}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch3.yml
new file mode 100644
index 0000000000..67be0e39a3
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/perl/changeBranch3.yml
@@ -0,0 +1,34 @@
+languageId: perl
+command:
+  version: 5
+  spokenForm: change branch
+  action: {name: clearAndSetSelection}
+  targets:
+    - type: primitive
+      modifiers:
+        - type: containingScope
+          scopeType: {type: branch}
+  usePrePhraseSnapshot: true
+initialState:
+  documentContents: |
+    if ( 1 ) {
+        2;
+    } elsif ( 3 ) {
+        4;
+    } else {
+        5;
+    }
+  selections:
+    - anchor: {line: 4, character: 2}
+      active: {line: 4, character: 2}
+  marks: {}
+finalState:
+  documentContents: |
+    if ( 1 ) {
+        2;
+    } elsif ( 3 ) {
+        4;
+    } 
+  selections:
+    - anchor: {line: 4, character: 2}
+      active: {line: 4, character: 2}