diff --git a/data/playground/elixir/calls.ex b/data/playground/elixir/calls.ex new file mode 100644 index 0000000000..36506dbbb7 --- /dev/null +++ b/data/playground/elixir/calls.ex @@ -0,0 +1,19 @@ +defmodule Calls do + a() + b.() + c do + 1 + end + d() do + end + e() do + 1 + end + a x do + x + end + f(0) do + 1 + end + Alias.g() +end diff --git a/data/playground/elixir/data-structures.ex b/data/playground/elixir/data-structures.ex new file mode 100644 index 0000000000..d13657effe --- /dev/null +++ b/data/playground/elixir/data-structures.ex @@ -0,0 +1,36 @@ +defmodule DataStructures do + [] + [a] + [A] + [1] + [1, 2] + [[1], 1] + %{} + %{a: 1, b: 2} + %{:a => 1, "b" => 2, c => 3} + %{"a" => 1, b: 2, c: 3} + %{user | name: "Jane", email: "jane@example.com"} + %{user | "name" => "Jane"} + %AStruct{a: 1, b: 2} + %AnotherStruct{:a => 1, "b" => 2, c => 3} + %ThirdStruct{"a" => 1, b: 2, c: 3} + %User{user | name: "Jane", email: "jane@example.com"} + %User{user | "name" => "Jane"} + %{ + :a => 1, + "b" => 2, + c => 3 + } + %_{} + %name{} + %^name{} + %__MODULE__{} + %__MODULE__.Child{} + %:"Elixir.Mod"{} + %fun(){} + %Mod.fun(){} + %fun.(){} + {} + {1} + {1, 2} +end diff --git a/data/playground/elixir/functions.ex b/data/playground/elixir/functions.ex new file mode 100644 index 0000000000..1724f87973 --- /dev/null +++ b/data/playground/elixir/functions.ex @@ -0,0 +1,62 @@ +defmodule Funcs do + def no_args() do + end + + def no_args_no_parens do + end + + def one_arg(x) do + x + end + + def one_arg_no_parens(x) do + x + end + + def two_args(x, y) do + x + y + end + + def two_args_no_parens(x, y) do + x + y + end + + def default_args_no_parens(x, y \\ 1) do + x + y + end + + def default_args(x, y \\ 1) do + x + y + end + + def do_block(), do: 1 + def do_block(x), do: x + + def pattern_matching([{x, y} | tail]) do + x + y + end + + def one_guard(x) when x == 1 do + x + end + + def multiple_guard(x) when x > 10 when x < 5 do + x + end + + defp private(x) do + x + end + + defmacro macro(x) do + quote do + [unquote(x)] + end + end + + defguard guard(term) when is_integer(term) and rem(term, 2) == 0 + + def unquote(name)(unquote_splicing(args)) do + unquote(compiled) + end +end diff --git a/packages/common/src/extensionDependencies.ts b/packages/common/src/extensionDependencies.ts index 5139c3b3a7..3043dfa9e9 100644 --- a/packages/common/src/extensionDependencies.ts +++ b/packages/common/src/extensionDependencies.ts @@ -3,9 +3,10 @@ export const extensionDependencies = [ "pokey.parse-tree", // Register necessary language-IDs for tests - "scala-lang.scala", // scala - "mrob95.vscode-talonscript", // talon + "jakebecker.elixir-ls", // elixir "jrieken.vscode-tree-sitter-query", // scm + "mrob95.vscode-talonscript", // talon + "scala-lang.scala", // scala // Necessary for the `drink cell` and `pour cell` tests "ms-toolsai.jupyter", diff --git a/packages/common/src/scopeSupportFacets/elixir.ts b/packages/common/src/scopeSupportFacets/elixir.ts new file mode 100644 index 0000000000..82e72ebcb5 --- /dev/null +++ b/packages/common/src/scopeSupportFacets/elixir.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import { + LanguageScopeSupportFacetMap, + ScopeSupportFacetLevel, +} from "./scopeSupportFacets.types"; + +const { supported } = ScopeSupportFacetLevel; + +export const elixirScopeSupport: LanguageScopeSupportFacetMap = { + "collectionItem.map": supported, + "collectionItem.map.iteration": supported, +}; diff --git a/packages/common/src/scopeSupportFacets/getLanguageScopeSupport.ts b/packages/common/src/scopeSupportFacets/getLanguageScopeSupport.ts index 76f57d6216..6b525c0d0a 100644 --- a/packages/common/src/scopeSupportFacets/getLanguageScopeSupport.ts +++ b/packages/common/src/scopeSupportFacets/getLanguageScopeSupport.ts @@ -1,3 +1,4 @@ +import { elixirScopeSupport } from "./elixir"; import { htmlScopeSupport } from "./html"; import { javaScopeSupport } from "./java"; import { javascriptScopeSupport } from "./javascript"; @@ -25,6 +26,8 @@ export function getLanguageScopeSupport( return talonScopeSupport; case "typescript": return typescriptScopeSupport; + case "elixir": + return elixirScopeSupport; } throw Error(`Unsupported language: '${languageId}'`); } diff --git a/packages/common/src/scopeSupportFacets/scopeSupportFacetInfos.ts b/packages/common/src/scopeSupportFacets/scopeSupportFacetInfos.ts index ccb8dc057f..5005116840 100644 --- a/packages/common/src/scopeSupportFacets/scopeSupportFacetInfos.ts +++ b/packages/common/src/scopeSupportFacets/scopeSupportFacetInfos.ts @@ -141,6 +141,16 @@ export const scopeSupportFacetInfos: Record< scopeType: "argumentOrParameter", isIteration: true, }, + "collectionItem.map": { + description: "An entry in a map/dictionary", + scopeType: "collectionItem", + }, + "collectionItem.map.iteration": { + description: + "Iteration scope for entries in a map/dictionary; should be between the braces", + scopeType: "collectionItem", + isIteration: true, + }, "comment.line": { description: "A line comment", diff --git a/packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts b/packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts index 2e052384b1..1e2473b7a7 100644 --- a/packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts +++ b/packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts @@ -37,6 +37,9 @@ const scopeSupportFacets = [ "argument.formal", "argument.formal.iteration", + "collectionItem.map", + "collectionItem.map.iteration", + "comment.line", "comment.block", diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk.yml new file mode 100644 index 0000000000..591582707e --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk.yml @@ -0,0 +1,27 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: | + def fun() do + # body + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunk10.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk10.yml new file mode 100644 index 0000000000..3cbf333698 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk10.yml @@ -0,0 +1,26 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def fun(x) when x == 1 do + x + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunk11.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk11.yml new file mode 100644 index 0000000000..c70aeebb75 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk11.yml @@ -0,0 +1,26 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + defp fun(x) do + x + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunk12.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk12.yml new file mode 100644 index 0000000000..afe4bd0370 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk12.yml @@ -0,0 +1,28 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + defmacro fun(x) do + quote do + [unquote(x)] + end + end + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, 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/elixir/changeFunk13.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk13.yml new file mode 100644 index 0000000000..30767e532b --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk13.yml @@ -0,0 +1,25 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: | + defguard is_even(term) when is_integer(term) and rem(term, 2) == 0 + selections: + - anchor: {line: 0, character: 26} + active: {line: 0, character: 26} + 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/elixir/changeFunk14.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk14.yml new file mode 100644 index 0000000000..7de4c4a748 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk14.yml @@ -0,0 +1,22 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + a x do + x + end + selections: + - anchor: {line: 1, character: 3} + active: {line: 1, character: 3} + marks: {} +thrownError: {name: NoContainingScopeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk2.yml new file mode 100644 index 0000000000..96cc853d71 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk2.yml @@ -0,0 +1,27 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: | + def fun do + # body + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunk3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk3.yml new file mode 100644 index 0000000000..31bd410663 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk3.yml @@ -0,0 +1,25 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: | + def fun, do: 1 + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + 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/elixir/changeFunk4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk4.yml new file mode 100644 index 0000000000..a2e32aba68 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk4.yml @@ -0,0 +1,25 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: | + def fun(), do: 1 + selections: + - anchor: {line: 0, character: 16} + active: {line: 0, character: 16} + 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/elixir/changeFunk5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk5.yml new file mode 100644 index 0000000000..c10c01c339 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk5.yml @@ -0,0 +1,26 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def fun(x) do + x + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunk6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk6.yml new file mode 100644 index 0000000000..d9ec85f355 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk6.yml @@ -0,0 +1,26 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def fun x do + x + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunk7.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk7.yml new file mode 100644 index 0000000000..cb6bfc73bf --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk7.yml @@ -0,0 +1,27 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: | + def fun(x, y) do + x + y + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunk8.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk8.yml new file mode 100644 index 0000000000..f842c7f924 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk8.yml @@ -0,0 +1,26 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def fun x, y do + x + y + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunk9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk9.yml new file mode 100644 index 0000000000..774bbc8ce6 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunk9.yml @@ -0,0 +1,26 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def fun(x, y \\ 1) do + x + y + end + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + 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/elixir/changeFunkName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunkName.yml new file mode 100644 index 0000000000..6b12bca374 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeFunkName.yml @@ -0,0 +1,29 @@ +languageId: elixir +command: + version: 6 + spokenForm: change funk name + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: functionName} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def elixir(x, something) do + x = 5 + end + selections: + - anchor: {line: 1, character: 5} + active: {line: 1, character: 5} + marks: {} +finalState: + documentContents: |- + def (x, something) do + x = 5 + end + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeKey.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeKey.yml new file mode 100644 index 0000000000..f94c03f260 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeKey.yml @@ -0,0 +1,33 @@ +languageId: elixir +command: + version: 6 + spokenForm: change key + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionKey} + usePrePhraseSnapshot: true +initialState: + documentContents: | + %{ + :a => "lorem", + "b" => "ipsum", + 3 => "dolor" + } + selections: + - anchor: {line: 2, character: 13} + active: {line: 2, character: 13} + marks: {} +finalState: + documentContents: | + %{ + :a => "lorem", + => "ipsum", + 3 => "dolor" + } + selections: + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeKeyFine.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeKeyFine.yml new file mode 100644 index 0000000000..f2bb375041 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeKeyFine.yml @@ -0,0 +1,27 @@ +languageId: elixir +command: + version: 6 + spokenForm: change key fine + action: + name: clearAndSetSelection + target: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: f} + modifiers: + - type: containingScope + scopeType: {type: collectionKey} + usePrePhraseSnapshot: true +initialState: + documentContents: "%{ \"foo\" => \"lorem\", \"bar\" => \"ipsum\", \"baz\" => \"dolor\"}" + selections: + - anchor: {line: 0, character: 56} + active: {line: 0, character: 56} + marks: + default.f: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: "%{ => \"lorem\", \"bar\" => \"ipsum\", \"baz\" => \"dolor\"}" + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeLambda.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeLambda.yml new file mode 100644 index 0000000000..2b9110f3b1 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeLambda.yml @@ -0,0 +1,23 @@ +languageId: elixir +command: + version: 6 + spokenForm: change lambda + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: anonymousFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: add = fn x, y -> x + y end + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + marks: {} +finalState: + documentContents: "add = " + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeNextComment.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeNextComment.yml new file mode 100644 index 0000000000..93d411054f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeNextComment.yml @@ -0,0 +1,29 @@ +languageId: elixir +command: + version: 6 + spokenForm: change next comment + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: comment} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: |- + + # blah + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: |+ + + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeValue.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeValue.yml new file mode 100644 index 0000000000..21d197cbbb --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/changeValue.yml @@ -0,0 +1,33 @@ +languageId: elixir +command: + version: 6 + spokenForm: change value + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: value} + usePrePhraseSnapshot: true +initialState: + documentContents: | + %{ + :a => "lorem", + "b" => "ipsum", + 3 => "dolor" + } + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: | + %{ + :a => "lorem", + "b" => , + 3 => "dolor" + } + selections: + - anchor: {line: 2, character: 9} + active: {line: 2, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckInsideFunk.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckInsideFunk.yml new file mode 100644 index 0000000000..31d5bb91ef --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckInsideFunk.yml @@ -0,0 +1,31 @@ +languageId: elixir +command: + version: 6 + spokenForm: chuck inside funk + action: + name: remove + target: + type: primitive + modifiers: + - {type: interiorOnly} + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + defmacro macro(x) do + quote do + [unquote(x)] + end + end + selections: + - anchor: {line: 2, character: 16} + active: {line: 2, character: 16} + marks: {} +finalState: + documentContents: |- + defmacro macro(x) do + end + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckInsideFunk2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckInsideFunk2.yml new file mode 100644 index 0000000000..adb8199602 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckInsideFunk2.yml @@ -0,0 +1,35 @@ +languageId: elixir +command: + version: 6 + spokenForm: chuck inside funk + action: + name: remove + target: + type: primitive + modifiers: + - {type: interiorOnly} + - type: containingScope + scopeType: {type: namedFunction} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + defmodule Mod do + defmacro macro(x) do + quote do + [unquote(x)] + end + end + end + selections: + - anchor: {line: 3, character: 18} + active: {line: 3, character: 18} + marks: {} +finalState: + documentContents: |- + defmodule Mod do + defmacro macro(x) do + end + end + selections: + - anchor: {line: 2, character: 0} + active: {line: 2, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo.yml new file mode 100644 index 0000000000..fd015cc97a --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo.yml @@ -0,0 +1,27 @@ +languageId: elixir +command: + version: 6 + spokenForm: chuck item two + action: + name: remove + target: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: '2'} + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: "[1, 2, 3]" + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + marks: + default.2: + start: {line: 0, character: 4} + end: {line: 0, character: 5} +finalState: + documentContents: "[1, 3]" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo2.yml new file mode 100644 index 0000000000..7b3a3592f1 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo2.yml @@ -0,0 +1,27 @@ +languageId: elixir +command: + version: 6 + spokenForm: chuck item two + action: + name: remove + target: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: '2'} + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: "%{a: 1, b: 2, c: 3}" + selections: + - anchor: {line: 0, character: 19} + active: {line: 0, character: 19} + marks: + default.2: + start: {line: 0, character: 11} + end: {line: 0, character: 12} +finalState: + documentContents: "%{a: 1, c: 3}" + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo3.yml new file mode 100644 index 0000000000..8595dd9012 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/elixir/chuckItemTwo3.yml @@ -0,0 +1,27 @@ +languageId: elixir +command: + version: 6 + spokenForm: chuck item two + action: + name: remove + target: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: '2'} + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: "%{\"a\" => 1, :b => 2, 3 => 3}" + selections: + - anchor: {line: 0, character: 28} + active: {line: 0, character: 28} + marks: + default.2: + start: {line: 0, character: 18} + end: {line: 0, character: 19} +finalState: + documentContents: "%{\"a\" => 1, 3 => 3}" + selections: + - anchor: {line: 0, character: 19} + active: {line: 0, character: 19} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map.iteration.scope b/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map.iteration.scope new file mode 100644 index 0000000000..0fdb9cf4af --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map.iteration.scope @@ -0,0 +1,12 @@ +defmodule DataStructures do + %{a: 1, b: 2} +end +--- + +[Range] = +[Domain] = 1:4-1:14 +0| defmodule DataStructures do + +1| %{a: 1, b: 2} + >----------< +2| end diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map.scope b/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map.scope new file mode 100644 index 0000000000..5dfec3f6eb --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map.scope @@ -0,0 +1,59 @@ +defmodule DataStructures do + %{a: 1, b: 2} +end +--- + +[#1 Content] = +[#1 Domain] = 1:4-1:8 +0| defmodule DataStructures do + +1| %{a: 1, b: 2} + >----< +2| end + + +[#1 Removal] = 1:4-1:10 +0| defmodule DataStructures do + +1| %{a: 1, b: 2} + >------< +2| end + + +[#1 Trailing delimiter] = 1:8-1:10 +0| defmodule DataStructures do + +1| %{a: 1, b: 2} + >--< +2| end + + +[#1 Insertion delimiter] = ", " + + +[#2 Content] = +[#2 Domain] = 1:10-1:14 +0| defmodule DataStructures do + +1| %{a: 1, b: 2} + >----< +2| end + + +[#2 Removal] = 1:8-1:14 +0| defmodule DataStructures do + +1| %{a: 1, b: 2} + >------< +2| end + + +[#2 Leading delimiter] = 1:8-1:10 +0| defmodule DataStructures do + +1| %{a: 1, b: 2} + >--< +2| end + + +[#2 Insertion delimiter] = ", " diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map2.scope b/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map2.scope new file mode 100644 index 0000000000..a9b099331b --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/scopes/elixir/collectionItem.map2.scope @@ -0,0 +1,98 @@ +defmodule DataStructures do + %{ + a: 1, + b: 2 + } +end +--- + +[#1 Content] = +[#1 Domain] = 2:4-2:8 +0| defmodule DataStructures do + +1| %{ + +2| a: 1, + >----< +3| b: 2 + +4| } + +5| end + + +[#1 Removal] = 2:4-3:4 +0| defmodule DataStructures do + +1| %{ + +2| a: 1, + >----- +3| b: 2 + ----< +4| } + +5| end + + +[#1 Trailing delimiter] = 2:8-3:4 +0| defmodule DataStructures do + +1| %{ + +2| a: 1, + >- +3| b: 2 + ----< +4| } + +5| end + + +[#1 Insertion delimiter] = ",\n" + + +[#2 Content] = +[#2 Domain] = 3:4-3:8 +0| defmodule DataStructures do + +1| %{ + +2| a: 1, + +3| b: 2 + >----< +4| } + +5| end + + +[#2 Removal] = 2:8-3:8 +0| defmodule DataStructures do + +1| %{ + +2| a: 1, + >- +3| b: 2 + --------< +4| } + +5| end + + +[#2 Leading delimiter] = 2:8-3:4 +0| defmodule DataStructures do + +1| %{ + +2| a: 1, + >- +3| b: 2 + ----< +4| } + +5| end + + +[#2 Insertion delimiter] = ",\n" diff --git a/queries/elixir.scm b/queries/elixir.scm new file mode 100644 index 0000000000..14566a195e --- /dev/null +++ b/queries/elixir.scm @@ -0,0 +1,126 @@ +(comment) @comment @textFragment +(string) @string @textFragment +(anonymous_function) @anonymousFunction +(call + target: (_) @functionCallee +) @functionCall @functionCallee.domain + +;; Map +;; %{:a => "lorem", "b" => "ipsum", 3 => "dolor"} +( + (map_content + (_)? @_.leading.endOf + . + (binary_operator) @collectionItem + . + (_)? @_.trailing.startOf + ) @dummy + (#single-or-multi-line-delimiter! @collectionItem @dummy ", " ",\n") +) +(map_content + (binary_operator + left: (_) @collectionKey + right: (_) @value + ) @collectionKey.domain @value.domain +) + +;; Shorthand map syntax +;; %{a: "lorem", b: "ipsum"} +( + (map_content + (keywords + (_)? @_.leading.endOf + . + (pair) @collectionItem + . + (_)? @_.trailing.startOf + ) + (#single-or-multi-line-delimiter! @collectionItem @dummy ", " ",\n") + ) @dummy +) + +(map_content) @collectionItem.iteration @collectionKey.iteration @value.iteration + +(map_content + (keywords + (pair + key: (_) @collectionKey + value: (_) @value + ) @collectionKey.domain @value.domain + ) +) + +(call + target: (identifier) @_target + (#match? @_target "^(def|defp|defdelegate|defmacro|defmacrop|defn|defnp)$") + (arguments + [ + ; zero-arity functions with no parentheses + (identifier) @functionName + ; regular function clause + (call + target: (identifier) @functionName + ) + ; function clause with a guard clause + (binary_operator + left: (call + target: (identifier) @functionName + ) + operator: "when" + ) + ] + ) + (do_block) @namedFunction.interior + (#shrink-to-match! @namedFunction.interior "^do\\n?\\w*(?.*?\\n)\\s*end$") +) @namedFunction @functionName.domain + +;; def fun(), do: 1 +(call + target: (identifier) @_target + (#match? @_target "^(def|defp|defdelegate|defmacro|defmacrop|defn|defnp)$") + (arguments + (keywords + (pair + key: (_) @do_keyword + value: (_) @namedFunction.interior + ) + ) + (#match? @do_keyword "^do: $") + ) +) @namedFunction @functionName.domain + +;; defguard guard(term) when is_integer(term) and rem(term, 2) == 0 +(call + target: (identifier) @_target + (#match? @_target "^(defguard|defguardp)$") + (arguments + (binary_operator + left: (call + target: (identifier) @functionName + ) + operator: "when" + ) + ) +) @namedFunction @functionName.domain + +(call + target: (identifier) @_target + (#match? @_target "^(defmodule)$") + (arguments + (alias) @className + ) + (do_block + . + "do" @class.interior.start.endOf + "end" @class.interior.end.startOf + . + ) +) @class @className.domain + +(source) @className.iteration @class.iteration +(source) @statement.iteration +(source) @namedFunction.iteration @functionName.iteration +(call + target: (identifier) @_target + (#match? @_target "^(defmodule)$") +) @namedFunction.iteration @functionName.iteration