diff --git a/.gitignore b/.gitignore
index fa67b4db01..ab3f0caee6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,6 @@ node_modules/
*.vsix
.DS_Store
.luacheckcache
+
+# Swift Test Project
+/resources/playground/swift/cursorless-test-project/.build
diff --git a/packages/app-web-docs/src/docs/user/languages/swift.mdx b/packages/app-web-docs/src/docs/user/languages/swift.mdx
new file mode 100644
index 0000000000..3735a04806
--- /dev/null
+++ b/packages/app-web-docs/src/docs/user/languages/swift.mdx
@@ -0,0 +1,5 @@
+import { Language } from "./components/Language";
+
+# Swift
+
+
diff --git a/packages/lib-common/src/scopeSupportFacets/languageScopeSupport.ts b/packages/lib-common/src/scopeSupportFacets/languageScopeSupport.ts
index 39d5d106f7..ac6beb2ca9 100644
--- a/packages/lib-common/src/scopeSupportFacets/languageScopeSupport.ts
+++ b/packages/lib-common/src/scopeSupportFacets/languageScopeSupport.ts
@@ -27,6 +27,7 @@ import { scalaScopeSupport } from "./scala";
import { scmScopeSupport } from "./scm";
import type { LanguageScopeSupportFacetMap } from "./scopeSupportFacets.types";
import { scssScopeSupport } from "./scss";
+import { swiftScopeSupport } from "./swift";
import { talonScopeSupport } from "./talon";
import { talonListScopeSupport } from "./talonList";
import { typescriptScopeSupport } from "./typescript";
@@ -64,6 +65,7 @@ export const languageScopeSupport: StringRecord =
scala: scalaScopeSupport,
scm: scmScopeSupport,
scss: scssScopeSupport,
+ swift: swiftScopeSupport,
"talon-list": talonListScopeSupport,
talon: talonScopeSupport,
typescript: typescriptScopeSupport,
diff --git a/packages/lib-common/src/scopeSupportFacets/swift.ts b/packages/lib-common/src/scopeSupportFacets/swift.ts
new file mode 100644
index 0000000000..cdb3fb9623
--- /dev/null
+++ b/packages/lib-common/src/scopeSupportFacets/swift.ts
@@ -0,0 +1,386 @@
+import type { LanguageScopeSupportFacetMap } from "./scopeSupportFacets.types";
+import { ScopeSupportFacetLevel } from "./scopeSupportFacets.types";
+
+const { supported, unsupported, notApplicable } = ScopeSupportFacetLevel;
+
+export const swiftScopeSupport: LanguageScopeSupportFacetMap = {
+ /* SUPPORTED OR UNSUPPORTED (PLANNED) */
+
+ // if/else/elif
+ ifStatement: supported,
+ "branch.if": supported,
+ "branch.if.else": supported,
+ "branch.if.elif.else": supported,
+ "branch.if.iteration": supported,
+ "condition.if": supported,
+ "interior.if": supported,
+
+ // ternary operator
+ "branch.ternary": unsupported,
+ "branch.ternary.iteration": unsupported,
+ "condition.ternary": unsupported,
+
+ // for loop
+ "statement.foreach": supported,
+ "name.foreach": supported,
+ "value.foreach": supported,
+ "type.foreach": supported,
+
+ // while loop
+ "statement.while": unsupported,
+ "condition.while": unsupported,
+ "interior.while": unsupported,
+
+ // repeat-while loop (equivalent to do-while loops in other languages)
+ "statement.doWhile": unsupported,
+ "condition.doWhile": unsupported,
+ "interior.doWhile": unsupported,
+
+ // do-catch (equivalent to try-catch in other languages)
+ // we're probably going to want a new scope facet for swift's try statements (`statement.tryErrorable`?)
+ "statement.try": unsupported,
+ "branch.try": unsupported,
+ "branch.try.iteration": unsupported,
+ "interior.try": unsupported,
+
+ // switch
+ "statement.switch": unsupported,
+ "branch.switchCase": unsupported,
+ "branch.switchCase.iteration": unsupported,
+ "condition.switchCase": unsupported,
+ "condition.switchCase.iteration": unsupported,
+ "value.switch": unsupported,
+ "interior.switch": unsupported,
+ "interior.switchCase": unsupported,
+
+ // misc control transfer (returns, throw/break/continue statements, etc)
+ "statement.return": unsupported,
+ "value.return": unsupported,
+ "value.return.lambda": unsupported,
+ "statement.throw": unsupported,
+ "value.throw": unsupported,
+ "statement.break": unsupported,
+ "statement.continue": unsupported,
+
+ // enum
+ "statement.enum": supported,
+ "name.enum": supported,
+ "name.iteration.enum": supported,
+ "type.enum": supported,
+ "interior.enum": supported,
+ "value.iteration.enum": supported,
+
+ // class
+ class: supported,
+ "statement.class": supported,
+ "name.class": supported,
+
+ // protocol (equivalent to interfaces in other languages)
+ "statement.interface": supported,
+ "name.interface": supported,
+
+ // "standard" functions & methods
+ namedFunction: unsupported,
+ "namedFunction.method": unsupported,
+ "statement.function": unsupported,
+ "statement.method": unsupported,
+ "name.function": unsupported,
+ "name.method": unsupported,
+
+ // constructors
+ "namedFunction.constructor": unsupported,
+ "statement.constructor": unsupported,
+ "name.constructor": unsupported,
+
+ // protocol method declarations
+ "statement.method.interface": unsupported,
+ "name.method.interface": unsupported,
+
+ // closures/lambda functions
+ anonymousFunction: unsupported,
+ "interior.lambda": unsupported,
+
+ // function calls
+ functionCall: unsupported,
+ "functionCall.constructor": unsupported,
+ "functionCall.method": unsupported,
+ "functionCall.chain": unsupported,
+ "functionCall.generic": unsupported,
+ "functionCall.enum": unsupported,
+
+ // function callee
+ functionCallee: unsupported,
+ "functionCallee.constructor": unsupported,
+ "functionCallee.method": unsupported,
+ "functionCallee.chain": unsupported,
+ "functionCallee.generic": unsupported,
+ "functionCallee.enum": unsupported,
+
+ // argument (actual)
+ "argument.actual.singleLine": unsupported,
+ "argument.actual.multiLine": unsupported,
+ "argument.actual.iteration": unsupported,
+ "argument.actual.method.singleLine": unsupported,
+ "argument.actual.method.multiLine": unsupported,
+ "argument.actual.method.iteration": unsupported,
+ "argument.actual.constructor.singleLine": unsupported,
+ "argument.actual.constructor.multiLine": unsupported,
+ "argument.actual.constructor.iteration": unsupported,
+ "argument.actual.enum.singleLine": unsupported,
+ "argument.actual.enum.multiLine": unsupported,
+ "argument.actual.enum.iteration": unsupported,
+
+ // argument (formal)
+ "argument.formal.singleLine": unsupported,
+ "argument.formal.multiLine": unsupported,
+ "argument.formal.iteration": unsupported,
+ "argument.formal.method.singleLine": unsupported,
+ "argument.formal.method.multiLine": unsupported,
+ "argument.formal.method.iteration": unsupported,
+ "argument.formal.constructor.singleLine": unsupported,
+ "argument.formal.constructor.multiLine": unsupported,
+ "argument.formal.constructor.iteration": unsupported,
+ "argument.formal.lambda.singleLine": unsupported,
+ "argument.formal.lambda.multiLine": unsupported,
+ "argument.formal.lambda.iteration": unsupported,
+ "argument.formal.catch": unsupported,
+
+ // argument list (actual)
+ "argumentList.actual.empty": unsupported,
+ "argumentList.actual.singleLine": unsupported,
+ "argumentList.actual.multiLine": unsupported,
+ "argumentList.actual.method.empty": unsupported,
+ "argumentList.actual.method.singleLine": unsupported,
+ "argumentList.actual.method.multiLine": unsupported,
+ "argumentList.actual.constructor.empty": unsupported,
+ "argumentList.actual.constructor.singleLine": unsupported,
+ "argumentList.actual.constructor.multiLine": unsupported,
+ "argumentList.actual.enum.empty": unsupported,
+ "argumentList.actual.enum.singleLine": unsupported,
+ "argumentList.actual.enum.multiLine": unsupported,
+
+ // argument list (formal)
+ "argumentList.formal.empty": unsupported,
+ "argumentList.formal.singleLine": unsupported,
+ "argumentList.formal.multiLine": unsupported,
+ "argumentList.formal.lambda.empty": unsupported,
+ "argumentList.formal.lambda.singleLine": unsupported,
+ "argumentList.formal.lambda.multiLine": unsupported,
+ "argumentList.formal.method.empty": unsupported,
+ "argumentList.formal.method.singleLine": unsupported,
+ "argumentList.formal.method.multiLine": unsupported,
+ "argumentList.formal.constructor.empty": unsupported,
+ "argumentList.formal.constructor.singleLine": unsupported,
+ "argumentList.formal.constructor.multiLine": unsupported,
+
+ // variables and constants (var, let)
+ fieldAccess: unsupported,
+
+ "statement.field.class": unsupported,
+ "statement.field.interface": unsupported,
+ "statement.variable.uninitialized": unsupported,
+ "statement.variable.initialized": unsupported,
+ "statement.variable.destructuring": unsupported,
+ "statement.constant": unsupported,
+
+ "name.field.class": notApplicable,
+ "name.field.interface": notApplicable,
+ "name.field.enum": notApplicable,
+ "name.variable.uninitialized": unsupported,
+ "name.variable.initialized": unsupported,
+ "name.variable.destructuring": unsupported,
+ "name.constant": unsupported,
+
+ "value.constant": unsupported,
+ "value.field.class": unsupported,
+ "value.field.interface": unsupported,
+ "value.field.enum": unsupported,
+
+ // assignments
+ "statement.assignment": unsupported,
+ "statement.assignment.destructuring": unsupported,
+ "statement.assignment.compound": unsupported,
+ "name.assignment": unsupported,
+ "name.assignment.destructuring": unsupported,
+ "name.assignment.compound": unsupported,
+
+ // comments
+ "comment.line": supported,
+ "textFragment.comment.line": supported,
+ "textFragment.comment.block": supported,
+ "comment.block": supported,
+
+ // strings
+ "string.singleLine": supported,
+ "string.multiLine": supported,
+ "textFragment.string.multiLine": supported,
+ "textFragment.string.singleLine": supported,
+
+ // document-wide iteration
+ "statement.iteration.document": supported,
+ "class.iteration.document": supported,
+ "namedFunction.iteration.document": supported,
+ "name.iteration.document": supported,
+ "value.iteration.document": supported,
+ "type.iteration.document": supported,
+
+ // per-class iteration
+ "statement.iteration.class": supported,
+ "class.iteration.class": supported,
+ "namedFunction.iteration.class": supported,
+ "name.iteration.class": supported,
+ "value.iteration.class": supported,
+ "type.iteration.class": supported,
+
+ // per-protocol iteration
+ "statement.iteration.interface": supported,
+ "name.iteration.interface": supported,
+ "type.iteration.interface": supported,
+
+ // per-block iteration -- todo: do classlikes, functions, protocols, branches, etc. count as blocks?
+ "statement.iteration.block": unsupported,
+ "name.iteration.block": unsupported,
+ "value.iteration.block": unsupported,
+ "type.iteration.block": unsupported,
+
+ // unenclosed collection item
+ "collectionItem.unenclosed.singleLine": unsupported,
+ "collectionItem.unenclosed.multiLine": unsupported,
+ "collectionItem.unenclosed.iteration": unsupported,
+
+ // enclosed collections
+ map: unsupported,
+ list: unsupported,
+ "key.mapPair": unsupported,
+ "key.mapPair.iteration": unsupported,
+
+ // misc
+ regularExpression: unsupported,
+ disqualifyDelimiter: unsupported,
+ pairDelimiter: unsupported,
+ "name.typeAlias": unsupported,
+ "statement.typeAlias": unsupported,
+ "statement.misc": unsupported,
+ // todo: do static variables/constants fulfill the scope facet "statement.static", or is that only for static blocks?
+ // "statement.static": unsupported,
+
+ /* NOT APPLICABLE */
+
+ // c-style for loop
+ "statement.for": notApplicable,
+ "condition.for": notApplicable,
+ "interior.for": notApplicable,
+
+ // loop branches
+ "branch.loop": notApplicable,
+ "branch.loop.iteration": notApplicable,
+
+ // XML/CSS/LaTeX/Markdown specific
+ section: notApplicable,
+ "section.iteration.document": notApplicable,
+ "section.iteration.parent": notApplicable,
+ element: notApplicable,
+ tags: notApplicable,
+ startTag: notApplicable,
+ endTag: notApplicable,
+ "interior.element": notApplicable,
+ "textFragment.element": notApplicable,
+ attribute: notApplicable,
+ "key.attribute": notApplicable,
+ "value.attribute": notApplicable,
+ environment: notApplicable,
+ notebookCell: notApplicable,
+ selector: notApplicable,
+ unit: notApplicable,
+ "interior.cell": notApplicable,
+
+ // Command
+ command: notApplicable,
+ "statement.command": notApplicable,
+ "name.command": notApplicable,
+ "value.command": notApplicable,
+ "interior.command": notApplicable,
+
+ // Resource
+ "statement.resource": notApplicable,
+ "name.resource": notApplicable,
+ "value.resource": notApplicable,
+ "type.resource": notApplicable,
+ "interior.resource": notApplicable,
+
+ // Explicit namespace declarations
+ "statement.namespace": notApplicable,
+ "name.namespace": notApplicable,
+ "interior.namespace": notApplicable,
+
+ // Misc
+ "statement.update": notApplicable,
+ "statement.package": notApplicable,
+ "statement.yield": notApplicable,
+ "value.yield": notApplicable,
+ "interior.static": notApplicable,
+
+ // TODO: reorganize these!!!
+ "name.argument.actual": notApplicable,
+ "name.argument.actual.iteration": notApplicable,
+ "name.argument.formal": notApplicable,
+ "name.argument.formal.iteration": notApplicable,
+ "name.argument.formal.method": notApplicable,
+ "name.argument.formal.method.iteration": notApplicable,
+ "name.argument.formal.lambda": notApplicable,
+ "name.argument.formal.lambda.iteration": notApplicable,
+ "name.argument.formal.constructor": notApplicable,
+ "name.argument.formal.constructor.iteration": notApplicable,
+ "name.argument.catch": notApplicable,
+
+ "value.variable": notApplicable,
+ "value.variable.destructuring": notApplicable,
+ "value.assignment": notApplicable,
+ "value.assignment.destructuring": notApplicable,
+ "value.assignment.compound": notApplicable,
+ "value.mapPair": notApplicable,
+ "value.mapPair.iteration": notApplicable,
+ "value.typeAlias": notApplicable,
+ "value.argument.actual": notApplicable,
+ "value.argument.actual.iteration": notApplicable,
+ "value.argument.formal": notApplicable,
+ "value.argument.formal.iteration": notApplicable,
+ "value.argument.formal.method": notApplicable,
+ "value.argument.formal.method.iteration": notApplicable,
+ "value.argument.formal.constructor": notApplicable,
+ "value.argument.formal.constructor.iteration": notApplicable,
+ "value.argument.formal.lambda": notApplicable,
+ "value.argument.formal.lambda.iteration": notApplicable,
+
+ "type.variable.uninitialized": notApplicable,
+ "type.variable.initialized": notApplicable,
+ "type.constant": notApplicable,
+ "type.return": notApplicable,
+ "type.return.method": notApplicable,
+ "type.return.lambda": notApplicable,
+ "type.field.class": notApplicable,
+ "type.field.interface": notApplicable,
+ "type.alias": notApplicable,
+ "type.cast": notApplicable,
+ "type.class": notApplicable,
+ "type.interface": notApplicable,
+ "type.typeArgument": notApplicable,
+ "type.typeArgument.iteration": notApplicable,
+ "type.argument.formal": notApplicable,
+ "type.argument.formal.iteration": notApplicable,
+ "type.argument.formal.method": notApplicable,
+ "type.argument.formal.method.iteration": notApplicable,
+ "type.argument.formal.lambda": notApplicable,
+ "type.argument.formal.lambda.iteration": notApplicable,
+ "type.argument.formal.constructor": notApplicable,
+ "type.argument.formal.constructor.iteration": notApplicable,
+ "type.argument.catch": notApplicable,
+
+ "interior.class": notApplicable,
+ "interior.interface": notApplicable,
+ "interior.function": notApplicable,
+ "interior.constructor": notApplicable,
+ "interior.method": notApplicable,
+ "interior.foreach": notApplicable,
+
+};
diff --git a/packages/lib-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts b/packages/lib-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts
index 796fcfce00..d65ac56d2a 100644
--- a/packages/lib-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts
+++ b/packages/lib-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts
@@ -92,6 +92,43 @@ class NotParentType extends QueryPredicateOperator {
}
}
+/**
+ * A predicate operator that returns true if no node from which this node
+ * descends is of the given type. For example,
+ *
+ * ```scm
+ * (#not-any-ancestor-type? @foo string)
+ * ```
+ *
+ * will reject the match if the `@foo` capture has any ancestor which is a `string` node,
+ * taking into account the capture's parent, said parent's parent, and so
+ * on until a root node is reached.
+ *
+ * Like with `#not-parent-type?`, you may pass multiple types to this predicate, in which case
+ * the math will be rejected has any of said types as any of its ancestor nodes.
+ */
+class NotAnyAncestorType extends QueryPredicateOperator {
+ name = "not-any-ancestor-type?" as const;
+ schema = z.tuple([q.node, q.string]).rest(q.string);
+ run(capture: MutableQueryCapture, ...types: string[]) {
+ var target = getNode(capture).parent;
+ const ancestorTypes: string[] = []
+ if (target != null) {
+ while (target != null) {
+ ancestorTypes.push(target.type);
+ target = target.parent;
+ }
+ for (const type of types) {
+ if (ancestorTypes.includes(type)) {
+ return false;
+ }
+ }
+ }
+ // Fall through if node has no parent or if ancestorTypes and types have no matches
+ return true;
+ }
+}
+
/**
* A predicate operator that returns true if the node is the nth child of its
* parent. For example, `(#is-nth-child? @foo 0)` will reject the match if the
@@ -462,6 +499,7 @@ export const queryPredicateOperators = [
new TrimEnd(),
new DocumentRange(),
new NotParentType(),
+ new NotAnyAncestorType(),
new IsNthChild(),
new ChildRange(),
new CharacterRange(),
diff --git a/resources/fixtures/scopes/swift/branch.if.elif.else.scope b/resources/fixtures/scopes/swift/branch.if.elif.else.scope
new file mode 100644
index 0000000000..c8aa5e8b4e
--- /dev/null
+++ b/resources/fixtures/scopes/swift/branch.if.elif.else.scope
@@ -0,0 +1,95 @@
+if foo {}
+else if bar {}
+else {}
+---
+
+[#1 Content] =
+[#1 Domain] = 0:0-0:9
+ >---------<
+0| if foo {}
+
+[#1 Removal] = 0:0-1:0
+ >---------
+0| if foo {}
+1| else if bar {}
+ <
+
+[#1 Insertion delimiter] = "\n"
+
+
+[#2 Content] =
+[#2 Domain] = 1:0-1:14
+ >--------------<
+1| else if bar {}
+
+[#2 Removal] = 1:0-2:0
+ >--------------
+1| else if bar {}
+2| else {}
+ <
+
+[#2 Insertion delimiter] = "\n"
+
+
+[#3 Content] =
+[#3 Domain] = 1:0-2:7
+ >--------------
+1| else if bar {}
+2| else {}
+ -------<
+
+[#3 Removal] = 0:9-2:7
+ >
+0| if foo {}
+1| else if bar {}
+2| else {}
+ -------<
+
+[#3 Insertion delimiter] = "\n"
+
+
+[#4 Content] =
+[#4 Domain] = 1:5-1:14
+ >---------<
+1| else if bar {}
+
+[#4 Removal] = 1:5-2:0
+ >---------
+1| else if bar {}
+2| else {}
+ <
+
+[#4 Leading delimiter] = 1:4-1:5
+ >-<
+1| else if bar {}
+
+[#4 Insertion delimiter] = "\n"
+
+
+[#5 Content] =
+[#5 Removal] =
+[#5 Domain] = 1:5-2:7
+ >---------
+1| else if bar {}
+2| else {}
+ -------<
+
+[#5 Leading delimiter] = 1:4-1:5
+ >-<
+1| else if bar {}
+
+[#5 Insertion delimiter] = "\n"
+
+
+[#6 Content] =
+[#6 Domain] = 2:0-2:7
+ >-------<
+2| else {}
+
+[#6 Removal] = 1:14-2:7
+ >
+1| else if bar {}
+2| else {}
+ -------<
+
+[#6 Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/branch.if.else.scope b/resources/fixtures/scopes/swift/branch.if.else.scope
new file mode 100644
index 0000000000..fb73666f03
--- /dev/null
+++ b/resources/fixtures/scopes/swift/branch.if.else.scope
@@ -0,0 +1,41 @@
+if true {}
+else {}
+---
+
+[#1 Content] =
+[#1 Domain] = 0:0-0:10
+ >----------<
+0| if true {}
+
+[#1 Removal] = 0:0-1:0
+ >----------
+0| if true {}
+1| else {}
+ <
+
+[#1 Insertion delimiter] = "\n"
+
+
+[#2 Content] =
+[#2 Removal] =
+[#2 Domain] = 0:0-1:7
+ >----------
+0| if true {}
+1| else {}
+ -------<
+
+[#2 Insertion delimiter] = "\n"
+
+
+[#3 Content] =
+[#3 Domain] = 1:0-1:7
+ >-------<
+1| else {}
+
+[#3 Removal] = 0:10-1:7
+ >
+0| if true {}
+1| else {}
+ -------<
+
+[#3 Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/branch.if.iteration.scope b/resources/fixtures/scopes/swift/branch.if.iteration.scope
new file mode 100644
index 0000000000..4284edc393
--- /dev/null
+++ b/resources/fixtures/scopes/swift/branch.if.iteration.scope
@@ -0,0 +1,12 @@
+if foo {}
+else if bar {}
+else {}
+---
+
+[Content] =
+[Domain] = 0:0-2:7
+ >---------
+0| if foo {}
+1| else if bar {}
+2| else {}
+ -------<
diff --git a/resources/fixtures/scopes/swift/branch.if.scope b/resources/fixtures/scopes/swift/branch.if.scope
new file mode 100644
index 0000000000..95d75aa3fb
--- /dev/null
+++ b/resources/fixtures/scopes/swift/branch.if.scope
@@ -0,0 +1,10 @@
+if true {}
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:10
+ >----------<
+0| if true {}
+
+[Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/class.scope b/resources/fixtures/scopes/swift/class.scope
new file mode 100644
index 0000000000..80a9ab592c
--- /dev/null
+++ b/resources/fixtures/scopes/swift/class.scope
@@ -0,0 +1,10 @@
+struct Foo {}
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:13
+ >-------------<
+0| struct Foo {}
+
+[Insertion delimiter] = "\n\n"
diff --git a/resources/fixtures/scopes/swift/comment.block.scope b/resources/fixtures/scopes/swift/comment.block.scope
new file mode 100644
index 0000000000..477e57d0ee
--- /dev/null
+++ b/resources/fixtures/scopes/swift/comment.block.scope
@@ -0,0 +1,15 @@
+/*
+aaa
+*/
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-2:2
+ >--
+0| /*
+1| aaa
+2| */
+ --<
+
+[Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/comment.line.scope b/resources/fixtures/scopes/swift/comment.line.scope
new file mode 100644
index 0000000000..a469659c87
--- /dev/null
+++ b/resources/fixtures/scopes/swift/comment.line.scope
@@ -0,0 +1,10 @@
+// aaa
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:6
+ >------<
+0| // aaa
+
+[Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/condition.if.scope b/resources/fixtures/scopes/swift/condition.if.scope
new file mode 100644
index 0000000000..39fee6c27b
--- /dev/null
+++ b/resources/fixtures/scopes/swift/condition.if.scope
@@ -0,0 +1,24 @@
+if foo {}
+---
+
+[Content] = 0:3-0:6
+ >---<
+0| if foo {}
+
+[Removal] = 0:3-0:7
+ >----<
+0| if foo {}
+
+[Leading delimiter] = 0:2-0:3
+ >-<
+0| if foo {}
+
+[Trailing delimiter] = 0:6-0:7
+ >-<
+0| if foo {}
+
+[Domain] = 0:0-0:9
+ >---------<
+0| if foo {}
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/ifStatement.scope b/resources/fixtures/scopes/swift/ifStatement.scope
new file mode 100644
index 0000000000..95d75aa3fb
--- /dev/null
+++ b/resources/fixtures/scopes/swift/ifStatement.scope
@@ -0,0 +1,10 @@
+if true {}
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:10
+ >----------<
+0| if true {}
+
+[Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/interior.if.scope b/resources/fixtures/scopes/swift/interior.if.scope
new file mode 100644
index 0000000000..9a35dae364
--- /dev/null
+++ b/resources/fixtures/scopes/swift/interior.if.scope
@@ -0,0 +1,10 @@
+if foo { }
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:8-0:10
+ >--<
+0| if foo { }
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/name.class.scope b/resources/fixtures/scopes/swift/name.class.scope
new file mode 100644
index 0000000000..c4ecaeeb34
--- /dev/null
+++ b/resources/fixtures/scopes/swift/name.class.scope
@@ -0,0 +1,21 @@
+class Foo {}
+---
+
+[Content] =
+[Domain] = 0:6-0:9
+ >---<
+0| class Foo {}
+
+[Removal] = 0:6-0:10
+ >----<
+0| class Foo {}
+
+[Leading delimiter] = 0:5-0:6
+ >-<
+0| class Foo {}
+
+[Trailing delimiter] = 0:9-0:10
+ >-<
+0| class Foo {}
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/name.enum.scope b/resources/fixtures/scopes/swift/name.enum.scope
new file mode 100644
index 0000000000..2162ad8604
--- /dev/null
+++ b/resources/fixtures/scopes/swift/name.enum.scope
@@ -0,0 +1,21 @@
+enum Foo {}
+---
+
+[Content] =
+[Domain] = 0:5-0:8
+ >---<
+0| enum Foo {}
+
+[Removal] = 0:5-0:9
+ >----<
+0| enum Foo {}
+
+[Leading delimiter] = 0:4-0:5
+ >-<
+0| enum Foo {}
+
+[Trailing delimiter] = 0:8-0:9
+ >-<
+0| enum Foo {}
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/name.foreach.scope b/resources/fixtures/scopes/swift/name.foreach.scope
new file mode 100644
index 0000000000..c8e077322e
--- /dev/null
+++ b/resources/fixtures/scopes/swift/name.foreach.scope
@@ -0,0 +1,24 @@
+for foo in bar {}
+---
+
+[Content] = 0:4-0:7
+ >---<
+0| for foo in bar {}
+
+[Removal] = 0:4-0:8
+ >----<
+0| for foo in bar {}
+
+[Leading delimiter] = 0:3-0:4
+ >-<
+0| for foo in bar {}
+
+[Trailing delimiter] = 0:7-0:8
+ >-<
+0| for foo in bar {}
+
+[Domain] = 0:0-0:17
+ >-----------------<
+0| for foo in bar {}
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/name.interface.scope b/resources/fixtures/scopes/swift/name.interface.scope
new file mode 100644
index 0000000000..7268b1a3e0
--- /dev/null
+++ b/resources/fixtures/scopes/swift/name.interface.scope
@@ -0,0 +1,21 @@
+protocol Foo {}
+---
+
+[Content] =
+[Domain] = 0:9-0:12
+ >---<
+0| protocol Foo {}
+
+[Removal] = 0:9-0:13
+ >----<
+0| protocol Foo {}
+
+[Leading delimiter] = 0:8-0:9
+ >-<
+0| protocol Foo {}
+
+[Trailing delimiter] = 0:12-0:13
+ >-<
+0| protocol Foo {}
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/statement.class.scope b/resources/fixtures/scopes/swift/statement.class.scope
new file mode 100644
index 0000000000..a6008d311f
--- /dev/null
+++ b/resources/fixtures/scopes/swift/statement.class.scope
@@ -0,0 +1,10 @@
+class Foo {}
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:12
+ >------------<
+0| class Foo {}
+
+[Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/statement.enum.scope b/resources/fixtures/scopes/swift/statement.enum.scope
new file mode 100644
index 0000000000..88dd9cd028
--- /dev/null
+++ b/resources/fixtures/scopes/swift/statement.enum.scope
@@ -0,0 +1,10 @@
+enum Foo {}
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:11
+ >-----------<
+0| enum Foo {}
+
+[Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/statement.foreach.scope b/resources/fixtures/scopes/swift/statement.foreach.scope
new file mode 100644
index 0000000000..005a6562ec
--- /dev/null
+++ b/resources/fixtures/scopes/swift/statement.foreach.scope
@@ -0,0 +1,10 @@
+for foo in bar {}
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:17
+ >-----------------<
+0| for foo in bar {}
+
+[Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/statement.interface.scope b/resources/fixtures/scopes/swift/statement.interface.scope
new file mode 100644
index 0000000000..ea6dad63a6
--- /dev/null
+++ b/resources/fixtures/scopes/swift/statement.interface.scope
@@ -0,0 +1,10 @@
+protocol Foo {}
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:15
+ >---------------<
+0| protocol Foo {}
+
+[Insertion delimiter] = "\n"
diff --git a/resources/fixtures/scopes/swift/string.multiLine.scope b/resources/fixtures/scopes/swift/string.multiLine.scope
new file mode 100644
index 0000000000..e17fb9fb0f
--- /dev/null
+++ b/resources/fixtures/scopes/swift/string.multiLine.scope
@@ -0,0 +1,15 @@
+"""
+aaa
+"""
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-2:3
+ >---
+0| """
+1| aaa
+2| """
+ ---<
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/string.singleLine.scope b/resources/fixtures/scopes/swift/string.singleLine.scope
new file mode 100644
index 0000000000..f0d1750ce4
--- /dev/null
+++ b/resources/fixtures/scopes/swift/string.singleLine.scope
@@ -0,0 +1,10 @@
+"aaa"
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:5
+ >-----<
+0| "aaa"
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/textFragment.comment.block.scope b/resources/fixtures/scopes/swift/textFragment.comment.block.scope
new file mode 100644
index 0000000000..2bc3b6e027
--- /dev/null
+++ b/resources/fixtures/scopes/swift/textFragment.comment.block.scope
@@ -0,0 +1,15 @@
+/*
+aaa
+*/
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-2:2
+ >--
+0| /*
+1| aaa
+2| */
+ --<
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/textFragment.comment.line.scope b/resources/fixtures/scopes/swift/textFragment.comment.line.scope
new file mode 100644
index 0000000000..e47b42fed9
--- /dev/null
+++ b/resources/fixtures/scopes/swift/textFragment.comment.line.scope
@@ -0,0 +1,10 @@
+// aaa
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:0-0:6
+ >------<
+0| // aaa
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/textFragment.string.multiLine.scope b/resources/fixtures/scopes/swift/textFragment.string.multiLine.scope
new file mode 100644
index 0000000000..43868aa5c8
--- /dev/null
+++ b/resources/fixtures/scopes/swift/textFragment.string.multiLine.scope
@@ -0,0 +1,15 @@
+"""
+aaa
+"""
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:3-2:0
+ >
+0| """
+1| aaa
+2| """
+ <
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/textFragment.string.singleLine.scope b/resources/fixtures/scopes/swift/textFragment.string.singleLine.scope
new file mode 100644
index 0000000000..0ab585ba66
--- /dev/null
+++ b/resources/fixtures/scopes/swift/textFragment.string.singleLine.scope
@@ -0,0 +1,10 @@
+"aaa"
+---
+
+[Content] =
+[Removal] =
+[Domain] = 0:1-0:4
+ >---<
+0| "aaa"
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/type.foreach.scope b/resources/fixtures/scopes/swift/type.foreach.scope
new file mode 100644
index 0000000000..8e867374c0
--- /dev/null
+++ b/resources/fixtures/scopes/swift/type.foreach.scope
@@ -0,0 +1,20 @@
+for foo: Type in bar {}
+---
+
+[Content] = 0:9-0:13
+ >----<
+0| for foo: Type in bar {}
+
+[Removal] = 0:7-0:13
+ >------<
+0| for foo: Type in bar {}
+
+[Leading delimiter] = 0:7-0:9
+ >--<
+0| for foo: Type in bar {}
+
+[Domain] = 0:0-0:23
+ >-----------------------<
+0| for foo: Type in bar {}
+
+[Insertion delimiter] = " "
diff --git a/resources/fixtures/scopes/swift/value.foreach.scope b/resources/fixtures/scopes/swift/value.foreach.scope
new file mode 100644
index 0000000000..d1ab264c5d
--- /dev/null
+++ b/resources/fixtures/scopes/swift/value.foreach.scope
@@ -0,0 +1,24 @@
+for foo in bar {}
+---
+
+[Content] = 0:11-0:14
+ >---<
+0| for foo in bar {}
+
+[Removal] = 0:11-0:15
+ >----<
+0| for foo in bar {}
+
+[Leading delimiter] = 0:10-0:11
+ >-<
+0| for foo in bar {}
+
+[Trailing delimiter] = 0:14-0:15
+ >-<
+0| for foo in bar {}
+
+[Domain] = 0:0-0:17
+ >-----------------<
+0| for foo in bar {}
+
+[Insertion delimiter] = " "
diff --git a/resources/playground/Java.java b/resources/playground/Java.java
index 2001b365a2..0b65e8e8d2 100644
--- a/resources/playground/Java.java
+++ b/resources/playground/Java.java
@@ -2,5 +2,10 @@ public class Java {
private Java(String name) {
String value = "hello";
System.out.println(value);
+ if (true) {
+ //1
+ } else if (false) {
+ //2
+ }
}
}
diff --git a/resources/playground/rust.rs b/resources/playground/rust.rs
new file mode 100644
index 0000000000..1cfa874e89
--- /dev/null
+++ b/resources/playground/rust.rs
@@ -0,0 +1,8 @@
+fn hello() {
+ let apple: Bool = false
+ if true {
+ //1
+ } else if false {
+ //2
+ }
+}
\ No newline at end of file
diff --git a/resources/playground/swift/cursorless-test-project/Package.swift b/resources/playground/swift/cursorless-test-project/Package.swift
new file mode 100644
index 0000000000..09374339a2
--- /dev/null
+++ b/resources/playground/swift/cursorless-test-project/Package.swift
@@ -0,0 +1,8 @@
+// swift-tools-version: 6.2
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "cursorless-test-project"
+)
diff --git a/resources/playground/swift/cursorless-test-project/test.swift b/resources/playground/swift/cursorless-test-project/test.swift
new file mode 100644
index 0000000000..d425f8e351
--- /dev/null
+++ b/resources/playground/swift/cursorless-test-project/test.swift
@@ -0,0 +1,92 @@
+struct ExamplesStruct {
+ static let constant = "hello world"
+ public var publicVariable: UInt128 = 0
+ var uninitializedVariable: String?
+ internal static func exampleFunction(exampleLabel: String) -> String {
+ if exampleLabel.isEmpty {
+ return "goodbye world"
+ } else if (publicVariable % 2 == 0) {
+ return constant + ", " + exampleLabel + "!"
+ } else if (publicVariable % 3 == 0){
+ return constant + ", " + exampleLabel + "?!"
+ } else {
+ return constant + ", " + exampleLabel + "..."
+ }
+ }
+ public mutating func secondExampleFunction(exampleLabel: String, times: any UnsignedInteger) {
+ let cast: UInt128 = numericCast(times)
+ publicVariable += cast
+ let fizz = publicVariable % 3 == 0
+ let buzz: Bool = publicVariable % 5 == 0
+ if fizz || buzz {
+ print((fizz ? "fizz" : "") + (buzz ? "buzz" : ""))
+ }
+ for _ in 0 ..< cast {
+ print(ExamplesStruct.exampleFunction(exampleLabel: exampleLabel))
+ }
+ for c: Character in "test" {
+
+ }
+ }
+}
+
+let basicMultilineString = """
+I am
+a multiline string
+!!
+"""
+
+let extendedDelimiter = #"The radio said "No, John. You are the zombie." And then John was the zombie."#
+
+let multilineExtendedDelimiter = #"""
+""""""""" waow
+"""#
+
+enum ExampleEnum {
+ case exampleCaseOne
+ case exampleCaseTwo
+}
+/*
+ These Examples are Pissing me off...
+ I'm the original
+ Multiline walker
+*/
+
+enum ExampleCompactEnum {
+ case compactCaseOne, compactCaseTwo
+}
+
+protocol ExampleProtocol {
+ var setGetMember: String? { set get }
+ var getOnlyNumber: Double! {get} // using an explicit unwrap type like this is legal, but poor practice in real code
+}
+
+actor ExampleActor {
+
+}
+
+func nestedClassInFunc() {
+ let ternaryDecider = true
+ let ternaryOp = ternaryDecider ? "foo" : "bar"
+ let switchExample: UInt8 = 22
+ var foobart = 0
+ switch switchExample {
+ case 0: foobart = 5
+ case 22: foobart = 42
+ default: foobart = 69
+ }
+ class Nested {
+ func veryNested() {
+ var i = 0
+ while i < 10 {
+ i += 1
+ }
+ repeat {
+ i -= 1
+ } while i > 0
+ struct HyperNested {
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/queries/swift.scm b/resources/queries/swift.scm
new file mode 100644
index 0000000000..d2e8e8b8c9
--- /dev/null
+++ b/resources/queries/swift.scm
@@ -0,0 +1,146 @@
+;; https://github.com/alex-pinkus/tree-sitter-swift/blob/with-generated-files/src/grammar.json
+
+;; document-wide
+(source_file) @class.iteration @statement.iteration @name.iteration @value.iteration @type.iteration
+(#document-range! @class.iteration @statement.iteration @name.iteration @value.iteration @type.iteration)
+
+;; single line comment
+(comment) @comment @textFragment
+
+;; multiline comment
+(multiline_comment) @comment @textFragment
+
+;; single line string
+(line_string_literal
+ text: (_) @interior @textFragment
+) @string
+
+;; multiline string
+(multi_line_string_literal
+ text: (_) @interior @textFragment
+) @string
+
+;; extended delimiter/"raw" strings (both multiline and single line) -- waiting on better tree-sitter support for these
+;;(raw_string_literal
+;; text: (_) @interior @textFragment
+;;) @string
+
+;; Protocol decl.
+(protocol_declaration
+ name: (_) @name
+ body: (protocol_body
+ "{" @interior.start.endOf
+ "}" @interior.end.startOf
+ )
+) @statement
+
+;; if statement
+(
+ (if_statement) @ifStatement @statement @branch.iteration
+ (#not-parent-type? @ifStatement if_statement)
+)
+
+;; if statement w/ condition & child branches
+(
+ (if_statement
+ "if" @branch.start @branch.removal.start
+ condition: (_) @condition
+ "}" @branch.end @branch.removal.end
+ (else)? @branch.removal.end.startOf
+ ) @condition.domain
+ (#not-parent-type? @condition.domain else)
+)
+
+;; else if
+(
+ (else) @branch.start @condition.domain.start
+ (if_statement
+ condition: (_) @condition @condition.domain.end
+ "}" @branch.end
+ )
+)
+
+;; else
+(
+ (else) @branch.start
+ "}" @branch.end
+)
+
+;; generic property delc
+(property_declaration
+ name: (_) @name
+ ;;(type_annotation:
+ ;; ":"
+ ;;)
+) @statement
+
+;; Generic interior w/ top-level iterations
+(_
+ "{" @interior.start.endOf @statement.iteration.start.endOf @name.iteration.start.endOf
+ "}" @interior.end.startOf @statement.iteration.end.startOf @name.iteration.end.startOf
+)
+
+(_
+ "{" @value.iteration.start.endOf @type.iteration.start.endOf @namedFunction.iteration.start.endOf
+ "}" @value.iteration.end.startOf @type.iteration.end.startOf @namedFunction.iteration.end.startOf
+)
+
+;; Generic interior -- class iteration
+;; Classlikes (class/struct/actor) can be nested within other classlikes, and within both top-level functions and member functions.
+;; They, however, cannot be nested within branches or loops of any kind.
+(
+ (
+ (_
+ "{" @class.iteration.start.endOf
+ "}" @class.iteration.end.startOf
+ ) @_dummy
+ ) (#type? @_dummy class_declaration function_declaration)
+ (#not-any-ancestor-type? @_dummy if_statement switch_statement for_statement while_statement)
+ (#not-any-ancestor-type? @_dummy do_statement repeat_while_statement protocol_declaration)
+)
+
+;; Generic interior -- branch and condition iteration
+;; Branches and their conditions cannot be top-level but otherwise have no restrictions
+(
+ (
+ (_
+ "{" @branch.iteration.start.endOf @condition.iteration.start.endOf
+ "}" @condition.iteration.end.startOf @branch.iteration.end.startOf
+ ) @_dummy
+ ) (#not-parent-type? @_dummy source_file)
+)
+
+;; Non-enum class (struct/class/actor) decl.
+(class_declaration
+ name: (_) @name
+ body: (class_body
+ "{" @interior.start.endOf
+ "}" @interior.end.startOf
+ .
+ )
+) @statement @class
+
+;; Enum "class" decl.
+(class_declaration
+ name: (_) @name @type
+ body: (enum_class_body
+ "{" @interior.start.endOf
+ "}" @interior.end.startOf
+ .
+ )
+) @statement
+
+;; For loop
+(
+ (for_statement
+ "for"
+ item: (_) @name
+ (type_annotation
+ ":" @type.leading
+ .
+ _ @type @name.trailing
+ )?
+ "in"
+ collection: (_) @value
+ ) @statement @_.domain
+)