diff --git a/.credo.exs b/.credo.exs
index b21d62c4..db7fda8a 100644
--- a/.credo.exs
+++ b/.credo.exs
@@ -4,7 +4,7 @@
name: "default",
files: %{
included: ["lib/", "src/", "web/", "apps/"],
- excluded: ["apps/remote_control/test/fixtures/**/*.ex", "apps/common/lib/future/**/*.ex"]
+ excluded: ["apps/engine/test/fixtures/**/*.ex", "apps/forge/lib/future/**/*.ex"]
},
plugins: [],
requires: [],
diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml
index b1ac5ebc..46498b49 100644
--- a/.github/workflows/elixir.yml
+++ b/.github/workflows/elixir.yml
@@ -114,10 +114,10 @@ jobs:
uses: actions/cache@v3
with:
path: "priv/plts"
- key: lexical-plts-2-${{ env.DEFAULT_OTP }}-${{ env.DEFAULT_ELIXIR }}-${{ hashFiles('apps/**/mix.lock' ) }}-${{ github.run_id }}
+ key: expert-plts-2-${{ env.DEFAULT_OTP }}-${{ env.DEFAULT_ELIXIR }}-${{ hashFiles('apps/**/mix.lock' ) }}-${{ github.run_id }}
restore-keys: |
- lexical-plts-2-${{ env.DEFAULT_OTP }}-${{ env.DEFAULT_ELIXIR }}-${{ hashFiles('apps/**/mix.lock') }}-
- lexical-plts-2-${{ env.DEFAULT_OTP }}-${{ env.DEFAULT_ELIXIR }}-
+ expert-plts-2-${{ env.DEFAULT_OTP }}-${{ env.DEFAULT_ELIXIR }}-${{ hashFiles('apps/**/mix.lock') }}-
+ expert-plts-2-${{ env.DEFAULT_OTP }}-${{ env.DEFAULT_ELIXIR }}-
# Step: Download project dependencies. If unchanged, uses
# the cached version.
@@ -215,7 +215,7 @@ jobs:
with:
context: .
file: ./integration/Dockerfile
- tags: lx
+ tags: xp
# GitHub Actions cache
# https://docs.docker.com/build/ci/github-actions/cache/
cache-from: type=gha
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5622543b..e714ab11 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,4 +1,4 @@
-name: Release Lexical
+name: Release Expert
on:
push:
@@ -7,7 +7,7 @@ on:
jobs:
release:
runs-on: ubuntu-latest
- name: Build and release Lexical
+ name: Build and release Expert
permissions:
contents: write
steps:
@@ -43,12 +43,12 @@ jobs:
- name: Archive release
run: |
- cp lexical.zip lexical-${{ github.ref_name }}.zip
+ cp expert.zip expert-${{ github.ref_name }}.zip
- name: Publish release
uses: ncipollo/release-action@v1
with:
- artifacts: lexical*.zip
+ artifacts: expert*.zip
makeLatest: true
generateReleaseNotes: false
allowUpdates: true
diff --git a/.gitignore b/.gitignore
index 1ab6d514..48992149 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,10 +22,10 @@ erl_crash.dump
# Temporary files, for example, from tests.
/tmp/
-lexical.log
+expert.log
-lexical_debug
+expert_debug
priv/plts
-apps/common/src/future_elixir_parser.erl
+apps/forge/src/future_elixir_parser.erl
diff --git a/.iex.exs b/.iex.exs
index ba228cdf..9776e5b0 100644
--- a/.iex.exs
+++ b/.iex.exs
@@ -1,4 +1,4 @@
-use Lexical.Server.IEx.Helpers
+use Expert.IEx.Helpers
try do
Mix.ensure_application!(:observer)
diff --git a/.iex.namespaced.exs b/.iex.namespaced.exs
index 0cc651b5..18b495db 100644
--- a/.iex.namespaced.exs
+++ b/.iex.namespaced.exs
@@ -1,2 +1,2 @@
-use LXical.Server.IEx.Helpers
-alias LXical, as: Lexical
+use XPExpert.Server.IEx.Helpers
+alias XPExpert, as: EXpert
diff --git a/Makefile b/Makefile
index 567b6a8d..6521363d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-poncho_dirs = common lexical_credo proto protocol remote_control server
+poncho_dirs = forge expert_credo proto protocol engine expert
compile.all: compile.poncho
diff --git a/README.md b/README.md
index eb22d221..42820261 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,9 @@
-# Lexical
+# Expert
-Lexical is a next-generation language server for the Elixir programming language.
+Expert is a next-generation language server for the Elixir programming language.
@@ -154,7 +154,7 @@ language server. This allows you to investigate processes, make
changes to the running code, or run `:observer`.
While in the debugging shell, all the functions in
-`Lexical.Server.IEx.Helpers` are imported for you, and some common
+`Expert.IEx.Helpers` are imported for you, and some common
modules, like `Lexical.Project` and `Lexical.Document` are
aliased.
diff --git a/apps/common/.formatter.exs b/apps/common/.formatter.exs
deleted file mode 100644
index 92005875..00000000
--- a/apps/common/.formatter.exs
+++ /dev/null
@@ -1,27 +0,0 @@
-# Used by "mix format"
-eventual_assertions = [
- assert_eventually: 1,
- assert_eventually: 2,
- refute_eventually: 1,
- refute_eventually: 2
-]
-
-detected_assertions = [
- assert_detected: 1,
- assert_detected: 2,
- refute_detected: 1,
- refute_detected: 2
-]
-
-assertions = eventual_assertions ++ detected_assertions
-
-[
- inputs: [
- "{mix,.formatter}.exs",
- "{config,test}/**/*.{ex,exs}",
- "lib/lexical/**/*.{ex,ex}",
- "lib/mix/**/*.{ex,exs}"
- ],
- locals_without_parens: assertions,
- export: [locals_without_parens: assertions]
-]
diff --git a/apps/common/.gitignore b/apps/common/.gitignore
deleted file mode 100644
index d3a0cc38..00000000
--- a/apps/common/.gitignore
+++ /dev/null
@@ -1,26 +0,0 @@
-# The directory Mix will write compiled artifacts to.
-/_build/
-
-# If you run "mix test --cover", coverage assets end up here.
-/cover/
-
-# The directory Mix downloads your dependencies sources to.
-/deps/
-
-# Where third-party dependencies like ExDoc output generated docs.
-/doc/
-
-# Ignore .fetch files in case you like to edit your project deps locally.
-/.fetch
-
-# If the VM crashes, it generates a dump, let's ignore it too.
-erl_crash.dump
-
-# Also ignore archive artifacts (built via "mix archive.build").
-*.ez
-
-# Ignore package tarball (built via "mix hex.build").
-common-*.tar
-
-# Temporary files, for example, from tests.
-/tmp/
diff --git a/apps/common/lib/lexical.ex b/apps/common/lib/lexical.ex
deleted file mode 100644
index 44382a7d..00000000
--- a/apps/common/lib/lexical.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule Lexical do
- @moduledoc """
- Common data structures and utilities for the Lexical Language Server.
-
- If you're building a plugin, You're probably going to want to look at the documentation
- for core data structures like
-
- `Lexical.Project` - The lexical project structure
-
- `Lexical.Document` - A text document, given to you by the language server
-
- `Lexical.Document.Position` - A position inside a document
-
- `Lexical.Document.Range` - A range of text inside a document
- """
- @typedoc "A string representation of a uri"
- @type uri :: String.t()
-
- @typedoc "A string representation of a path on the filesystem"
- @type path :: String.t()
-end
diff --git a/apps/common/lib/lexical/ast/analysis/alias.ex b/apps/common/lib/lexical/ast/analysis/alias.ex
deleted file mode 100644
index 4e9ecf62..00000000
--- a/apps/common/lib/lexical/ast/analysis/alias.ex
+++ /dev/null
@@ -1,52 +0,0 @@
-defmodule Lexical.Ast.Analysis.Alias do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
-
- defstruct [:module, :as, :range, explicit?: true]
-
- @type t :: %__MODULE__{
- module: [atom],
- as: module(),
- range: Range.t() | nil
- }
-
- def explicit(%Document{} = document, ast, module, as) when is_list(module) do
- range = range_for_ast(document, ast, module, as)
- %__MODULE__{module: module, as: as, range: range}
- end
-
- def implicit(%Document{} = document, ast, module, as) when is_list(module) do
- range = implicit_range(document, ast)
- %__MODULE__{module: module, as: as, range: range, explicit?: false}
- end
-
- def to_module(%__MODULE__{} = alias) do
- Module.concat(alias.module)
- end
-
- @implicit_aliases [:__MODULE__, :"@for", :"@protocol"]
- defp range_for_ast(document, ast, _alias, as) when as in @implicit_aliases do
- implicit_range(document, ast)
- end
-
- defp range_for_ast(document, ast, _alias, _as) do
- # All other kinds of aliases defined with the `alias` special form
- Ast.Range.get(ast, document)
- end
-
- defp implicit_range(%Document{} = document, ast) do
- # There are kinds of aliases that are automatically generated by elixir
- # such as __MODULE__, these don't really have any code that defines them,
- with [line: line, column: _] <- Sourceror.get_start_position(ast),
- {:ok, line_text} <- Document.fetch_text_at(document, line) do
- line_length = String.length(line_text)
- alias_start = Position.new(document, line, line_length)
- Range.new(alias_start, alias_start)
- else
- _ ->
- nil
- end
- end
-end
diff --git a/apps/common/lib/lexical/ast/analysis/import.ex b/apps/common/lib/lexical/ast/analysis/import.ex
deleted file mode 100644
index 1be02a54..00000000
--- a/apps/common/lib/lexical/ast/analysis/import.ex
+++ /dev/null
@@ -1,92 +0,0 @@
-defmodule Lexical.Ast.Analysis.Import do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Document.Range
-
- defstruct module: nil, selector: :all, range: nil, explicit?: true
-
- @type function_name :: atom()
- @type function_arity :: {function_name(), arity()}
- @type selector ::
- :functions
- | :macros
- | :sigils
- | [only: [function_arity()]]
- | [except: [function_arity()]]
- @type t :: %{
- module: module(),
- selector: selector(),
- line: non_neg_integer()
- }
- def new(%Document{} = document, ast, module) do
- %__MODULE__{module: module, range: Ast.Range.get(ast, document)}
- end
-
- def new(%Document{} = document, ast, module, selector) do
- %__MODULE__{
- module: module,
- selector: expand_selector(selector),
- range: Ast.Range.get(ast, document)
- }
- end
-
- def implicit(%Range{} = range, module) do
- %__MODULE__{module: module, range: range, explicit?: false}
- end
-
- defp expand_selector(selectors) when is_list(selectors) do
- selectors =
- Enum.reduce(selectors, [], fn
- {{:__block__, _, [type]}, {:__block__, _, [selector]}}, acc
- when type in [:only, :except] ->
- expanded =
- case selector do
- :functions ->
- :functions
-
- :macros ->
- :macros
-
- :sigils ->
- :sigils
-
- keyword when is_list(keyword) ->
- keyword
- |> Enum.reduce([], &expand_function_keywords/2)
- |> Enum.reverse()
-
- _ ->
- # they're likely in the middle of typing in something, and have produced an
- # invalid import
- []
- end
-
- [{type, expanded} | acc]
-
- _, acc ->
- acc
- end)
-
- if selectors == [] do
- :all
- else
- selectors
- end
- end
-
- # If the selectors is not valid, like: `import SomeModule, o `, we default to :all
- defp expand_selector(_) do
- :all
- end
-
- defp expand_function_keywords(
- {{:__block__, _, [function_name]}, {:__block__, _, [arity]}},
- acc
- )
- when is_atom(function_name) and is_number(arity) do
- [{function_name, arity} | acc]
- end
-
- defp expand_function_keywords(_ignored, acc),
- do: acc
-end
diff --git a/apps/common/lib/lexical/ast/analysis/require.ex b/apps/common/lib/lexical/ast/analysis/require.ex
deleted file mode 100644
index 190cbe7d..00000000
--- a/apps/common/lib/lexical/ast/analysis/require.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-defmodule Lexical.Ast.Analysis.Require do
- alias Lexical.Ast
- alias Lexical.Document
- defstruct [:module, :as, :range]
-
- def new(%Document{} = document, ast, module, as \\ nil) when is_list(module) do
- range = Ast.Range.get(ast, document)
- %__MODULE__{module: module, as: as || module, range: range}
- end
-end
diff --git a/apps/common/lib/lexical/ast/analysis/state.ex b/apps/common/lib/lexical/ast/analysis/state.ex
deleted file mode 100644
index 49f315bf..00000000
--- a/apps/common/lib/lexical/ast/analysis/state.ex
+++ /dev/null
@@ -1,180 +0,0 @@
-defmodule Lexical.Ast.Analysis.State do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Alias
- alias Lexical.Ast.Analysis.Import
- alias Lexical.Ast.Analysis.Require
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Ast.Analysis.Use
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
-
- defstruct [:document, scopes: [], visited: %{}]
-
- def new(%Document{} = document) do
- state = %__MODULE__{document: document}
-
- scope =
- document
- |> global_range()
- |> Scope.global()
-
- push_scope(state, scope)
- end
-
- def current_scope(%__MODULE__{scopes: [scope | _]}), do: scope
-
- def current_module(%__MODULE__{} = state) do
- current_scope(state).module
- end
-
- def push_scope(%__MODULE__{} = state, %Scope{} = scope) do
- Map.update!(state, :scopes, &[scope | &1])
- end
-
- def push_scope(%__MODULE__{} = state, id, %Range{} = range, module) when is_list(module) do
- scope =
- state
- |> current_scope()
- |> Scope.new(id, range, module)
-
- push_scope(state, scope)
- end
-
- def push_scope_for(%__MODULE__{} = state, quoted, %Range{} = range, module) do
- module = module || current_module(state)
-
- id = Analysis.scope_id(quoted)
- push_scope(state, id, range, module)
- end
-
- def push_scope_for(%__MODULE__{} = state, quoted, module) do
- range = get_range(quoted, state.document)
- push_scope_for(state, quoted, range, module)
- end
-
- def maybe_push_scope_for(%__MODULE__{} = state, quoted) do
- case get_range(quoted, state.document) do
- %Range{} = range ->
- push_scope_for(state, quoted, range, nil)
-
- nil ->
- state
- end
- end
-
- def pop_scope(%__MODULE__{scopes: [scope | rest]} = state) do
- %__MODULE__{state | scopes: rest, visited: Map.put(state.visited, scope.id, scope)}
- end
-
- def push_alias(%__MODULE__{} = state, %Alias{} = alias) do
- update_current_scope(state, fn %Scope{} = scope ->
- [prefix | rest] = alias.module
-
- alias =
- case Scope.fetch_alias_with_prefix(scope, prefix) do
- {:ok, %Alias{} = existing_alias} ->
- %Alias{alias | module: existing_alias.module ++ rest}
-
- :error ->
- alias
- end
-
- Map.update!(scope, :aliases, &[alias | &1])
- end)
- end
-
- def push_import(%__MODULE__{} = state, %Import{} = import) do
- update_current_scope(state, fn %Scope{} = scope ->
- Map.update!(scope, :imports, &[import | &1])
- end)
- end
-
- def push_require(%__MODULE__{} = state, %Require{} = require) do
- update_current_scope(state, fn %Scope{} = scope ->
- Map.update!(scope, :requires, &[require | &1])
- end)
- end
-
- def push_use(%__MODULE__{} = state, %Use{} = use) do
- update_current_scope(state, fn %Scope{} = scope ->
- Map.update!(scope, :uses, &[use | &1])
- end)
- end
-
- defp update_current_scope(%__MODULE__{} = state, fun) do
- update_in(state, [Access.key(:scopes), Access.at!(0)], fn %Scope{} = scope ->
- fun.(scope)
- end)
- end
-
- # if there is no code after a stab operator, then the end position
- # it gives us can be in the middle of the line, as it's derived from
- # the start of some entity on the last line. So we increment the line
- # by one, and that should be the end of the stab block
- defp get_range({:->, _, _} = quoted, %Document{} = document) do
- start_pos = get_start_position(quoted)
-
- case Sourceror.get_end_position(quoted, line: -1, column: -1) do
- [line: -1, column: -1] ->
- nil
-
- [line: line, column: 1] ->
- Range.new(
- Position.new(document, start_pos[:line], start_pos[:column]),
- Position.new(document, line + 1, 1)
- )
-
- [line: line, column: _] ->
- Range.new(
- Position.new(document, start_pos[:line], start_pos[:column]),
- Position.new(document, line + 1, 1)
- )
- end
- end
-
- defp get_range(quoted, %Document{} = document) do
- start_pos = get_start_position(quoted)
-
- case Sourceror.get_end_position(quoted, line: -1, column: -1) do
- [line: -1, column: -1] ->
- nil
-
- [line: end_line, column: end_column] ->
- Range.new(
- Position.new(document, start_pos[:line], start_pos[:column]),
- Position.new(document, end_line, end_column)
- )
- end
- end
-
- defp global_range(%Document{} = document) do
- num_lines = Document.size(document)
-
- Range.new(
- Position.new(document, 1, 1),
- Position.new(document, num_lines + 1, 1)
- )
- end
-
- defp get_start_position({_, metadata, _} = ast) do
- case Keyword.fetch(metadata, :do) do
- {:ok, [line: line, column: column]} ->
- # add 2 to position us after the do keyword
- [line: line, column: column + 2]
-
- _ ->
- Sourceror.get_start_position(ast)
- end
- end
-
- defp get_start_position({block_meta, _rest}) do
- case Sourceror.get_start_position(block_meta) do
- [line: line, column: column] ->
- [line: line, column: column + 2]
-
- other ->
- other
- end
- end
-end
diff --git a/apps/common/lib/lexical/ast/analysis/use.ex b/apps/common/lib/lexical/ast/analysis/use.ex
deleted file mode 100644
index e98c1045..00000000
--- a/apps/common/lib/lexical/ast/analysis/use.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-defmodule Lexical.Ast.Analysis.Use do
- alias Lexical.Ast
- alias Lexical.Document
- defstruct [:module, :range, :opts]
-
- def new(%Document{} = document, ast, module, opts) do
- range = Ast.Range.get(ast, document)
- %__MODULE__{range: range, module: module, opts: opts}
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/alias.ex b/apps/common/lib/lexical/ast/detection/alias.ex
deleted file mode 100644
index 72cac9ef..00000000
--- a/apps/common/lib/lexical/ast/detection/alias.ex
+++ /dev/null
@@ -1,64 +0,0 @@
-defmodule Lexical.Ast.Detection.Alias do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Tokens
- alias Lexical.Document.Position
-
- use Detection
-
- @doc """
- Recognizes an alias at the current position
-
- Aliases are complicated, especially if we're trying to find out if we're in
- them from the current cursor position moving backwards.
- I'll try to describe the state machine below.
- First off, if we're outside of a } on the current line, we cannot be in an alias, so that
- halts with false.
- Similarly an alias on the current line is also simple, we just backtrack until we see the alias identifier.
- However, if we're on the current line, and see an EOL, we set that as our accumulator, then we get
- to the previous line, we see if it ends in a comma. If not, we can't be in an alias. If it does, we keep
- backtracking until we hit the alias keyword.
- So basically, if we hit an EOL, and the previous token isn't an open curly or a comma, we stop, otherwise
- we backtrack until we hit the alias keyword
- """
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- result =
- analysis.document
- |> Tokens.prefix_stream(position)
- |> Stream.with_index()
- |> Enum.reduce_while(false, fn
- {{:curly, :"{", _}, _index}, :eol ->
- {:cont, false}
-
- {{:comma, _, _}, _index}, :eol ->
- {:cont, false}
-
- {{:eol, _, _}, _index}, _acc ->
- {:cont, :eol}
-
- {{_, _, _}, _}, :eol ->
- {:halt, false}
-
- {{:curly, :"}", _}, _index}, _ ->
- {:halt, false}
-
- {{:identifier, ~c"alias", _}, 0}, _ ->
- # there is nothing after the alias directive, so we're not
- # inside the context *yet*
-
- {:halt, false}
-
- {{:identifier, ~c"alias", _}, _index}, _ ->
- {:halt, true}
-
- _, _ ->
- {:cont, false}
- end)
-
- case result do
- b when is_boolean(b) -> b
- :eol -> false
- end
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/bitstring.ex b/apps/common/lib/lexical/ast/detection/bitstring.ex
deleted file mode 100644
index a03bbef3..00000000
--- a/apps/common/lib/lexical/ast/detection/bitstring.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-defmodule Lexical.Ast.Detection.Bitstring do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Tokens
- alias Lexical.Document
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- document = analysis.document
- Document.fragment(document, Position.new(document, position.line, 1), position)
-
- document
- |> Tokens.prefix_stream(position)
- |> Enum.reduce_while(
- false,
- fn
- {:operator, :">>", _}, _ -> {:halt, false}
- {:operator, :"<<", _}, _ -> {:halt, true}
- _, _ -> {:cont, false}
- end
- )
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/comment.ex b/apps/common/lib/lexical/ast/detection/comment.ex
deleted file mode 100644
index 743d1cb1..00000000
--- a/apps/common/lib/lexical/ast/detection/comment.ex
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule Lexical.Ast.Detection.Comment do
- alias Lexical.Ast.Analysis
- alias Lexical.Document.Position
-
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- Analysis.commented?(analysis, position)
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/directive.ex b/apps/common/lib/lexical/ast/detection/directive.ex
deleted file mode 100644
index 76cde173..00000000
--- a/apps/common/lib/lexical/ast/detection/directive.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule Lexical.Ast.Detection.Directive do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Tokens
- alias Lexical.Document.Position
-
- def detected?(%Analysis{} = analysis, %Position{} = position, directive_type) do
- analysis.document
- |> Tokens.prefix_stream(position)
- |> Enum.to_list()
- |> Enum.reduce_while(false, fn
- {:identifier, ^directive_type, _}, _ ->
- {:halt, true}
-
- {:eol, _, _}, _ ->
- {:halt, false}
-
- _, _ ->
- {:cont, false}
- end)
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/import.ex b/apps/common/lib/lexical/ast/detection/import.ex
deleted file mode 100644
index 18150002..00000000
--- a/apps/common/lib/lexical/ast/detection/import.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-defmodule Lexical.Ast.Detection.Import do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Detection.Directive
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- Directive.detected?(analysis, position, ~c"import")
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/module_attribute.ex b/apps/common/lib/lexical/ast/detection/module_attribute.ex
deleted file mode 100644
index 32c3e0bf..00000000
--- a/apps/common/lib/lexical/ast/detection/module_attribute.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-defmodule Lexical.Ast.Detection.ModuleAttribute do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- ancestor_is_attribute?(analysis, position)
- end
-
- def detected?(%Analysis{} = analysis, %Position{} = position, name) do
- ancestor_is_attribute?(analysis, position, name)
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/require.ex b/apps/common/lib/lexical/ast/detection/require.ex
deleted file mode 100644
index fa07d47c..00000000
--- a/apps/common/lib/lexical/ast/detection/require.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-defmodule Lexical.Ast.Detection.Require do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Detection.Directive
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- Directive.detected?(analysis, position, ~c"require")
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/spec.ex b/apps/common/lib/lexical/ast/detection/spec.ex
deleted file mode 100644
index 20309e8a..00000000
--- a/apps/common/lib/lexical/ast/detection/spec.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defmodule Lexical.Ast.Detection.Spec do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- ancestor_is_spec?(analysis, position)
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/struct_field_key.ex b/apps/common/lib/lexical/ast/detection/struct_field_key.ex
deleted file mode 100644
index 31562c5f..00000000
--- a/apps/common/lib/lexical/ast/detection/struct_field_key.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule Lexical.Ast.Detection.StructFieldKey do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- cursor_path = Ast.cursor_path(analysis, position)
-
- match?(
- # in the key position, the cursor will always be followed by the
- # map node because, in any other case, there will minimally be a
- # 2-element key-value tuple containing the cursor
- [{:__cursor__, _, _}, {:%{}, _, _}, {:%, _, _} | _],
- cursor_path
- )
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/struct_field_value.ex b/apps/common/lib/lexical/ast/detection/struct_field_value.ex
deleted file mode 100644
index b7562f0a..00000000
--- a/apps/common/lib/lexical/ast/detection/struct_field_value.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-defmodule Lexical.Ast.Detection.StructFieldValue do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection.StructFieldKey
- alias Lexical.Ast.Detection.StructFields
- alias Lexical.Document.Position
-
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- StructFields.detected?(analysis, position) and
- not StructFieldKey.detected?(analysis, position)
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/struct_fields.ex b/apps/common/lib/lexical/ast/detection/struct_fields.ex
deleted file mode 100644
index 5239e9a6..00000000
--- a/apps/common/lib/lexical/ast/detection/struct_fields.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-defmodule Lexical.Ast.Detection.StructFields do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- analysis.document
- |> Ast.cursor_path(position)
- |> Enum.any?(&match?({:%, _, _}, &1))
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/struct_reference.ex b/apps/common/lib/lexical/ast/detection/struct_reference.ex
deleted file mode 100644
index 2b5b86da..00000000
--- a/apps/common/lib/lexical/ast/detection/struct_reference.ex
+++ /dev/null
@@ -1,43 +0,0 @@
-defmodule Lexical.Ast.Detection.StructReference do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Tokens
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- case Ast.cursor_context(analysis, position) do
- {:ok, {:struct, []}} ->
- false
-
- {:ok, {:struct, _}} ->
- true
-
- {:ok, {:local_or_var, [?_ | _rest] = possible_module_struct}} ->
- # a reference to `%__MODULE`, often in a function head, as in
- # def foo(%__)
-
- starts_with_percent? =
- analysis.document
- |> Tokens.prefix_stream(position)
- |> Enum.take(2)
- |> Enum.any?(fn
- {:percent, :%, _} -> true
- _ -> false
- end)
-
- starts_with_percent? and possible_dunder_module(possible_module_struct) and
- (ancestor_is_def?(analysis, position) or ancestor_is_type?(analysis, position))
-
- _ ->
- false
- end
- end
-
- def possible_dunder_module(charlist) do
- String.starts_with?("__MODULE__", to_string(charlist))
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/type.ex b/apps/common/lib/lexical/ast/detection/type.ex
deleted file mode 100644
index 9dc9a55a..00000000
--- a/apps/common/lib/lexical/ast/detection/type.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defmodule Lexical.Ast.Detection.Type do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- ancestor_is_type?(analysis, position)
- end
-end
diff --git a/apps/common/lib/lexical/ast/detection/use.ex b/apps/common/lib/lexical/ast/detection/use.ex
deleted file mode 100644
index 2115dcf8..00000000
--- a/apps/common/lib/lexical/ast/detection/use.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-defmodule Lexical.Ast.Detection.Use do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Detection.Directive
- alias Lexical.Document.Position
-
- use Detection
-
- @impl Detection
- def detected?(%Analysis{} = analysis, %Position{} = position) do
- Directive.detected?(analysis, position, ~c"use")
- end
-end
diff --git a/apps/common/lib/lexical/ast/module.ex b/apps/common/lib/lexical/ast/module.ex
deleted file mode 100644
index ea424f22..00000000
--- a/apps/common/lib/lexical/ast/module.ex
+++ /dev/null
@@ -1,99 +0,0 @@
-defmodule Lexical.Ast.Module do
- @moduledoc """
- Module utilities
- """
-
- @doc """
- Formats a module name as a string.
- """
- @spec name(module() | Macro.t() | String.t()) :: String.t()
- def name([{:__MODULE__, _, _} | rest]) do
- [__MODULE__ | rest]
- |> Module.concat()
- |> name()
- end
-
- def name(module_name) when is_list(module_name) do
- module_name
- |> Module.concat()
- |> name()
- end
-
- def name(module_name) when is_binary(module_name) do
- module_name
- end
-
- def name(module_name) when is_atom(module_name) do
- module_name
- |> inspect()
- |> name()
- end
-
- @doc """
- local module name is the last part of a module name
-
- ## Examples:
- iex> local_name('Lexical.Ast.Module')
- "Module"
- """
- def local_name(entity) when is_list(entity) do
- entity
- |> to_string()
- |> local_name()
- end
-
- def local_name(entity) when is_binary(entity) do
- entity
- |> String.split(".")
- |> List.last()
- end
-
- @doc """
- Splits a module into is parts, but handles erlang modules
-
- Module.split will explode violently when called on an erlang module. This
- implementation will tell you which kind of module it has split, and return the
- pieces. You can also use the options to determine if the pieces are returned as
- strings or atoms
-
- Options:
- `as` :atoms or :binaries. Default is :binary. Determines what type the elements
- of the returned list are.
-
- Returns:
- A tuple where the first element is either `:elixir` or `:erlang`, which tells you
- the kind of module that has been split. The second element is a list of the
- module's components. Note: Erlang modules will only ever have a single component.
- """
- @type split_opt :: {:as, :binaries | :atoms}
- @type split_opts :: [split_opt()]
- @type split_return :: {:elixir | :erlang, [String.t()] | [atom()]}
-
- @spec safe_split(module()) :: split_return()
- @spec safe_split(module(), split_opts()) :: split_return()
- def safe_split(module, opts \\ [])
-
- def safe_split(module, opts) when is_atom(module) do
- string_name = Atom.to_string(module)
-
- {type, split_module} =
- case String.split(string_name, ".") do
- ["Elixir" | rest] ->
- {:elixir, rest}
-
- [_erlang_module] = module ->
- {:erlang, module}
- end
-
- split_module =
- case Keyword.get(opts, :as, :binaries) do
- :binaries ->
- split_module
-
- :atoms ->
- Enum.map(split_module, &String.to_atom/1)
- end
-
- {type, split_module}
- end
-end
diff --git a/apps/common/lib/lexical/ast/range.ex b/apps/common/lib/lexical/ast/range.ex
deleted file mode 100644
index cb3afd71..00000000
--- a/apps/common/lib/lexical/ast/range.ex
+++ /dev/null
@@ -1,48 +0,0 @@
-defmodule Lexical.Ast.Range do
- @moduledoc """
- Utilities for extracting ranges from ast nodes
- """
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
-
- @spec fetch(Macro.t(), Document.t()) :: {:ok, Range.t()} | :error
- def fetch(ast, %Document{} = document) do
- case Sourceror.get_range(ast) do
- %{start: start_pos, end: end_pos} ->
- [line: start_line, column: start_column] = start_pos
- [line: end_line, column: end_column] = end_pos
-
- range =
- Range.new(
- Position.new(document, start_line, start_column),
- Position.new(document, end_line, end_column)
- )
-
- {:ok, range}
-
- _ ->
- :error
- end
- end
-
- @spec fetch!(Macro.t(), Document.t()) :: Range.t()
- def fetch!(ast, %Document{} = document) do
- case fetch(ast, document) do
- {:ok, range} ->
- range
-
- :error ->
- raise ArgumentError,
- message: "Could not get a range for #{inspect(ast)} in #{document.path}"
- end
- end
-
- @spec get(Macro.t(), Document.t()) :: Range.t() | nil
- def get(ast, %Document{} = document) do
- case fetch(ast, document) do
- {:ok, range} -> range
- :error -> nil
- end
- end
-end
diff --git a/apps/common/lib/lexical/document.ex b/apps/common/lib/lexical/document.ex
deleted file mode 100644
index 3d5abe94..00000000
--- a/apps/common/lib/lexical/document.ex
+++ /dev/null
@@ -1,401 +0,0 @@
-defmodule Lexical.Document do
- @moduledoc """
- A representation of a LSP text document
-
- A document is the fundamental data structure of the Lexical language server.
- All language server documents are represented and backed by documents, which
- provide functionality for fetching lines, applying changes, and tracking versions.
- """
- alias Lexical.Convertible
- alias Lexical.Document.Edit
- alias Lexical.Document.Line
- alias Lexical.Document.Lines
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Math
-
- import Lexical.Document.Line
-
- require Logger
-
- alias __MODULE__.Path, as: DocumentPath
-
- @derive {Inspect, only: [:path, :version, :dirty?, :lines]}
-
- defstruct [:uri, :language_id, :path, :version, dirty?: false, lines: nil]
-
- @type version :: non_neg_integer()
- @type fragment_position :: Position.t() | Convertible.t()
- @type t :: %__MODULE__{
- uri: String.t(),
- language_id: String.t(),
- version: version(),
- dirty?: boolean,
- lines: Lines.t(),
- path: String.t()
- }
-
- @type change_application_error :: {:error, {:invalid_range, map()}}
-
- # public
-
- @doc """
- Creates a new document from a uri or path, the source code
- as a binary and the vewrsion.
- """
- @spec new(Lexical.path() | Lexical.uri(), String.t(), version()) :: t
- def new(maybe_uri, text, version, language_id \\ nil) do
- uri = DocumentPath.ensure_uri(maybe_uri)
- path = DocumentPath.from_uri(uri)
-
- language_id =
- if String.ends_with?(path, ".exs") do
- "elixir-script"
- else
- language_id || language_id_from_path(path)
- end
-
- %__MODULE__{
- uri: uri,
- version: version,
- lines: Lines.new(text),
- path: path,
- language_id: language_id
- }
- end
-
- @doc """
- Returns the number of lines in the document
-
- This is a constant time operation.
- """
- @spec size(t) :: non_neg_integer()
- def size(%__MODULE__{} = document) do
- Lines.size(document.lines)
- end
-
- @doc """
- Marks the document file as dirty
-
- This function is mainly used internally by lexical
- """
- @spec mark_dirty(t) :: t
- def mark_dirty(%__MODULE__{} = document) do
- %__MODULE__{document | dirty?: true}
- end
-
- @doc """
- Marks the document file as clean
-
- This function is mainly used internally by lexical
- """
- @spec mark_clean(t) :: t
- def mark_clean(%__MODULE__{} = document) do
- %__MODULE__{document | dirty?: false}
- end
-
- @doc """
- Get the text at the given line using `fetch` semantics
-
- Returns `{:ok, text}` if the line exists, and `:error` if it doesn't. The line text is
- returned without the line end character(s).
-
- This is a constant time operation.
- """
- @spec fetch_text_at(t, version()) :: {:ok, String.t()} | :error
- def fetch_text_at(%__MODULE__{} = document, line_number) do
- case fetch_line_at(document, line_number) do
- {:ok, line(text: text)} -> {:ok, text}
- _ -> :error
- end
- end
-
- @doc """
- Get the `Lexical>Document.Line` at the given index using `fetch` semantics.
-
- This function is of limited utility, you probably want `fetch_text_at/2` instead.
- """
- @spec fetch_line_at(t, version()) :: {:ok, Line.t()} | :error
- def fetch_line_at(%__MODULE__{} = document, line_number) do
- case Lines.fetch_line(document.lines, line_number) do
- {:ok, line} -> {:ok, line}
- _ -> :error
- end
- end
-
- @doc """
- Returns a fragment defined by the from and to arguments
-
- Builds a string that represents the text of the document from the two positions given.
- The from position, defaults to `:beginning` meaning the start of the document.
- Positions can be a `Document.Position.t` or anything that will convert to a position using
- `Lexical.Convertible.to_native/2`.
- """
- @spec fragment(t, fragment_position() | :beginning, fragment_position()) :: String.t()
- @spec fragment(t, fragment_position()) :: String.t()
- def fragment(%__MODULE__{} = document, from \\ :beginning, to) do
- line_count = size(document)
- from_pos = convert_fragment_position(document, from)
- to_pos = convert_fragment_position(document, to)
-
- from_line = Math.clamp(from_pos.line, document.lines.starting_index, line_count)
- to_line = Math.clamp(to_pos.line, from_line, line_count + 1)
-
- # source code positions are 1 based, but string slices are zero-based. Need an ad-hoc conversion
- # here.
- from_character = from_pos.character - 1
- to_character = to_pos.character - 1
-
- line_range = from_line..to_line
-
- line_range
- |> Enum.reduce([], fn line_number, acc ->
- to_append =
- case fetch_line_at(document, line_number) do
- {:ok, line(text: text, ending: ending)} ->
- line_text = text <> ending
-
- cond do
- line_number == from_line and line_number == to_line ->
- slice_length = to_character - from_character
- String.slice(line_text, from_character, slice_length)
-
- line_number == from_line ->
- slice_length = String.length(line_text) - from_character
- String.slice(line_text, from_character, slice_length)
-
- line_number == to_line ->
- String.slice(line_text, 0, to_character)
-
- true ->
- line_text
- end
-
- :error ->
- []
- end
-
- [acc, to_append]
- end)
- |> IO.iodata_to_binary()
- end
-
- @doc false
- @spec apply_content_changes(t, version(), [Convertible.t() | nil]) ::
- {:ok, t} | change_application_error()
- def apply_content_changes(%__MODULE__{version: current_version}, new_version, _)
- when new_version <= current_version do
- {:error, :invalid_version}
- end
-
- def apply_content_changes(%__MODULE__{} = document, _, []) do
- {:ok, document}
- end
-
- def apply_content_changes(%__MODULE__{} = document, version, changes) when is_list(changes) do
- result =
- Enum.reduce_while(changes, document, fn
- nil, document ->
- {:cont, document}
-
- change, document ->
- case apply_change(document, change) do
- {:ok, new_document} ->
- {:cont, new_document}
-
- error ->
- {:halt, error}
- end
- end)
-
- case result do
- %__MODULE__{} = document ->
- document = mark_dirty(%__MODULE__{document | version: version})
-
- {:ok, document}
-
- error ->
- error
- end
- end
-
- @doc """
- Converts a document to a string
-
- This function converts the given document back into a string, with line endings
- preserved.
- """
- def to_string(%__MODULE__{} = document) do
- document
- |> to_iodata()
- |> IO.iodata_to_binary()
- end
-
- @spec language_id_from_path(Lexical.path()) :: String.t()
- defp language_id_from_path(path) do
- case Path.extname(path) do
- ".ex" ->
- "elixir"
-
- ".exs" ->
- "elixir-script"
-
- ".eex" ->
- "eex"
-
- ".heex" ->
- "phoenix-heex"
-
- extension ->
- Logger.warning("can't infer lang ID for #{path}, ext: #{extension}.")
-
- "unsupported (#{extension})"
- end
- end
-
- # private
-
- defp line_count(%__MODULE__{} = document) do
- Lines.size(document.lines)
- end
-
- defp apply_change(
- %__MODULE__{} = document,
- %Range{start: %Position{} = start_pos, end: %Position{} = end_pos},
- new_text
- ) do
- start_line = start_pos.line
-
- new_lines_iodata =
- cond do
- start_line > line_count(document) ->
- append_to_end(document, new_text)
-
- start_line < 1 ->
- prepend_to_beginning(document, new_text)
-
- true ->
- apply_valid_edits(document, new_text, start_pos, end_pos)
- end
-
- new_document =
- new_lines_iodata
- |> IO.iodata_to_binary()
- |> Lines.new()
-
- {:ok, %__MODULE__{document | lines: new_document}}
- end
-
- defp apply_change(%__MODULE__{} = document, %Edit{range: nil} = edit) do
- {:ok, %__MODULE__{document | lines: Lines.new(edit.text)}}
- end
-
- defp apply_change(%__MODULE__{} = document, %Edit{range: %Range{}} = edit) do
- if valid_edit?(edit) do
- apply_change(document, edit.range, edit.text)
- else
- {:error, {:invalid_range, edit.range}}
- end
- end
-
- defp apply_change(%__MODULE__{} = document, %{range: range, text: text}) do
- with {:ok, native_range} <- Convertible.to_native(range, document) do
- apply_change(document, Edit.new(text, native_range))
- end
- end
-
- defp apply_change(%__MODULE__{} = document, convertable_edit) do
- with {:ok, edit} <- Convertible.to_native(convertable_edit, document) do
- apply_change(document, edit)
- end
- end
-
- defp valid_edit?(%Edit{
- range: %Range{start: %Position{} = start_pos, end: %Position{} = end_pos}
- }) do
- start_pos.line >= 0 and start_pos.character >= 0 and end_pos.line >= 0 and
- end_pos.character >= 0
- end
-
- defp append_to_end(%__MODULE__{} = document, edit_text) do
- [to_iodata(document), edit_text]
- end
-
- defp prepend_to_beginning(%__MODULE__{} = document, edit_text) do
- [edit_text, to_iodata(document)]
- end
-
- defp apply_valid_edits(%__MODULE__{} = document, edit_text, start_pos, end_pos) do
- Lines.reduce(document.lines, [], fn line() = line, acc ->
- case edit_action(line, edit_text, start_pos, end_pos) do
- :drop ->
- acc
-
- {:append, io_data} ->
- [acc, io_data]
- end
- end)
- end
-
- defp edit_action(line() = line, edit_text, %Position{} = start_pos, %Position{} = end_pos) do
- %Position{line: start_line, character: start_char} = start_pos
- %Position{line: end_line, character: end_char} = end_pos
-
- line(line_number: line_number, text: text, ending: ending) = line
-
- cond do
- line_number < start_line ->
- {:append, [text, ending]}
-
- line_number > end_line ->
- {:append, [text, ending]}
-
- line_number == start_line && line_number == end_line ->
- prefix_text = utf8_prefix(line, start_char)
- suffix_text = utf8_suffix(line, end_char)
- {:append, [prefix_text, edit_text, suffix_text, ending]}
-
- line_number == start_line ->
- prefix_text = utf8_prefix(line, start_char)
- {:append, [prefix_text, edit_text]}
-
- line_number == end_line ->
- suffix_text = utf8_suffix(line, end_char)
- {:append, [suffix_text, ending]}
-
- true ->
- :drop
- end
- end
-
- defp utf8_prefix(line(text: text), start_code_unit) do
- length = max(0, start_code_unit - 1)
- binary_part(text, 0, length)
- end
-
- defp utf8_suffix(line(text: text), start_code_unit) do
- byte_count = byte_size(text)
- start_index = min(start_code_unit - 1, byte_count)
- length = byte_count - start_index
-
- binary_part(text, start_index, length)
- end
-
- defp to_iodata(%__MODULE__{} = document) do
- Lines.to_iodata(document.lines)
- end
-
- @spec convert_fragment_position(t, Position.t() | :beginning | Convertible.t()) :: Position.t()
- defp convert_fragment_position(%__MODULE__{}, %Position{} = pos) do
- pos
- end
-
- defp convert_fragment_position(%__MODULE__{} = document, :beginning) do
- Position.new(document, 1, 1)
- end
-
- defp convert_fragment_position(%__MODULE__{} = document, convertible) do
- {:ok, %Position{} = converted} = Convertible.to_native(convertible, document)
- converted
- end
-end
diff --git a/apps/common/lib/lexical/document/edit.ex b/apps/common/lib/lexical/document/edit.ex
deleted file mode 100644
index 1ba85a17..00000000
--- a/apps/common/lib/lexical/document/edit.ex
+++ /dev/null
@@ -1,35 +0,0 @@
-defmodule Lexical.Document.Edit do
- @moduledoc """
- A change to a document
-
- A `Lexical.Document.Edit` represents a single change to a document. It contains
- the new text and a range where the edit applies.
- """
- alias Lexical.Document.Range
- alias Lexical.StructAccess
-
- defstruct [:text, :range]
-
- @type t :: %__MODULE__{
- text: String.t(),
- range: Range.t() | nil
- }
-
- use StructAccess
-
- @doc "Creates a new edit that replaces all text in the document"
- @spec new(String.t(), Range.t() | nil) :: t
- @spec new(String.t()) :: t
- def new(text) when is_binary(text) do
- %__MODULE__{text: text}
- end
-
- @doc "Creates a new edit that replaces text in the given range"
- def new(text, %Range{} = range) do
- %__MODULE__{text: text, range: range}
- end
-
- def new(text, nil) when is_binary(text) do
- %__MODULE__{text: text}
- end
-end
diff --git a/apps/common/lib/lexical/document/location.ex b/apps/common/lib/lexical/document/location.ex
deleted file mode 100644
index 6db3d322..00000000
--- a/apps/common/lib/lexical/document/location.ex
+++ /dev/null
@@ -1,41 +0,0 @@
-defmodule Lexical.Document.Location do
- @moduledoc """
- A location in a document
-
- One of the fundamental LSP structures, this represents a subset of text in a document.
- The location is bounded by the given range, and the document can be given as a `Lexical.Document`
- struct, or a uri referencing the document
- """
- alias Lexical.Document
- alias Lexical.Document.Range
-
- defstruct [:range, :document, :uri]
-
- @type t :: %__MODULE__{
- range: Range.t(),
- document: Document.t() | nil,
- uri: Lexical.uri()
- }
- use Lexical.StructAccess
-
- @spec new(Range.t(), Document.t() | String.t()) :: t()
- def new(%Range{} = range, %Document{} = document) do
- %__MODULE__{range: range, document: document, uri: document.uri}
- end
-
- def new(%Range{} = range, uri) when is_binary(uri) do
- %__MODULE__{range: range, uri: uri}
- end
-
- def uri(%__MODULE__{document: %Document{} = document}) do
- document.uri
- end
-
- @doc """
- Returns the location document's uri.
- """
- @spec uri(t) :: Lexical.uri()
- def uri(%__MODULE__{} = location) do
- location.uri
- end
-end
diff --git a/apps/common/lib/lexical/document/position.ex b/apps/common/lib/lexical/document/position.ex
deleted file mode 100644
index 86c9990e..00000000
--- a/apps/common/lib/lexical/document/position.ex
+++ /dev/null
@@ -1,117 +0,0 @@
-defmodule Lexical.Document.Position do
- @moduledoc """
- A position inside of a document
-
- This struct represents a cursor position inside a document, using one-based line and character
- numbers. It's important to note that the position starts before the character given, so positions
- are inclusive of the given character.
-
- Given the following line of text:
- "Hello there, welcome to lexical"
-
- the position: `%Lexical.Document.Position{line: 1, character: 1}` starts before the "H" in "Hello"
- """
-
- alias Lexical.Document
- alias Lexical.Document.Lines
-
- defstruct [
- :line,
- :character,
- valid?: false,
- context_line: nil,
- document_line_count: 0,
- starting_index: 1
- ]
-
- @type line :: non_neg_integer()
- @type character :: non_neg_integer()
- @type line_container :: Document.t() | Lines.t()
-
- @type t :: %__MODULE__{
- character: character(),
- context_line: Document.Line.t() | nil,
- document_line_count: non_neg_integer(),
- line: line(),
- starting_index: non_neg_integer(),
- valid?: boolean()
- }
-
- use Lexical.StructAccess
-
- @spec new(line_container(), line(), character()) :: t
- def new(%Document{} = document, line, character)
- when is_number(line) and is_number(character) do
- new(document.lines, line, character)
- end
-
- def new(%Document.Lines{} = lines, line, character)
- when is_number(line) and is_number(character) do
- line_count = Document.Lines.size(lines)
- starting_index = lines.starting_index
-
- case Lines.fetch_line(lines, line) do
- {:ok, context_line} ->
- %__MODULE__{
- character: character,
- context_line: context_line,
- document_line_count: line_count,
- line: line,
- starting_index: starting_index,
- valid?: true
- }
-
- :error ->
- %__MODULE__{
- line: line,
- character: character,
- document_line_count: line_count,
- starting_index: starting_index
- }
- end
- end
-
- @doc """
- Compares two positions.
-
- Returns `:gt`, `:lt`, or `:eq` depending on the location of the first
- position relative to the second.
- """
- @spec compare(t | {line, character}, t | {line, character}) :: :lt | :eq | :gt
- def compare(%__MODULE__{} = pos1, %__MODULE__{} = pos2) do
- compare({pos1.line, pos1.character}, {pos2.line, pos2.character})
- end
-
- def compare(%__MODULE__{} = pos1, {_, _} = pos2) do
- compare({pos1.line, pos1.character}, pos2)
- end
-
- def compare({_, _} = pos1, %__MODULE__{} = pos2) do
- compare(pos1, {pos2.line, pos2.character})
- end
-
- def compare({l1, c1} = first, {l2, c2} = second)
- when is_integer(l1) and is_integer(c1) and is_integer(l2) and is_integer(c2) do
- cond do
- first < second -> :lt
- first > second -> :gt
- true -> :eq
- end
- end
-end
-
-defimpl Inspect, for: Lexical.Document.Position do
- import Inspect.Algebra
-
- def inspect(nil, _), do: "nil"
-
- def inspect(pos, _) do
- concat(["LxPos", to_string(pos)])
- end
-end
-
-defimpl String.Chars, for: Lexical.Document.Position do
- def to_string(pos) do
- "<<#{pos.line}, #{pos.character}>>"
- end
-end
diff --git a/apps/common/lib/lexical/document/range.ex b/apps/common/lib/lexical/document/range.ex
deleted file mode 100644
index e3ee4119..00000000
--- a/apps/common/lib/lexical/document/range.ex
+++ /dev/null
@@ -1,65 +0,0 @@
-defmodule Lexical.Document.Range do
- @moduledoc """
- A range in a document
-
- Note that ranges represent a cursor position, and so are inclusive of
- lines, but exclusive of the end position.
-
- Note: To select an entire line, construct a range that runs from the
- first character on the line to the first character on the next line.
-
- ```
- whole_line =
- Range.new(
- Position.new(doc, 1, 1),
- Position.new(doc, 2, 1)
- )
- ```
- """
- alias Lexical.Document.Position
-
- defstruct start: nil, end: nil
-
- @type t :: %__MODULE__{
- start: Position.t(),
- end: Position.t()
- }
-
- use Lexical.StructAccess
-
- @doc """
- Builds a new range.
- """
- def new(%Position{} = start_pos, %Position{} = end_pos) do
- %__MODULE__{start: start_pos, end: end_pos}
- end
-
- @doc """
- Returns whether the range contains the given position.
- """
- def contains?(%__MODULE__{} = range, %Position{} = position) do
- %__MODULE__{start: start_pos, end: end_pos} = range
-
- cond do
- position.line == start_pos.line and position.line == end_pos.line ->
- position.character >= start_pos.character and position.character <= end_pos.character
-
- position.line == start_pos.line ->
- position.character >= start_pos.character
-
- position.line == end_pos.line ->
- position.character < end_pos.character
-
- true ->
- position.line > start_pos.line and position.line < end_pos.line
- end
- end
-end
-
-defimpl Inspect, for: Lexical.Document.Range do
- import Inspect.Algebra
-
- def inspect(range, _) do
- concat(["LxRange[", to_string(range.start), "...", to_string(range.end), "]"])
- end
-end
diff --git a/apps/common/lib/lexical/document/store.ex b/apps/common/lib/lexical/document/store.ex
deleted file mode 100644
index d305ba35..00000000
--- a/apps/common/lib/lexical/document/store.ex
+++ /dev/null
@@ -1,423 +0,0 @@
-defmodule Lexical.Document.Store do
- @moduledoc """
- Backing store for source file documents.
- """
-
- alias Lexical.Document
- alias Lexical.ProcessCache
-
- use GenServer
-
- @type updater :: (Document.t() -> {:ok, Document.t()} | {:error, any()})
-
- @type derivations :: [derivation]
- @type derivation :: {derivation_key, derivation_fun}
- @type derivation_key :: atom()
- @type derivation_fun :: (Document.t() -> derived_value)
- @type derived_value :: any()
-
- @type start_opts :: [start_opt]
- @type start_opt :: {:derive, derivations}
-
- defmodule State do
- @moduledoc false
-
- alias Lexical.Document
- alias Lexical.Document.Store
-
- require Logger
-
- import Record
-
- defstruct open: %{}, temporary_open_refs: %{}, derivation_funs: %{}
-
- @type t :: %__MODULE__{}
-
- defrecord :open_doc, document: nil, derived: %{}
-
- def new(opts \\ []) do
- {derivation_funs, invalid} =
- opts
- |> Keyword.validate!(derive: [])
- |> Keyword.fetch!(:derive)
- |> Enum.split_with(fn
- {atom, fun} when is_atom(atom) and is_function(fun, 1) -> true
- _ -> false
- end)
-
- if invalid != [] do
- raise ArgumentError, "invalid derive: #{inspect(invalid)}"
- end
-
- %__MODULE__{derivation_funs: Map.new(derivation_funs)}
- end
-
- @spec fetch(t, Lexical.uri()) :: {:ok, Document.t(), t} | {:error, :not_open}
- def fetch(%__MODULE__{} = store, uri) do
- case store.open do
- %{^uri => open_doc(document: document)} -> {:ok, document, store}
- _ -> {:error, :not_open}
- end
- end
-
- @spec fetch(t, Lexical.uri(), Store.derivation_key()) ::
- {:ok, Document.t(), Store.derived_value(), t} | {:error, :not_open}
- def fetch(%__MODULE__{} = store, uri, key) do
- case store.open do
- %{^uri => open_doc(document: document, derived: %{^key => derivation})} ->
- {:ok, document, derivation, store}
-
- %{^uri => open_doc(document: document, derived: derived)} ->
- derivation = derive(store, key, document)
- derived = Map.put(derived, key, derivation)
- store = put_open_doc(store, document, derived)
- {:ok, document, derivation, store}
-
- _ ->
- {:error, :not_open}
- end
- end
-
- @spec save(t, Lexical.uri()) :: {:ok, t} | {:error, :not_open}
- def save(%__MODULE__{} = store, uri) do
- case store.open do
- %{^uri => open_doc(document: document, derived: derived)} ->
- document = Document.mark_clean(document)
- store = put_open_doc(store, document, derived)
- {:ok, store}
-
- _ ->
- {:error, :not_open}
- end
- end
-
- @spec open(t, Lexical.uri(), String.t(), pos_integer(), String.t()) ::
- {:ok, t} | {:error, :already_open}
- def open(%__MODULE__{temporary_open_refs: refs} = store, uri, text, version, language_id)
- when is_map_key(refs, uri) do
- {_, store} =
- store
- |> maybe_cancel_ref(uri)
- |> pop_open_doc(uri)
-
- open(store, uri, text, version, language_id)
- end
-
- def open(%__MODULE__{} = store, uri, text, version, language_id) do
- case store.open do
- %{^uri => _} ->
- {:error, :already_open}
-
- _ ->
- document = Document.new(uri, text, version, language_id)
- store = put_open_doc(store, document)
- {:ok, store}
- end
- end
-
- @spec open?(t, Lexical.uri()) :: boolean
- def open?(%__MODULE__{} = store, uri) do
- Map.has_key?(store.open, uri)
- end
-
- @spec close(t, Lexical.uri()) :: {:ok, t} | {:error, :not_open}
- def close(%__MODULE__{} = store, uri) do
- case pop_open_doc(store, uri) do
- {nil, _} ->
- {:error, :not_open}
-
- {_, store} ->
- {:ok, maybe_cancel_ref(store, uri)}
- end
- end
-
- @spec get_and_update(t, Lexical.uri(), Store.updater()) ::
- {:ok, Document.t(), t} | {:error, any()}
- def get_and_update(%__MODULE__{} = store, uri, updater_fn) do
- with {:ok, open_doc(document: document)} <- Map.fetch(store.open, uri),
- {:ok, document} <- updater_fn.(document) do
- {:ok, document, put_open_doc(store, document)}
- else
- error ->
- normalize_error(error)
- end
- end
-
- @spec update(t, Lexical.uri(), Store.updater()) :: {:ok, t} | {:error, any()}
- def update(%__MODULE__{} = store, uri, updater_fn) do
- with {:ok, _, store} <- get_and_update(store, uri, updater_fn) do
- {:ok, store}
- end
- end
-
- @spec open_temporarily(t, Lexical.uri() | Path.t(), timeout()) ::
- {:ok, Document.t(), t} | {:error, term()}
- def open_temporarily(%__MODULE__{} = store, path_or_uri, timeout) do
- uri = Document.Path.ensure_uri(path_or_uri)
- path = Document.Path.ensure_path(path_or_uri)
-
- with {:ok, contents} <- File.read(path) do
- document = Document.new(uri, contents, 0)
- ref = schedule_unload(uri, timeout)
-
- new_store =
- store
- |> maybe_cancel_ref(uri)
- |> put_ref(uri, ref)
- |> put_open_doc(document)
-
- {:ok, document, new_store}
- end
- end
-
- @spec extend_timeout(t, Lexical.uri(), timeout()) :: t
- def extend_timeout(%__MODULE__{} = store, uri, timeout) do
- case store.temporary_open_refs do
- %{^uri => ref} ->
- Process.cancel_timer(ref)
- new_ref = schedule_unload(uri, timeout)
- put_ref(store, uri, new_ref)
-
- _ ->
- store
- end
- end
-
- @spec unload(t, Lexical.uri()) :: t
- def unload(%__MODULE__{} = store, uri) do
- {_, store} = pop_open_doc(store, uri)
- maybe_cancel_ref(store, uri)
- end
-
- defp put_open_doc(%__MODULE__{} = store, %Document{} = document, derived \\ %{}) do
- put_in(store.open[document.uri], open_doc(document: document, derived: derived))
- end
-
- defp pop_open_doc(%__MODULE__{} = store, uri) do
- case Map.pop(store.open, uri) do
- {open_doc() = doc, open} -> {doc, %__MODULE__{store | open: open}}
- {nil, _} -> {nil, store}
- end
- end
-
- defp put_ref(%__MODULE__{} = store, uri, ref) do
- put_in(store.temporary_open_refs[uri], ref)
- end
-
- defp maybe_cancel_ref(%__MODULE__{} = store, uri) do
- case pop_in(store.temporary_open_refs[uri]) do
- {ref, store} when is_reference(ref) ->
- Process.cancel_timer(ref)
- store
-
- _ ->
- store
- end
- end
-
- defp schedule_unload(uri, timeout) do
- Process.send_after(self(), {:unload, uri}, timeout)
- end
-
- defp normalize_error(:error), do: {:error, :not_open}
- defp normalize_error(e), do: e
-
- defp derive(%__MODULE__{} = store, key, document) do
- case store.derivation_funs do
- %{^key => fun} ->
- fun.(document)
-
- _ ->
- known = Map.keys(store.derivation_funs)
-
- raise ArgumentError,
- "No derivation for #{inspect(key)}, expected one of #{inspect(known)}"
- end
- end
- end
-
- @spec fetch(Lexical.uri()) :: {:ok, Document.t()} | {:error, :not_open}
- def fetch(uri) do
- GenServer.call(name(), {:fetch, uri})
- end
-
- @spec fetch(Lexical.uri(), derivation_key) ::
- {:ok, Document.t(), derived_value} | {:error, :not_open}
- def fetch(uri, key) do
- GenServer.call(name(), {:fetch, uri, key})
- end
-
- @spec save(Lexical.uri()) :: :ok | {:error, :not_open}
- def save(uri) do
- GenServer.call(name(), {:save, uri})
- end
-
- @spec open?(Lexical.uri()) :: boolean()
- def open?(uri) do
- GenServer.call(name(), {:open?, uri})
- end
-
- @spec open(Lexical.uri(), String.t(), String.t(), pos_integer() | nil) ::
- :ok | {:error, :already_open}
- def open(uri, text, version, language_id \\ nil) do
- GenServer.call(name(), {:open, uri, text, version, language_id})
- end
-
- @spec open_temporary(Lexical.uri() | Path.t()) ::
- {:ok, Document.t()} | {:error, term()}
-
- @spec open_temporary(Lexical.uri() | Path.t(), timeout()) ::
- {:ok, Document.t()} | {:error, term()}
- def open_temporary(uri, timeout \\ 5000) when is_binary(uri) do
- ProcessCache.trans(uri, 50, fn ->
- GenServer.call(name(), {:open_temporarily, uri, timeout})
- end)
- end
-
- @spec close(Lexical.uri()) :: :ok | {:error, :not_open}
- def close(uri) do
- GenServer.call(name(), {:close, uri})
- end
-
- @spec get_and_update(Lexical.uri(), updater) :: {:ok, Document.t()} | {:error, any()}
- def get_and_update(uri, update_fn) do
- GenServer.call(name(), {:get_and_update, uri, update_fn})
- end
-
- @spec update(Lexical.uri(), updater) :: :ok | {:error, any()}
- def update(uri, update_fn) do
- GenServer.call(name(), {:update, uri, update_fn})
- end
-
- @spec start_link(start_opts) :: GenServer.on_start()
- def start_link(opts) do
- GenServer.start_link(__MODULE__, opts, name: name())
- end
-
- @impl GenServer
- def init(opts) do
- {:ok, State.new(opts)}
- end
-
- @impl GenServer
- def handle_call({:save, uri}, _from, %State{} = state) do
- {reply, new_state} =
- case State.save(state, uri) do
- {:ok, _} = success -> success
- error -> {error, state}
- end
-
- {:reply, reply, new_state}
- end
-
- def handle_call({:open, uri, text, version, language_id}, _from, %State{} = state) do
- {reply, new_state} =
- case State.open(state, uri, text, version, language_id) do
- {:ok, _} = success -> success
- error -> {error, state}
- end
-
- {:reply, reply, new_state}
- end
-
- def handle_call({:open?, uri}, _from, %State{} = state) do
- reply = State.open?(state, uri)
- {:reply, reply, state}
- end
-
- def handle_call({:open_temporarily, uri, timeout_ms}, _, %State{} = state) do
- {reply, new_state} =
- with {:error, :not_open} <- State.fetch(state, uri),
- {:ok, document, new_state} <- State.open_temporarily(state, uri, timeout_ms) do
- {{:ok, document}, new_state}
- else
- {:ok, document, new_state} ->
- {{:ok, document}, State.extend_timeout(new_state, uri, timeout_ms)}
-
- error ->
- {error, state}
- end
-
- {:reply, reply, new_state}
- end
-
- def handle_call({:fetch, uri}, _from, %State{} = state) do
- {reply, new_state} =
- case State.fetch(state, uri) do
- {:ok, value, new_state} -> {{:ok, value}, new_state}
- error -> {error, state}
- end
-
- {:reply, reply, new_state}
- end
-
- def handle_call({:fetch, uri, key}, _from, %State{} = state) do
- {reply, new_state} =
- case State.fetch(state, uri, key) do
- {:ok, value, derived_value, new_state} -> {{:ok, value, derived_value}, new_state}
- error -> {error, state}
- end
-
- {:reply, reply, new_state}
- end
-
- def handle_call({:close, uri}, _from, %State{} = state) do
- {reply, new_state} =
- case State.close(state, uri) do
- {:ok, new_state} -> {:ok, new_state}
- error -> {error, state}
- end
-
- {:reply, reply, new_state}
- end
-
- def handle_call({:get_and_update, uri, update_fn}, _from, %State{} = state) do
- {reply, new_state} =
- case State.get_and_update(state, uri, update_fn) do
- {:ok, updated_source, new_state} -> {{:ok, updated_source}, new_state}
- error -> {error, state}
- end
-
- {:reply, reply, new_state}
- end
-
- def handle_call({:update, uri, updater_fn}, _, %State{} = state) do
- {reply, new_state} =
- case State.update(state, uri, updater_fn) do
- {:ok, new_state} -> {:ok, new_state}
- error -> {error, state}
- end
-
- {:reply, reply, new_state}
- end
-
- @impl GenServer
- def handle_info({:unload, uri}, %State{} = state) do
- {:noreply, State.unload(state, uri)}
- end
-
- def set_entropy(entropy) do
- :persistent_term.put(entropy_key(), entropy)
- entropy
- end
-
- def entropy do
- case :persistent_term.get(entropy_key(), :undefined) do
- :undefined ->
- [:positive]
- |> System.unique_integer()
- |> set_entropy()
-
- entropy ->
- entropy
- end
- end
-
- def name do
- {:via, :global, {__MODULE__, entropy()}}
- end
-
- defp entropy_key do
- {__MODULE__, :entropy}
- end
-end
diff --git a/apps/common/lib/lexical/identifier.ex b/apps/common/lib/lexical/identifier.ex
deleted file mode 100644
index a953458d..00000000
--- a/apps/common/lib/lexical/identifier.ex
+++ /dev/null
@@ -1,27 +0,0 @@
-defmodule Lexical.Identifier do
- @doc """
- Returns the next globally unique identifier.
- Raises a MatchError if this cannot be computed.
- """
- def next_global! do
- {:ok, next_id} = Snowflake.next_id()
- next_id
- end
-
- def to_unix(id) do
- Snowflake.Util.real_timestamp_of_id(id)
- end
-
- def to_datetime(id) do
- id
- |> to_unix()
- |> DateTime.from_unix!(:millisecond)
- end
-
- def to_erl(id) do
- %DateTime{year: year, month: month, day: day, hour: hour, minute: minute, second: second} =
- to_datetime(id)
-
- {{year, month, day}, {hour, minute, second}}
- end
-end
diff --git a/apps/common/lib/lexical/plugin/v1/diagnostic.ex b/apps/common/lib/lexical/plugin/v1/diagnostic.ex
deleted file mode 100644
index d23c702a..00000000
--- a/apps/common/lib/lexical/plugin/v1/diagnostic.ex
+++ /dev/null
@@ -1,93 +0,0 @@
-defmodule Lexical.Plugin.V1.Diagnostic do
- @moduledoc """
- A use-able diagnostic plugin module
-
- A diagnostic result examines a `diagnosable` data structure and emits
- a list of `Lexical.Plugin.V1.Diagnostic.Result` structs.
-
- Diagnostic plugins are called in two places. When a file is saved, they're called with the
- `Lexical.Project` struct and are expected to perform diagnostics across the whole project.
-
- On receipt of a change to a `Lexical.Document`, they're called with the changed document and
- are expected to analyze that document and emit any diagnostics they find.
-
- Both calls to `diagnose` operate on tight deadlines, though when `diagnose` is called with a `Lexical.Project`,
- the deadline is much longer than it is when `diagnose` is called with a `Lexical.Document`. This is because
- analyzing a project should take a lot longer than analyzing a single document. Currently, Lexical sets
- a deadline of around a second on project-level diagnostics and a tens-of-milliseconds deadline on document
- plugins.
-
-
- ## Errors and Timeouts
- Plugins are very sensitive to errors and are disabled by lexical if they cause too many. At present,
- a disabled plugin can only be re-enabled by restarting lexical, so ensure that your plugin doesn't crash,
- and if you don't have diagnostics, return `{:ok, []}` rather than some error response.
-
- ## Plugin lifecycle
-
- When a plugin is started, Lexical calls the `init/0` function, which can perform setup actions, like starting
- the application associated with the plugin. Implementing this function is optional.
- From then on, the plugin will be resident and run in tasks whenever files are changed or saved.
-
- ## A simple do-nothing plugin
-
- ```
- defmodule DoesNothing do
- use Lexical.Plugin.V1.Diagnostic
-
- def diagnose(%Lexical.Document{} = doc) do
- {:ok, []}
- end
-
- def diagnose(%Lexical.Project{}) do
- {:ok, []}
- end
- end
- ```
- Check out the README for a plugin that does something more.
- """
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.Project
-
- @type state :: any()
- @type results :: [Diagnostic.Result.t()]
-
- @type diagnosable :: Project.t() | Document.t()
- @type diagnostics_reply :: {:ok, results} | {:error, any()}
-
- @callback diagnose(diagnosable) :: diagnostics_reply()
-
- defmacro __using__(opts) do
- name = Keyword.get(opts, :name)
-
- quote location: :keep do
- require Logger
- Module.register_attribute(__MODULE__, :lexical_plugin, persist: true)
- @lexical_plugin true
- @behaviour unquote(__MODULE__)
-
- def __lexical_plugin__ do
- __MODULE__
- end
-
- def __plugin_type__ do
- :diagnostic
- end
-
- def name do
- unquote(name)
- end
-
- def init do
- :ok
- end
-
- def diagnose(_) do
- {:ok, []}
- end
-
- defoverridable init: 0, diagnose: 1
- end
- end
-end
diff --git a/apps/common/lib/lexical/plugin/v1/diagnostic/result.ex b/apps/common/lib/lexical/plugin/v1/diagnostic/result.ex
deleted file mode 100644
index 25fee603..00000000
--- a/apps/common/lib/lexical/plugin/v1/diagnostic/result.ex
+++ /dev/null
@@ -1,105 +0,0 @@
-defmodule Lexical.Plugin.V1.Diagnostic.Result do
- @moduledoc """
- The result of a diagnostic run
-
- A diagnostic plugin emits a list of `Result` structs that inform the user about issues
- the plugin has found. The results contain the following keys:
-
- `uri` - The URI of the document where the error occurs. `Lexical.Document` structs contain a
- `uri` field which can be used to fill out this field. If you have a filesystem path, the function
- `Lexical.Document.Path.to_uri/1` can be used to transform a path to a URI.
-
- `message` - The diagnostic message displayed to the user
-
- `details` - Further details about the message
-
- `position` - Where the message occurred (see the `Positions` section for details)
-
- `severity` - How important the issue is. Can be one of (from least severe to most severe)
- `:hint`, `:information`, `:warning`, `:error`
-
- `source` - The name of the plugin that produced this as a human-readable string.
-
-
- ## Positions
-
- Diagnostics need to inform the language client where the error occurs. Positions are the
- mechanism they use to do so. Positions take several forms, which are:
-
- `line number` - If the position is a one-based line number, the diagnostic will refer to
- the entire flagged line
-
- `{line_number, column}` - If the position is a two-tuple of a one-based line number and a one-based
- column, then the diagnostic will begin on the line indicated, and start at the column indicated. The
- diagnostic will run to the end of the line.
-
- `{start_line, start_column, end_line, end_column}` - If the position is a four-tuple of of one-based
- line and column numbers, the diagnostic will start on `start_line` at `start_column` and run until
- `end_line` at `end_column`. This is the most detailed form of describing a position, and should be preferred
- to the others, as it will produce the most accurate highlighting of the diagnostic.
-
- `Document.Range.t` - Equivalent to the {start_line, start_column, end_line, end_column}, but saves a
- conversion step
-
- `Document.Position.t` - Equivalent to `{line_number, column}`, but saves a conversion step.
- """
- alias Lexical.Document
- defstruct [:details, :message, :position, :severity, :source, :uri]
-
- @typedoc """
- A path or a uri.
- """
- @type path_or_uri :: Lexical.path() | Lexical.uri()
-
- @typedoc """
- The severity of the diagnostic.
- """
- @type severity :: :hint | :information | :warning | :error
-
- @typedoc false
- @type mix_position ::
- non_neg_integer()
- | {pos_integer(), non_neg_integer()}
- | {pos_integer(), non_neg_integer(), pos_integer(), non_neg_integer()}
-
- @typedoc """
- Where the error occurs in the document.
- """
- @type position :: mix_position() | Document.Range.t() | Document.Position.t()
-
- @typedoc """
- A result emitted by a diagnostic plugin.
-
- These results are displayed in the editor to the user.
- """
- @type t :: %__MODULE__{
- position: position,
- message: iodata(),
- severity: severity(),
- source: String.t(),
- uri: Lexical.uri()
- }
-
- @doc """
- Creates a new diagnostic result.
- """
- @spec new(path_or_uri, position, iodata(), severity(), String.t()) :: t
- @spec new(path_or_uri, position, iodata(), severity(), String.t(), any()) :: t
- def new(maybe_uri_or_path, position, message, severity, source, details \\ nil) do
- uri =
- if maybe_uri_or_path do
- Document.Path.ensure_uri(maybe_uri_or_path)
- end
-
- message = IO.iodata_to_binary(message)
-
- %__MODULE__{
- uri: uri,
- position: position,
- message: message,
- source: source,
- severity: severity,
- details: details
- }
- end
-end
diff --git a/apps/common/lib/lexical/project.ex b/apps/common/lib/lexical/project.ex
deleted file mode 100644
index 054d172f..00000000
--- a/apps/common/lib/lexical/project.ex
+++ /dev/null
@@ -1,324 +0,0 @@
-defmodule Lexical.Project do
- @moduledoc """
- The representation of the current state of an elixir project.
-
- This struct contains all the information required to build a project and interrogate its configuration,
- as well as business logic for how to change its attributes.
- """
- alias Lexical.Document
-
- defstruct root_uri: nil,
- mix_exs_uri: nil,
- mix_project?: false,
- mix_env: nil,
- mix_target: nil,
- env_variables: %{},
- project_module: nil,
- entropy: 1
-
- @type message :: String.t()
- @type restart_notification :: {:restart, Logger.level(), String.t()}
- @type t :: %__MODULE__{
- root_uri: Lexical.uri() | nil,
- mix_exs_uri: Lexical.uri() | nil,
- entropy: non_neg_integer()
- # mix_env: atom(),
- # mix_target: atom(),
- # env_variables: %{String.t() => String.t()}
- }
- @type error_with_message :: {:error, message}
-
- @workspace_directory_name ".lexical"
-
- # Public
- @spec new(Lexical.uri()) :: t
- def new(root_uri) do
- entropy = :rand.uniform(65_536)
-
- %__MODULE__{entropy: entropy}
- |> maybe_set_root_uri(root_uri)
- |> maybe_set_mix_exs_uri()
- end
-
- @spec set_project_module(t(), module() | nil) :: t()
- def set_project_module(%__MODULE__{} = project, nil) do
- project
- end
-
- def set_project_module(%__MODULE__{} = project, module) when is_atom(module) do
- %__MODULE__{project | project_module: module}
- end
-
- @doc """
- Retrieves the name of the project
- """
- @spec name(t) :: String.t()
-
- def name(%__MODULE__{} = project) do
- sanitized =
- project
- |> folder_name()
- |> String.replace(~r/[^a-zA-Z0-9_]/, "_")
-
- # This might be a litte verbose, but this code is hot.
- case sanitized do
- <> when c in ?a..?z ->
- sanitized
-
- <> when c in ?A..?Z ->
- String.downcase("#{[c]}") <> rest
-
- other ->
- "p_#{other}"
- end
- end
-
- @doc """
- The project node's name
- """
- def node_name(%__MODULE__{} = project) do
- :"project-#{name(project)}-#{entropy(project)}@127.0.0.1"
- end
-
- def entropy(%__MODULE__{} = project) do
- project.entropy
- end
-
- def config(%__MODULE__{} = project) do
- config_key = {__MODULE__, name(project), :config}
-
- case :persistent_term.get(config_key, :not_found) do
- :not_found ->
- config = project.project_module.project()
- :persistent_term.put(config_key, config)
- config
-
- config ->
- config
- end
- end
-
- @doc """
- Returns the the name definied in the `project/0` of mix.exs file
- """
- def display_name(%__MODULE__{} = project) do
- case config(project) do
- [] ->
- folder_name(project)
-
- config ->
- Keyword.get(config, :name, folder_name(project))
- end
- end
-
- @doc """
- Retrieves the name of the project as an atom
- """
- @spec atom_name(t) :: atom
- def atom_name(%__MODULE__{project_module: nil} = project) do
- project
- |> name()
- |> String.to_atom()
- end
-
- def atom_name(%__MODULE__{} = project) do
- project.project_module
- end
-
- @doc """
- Returns the full path of the project's root directory
- """
- @spec root_path(t) :: Path.t() | nil
- def root_path(%__MODULE__{root_uri: nil}) do
- nil
- end
-
- def root_path(%__MODULE__{} = project) do
- Document.Path.from_uri(project.root_uri)
- end
-
- @spec project_path(t) :: Path.t() | nil
- def project_path(%__MODULE__{root_uri: nil}) do
- nil
- end
-
- def project_path(%__MODULE__{} = project) do
- Document.Path.from_uri(project.root_uri)
- end
-
- @doc """
- Returns the full path to the project's mix.exs file
- """
- @spec mix_exs_path(t) :: Path.t() | nil
- def mix_exs_path(%__MODULE__{mix_exs_uri: nil}) do
- nil
- end
-
- def mix_exs_path(%__MODULE__{mix_exs_uri: mix_exs_uri}) do
- Document.Path.from_uri(mix_exs_uri)
- end
-
- @spec change_environment_variables(t, map() | nil) ::
- {:ok, t} | error_with_message() | restart_notification()
- def change_environment_variables(%__MODULE__{} = project, environment_variables) do
- set_env_vars(project, environment_variables)
- end
-
- @doc """
- Returns the full path to the project's lexical workspace directory
-
- Lexical maintains a workspace directory in project it konws about, and places various
- artifacts there. This function returns the full path to that directory
- """
- @spec workspace_path(t) :: String.t()
- def workspace_path(%__MODULE__{} = project) do
- project
- |> root_path()
- |> Path.join(@workspace_directory_name)
- end
-
- @doc """
- Returns the full path to a file in lexical's workspace directory
- """
- @spec workspace_path(t, String.t() | [String.t()]) :: String.t()
- def workspace_path(%__MODULE__{} = project, relative_path) when is_binary(relative_path) do
- workspace_path(project, [relative_path])
- end
-
- def workspace_path(%__MODULE__{} = project, relative_path) when is_list(relative_path) do
- Path.join([workspace_path(project) | relative_path])
- end
-
- @doc """
- Returns the full path to the directory where lexical puts build artifacts
- """
- def build_path(%__MODULE__{} = project) do
- project
- |> workspace_path()
- |> Path.join("build")
- end
-
- @doc """
- Creates and initializes lexical's workspace directory if it doesn't already exist
- """
- def ensure_workspace(%__MODULE__{} = project) do
- with :ok <- ensure_workspace_directory(project) do
- ensure_git_ignore(project)
- end
- end
-
- defp ensure_workspace_directory(project) do
- workspace_path = workspace_path(project)
-
- cond do
- File.exists?(workspace_path) and File.dir?(workspace_path) ->
- :ok
-
- File.exists?(workspace_path) ->
- :ok = File.rm(workspace_path)
- :ok = File.mkdir_p(workspace_path)
-
- true ->
- :ok = File.mkdir(workspace_path)
- end
- end
-
- defp ensure_git_ignore(project) do
- contents = """
- *
- """
-
- path = workspace_path(project, ".gitignore")
-
- if File.exists?(path) do
- :ok
- else
- File.write(path, contents)
- end
- end
-
- defp maybe_set_root_uri(%__MODULE__{} = project, nil),
- do: %__MODULE__{project | root_uri: nil}
-
- defp maybe_set_root_uri(%__MODULE__{} = project, "file://" <> _ = root_uri) do
- root_path =
- root_uri
- |> Document.Path.absolute_from_uri()
- |> Path.expand()
-
- if File.exists?(root_path) do
- expanded_uri = Document.Path.to_uri(root_path)
- %__MODULE__{project | root_uri: expanded_uri}
- else
- project
- end
- end
-
- defp maybe_set_mix_exs_uri(%__MODULE__{} = project) do
- possible_mix_exs_path =
- project
- |> root_path()
- |> find_mix_exs_path()
-
- if mix_exs_exists?(possible_mix_exs_path) do
- %__MODULE__{
- project
- | mix_exs_uri: Document.Path.to_uri(possible_mix_exs_path),
- mix_project?: true
- }
- else
- project
- end
- end
-
- # Project Path
-
- # Environment variables
-
- def set_env_vars(%__MODULE__{} = old_project, %{} = env_vars) do
- case {old_project.env_variables, env_vars} do
- {nil, vars} when map_size(vars) == 0 ->
- {:ok, %__MODULE__{old_project | env_variables: vars}}
-
- {nil, new_vars} ->
- System.put_env(new_vars)
- {:ok, %__MODULE__{old_project | env_variables: new_vars}}
-
- {same, same} ->
- {:ok, old_project}
-
- _ ->
- {:restart, :warning, "Environment variables have changed. Lexical needs to restart"}
- end
- end
-
- def set_env_vars(%__MODULE__{} = old_project, _) do
- {:ok, old_project}
- end
-
- defp find_mix_exs_path(nil) do
- System.get_env("MIX_EXS")
- end
-
- defp find_mix_exs_path(project_directory) do
- case System.get_env("MIX_EXS") do
- nil ->
- Path.join(project_directory, "mix.exs")
-
- mix_exs ->
- mix_exs
- end
- end
-
- defp mix_exs_exists?(nil), do: false
-
- defp mix_exs_exists?(mix_exs_path) do
- File.exists?(mix_exs_path)
- end
-
- defp folder_name(project) do
- project
- |> root_path()
- |> Path.basename()
- end
-end
diff --git a/apps/common/lib/lexical/text.ex b/apps/common/lib/lexical/text.ex
deleted file mode 100644
index a35c65a9..00000000
--- a/apps/common/lib/lexical/text.ex
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule Lexical.Text do
- def count_leading_spaces(str), do: count_leading_spaces(str, 0)
-
- def count_leading_spaces(<>, count) when c in [?\s, ?\t],
- do: count_leading_spaces(rest, count + 1)
-
- def count_leading_spaces(_, count), do: count
-end
diff --git a/apps/common/mix.exs b/apps/common/mix.exs
deleted file mode 100644
index 77c327dd..00000000
--- a/apps/common/mix.exs
+++ /dev/null
@@ -1,43 +0,0 @@
-defmodule Common.MixProject do
- use Mix.Project
- Code.require_file("../../mix_includes.exs")
-
- def project do
- [
- app: :common,
- version: "0.7.2",
- elixir: "~> 1.15",
- elixirc_paths: elixirc_paths(Mix.env()),
- start_permanent: Mix.env() == :prod,
- deps: deps(),
- compilers: [:yecc] ++ Mix.compilers(),
- dialyzer: Mix.Dialyzer.config()
- ]
- end
-
- def application do
- [
- extra_applications: [:logger]
- ]
- end
-
- defp elixirc_paths(:test) do
- ["lib", "test/support"]
- end
-
- defp elixirc_paths(_) do
- ["lib"]
- end
-
- defp deps do
- [
- {:benchee, "~> 1.3", only: :test},
- Mix.Credo.dependency(),
- Mix.Dialyzer.dependency(),
- {:snowflake, "~> 1.0"},
- {:sourceror, "~> 1.9"},
- {:stream_data, "~> 1.1", only: [:test], runtime: false},
- {:patch, "~> 0.15", only: [:test], optional: true, runtime: false}
- ]
- end
-end
diff --git a/apps/common/test/lexical/ast/detection/alias_test.exs b/apps/common/test/lexical/ast/detection/alias_test.exs
deleted file mode 100644
index 43c93ccf..00000000
--- a/apps/common/test/lexical/ast/detection/alias_test.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule Lexical.Ast.Detection.AliasTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.Alias,
- assertions: [[:alias, :*]]
-end
diff --git a/apps/common/test/lexical/ast/detection/bitstring_test.exs b/apps/common/test/lexical/ast/detection/bitstring_test.exs
deleted file mode 100644
index 4ac1ee53..00000000
--- a/apps/common/test/lexical/ast/detection/bitstring_test.exs
+++ /dev/null
@@ -1,6 +0,0 @@
-defmodule Lexical.Ast.Detection.BitstringTest do
- use Lexical.Test.DetectionCase,
- for: Lexical.Ast.Detection.Bitstring,
- assertions: [[:bitstring, :*]],
- variations: [:match, :function_arguments, :function_body]
-end
diff --git a/apps/common/test/lexical/ast/detection/comment_test.exs b/apps/common/test/lexical/ast/detection/comment_test.exs
deleted file mode 100644
index acc11b36..00000000
--- a/apps/common/test/lexical/ast/detection/comment_test.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule Lexical.Ast.Detection.CommentTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.Comment,
- assertions: [[:comment, :*]]
-end
diff --git a/apps/common/test/lexical/ast/detection/import_test.exs b/apps/common/test/lexical/ast/detection/import_test.exs
deleted file mode 100644
index ad578d25..00000000
--- a/apps/common/test/lexical/ast/detection/import_test.exs
+++ /dev/null
@@ -1,16 +0,0 @@
-defmodule Lexical.Ast.Detection.ImportTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.Import,
- assertions: [[:import, :*]]
-
- test "works on multi line" do
- assert_detected ~q(
- import« Some.Module, only: »[
- foo: 3,
- bar: 6
- ]
- )
- end
-end
diff --git a/apps/common/test/lexical/ast/detection/module_attribute_test.exs b/apps/common/test/lexical/ast/detection/module_attribute_test.exs
deleted file mode 100644
index 68c6249b..00000000
--- a/apps/common/test/lexical/ast/detection/module_attribute_test.exs
+++ /dev/null
@@ -1,17 +0,0 @@
-defmodule Lexical.Ast.Detection.ModuleAttributeTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.ModuleAttribute,
- assertions: [
- [:module_attribute, :*],
- [:callbacks, :*]
- ],
- skip: [
- [:doc, :*],
- [:module_doc, :*],
- [:spec, :*],
- [:type, :*]
- ],
- variations: [:module]
-end
diff --git a/apps/common/test/lexical/ast/detection/pipe_test.exs b/apps/common/test/lexical/ast/detection/pipe_test.exs
deleted file mode 100644
index ef0c259a..00000000
--- a/apps/common/test/lexical/ast/detection/pipe_test.exs
+++ /dev/null
@@ -1,13 +0,0 @@
-defmodule Lexical.Ast.Detection.PipeTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.Pipe,
- assertions: [[:pipe, :*]],
- variations: [:function_arguments],
- skip: [[:module_attribute, :multi_line_pipe]]
-
- test "is false if there is no pipe in the string" do
- refute_detected ~q[Enum.foo]
- end
-end
diff --git a/apps/common/test/lexical/ast/detection/require_test.exs b/apps/common/test/lexical/ast/detection/require_test.exs
deleted file mode 100644
index f57d81d9..00000000
--- a/apps/common/test/lexical/ast/detection/require_test.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule Lexical.Ast.Detection.RequireTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.Require,
- assertions: [[:require, :*]]
-end
diff --git a/apps/common/test/lexical/ast/detection/spec_test.exs b/apps/common/test/lexical/ast/detection/spec_test.exs
deleted file mode 100644
index 3222a086..00000000
--- a/apps/common/test/lexical/ast/detection/spec_test.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule Lexical.Ast.Detection.SpecTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.Spec,
- assertions: [[:spec, :*]]
-end
diff --git a/apps/common/test/lexical/ast/detection/struct_field_key_test.exs b/apps/common/test/lexical/ast/detection/struct_field_key_test.exs
deleted file mode 100644
index 7267d184..00000000
--- a/apps/common/test/lexical/ast/detection/struct_field_key_test.exs
+++ /dev/null
@@ -1,17 +0,0 @@
-defmodule Lexical.Ast.Detection.StructFieldKeyTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.StructFieldKey,
- assertions: [[:struct_field_key, :*]],
- skip: [
- [:struct_fields, :*],
- [:struct_reference, :*],
- [:struct_field_value, :*]
- ],
- variations: [:module]
-
- test "is detected if a key is partially typed" do
- assert_detected ~q[%User{«fo»}]
- end
-end
diff --git a/apps/common/test/lexical/ast/detection/struct_reference_test.exs b/apps/common/test/lexical/ast/detection/struct_reference_test.exs
deleted file mode 100644
index 426ffb01..00000000
--- a/apps/common/test/lexical/ast/detection/struct_reference_test.exs
+++ /dev/null
@@ -1,45 +0,0 @@
-defmodule Lexical.Ast.Detection.StructReferenceTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.StructReference,
- assertions: [[:struct_reference, :*]],
- skip: [[:struct_fields, :*], [:struct_field_value, :*], [:struct_field_key, :*]],
- variations: [:match, :function_arguments]
-
- test "is detected if a module reference starts in function arguments" do
- assert_detected ~q[def my_function(%_«»)]
- end
-
- test "is detected if a module reference start in a t type spec" do
- assert_detected ~q[@type t :: %_«»]
- end
-
- test "is detected if the reference is for %__MOD in a function definition " do
- assert_detected ~q[def my_fn(%_«_MOD»]
- end
-
- test "is detected if the reference is on the right side of a match" do
- assert_detected ~q[foo = %U«se»]
- end
-
- test "is detected if the reference is on the left side of a match" do
- assert_detected ~q[ %U«se» = foo]
- end
-
- test "is detected if the reference is for %__} " do
- assert_detected ~q[%__]
- end
-
- test "is not detected if the reference is for %__MOC in a function definition" do
- refute_detected ~q[def my_fn(%__MOC)]
- end
-
- test "is detected if module reference starts with %" do
- assert_detected ~q[def something(my_thing, %S«truct»{})]
- end
-
- test "is not detected if a module reference lacks a %" do
- refute_detected ~q[def my_function(__)]
- end
-end
diff --git a/apps/common/test/lexical/ast/detection/type_test.exs b/apps/common/test/lexical/ast/detection/type_test.exs
deleted file mode 100644
index 1d40ec4f..00000000
--- a/apps/common/test/lexical/ast/detection/type_test.exs
+++ /dev/null
@@ -1,18 +0,0 @@
-defmodule Lexical.Ast.Detection.TypeTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.Type,
- assertions: [[:type, :*]]
-
- test "is not detected if you're in a variable named type" do
- refute_detected ~q[type = 3]
- end
-
- test "is not detected right after the type ends" do
- refute_detected ~q[
- @type« my_type :: atom»
-
- ]
- end
-end
diff --git a/apps/common/test/lexical/ast/detection/use_test.exs b/apps/common/test/lexical/ast/detection/use_test.exs
deleted file mode 100644
index 4dd0ec3d..00000000
--- a/apps/common/test/lexical/ast/detection/use_test.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule Lexical.Ast.Detection.UseTest do
- alias Lexical.Ast.Detection
-
- use Lexical.Test.DetectionCase,
- for: Detection.Use,
- assertions: [[:use, :*]]
-end
diff --git a/apps/common/test/lexical/ast/module_test.exs b/apps/common/test/lexical/ast/module_test.exs
deleted file mode 100644
index 7930b646..00000000
--- a/apps/common/test/lexical/ast/module_test.exs
+++ /dev/null
@@ -1,26 +0,0 @@
-defmodule Lexical.Ast.ModuleTest do
- import Lexical.Ast.Module
- use ExUnit.Case, async: true
-
- describe "safe_split/2" do
- test "splits elixir modules into binaries by default" do
- assert {:elixir, ~w(Lexical Document Store)} == safe_split(Lexical.Document.Store)
- end
-
- test "splits elixir modules into binaries" do
- assert {:elixir, ~w(Lexical Document Store)} ==
- safe_split(Lexical.Document.Store, as: :binaries)
- end
-
- test "splits elixir modules into atoms" do
- assert {:elixir, ~w(Lexical Document Store)a} ==
- safe_split(Lexical.Document.Store, as: :atoms)
- end
-
- test "splits erlang modules" do
- assert {:erlang, ["ets"]} = safe_split(:ets)
- assert {:erlang, ["ets"]} = safe_split(:ets, as: :binaries)
- assert {:erlang, [:ets]} = safe_split(:ets, as: :atoms)
- end
- end
-end
diff --git a/apps/common/test/lexical/document/store_test.exs b/apps/common/test/lexical/document/store_test.exs
deleted file mode 100644
index 3a466c63..00000000
--- a/apps/common/test/lexical/document/store_test.exs
+++ /dev/null
@@ -1,265 +0,0 @@
-defmodule Lexical.Document.StoreTest do
- alias Lexical.Document
- alias Lexical.Document.Edit
- alias Lexical.Document.Position
- alias Lexical.Document.Range
-
- use ExUnit.Case
-
- def with_store(%{} = context) do
- store_opts = Map.get(context, :store, [])
- {:ok, _} = start_supervised({Document.Store, store_opts})
- :ok
- end
-
- def with_an_open_document(_) do
- :ok = Document.Store.open(uri(), "hello", 1)
- end
-
- def uri do
- "file:///file.ex"
- end
-
- defp build_position(_, nil) do
- nil
- end
-
- defp build_position(%Document{} = document, opts) do
- line = Keyword.get(opts, :line)
- character = Keyword.get(opts, :character)
- Position.new(document, line, character)
- end
-
- defp build_range(_, nil) do
- nil
- end
-
- defp build_range(%Document{} = document, opts) do
- start_pos = build_position(document, Keyword.get(opts, :start))
-
- end_pos = build_position(document, Keyword.get(opts, :end))
-
- Range.new(start_pos, end_pos)
- end
-
- defp build_change(opts) do
- text = Keyword.get(opts, :text, "")
- document = Document.new("file:///file.ex", text, 1)
-
- range = build_range(document, Keyword.get(opts, :range))
- Edit.new(text, range)
- end
-
- describe "startup" do
- test "succeeds without options" do
- assert {:ok, _} = start_supervised(Document.Store)
- end
-
- test "succeeds with empty :derive" do
- assert {:ok, _} = start_supervised({Document.Store, [derive: []]})
- end
-
- test "succeeds with valid :derive" do
- valid_fun = fn _ -> :ok end
- assert {:ok, _} = start_supervised({Document.Store, [derive: [valid: valid_fun]]})
- end
-
- test "fails with invalid :derive" do
- invalid_fun = fn _, _ -> :ok end
- assert {:error, _} = start_supervised({Document.Store, [derive: [invalid: invalid_fun]]})
- end
-
- test "fails with invalid options" do
- assert {:error, _} = start_supervised({Document.Store, [invalid: []]})
- end
- end
-
- describe "a clean store" do
- setup [:with_store]
-
- test "a document can be opened" do
- :ok = Document.Store.open(uri(), "hello", 1)
- assert {:ok, file} = Document.Store.fetch(uri())
- assert Document.to_string(file) == "hello"
- assert file.version == 1
- end
-
- test "rejects changes to a file that isn't open" do
- event = build_change(text: "dog", range: nil)
-
- assert {:error, :not_open} =
- Document.Store.get_and_update(
- "file:///another.ex",
- &Document.apply_content_changes(&1, 3, [event])
- )
- end
- end
-
- describe "a document that is already open" do
- setup [:with_store, :with_an_open_document]
-
- test "can be fetched" do
- assert {:ok, doc} = Document.Store.fetch(uri())
- assert doc.uri == uri()
- assert Document.to_string(doc) == "hello"
- end
-
- test "can be closed" do
- assert :ok = Document.Store.close(uri())
- assert {:error, :not_open} = Document.Store.fetch(uri())
- end
-
- test "can be saved" do
- assert :ok = Document.Store.save(uri())
- assert {:ok, %{dirty?: false}} = Document.Store.fetch(uri())
- end
-
- test "can have its content changed" do
- event =
- build_change(
- text: "dog",
- range: [
- start: [line: 1, character: 1],
- end: [line: 1, character: 4]
- ]
- )
-
- assert {:ok, doc} =
- Document.Store.get_and_update(uri(), fn document ->
- Document.apply_content_changes(document, 2, [
- event
- ])
- end)
-
- assert Document.to_string(doc) == "doglo"
- assert {:ok, file} = Document.Store.fetch(uri())
- assert Document.to_string(file) == "doglo"
- end
-
- test "rejects a change if the version is less than the current version" do
- event = build_change(text: "dog", range: nil)
-
- assert {:error, :invalid_version} =
- Document.Store.get_and_update(
- uri(),
- &Document.apply_content_changes(&1, -1, [event])
- )
- end
-
- test "a change cannot be applied once a file is closed" do
- event = build_change(text: "dog", range: nil)
- assert :ok = Document.Store.close(uri())
-
- assert {:error, :not_open} =
- Document.Store.get_and_update(
- uri(),
- &Document.apply_content_changes(&1, 3, [event])
- )
- end
- end
-
- def with_a_temp_document(_) do
- contents = """
- defmodule FakeLines do
- end
- """
-
- :ok = File.write("/tmp/file.ex", contents)
-
- on_exit(fn ->
- File.rm!("/tmp/file.ex")
- end)
-
- {:ok, contents: contents, uri: "file:///tmp/file.ex"}
- end
-
- describe "a temp document" do
- setup [:with_store, :with_a_temp_document]
-
- test "can be opened", ctx do
- assert {:ok, doc} = Document.Store.open_temporary(ctx.uri, 100)
- assert Document.to_string(doc) == ctx.contents
- end
-
- test "closes after a timeout", ctx do
- assert {:ok, _} = Document.Store.open_temporary(ctx.uri, 100)
- Process.sleep(101)
- refute Document.Store.open?(ctx.uri)
- assert Document.Store.fetch(ctx.uri) == {:error, :not_open}
- end
-
- test "the extension is extended on subsequent access", ctx do
- assert {:ok, _doc} = Document.Store.open_temporary(ctx.uri, 100)
- Process.sleep(75)
- assert {:ok, _} = Document.Store.open_temporary(ctx.uri, 100)
- Process.sleep(75)
- assert Document.Store.open?(ctx.uri)
- Process.sleep(50)
- refute Document.Store.open?(ctx.uri)
- end
-
- test "opens permanently when a call to open is made", ctx do
- assert {:ok, _doc} = Document.Store.open_temporary(ctx.uri, 100)
- assert :ok = Document.Store.open(ctx.uri, ctx.contents, 1)
- Process.sleep(120)
- assert Document.Store.open?(ctx.uri)
- end
- end
-
- describe "derived values" do
- setup context do
- me = self()
-
- length_fun = fn doc ->
- send(me, :length_called)
-
- doc
- |> Document.to_string()
- |> String.length()
- end
-
- :ok = with_store(%{store: [derive: [length: length_fun]]})
- :ok = with_an_open_document(context)
- end
-
- test "can be fetched with the document by key" do
- assert {:ok, doc, 5} = Document.Store.fetch(uri(), :length)
- assert Document.to_string(doc) == "hello"
- end
-
- test "update when the document changes" do
- assert :ok =
- Document.Store.update(uri(), fn document ->
- Document.apply_content_changes(document, 2, [
- build_change(text: "dog")
- ])
- end)
-
- assert {:ok, doc, 3} = Document.Store.fetch(uri(), :length)
- assert Document.to_string(doc) == "dog"
- end
-
- test "are lazily computed when first fetched" do
- assert {:ok, %Document{}, 5} = Document.Store.fetch(uri(), :length)
- assert_received :length_called
- end
-
- test "are only computed again when the document changes" do
- assert {:ok, %Document{}, 5} = Document.Store.fetch(uri(), :length)
- assert_received :length_called
-
- assert {:ok, %Document{}, 5} = Document.Store.fetch(uri(), :length)
- refute_received :length_called
-
- assert :ok =
- Document.Store.update(uri(), fn document ->
- Document.apply_content_changes(document, 2, [
- build_change(text: "dog")
- ])
- end)
-
- assert {:ok, %Document{}, 3} = Document.Store.fetch(uri(), :length)
- assert_received :length_called
- end
- end
-end
diff --git a/apps/engine/.credo.exs b/apps/engine/.credo.exs
new file mode 100644
index 00000000..15f819c2
--- /dev/null
+++ b/apps/engine/.credo.exs
@@ -0,0 +1,3 @@
+Code.require_file("../../mix_credo.exs")
+
+Mix.Credo.config(excluded: ["test/fixtures/**/*.ex", "test/fixtures/**/*.exs"])
diff --git a/apps/engine/.formatter.exs b/apps/engine/.formatter.exs
new file mode 100644
index 00000000..b5eb9eee
--- /dev/null
+++ b/apps/engine/.formatter.exs
@@ -0,0 +1,32 @@
+# Used by "mix format"
+current_directory = Path.dirname(__ENV__.file)
+
+import_deps = [:forge]
+
+impossible_to_format = [
+ Path.join([
+ current_directory,
+ "test",
+ "fixtures",
+ "compilation_errors",
+ "lib",
+ "compilation_errors.ex"
+ ]),
+ Path.join([current_directory, "test", "fixtures", "parse_errors", "lib", "parse_errors.ex"])
+]
+
+locals_without_parens = [with_progress: 2, with_progress: 3, defkey: 2, defkey: 3, with_wal: 2]
+
+[
+ locals_without_parens: locals_without_parens,
+ export: [locals_without_parens: locals_without_parens],
+ import_deps: import_deps,
+ inputs:
+ Enum.flat_map(
+ [
+ Path.join(current_directory, "*.exs"),
+ Path.join(current_directory, "{lib,test}/**/*.{ex,exs}")
+ ],
+ &Path.wildcard(&1, match_dot: true)
+ ) -- impossible_to_format
+]
diff --git a/apps/engine/.gitignore b/apps/engine/.gitignore
new file mode 100644
index 00000000..487459f8
--- /dev/null
+++ b/apps/engine/.gitignore
@@ -0,0 +1,26 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where third-party dependencies like ExDoc output generated docs.
+/doc/
+
+# Ignore .fetch files in case you like to edit your project deps locally.
+/.fetch
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Ignore package tarball (built via "mix hex.build").
+engine-*.tar
+
+# Temporary files, for example, from tests.
+/tmp/
diff --git a/apps/engine/README.md b/apps/engine/README.md
new file mode 100644
index 00000000..e756dbf7
--- /dev/null
+++ b/apps/engine/README.md
@@ -0,0 +1,3 @@
+# Engine
+
+An application that is injected into the project's virtual machine to provide support for the language server
diff --git a/apps/remote_control/benchmarks/ast_analyze.exs b/apps/engine/benchmarks/ast_analyze.exs
similarity index 87%
rename from apps/remote_control/benchmarks/ast_analyze.exs
rename to apps/engine/benchmarks/ast_analyze.exs
index 95511ee2..362f2207 100644
--- a/apps/remote_control/benchmarks/ast_analyze.exs
+++ b/apps/engine/benchmarks/ast_analyze.exs
@@ -1,5 +1,5 @@
-alias Lexical.Ast
-alias Lexical.Document
+alias Forge.Ast
+alias Forge.Document
path =
[__DIR__, "**", "enum.ex"]
diff --git a/apps/remote_control/benchmarks/data/enum.ex b/apps/engine/benchmarks/data/enum.ex
similarity index 100%
rename from apps/remote_control/benchmarks/data/enum.ex
rename to apps/engine/benchmarks/data/enum.ex
diff --git a/apps/remote_control/benchmarks/data/source.index.v1.ets b/apps/engine/benchmarks/data/source.index.v1.ets
similarity index 100%
rename from apps/remote_control/benchmarks/data/source.index.v1.ets
rename to apps/engine/benchmarks/data/source.index.v1.ets
diff --git a/apps/remote_control/benchmarks/enum_index.exs b/apps/engine/benchmarks/enum_index.exs
similarity index 85%
rename from apps/remote_control/benchmarks/enum_index.exs
rename to apps/engine/benchmarks/enum_index.exs
index 6f55b57d..c00f9ee9 100644
--- a/apps/remote_control/benchmarks/enum_index.exs
+++ b/apps/engine/benchmarks/enum_index.exs
@@ -1,4 +1,4 @@
-alias Lexical.RemoteControl.Search.Indexer
+alias Engine.Search.Indexer
path =
[__DIR__, "**", "enum.ex"]
diff --git a/apps/remote_control/benchmarks/ets_bench.exs b/apps/engine/benchmarks/ets_bench.exs
similarity index 85%
rename from apps/remote_control/benchmarks/ets_bench.exs
rename to apps/engine/benchmarks/ets_bench.exs
index fd35aab5..6c90b520 100644
--- a/apps/remote_control/benchmarks/ets_bench.exs
+++ b/apps/engine/benchmarks/ets_bench.exs
@@ -1,9 +1,9 @@
-alias Lexical.Project
-alias Lexical.RemoteControl
-alias Lexical.RemoteControl.Search.Store.Backends.Ets
-alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schema
-alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schemas
-alias Lexical.VM.Versions
+alias Forge.Project
+
+alias Engine.Search.Store.Backends.Ets
+alias Engine.Search.Store.Backends.Ets.Schema
+alias Engine.Search.Store.Backends.Ets.Schemas
+alias Forge.VM.Versions
defmodule BenchHelper do
def wait_for_registration do
@@ -35,7 +35,7 @@ end
cwd = __DIR__
project = Project.new("file://#{cwd}")
-RemoteControl.set_project(project)
+Engine.set_project(project)
Project.ensure_workspace(project)
versions = Versions.current()
diff --git a/apps/remote_control/benchmarks/versions_bench.exs b/apps/engine/benchmarks/versions_bench.exs
similarity index 88%
rename from apps/remote_control/benchmarks/versions_bench.exs
rename to apps/engine/benchmarks/versions_bench.exs
index 269441c5..6b1dfcb1 100644
--- a/apps/remote_control/benchmarks/versions_bench.exs
+++ b/apps/engine/benchmarks/versions_bench.exs
@@ -1,4 +1,4 @@
-alias Lexical.VM.Versions
+alias Forge.VM.Versions
Benchee.run(%{
"versions" => fn ->
diff --git a/apps/remote_control/config/config.exs b/apps/engine/config/config.exs
similarity index 100%
rename from apps/remote_control/config/config.exs
rename to apps/engine/config/config.exs
diff --git a/apps/remote_control/config/dev.exs b/apps/engine/config/dev.exs
similarity index 100%
rename from apps/remote_control/config/dev.exs
rename to apps/engine/config/dev.exs
diff --git a/apps/remote_control/config/prod.exs b/apps/engine/config/prod.exs
similarity index 100%
rename from apps/remote_control/config/prod.exs
rename to apps/engine/config/prod.exs
diff --git a/apps/remote_control/config/test.exs b/apps/engine/config/test.exs
similarity index 88%
rename from apps/remote_control/config/test.exs
rename to apps/engine/config/test.exs
index 0dfb9734..a30e8851 100644
--- a/apps/remote_control/config/test.exs
+++ b/apps/engine/config/test.exs
@@ -2,7 +2,7 @@ import Config
config :logger, level: :none
-config :remote_control,
+config :engine,
edit_window_millis: 10,
modules_cache_expiry: {50, :millisecond},
search_store_quiescent_period_ms: 10
diff --git a/apps/engine/lib/engine/engine.ex b/apps/engine/lib/engine/engine.ex
new file mode 100644
index 00000000..ac8a3585
--- /dev/null
+++ b/apps/engine/lib/engine/engine.ex
@@ -0,0 +1,248 @@
+defmodule Engine do
+ @moduledoc """
+ The remote control boots another elixir application in a separate VM, injects
+ the remote control application into it and allows the language server to execute tasks in the
+ context of the remote VM.
+ """
+
+ alias Forge.Project
+
+ alias Engine.Api.Proxy
+ alias Engine.CodeAction
+ alias Engine.CodeIntelligence
+ alias Engine.ProjectNode
+
+ require Logger
+
+ @excluded_apps [:patch, :nimble_parsec]
+ @allowed_apps [:engine | Mix.Project.deps_apps()] -- @excluded_apps
+
+ defdelegate schedule_compile(force?), to: Proxy
+
+ defdelegate compile_document(document), to: Proxy
+
+ defdelegate format(document), to: Proxy
+
+ defdelegate reindex, to: Proxy
+
+ defdelegate index_running?, to: Proxy
+
+ defdelegate broadcast(message), to: Proxy
+
+ defdelegate expand_alias(segments_or_module, analysis, position), to: Engine.Analyzer
+
+ defdelegate list_modules, to: :code, as: :all_available
+
+ defdelegate code_actions(document, range, diagnostics, kinds, trigger_kind),
+ to: CodeAction,
+ as: :for_range
+
+ defdelegate complete(env), to: Engine.Completion, as: :elixir_sense_expand
+
+ defdelegate complete_struct_fields(analysis, position),
+ to: Engine.Completion,
+ as: :struct_fields
+
+ defdelegate definition(document, position), to: CodeIntelligence.Definition
+
+ defdelegate references(analysis, position, include_definitions?),
+ to: CodeIntelligence.References
+
+ defdelegate modules_with_prefix(prefix), to: Engine.Modules, as: :with_prefix
+
+ defdelegate modules_with_prefix(prefix, predicate), to: Engine.Modules, as: :with_prefix
+
+ defdelegate docs(module, opts \\ []), to: CodeIntelligence.Docs, as: :for_module
+
+ defdelegate register_listener(listener_pid, message_types), to: Engine.Dispatch
+
+ defdelegate resolve_entity(analysis, position), to: CodeIntelligence.Entity, as: :resolve
+
+ defdelegate struct_definitions, to: CodeIntelligence.Structs, as: :for_project
+
+ defdelegate document_symbols(document), to: CodeIntelligence.Symbols, as: :for_document
+
+ defdelegate workspace_symbols(query), to: CodeIntelligence.Symbols, as: :for_workspace
+
+ def start_link(%Project{} = project) do
+ :ok = ensure_epmd_started()
+ start_net_kernel(project)
+
+ apps_to_start = [:elixir | @allowed_apps] ++ [:runtime_tools]
+ node = Project.node_name(project)
+
+ with {:ok, node_pid} <- ProjectNode.start(project, glob_paths()),
+ :ok <- ensure_apps_started(node, apps_to_start) do
+ {:ok, node, node_pid}
+ end
+ end
+
+ def deps_paths do
+ case :persistent_term.get({__MODULE__, :deps_paths}, :error) do
+ :error ->
+ {:ok, deps_paths} =
+ Engine.Mix.in_project(fn _ ->
+ Mix.Task.run("loadpaths")
+ Mix.Project.deps_paths()
+ end)
+
+ :persistent_term.put({__MODULE__, :deps_paths}, deps_paths)
+ deps_paths
+
+ deps_paths ->
+ deps_paths
+ end
+ end
+
+ def with_lock(lock_type, func) do
+ :global.trans({lock_type, self()}, func, [Node.self()])
+ end
+
+ def project_node? do
+ !!:persistent_term.get({__MODULE__, :project}, false)
+ end
+
+ def get_project do
+ :persistent_term.get({__MODULE__, :project}, nil)
+ end
+
+ def set_project(%Project{} = project) do
+ :persistent_term.put({__MODULE__, :project}, project)
+ end
+
+ defdelegate stop(project), to: ProjectNode
+
+ def call(%Project{} = project, m, f, a \\ []) do
+ project
+ |> Project.node_name()
+ |> :erpc.call(m, f, a)
+ end
+
+ def manager_node_name(%Project{} = project) do
+ :"manager-#{Project.name(project)}-#{Project.entropy(project)}@127.0.0.1"
+ end
+
+ defp start_net_kernel(%Project{} = project) do
+ manager = manager_node_name(project)
+ :net_kernel.start(manager, %{name_domain: :longnames})
+ end
+
+ defp ensure_apps_started(node, app_names) do
+ Enum.reduce_while(app_names, :ok, fn app_name, _ ->
+ case :rpc.call(node, :application, :ensure_all_started, [app_name]) do
+ {:ok, _} -> {:cont, :ok}
+ error -> {:halt, error}
+ end
+ end)
+ end
+
+ defp glob_paths do
+ for entry <- :code.get_path(),
+ entry_string = List.to_string(entry),
+ entry_string != ".",
+ Enum.any?(app_globs(), &PathGlob.match?(entry_string, &1, match_dot: true)) do
+ entry
+ end
+ end
+
+ def elixir_executable(%Project{} = project) do
+ root_path = Project.root_path(project)
+
+ {path_result, env} =
+ with nil <- version_manager_path_and_env("asdf", root_path),
+ nil <- version_manager_path_and_env("mise", root_path),
+ nil <- version_manager_path_and_env("rtx", root_path) do
+ {File.cd!(root_path, fn -> System.find_executable("elixir") end), System.get_env()}
+ end
+
+ case path_result do
+ nil ->
+ {:error, :no_elixir}
+
+ executable when is_binary(executable) ->
+ {:ok, executable, env}
+ end
+ end
+
+ defp app_globs do
+ app_globs = Enum.map(@allowed_apps, fn app_name -> "/**/#{app_name}*/ebin" end)
+ ["/**/priv" | app_globs]
+ end
+
+ defp ensure_epmd_started do
+ case System.cmd("epmd", ~w(-daemon)) do
+ {"", 0} ->
+ :ok
+
+ _ ->
+ {:error, :epmd_failed}
+ end
+ end
+
+ defp version_manager_path_and_env(manager, root_path) do
+ with true <- is_binary(System.find_executable(manager)),
+ env = reset_env(manager, root_path),
+ {path, 0} <- System.cmd(manager, ~w(which elixir), cd: root_path, env: env) do
+ {String.trim(path), env}
+ else
+ _ ->
+ nil
+ end
+ end
+
+ # We launch expert by asking the version managers to provide an environment,
+ # which contains path munging. This initial environment is present in the running
+ # VM, and needs to be undone so we can find the correct elixir executable in the project.
+ defp reset_env("asdf", _root_path) do
+ orig_path = System.get_env("PATH_SAVE", System.get_env("PATH"))
+
+ Enum.map(System.get_env(), fn
+ {"ASDF_ELIXIR_VERSION", _} -> {"ASDF_ELIXIR_VERSION", nil}
+ {"ASDF_ERLANG_VERSION", _} -> {"ASDF_ERLANG_VERSION", nil}
+ {"PATH", _} -> {"PATH", orig_path}
+ other -> other
+ end)
+ end
+
+ defp reset_env("rtx", root_path) do
+ {env, _} = System.cmd("rtx", ~w(env -s bash), cd: root_path)
+
+ env
+ |> String.trim()
+ |> String.split("\n")
+ |> Enum.map(fn
+ "export " <> key_and_value ->
+ [key, value] =
+ key_and_value
+ |> String.split("=", parts: 2)
+ |> Enum.map(&String.trim/1)
+
+ {key, value}
+
+ _ ->
+ nil
+ end)
+ |> Enum.reject(&is_nil/1)
+ end
+
+ defp reset_env("mise", root_path) do
+ {env, _} = System.cmd("mise", ~w(env -s bash), cd: root_path)
+
+ env
+ |> String.trim()
+ |> String.split("\n")
+ |> Enum.map(fn
+ "export " <> key_and_value ->
+ [key, value] =
+ key_and_value
+ |> String.split("=", parts: 2)
+ |> Enum.map(&String.trim/1)
+
+ {key, value}
+
+ _ ->
+ nil
+ end)
+ |> Enum.reject(&is_nil/1)
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/analyzer.ex b/apps/engine/lib/engine/engine/analyzer.ex
similarity index 92%
rename from apps/remote_control/lib/lexical/remote_control/analyzer.ex
rename to apps/engine/lib/engine/engine/analyzer.ex
index 872c8171..2f678bac 100644
--- a/apps/remote_control/lib/lexical/remote_control/analyzer.ex
+++ b/apps/engine/lib/engine/engine/analyzer.ex
@@ -1,13 +1,13 @@
-defmodule Lexical.RemoteControl.Analyzer do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Require
- alias Lexical.Ast.Analysis.Use
- alias Lexical.Document.Position
- alias Lexical.RemoteControl.Analyzer.Aliases
- alias Lexical.RemoteControl.Analyzer.Imports
- alias Lexical.RemoteControl.Analyzer.Requires
- alias Lexical.RemoteControl.Analyzer.Uses
+defmodule Engine.Analyzer do
+ alias Engine.Analyzer.Aliases
+ alias Engine.Analyzer.Imports
+ alias Engine.Analyzer.Requires
+ alias Engine.Analyzer.Uses
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Require
+ alias Forge.Ast.Analysis.Use
+ alias Forge.Document.Position
require Logger
@@ -114,7 +114,7 @@ defmodule Lexical.RemoteControl.Analyzer do
) ::
{:ok, module()} | :error
def expand_alias([_ | _] = segments, %Analysis{} = analysis, %Position{} = position) do
- with %Analysis{valid?: true} = analysis <- Lexical.Ast.reanalyze_to(analysis, position),
+ with %Analysis{valid?: true} = analysis <- Forge.Ast.reanalyze_to(analysis, position),
aliases <- aliases_at(analysis, position),
{:ok, resolved} <- resolve_alias(segments, aliases) do
{:ok, Module.concat(resolved)}
diff --git a/apps/engine/lib/engine/engine/analyzer/aliases.ex b/apps/engine/lib/engine/engine/analyzer/aliases.ex
new file mode 100644
index 00000000..42636336
--- /dev/null
+++ b/apps/engine/lib/engine/engine/analyzer/aliases.ex
@@ -0,0 +1,62 @@
+defmodule Engine.Analyzer.Aliases do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Alias
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Document.Position
+
+ @spec at(Analysis.t(), Position.t()) :: %{atom() => module()}
+ def at(%Analysis{} = analysis, %Position{} = position) do
+ case Analysis.scopes_at(analysis, position) do
+ [%Scope{} = scope | _] ->
+ scope
+ |> Scope.alias_map(position)
+ |> Map.new(fn {as, %Alias{} = alias} ->
+ {as, Alias.to_module(alias)}
+ end)
+
+ [] ->
+ %{}
+ end
+ end
+
+ @doc """
+ Resolves an alias in the context of a line and a scope
+ (used internally when calculating imports)
+ """
+ def resolve_at(%Scope{} = scope, module, line) do
+ aliases = Scope.alias_map(scope, line)
+
+ case module do
+ # unquote(__MODULE__).SubModule
+ [{:unquote, _, [{:__MODULE__, _, _}]} | suffix] ->
+ resolve_current_module(aliases, suffix)
+
+ [{:__MODULE__, _, _} | suffix] ->
+ resolve_current_module(aliases, suffix)
+
+ [prefix | suffix] ->
+ case aliases do
+ %{^prefix => _} ->
+ current_module = resolve_alias(aliases, prefix, suffix)
+
+ Module.concat([current_module | suffix])
+
+ _ ->
+ Module.concat(module)
+ end
+ end
+ end
+
+ defp resolve_current_module(aliases, suffix) do
+ resolve_alias(aliases, :__MODULE__, suffix)
+ end
+
+ defp resolve_alias(aliases, prefix, suffix) do
+ current_module =
+ aliases
+ |> Map.get(prefix)
+ |> Alias.to_module()
+
+ Module.concat([current_module | suffix])
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/analyzer/imports.ex b/apps/engine/lib/engine/engine/analyzer/imports.ex
similarity index 92%
rename from apps/remote_control/lib/lexical/remote_control/analyzer/imports.ex
rename to apps/engine/lib/engine/engine/analyzer/imports.ex
index 77a1bb00..e3d1a9ce 100644
--- a/apps/remote_control/lib/lexical/remote_control/analyzer/imports.ex
+++ b/apps/engine/lib/engine/engine/analyzer/imports.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.RemoteControl.Analyzer.Imports do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Import
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.ProcessCache
- alias Lexical.RemoteControl.Analyzer.Aliases
- alias Lexical.RemoteControl.Module.Loader
+defmodule Engine.Analyzer.Imports do
+ alias Engine.Analyzer.Aliases
+ alias Engine.Module.Loader
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Import
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.ProcessCache
@spec at(Analysis.t(), Position.t()) :: [Scope.import_mfa()]
def at(%Analysis{} = analysis, %Position{} = position) do
diff --git a/apps/engine/lib/engine/engine/analyzer/requires.ex b/apps/engine/lib/engine/engine/analyzer/requires.ex
new file mode 100644
index 00000000..2f6fe9fb
--- /dev/null
+++ b/apps/engine/lib/engine/engine/analyzer/requires.ex
@@ -0,0 +1,26 @@
+defmodule Engine.Analyzer.Requires do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Require
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Document.Position
+
+ def at(%Analysis{} = analysis, %Position{} = position) do
+ case Analysis.scopes_at(analysis, position) do
+ [%Scope{} = scope | _] ->
+ scope.requires
+ |> Enum.filter(fn %Require{} = require ->
+ require_end = require.range.end
+
+ if require_end.line == position.line do
+ require_end.character <= position.character
+ else
+ require_end.line < position.line
+ end
+ end)
+ |> Enum.uniq_by(& &1.as)
+
+ _ ->
+ []
+ end
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/analyzer/uses.ex b/apps/engine/lib/engine/engine/analyzer/uses.ex
similarity index 75%
rename from apps/remote_control/lib/lexical/remote_control/analyzer/uses.ex
rename to apps/engine/lib/engine/engine/analyzer/uses.ex
index c0921e65..b21db05a 100644
--- a/apps/remote_control/lib/lexical/remote_control/analyzer/uses.ex
+++ b/apps/engine/lib/engine/engine/analyzer/uses.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.RemoteControl.Analyzer.Uses do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Document.Position
+defmodule Engine.Analyzer.Uses do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Document.Position
def at(%Analysis{} = analysis, %Position{} = position) do
case Analysis.scopes_at(analysis, position) do
diff --git a/apps/engine/lib/engine/engine/api.ex b/apps/engine/lib/engine/engine/api.ex
new file mode 100644
index 00000000..e3dae407
--- /dev/null
+++ b/apps/engine/lib/engine/engine/api.ex
@@ -0,0 +1,138 @@
+defmodule Engine.Api do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Env
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Project
+
+ alias Engine.CodeIntelligence
+
+ require Logger
+
+ def schedule_compile(%Project{} = project, force?) do
+ Engine.call(project, Engine, :schedule_compile, [force?])
+ end
+
+ def compile_document(%Project{} = project, %Document{} = document) do
+ Engine.call(project, Engine, :compile_document, [document])
+ end
+
+ def expand_alias(
+ %Project{} = project,
+ segments_or_module,
+ %Analysis{} = analysis,
+ %Position{} = position
+ ) do
+ Engine.call(project, Engine, :expand_alias, [
+ segments_or_module,
+ analysis,
+ position
+ ])
+ end
+
+ def list_modules(%Project{} = project) do
+ Engine.call(project, Engine, :list_modules)
+ end
+
+ def format(%Project{} = project, %Document{} = document) do
+ Engine.call(project, Engine, :format, [document])
+ end
+
+ def code_actions(
+ %Project{} = project,
+ %Document{} = document,
+ %Range{} = range,
+ diagnostics,
+ kinds,
+ trigger_kind
+ ) do
+ Engine.call(project, Engine, :code_actions, [
+ document,
+ range,
+ diagnostics,
+ kinds,
+ trigger_kind
+ ])
+ end
+
+ def complete(%Project{} = project, %Env{} = env) do
+ Logger.info("Completion for #{inspect(env.position)}")
+ Engine.call(project, Engine, :complete, [env])
+ end
+
+ def complete_struct_fields(%Project{} = project, %Analysis{} = analysis, %Position{} = position) do
+ Engine.call(project, Engine, :complete_struct_fields, [
+ analysis,
+ position
+ ])
+ end
+
+ def definition(%Project{} = project, %Document{} = document, %Position{} = position) do
+ Engine.call(project, Engine, :definition, [document, position])
+ end
+
+ def references(
+ %Project{} = project,
+ %Analysis{} = analysis,
+ %Position{} = position,
+ include_definitions?
+ ) do
+ Engine.call(project, Engine, :references, [
+ analysis,
+ position,
+ include_definitions?
+ ])
+ end
+
+ def modules_with_prefix(%Project{} = project, prefix)
+ when is_binary(prefix) or is_atom(prefix) do
+ Engine.call(project, Engine, :modules_with_prefix, [prefix])
+ end
+
+ def modules_with_prefix(%Project{} = project, prefix, predicate)
+ when is_binary(prefix) or is_atom(prefix) do
+ Engine.call(project, Engine, :modules_with_prefix, [prefix, predicate])
+ end
+
+ @spec docs(Project.t(), module()) :: {:ok, CodeIntelligence.Docs.t()} | {:error, any()}
+ def docs(%Project{} = project, module, opts \\ []) when is_atom(module) do
+ Engine.call(project, Engine, :docs, [module, opts])
+ end
+
+ def register_listener(%Project{} = project, listener_pid, message_types)
+ when is_pid(listener_pid) and is_list(message_types) do
+ Engine.call(project, Engine, :register_listener, [
+ listener_pid,
+ message_types
+ ])
+ end
+
+ def broadcast(%Project{} = project, message) do
+ Engine.call(project, Engine, :broadcast, [message])
+ end
+
+ def reindex(%Project{} = project) do
+ Engine.call(project, Engine, :reindex, [])
+ end
+
+ def index_running?(%Project{} = project) do
+ Engine.call(project, Engine, :index_running?, [])
+ end
+
+ def resolve_entity(%Project{} = project, %Analysis{} = analysis, %Position{} = position) do
+ Engine.call(project, Engine, :resolve_entity, [analysis, position])
+ end
+
+ def struct_definitions(%Project{} = project) do
+ Engine.call(project, Engine, :struct_definitions, [])
+ end
+
+ def document_symbols(%Project{} = project, %Document{} = document) do
+ Engine.call(project, Engine, :document_symbols, [document])
+ end
+
+ def workspace_symbols(%Project{} = project, query) do
+ Engine.call(project, Engine, :workspace_symbols, [query])
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/api/messages.ex b/apps/engine/lib/engine/engine/api/messages.ex
similarity index 85%
rename from apps/remote_control/lib/lexical/remote_control/api/messages.ex
rename to apps/engine/lib/engine/engine/api/messages.ex
index fa1baaad..49afdada 100644
--- a/apps/remote_control/lib/lexical/remote_control/api/messages.ex
+++ b/apps/engine/lib/engine/engine/api/messages.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Api.Messages do
- alias Lexical.Project
+defmodule Engine.Api.Messages do
+ alias Forge.Project
import Record
defrecord :project_compile_requested, project: nil, build_number: 0
@@ -53,12 +53,12 @@ defmodule Lexical.RemoteControl.Api.Messages do
@type project_compile_requested ::
record(:project_compile_requested,
- project: Lexical.Project.t(),
+ project: Forge.Project.t(),
build_number: non_neg_integer()
)
@type project_compiled ::
record(:project_compiled,
- project: Lexical.Project.t(),
+ project: Forge.Project.t(),
build_number: non_neg_integer(),
status: compile_status,
elapsed_ms: non_neg_integer
@@ -67,13 +67,13 @@ defmodule Lexical.RemoteControl.Api.Messages do
@type filesystem_event ::
record(:filesystem_event,
project: Project.t(),
- uri: Lexical.uri(),
+ uri: Forge.uri(),
event_type: :created | :updated | :deleted
)
@type file_changed ::
record(:file_changed,
- uri: Lexical.uri(),
+ uri: Forge.uri(),
from_version: maybe_version,
to_version: maybe_version,
open?: boolean()
@@ -81,16 +81,16 @@ defmodule Lexical.RemoteControl.Api.Messages do
@type file_compile_requested ::
record(:file_compile_requested,
- project: Lexical.Project.t(),
+ project: Forge.Project.t(),
build_number: non_neg_integer(),
- uri: Lexical.uri()
+ uri: Forge.uri()
)
@type file_compiled ::
record(:file_compiled,
- project: Lexical.Project.t(),
+ project: Forge.Project.t(),
build_number: non_neg_integer(),
- uri: Lexical.uri(),
+ uri: Forge.uri(),
status: compile_status,
elapsed_ms: non_neg_integer
)
@@ -105,14 +105,14 @@ defmodule Lexical.RemoteControl.Api.Messages do
@type project_diagnostics ::
record(:project_diagnostics,
- project: Lexical.Project.t(),
+ project: Forge.Project.t(),
diagnostics: diagnostics()
)
@type file_diagnostics ::
record(:file_diagnostics,
- project: Lexical.Project.t(),
- uri: Lexical.uri(),
+ project: Forge.Project.t(),
+ uri: Forge.uri(),
diagnostics: diagnostics()
)
@@ -125,14 +125,14 @@ defmodule Lexical.RemoteControl.Api.Messages do
@type struct_discovered :: record(:struct_discovered, module: module(), fields: field_list())
- @type project_index_ready :: record(:project_index_ready, project: Lexical.Project.t())
+ @type project_index_ready :: record(:project_index_ready, project: Forge.Project.t())
@type project_reindex_requested ::
- record(:project_reindex_requested, project: Lexical.Project.t())
+ record(:project_reindex_requested, project: Forge.Project.t())
@type project_reindexed ::
record(:project_reindexed,
- project: Lexical.Project.t(),
+ project: Forge.Project.t(),
elapsed_ms: non_neg_integer(),
status: :success | {:error, term()}
)
diff --git a/apps/remote_control/lib/lexical/remote_control/api/proxy.ex b/apps/engine/lib/engine/engine/api/proxy.ex
similarity index 90%
rename from apps/remote_control/lib/lexical/remote_control/api/proxy.ex
rename to apps/engine/lib/engine/engine/api/proxy.ex
index 790af722..9b669d24 100644
--- a/apps/remote_control/lib/lexical/remote_control/api/proxy.ex
+++ b/apps/engine/lib/engine/engine/api/proxy.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Api.Proxy do
+defmodule Engine.Api.Proxy do
@moduledoc """
A bimodal buffering proxy
@@ -30,16 +30,16 @@ defmodule Lexical.RemoteControl.Api.Proxy do
"""
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Api.Proxy.BufferingState
- alias Lexical.RemoteControl.Api.Proxy.DrainingState
- alias Lexical.RemoteControl.Api.Proxy.ProxyingState
- alias Lexical.RemoteControl.Api.Proxy.Records
- alias Lexical.RemoteControl.CodeMod
- alias Lexical.RemoteControl.Commands
+ alias Forge.Document
+ alias Forge.Document.Changes
+
+ alias Engine.Api.Messages
+ alias Engine.Api.Proxy.BufferingState
+ alias Engine.Api.Proxy.DrainingState
+ alias Engine.Api.Proxy.ProxyingState
+ alias Engine.Api.Proxy.Records
+ alias Engine.CodeMod
+ alias Engine.Commands
import Messages
import Record
@@ -63,25 +63,25 @@ defmodule Lexical.RemoteControl.Api.Proxy do
# proxied functions
def broadcast(percent_progress() = message) do
- RemoteControl.Dispatch.broadcast(message)
+ Engine.Dispatch.broadcast(message)
end
def broadcast(message) do
- mfa = to_mfa(RemoteControl.Dispatch.broadcast(message))
+ mfa = to_mfa(Engine.Dispatch.broadcast(message))
:gen_statem.call(__MODULE__, buffer(contents: mfa))
end
def schedule_compile(force? \\ false) do
- project = RemoteControl.get_project()
+ project = Engine.get_project()
- mfa = to_mfa(RemoteControl.Build.schedule_compile(project, force?))
+ mfa = to_mfa(Engine.Build.schedule_compile(project, force?))
:gen_statem.call(__MODULE__, buffer(contents: mfa))
end
def compile_document(document) do
- project = RemoteControl.get_project()
+ project = Engine.get_project()
- mfa = to_mfa(RemoteControl.Build.compile_document(project, document))
+ mfa = to_mfa(Engine.Build.compile_document(project, document))
:gen_statem.call(__MODULE__, buffer(contents: mfa))
end
diff --git a/apps/remote_control/lib/lexical/remote_control/api/proxy/buffering_state.ex b/apps/engine/lib/engine/engine/api/proxy/buffering_state.ex
similarity index 92%
rename from apps/remote_control/lib/lexical/remote_control/api/proxy/buffering_state.ex
rename to apps/engine/lib/engine/engine/api/proxy/buffering_state.ex
index ea00e51e..6cd23c40 100644
--- a/apps/remote_control/lib/lexical/remote_control/api/proxy/buffering_state.ex
+++ b/apps/engine/lib/engine/engine/api/proxy/buffering_state.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.Api.Proxy.BufferingState do
- alias Lexical.Document
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Commands
+defmodule Engine.Api.Proxy.BufferingState do
+ alias Forge.Document
+
+ alias Engine.Api
+ alias Engine.Build
+ alias Engine.Commands
import Api.Messages
import Api.Proxy.Records
@@ -23,7 +23,7 @@ defmodule Lexical.RemoteControl.Api.Proxy.BufferingState do
state.buffer
|> Enum.reverse()
|> Enum.split_with(fn value ->
- match?(mfa(module: RemoteControl.Dispatch, function: :broadcast), value)
+ match?(mfa(module: Engine.Dispatch, function: :broadcast), value)
end)
{project_compile, document_compiles, reindex} = collapse_commands(commands)
diff --git a/apps/remote_control/lib/lexical/remote_control/api/proxy/draining_state.ex b/apps/engine/lib/engine/engine/api/proxy/draining_state.ex
similarity index 79%
rename from apps/remote_control/lib/lexical/remote_control/api/proxy/draining_state.ex
rename to apps/engine/lib/engine/engine/api/proxy/draining_state.ex
index 4941fc12..6c1b33ae 100644
--- a/apps/remote_control/lib/lexical/remote_control/api/proxy/draining_state.ex
+++ b/apps/engine/lib/engine/engine/api/proxy/draining_state.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.RemoteControl.Api.Proxy.DrainingState do
- alias Lexical.RemoteControl.Api.Proxy.BufferingState
- alias Lexical.RemoteControl.Api.Proxy.ProxyingState
- alias Lexical.RemoteControl.Api.Proxy.Records
+defmodule Engine.Api.Proxy.DrainingState do
+ alias Engine.Api.Proxy.BufferingState
+ alias Engine.Api.Proxy.ProxyingState
+ alias Engine.Api.Proxy.Records
import Records
diff --git a/apps/remote_control/lib/lexical/remote_control/api/proxy/proxying_state.ex b/apps/engine/lib/engine/engine/api/proxy/proxying_state.ex
similarity index 88%
rename from apps/remote_control/lib/lexical/remote_control/api/proxy/proxying_state.ex
rename to apps/engine/lib/engine/engine/api/proxy/proxying_state.ex
index 81a723e4..466ac749 100644
--- a/apps/remote_control/lib/lexical/remote_control/api/proxy/proxying_state.ex
+++ b/apps/engine/lib/engine/engine/api/proxy/proxying_state.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Api.Proxy.ProxyingState do
- alias Lexical.RemoteControl.Api.Proxy.Records
+defmodule Engine.Api.Proxy.ProxyingState do
+ alias Engine.Api.Proxy.Records
defstruct refs_to_from: %{}
diff --git a/apps/remote_control/lib/lexical/remote_control/api/proxy/records.ex b/apps/engine/lib/engine/engine/api/proxy/records.ex
similarity index 91%
rename from apps/remote_control/lib/lexical/remote_control/api/proxy/records.ex
rename to apps/engine/lib/engine/engine/api/proxy/records.ex
index 5f625b73..1a9d344c 100644
--- a/apps/remote_control/lib/lexical/remote_control/api/proxy/records.ex
+++ b/apps/engine/lib/engine/engine/api/proxy/records.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Api.Proxy.Records do
- alias Lexical.Formats
+defmodule Engine.Api.Proxy.Records do
+ alias Forge.Formats
import Record
diff --git a/apps/engine/lib/engine/engine/application.ex b/apps/engine/lib/engine/engine/application.ex
new file mode 100644
index 00000000..395909f0
--- /dev/null
+++ b/apps/engine/lib/engine/engine/application.ex
@@ -0,0 +1,35 @@
+defmodule Engine.Application do
+ @moduledoc false
+
+ use Application
+ require Logger
+
+ @impl true
+ def start(_type, _args) do
+ children =
+ if Engine.project_node?() do
+ [
+ Engine.Api.Proxy,
+ Engine.Commands.Reindex,
+ Engine.Module.Loader,
+ {Engine.Dispatch, progress: true},
+ Engine.ModuleMappings,
+ Engine.Build,
+ Engine.Build.CaptureServer,
+ Engine.Plugin.Runner.Supervisor,
+ Engine.Plugin.Runner.Coordinator,
+ Engine.Search.Store.Backends.Ets,
+ {Engine.Search.Store,
+ [
+ &Engine.Search.Indexer.create_index/1,
+ &Engine.Search.Indexer.update_index/2
+ ]}
+ ]
+ else
+ []
+ end
+
+ opts = [strategy: :one_for_one, name: Engine.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/bootstrap.ex b/apps/engine/lib/engine/engine/bootstrap.ex
similarity index 93%
rename from apps/remote_control/lib/lexical/remote_control/bootstrap.ex
rename to apps/engine/lib/engine/engine/bootstrap.ex
index d7c43b9a..bd005ca6 100644
--- a/apps/remote_control/lib/lexical/remote_control/bootstrap.ex
+++ b/apps/engine/lib/engine/engine/bootstrap.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Bootstrap do
+defmodule Engine.Bootstrap do
@moduledoc """
Bootstraps the remote control node boot sequence.
@@ -6,12 +6,12 @@ defmodule Lexical.RemoteControl.Bootstrap do
the project's code paths, which are then added to the code paths from the language server. At this
point, it's safe to start the project, as we should have all the code present to compile the system.
"""
- alias Lexical.Project
- alias Lexical.RemoteControl
+ alias Forge.Project
+
require Logger
def init(%Project{} = project, document_store_entropy, app_configs) do
- Lexical.Document.Store.set_entropy(document_store_entropy)
+ Forge.Document.Store.set_entropy(document_store_entropy)
Application.put_all_env(app_configs)
@@ -24,7 +24,7 @@ defmodule Lexical.RemoteControl.Bootstrap do
{:ok, _} <- Application.ensure_all_started(:mix),
{:ok, _} <- Application.ensure_all_started(:logger) do
project = maybe_load_mix_exs(project)
- RemoteControl.set_project(project)
+ Engine.set_project(project)
Mix.env(:test)
ExUnit.start()
start_logger(project)
@@ -76,7 +76,7 @@ defmodule Lexical.RemoteControl.Bootstrap do
# app is an umbrella (umbrealla? returns false when started in a subapp)
# to no avail. This was the only thing that consistently worked
{:ok, configured_root} =
- RemoteControl.Mix.in_project(project, fn _ ->
+ Engine.Mix.in_project(project, fn _ ->
Mix.Project.config()
|> Keyword.get(:config_path)
|> Path.dirname()
diff --git a/apps/remote_control/lib/lexical/remote_control/build.ex b/apps/engine/lib/engine/engine/build.ex
similarity index 87%
rename from apps/remote_control/lib/lexical/remote_control/build.ex
rename to apps/engine/lib/engine/engine/build.ex
index 0137a352..32ab3e70 100644
--- a/apps/remote_control/lib/lexical/remote_control/build.ex
+++ b/apps/engine/lib/engine/engine/build.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Build do
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Build.Document.Compilers.HEEx
- alias Lexical.RemoteControl.Build.State
- alias Lexical.VM.Versions
+defmodule Engine.Build do
+ alias Forge.Document
+ alias Forge.Project
+
+ alias Engine.Build.Document.Compilers.HEEx
+ alias Engine.Build.State
+ alias Forge.VM.Versions
require Logger
use GenServer
@@ -46,7 +46,7 @@ defmodule Lexical.RemoteControl.Build do
end
def with_lock(func) do
- RemoteControl.with_lock(__MODULE__, func)
+ Engine.with_lock(__MODULE__, func)
end
# GenServer Callbacks
@@ -57,7 +57,7 @@ defmodule Lexical.RemoteControl.Build do
@impl GenServer
def init([]) do
- state = State.new(RemoteControl.get_project())
+ state = State.new(Engine.get_project())
with :ok <- State.set_compiler_options() do
{:ok, state, {:continue, :ensure_build_directory}}
diff --git a/apps/remote_control/lib/lexical/remote_control/build/capture_io.ex b/apps/engine/lib/engine/engine/build/capture_io.ex
similarity index 97%
rename from apps/remote_control/lib/lexical/remote_control/build/capture_io.ex
rename to apps/engine/lib/engine/engine/build/capture_io.ex
index 02e6b77a..9c571270 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/capture_io.ex
+++ b/apps/engine/lib/engine/engine/build/capture_io.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.Build.CaptureIO do
+defmodule Engine.Build.CaptureIO do
# Shamelessly stolen from ExUnit's CaptureIO
- alias Lexical.RemoteControl.Build
+ alias Engine.Build
def capture_io(fun) when is_function(fun, 0) do
capture_io(:stdio, [], fun)
diff --git a/apps/remote_control/lib/lexical/remote_control/build/capture_server.ex b/apps/engine/lib/engine/engine/build/capture_server.ex
similarity index 98%
rename from apps/remote_control/lib/lexical/remote_control/build/capture_server.ex
rename to apps/engine/lib/engine/engine/build/capture_server.ex
index 8818a4d4..dabd9a62 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/capture_server.ex
+++ b/apps/engine/lib/engine/engine/build/capture_server.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Build.CaptureServer do
+defmodule Engine.Build.CaptureServer do
@moduledoc false
@compile {:no_warn_undefined, Logger}
@timeout :infinity
diff --git a/apps/engine/lib/engine/engine/build/document.ex b/apps/engine/lib/engine/engine/build/document.ex
new file mode 100644
index 00000000..9a6ccdff
--- /dev/null
+++ b/apps/engine/lib/engine/engine/build/document.ex
@@ -0,0 +1,23 @@
+defmodule Engine.Build.Document do
+ alias Engine.Build
+ alias Engine.Build.Document.Compilers
+ alias Engine.Build.Isolation
+ alias Forge.Document
+
+ @compilers [Compilers.Config, Compilers.Elixir, Compilers.EEx, Compilers.HEEx, Compilers.NoOp]
+
+ def compile(%Document{} = document) do
+ compiler = Enum.find(@compilers, & &1.recognizes?(document))
+ compile_fun = fn -> compiler.compile(document) end
+
+ case Isolation.invoke(compile_fun) do
+ {:ok, result} ->
+ result
+
+ {:error, {exception, stack}} ->
+ diagnostic = Build.Error.error_to_diagnostic(document, exception, stack, nil)
+ diagnostics = Build.Error.refine_diagnostics([diagnostic])
+ {:error, diagnostics}
+ end
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compiler.ex b/apps/engine/lib/engine/engine/build/document/compiler.ex
similarity index 84%
rename from apps/remote_control/lib/lexical/remote_control/build/document/compiler.ex
rename to apps/engine/lib/engine/engine/build/document/compiler.ex
index 396d5dd8..4dd0d46e 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/document/compiler.ex
+++ b/apps/engine/lib/engine/engine/build/document/compiler.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.Build.Document.Compiler do
+defmodule Engine.Build.Document.Compiler do
@moduledoc """
A behaviour for document-level compilers
"""
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
@type compile_response :: {:ok, [Diagnostic.Result.t()]} | {:error, [Diagnostic.Result.t()]}
diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/config.ex b/apps/engine/lib/engine/engine/build/document/compilers/config.ex
similarity index 92%
rename from apps/remote_control/lib/lexical/remote_control/build/document/compilers/config.ex
rename to apps/engine/lib/engine/engine/build/document/compilers/config.ex
index d135f580..ac72a69b 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/config.ex
+++ b/apps/engine/lib/engine/engine/build/document/compilers/config.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.Config do
+defmodule Engine.Build.Document.Compilers.Config do
@moduledoc """
A compiler for elixir configuration
"""
alias Elixir.Features
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.Error.Location
+ alias Engine.Build
+ alias Engine.Build.Error.Location
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
@elixir_source "Elixir"
diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex b/apps/engine/lib/engine/engine/build/document/compilers/eex.ex
similarity index 92%
rename from apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex
rename to apps/engine/lib/engine/engine/build/document/compilers/eex.ex
index 243ef224..1dd941d2 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex
+++ b/apps/engine/lib/engine/engine/build/document/compilers/eex.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.EEx do
+defmodule Engine.Build.Document.Compilers.EEx do
@moduledoc """
A compiler for .eex files
"""
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic.Result
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.Document.Compiler
- alias Lexical.RemoteControl.Build.Document.Compilers
+ alias Engine.Build
+ alias Engine.Build.Document.Compiler
+ alias Engine.Build.Document.Compilers
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic.Result
@behaviour Compiler
diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/elixir.ex b/apps/engine/lib/engine/engine/build/document/compilers/elixir.ex
similarity index 84%
rename from apps/remote_control/lib/lexical/remote_control/build/document/compilers/elixir.ex
rename to apps/engine/lib/engine/engine/build/document/compilers/elixir.ex
index e53ab498..689d98d7 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/elixir.ex
+++ b/apps/engine/lib/engine/engine/build/document/compilers/elixir.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.Elixir do
+defmodule Engine.Build.Document.Compilers.Elixir do
@moduledoc """
A compiler for elixir source files (.ex and .exs)
"""
alias Elixir.Features
- alias Lexical.Document
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.Document.Compilers
+ alias Engine.Build
+ alias Engine.Build.Document.Compilers
+ alias Forge.Document
@behaviour Build.Document.Compiler
diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex b/apps/engine/lib/engine/engine/build/document/compilers/heex.ex
similarity index 77%
rename from apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex
rename to apps/engine/lib/engine/engine/build/document/compilers/heex.ex
index 69a0338b..6b6ed504 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex
+++ b/apps/engine/lib/engine/engine/build/document/compilers/heex.ex
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do
+defmodule Engine.Build.Document.Compilers.HEEx do
@moduledoc """
A compiler for .heex files
"""
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic.Result
- alias Lexical.RemoteControl.Build.Document.Compiler
- alias Lexical.RemoteControl.Build.Document.Compilers
+ alias Engine.Build.Document.Compiler
+ alias Engine.Build.Document.Compilers
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic.Result
require Logger
@behaviour Compiler
@@ -47,15 +47,14 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do
try do
source = Document.to_string(document)
- opts =
- [
- source: source,
- file: document.path,
- caller: __ENV__,
- engine: Phoenix.LiveView.TagEngine,
- subengine: Phoenix.LiveView.Engine,
- tag_handler: Phoenix.LiveView.HTMLEngine
- ]
+ opts = [
+ source: source,
+ file: document.path,
+ caller: __ENV__,
+ engine: Phoenix.LiveView.TagEngine,
+ subengine: Phoenix.LiveView.Engine,
+ tag_handler: Phoenix.LiveView.HTMLEngine
+ ]
quoted = EEx.compile_string(source, opts)
diff --git a/apps/engine/lib/engine/engine/build/document/compilers/no_op.ex b/apps/engine/lib/engine/engine/build/document/compilers/no_op.ex
new file mode 100644
index 00000000..b4f7277a
--- /dev/null
+++ b/apps/engine/lib/engine/engine/build/document/compilers/no_op.ex
@@ -0,0 +1,14 @@
+defmodule Engine.Build.Document.Compilers.NoOp do
+ @moduledoc """
+ A no-op, catch-all compiler. Always enabled, recognizes everything and returns no errors
+ """
+ alias Engine.Build.Document
+
+ @behaviour Document.Compiler
+
+ def recognizes?(_), do: true
+
+ def enabled?, do: true
+
+ def compile(_), do: {:ok, []}
+end
diff --git a/apps/engine/lib/engine/engine/build/document/compilers/quoted.ex b/apps/engine/lib/engine/engine/build/document/compilers/quoted.ex
new file mode 100644
index 00000000..740de9af
--- /dev/null
+++ b/apps/engine/lib/engine/engine/build/document/compilers/quoted.ex
@@ -0,0 +1,282 @@
+defmodule Engine.Build.Document.Compilers.Quoted do
+ alias Elixir.Features
+ alias Engine.Build
+ alias Engine.ModuleMappings
+ alias Forge.Ast
+ alias Forge.Document
+
+ import Engine.Build.CaptureIO, only: [capture_io: 2]
+
+ def compile(%Document{} = document, quoted_ast, compiler_name) do
+ prepare_compile(document.path)
+
+ quoted_ast =
+ if document.language_id == "elixir-script" do
+ wrap_top_level_forms(quoted_ast)
+ else
+ quoted_ast
+ end
+
+ {status, diagnostics} =
+ if Features.with_diagnostics?() do
+ do_compile(quoted_ast, document)
+ else
+ do_compile_and_capture_io(quoted_ast, document)
+ end
+
+ {status, Enum.map(diagnostics, &replace_source(&1, compiler_name))}
+ end
+
+ defp do_compile(quoted_ast, document) do
+ old_modules = ModuleMappings.modules_in_file(document.path)
+
+ case compile_quoted_with_diagnostics(quoted_ast, document.path) do
+ {{:ok, modules}, []} ->
+ purge_removed_modules(old_modules, modules)
+ {:ok, []}
+
+ {{:ok, modules}, all_errors_and_warnings} ->
+ purge_removed_modules(old_modules, modules)
+
+ diagnostics =
+ document
+ |> Build.Error.diagnostics_from_mix(all_errors_and_warnings)
+ |> Build.Error.refine_diagnostics()
+
+ {:ok, diagnostics}
+
+ {{:exception, exception, stack, quoted_ast}, all_errors_and_warnings} ->
+ converted = Build.Error.error_to_diagnostic(document, exception, stack, quoted_ast)
+ maybe_diagnostics = Build.Error.diagnostics_from_mix(document, all_errors_and_warnings)
+
+ diagnostics =
+ [converted | maybe_diagnostics]
+ |> Enum.reverse()
+ |> Build.Error.refine_diagnostics()
+
+ {:error, diagnostics}
+ end
+ end
+
+ defp do_compile_and_capture_io(quoted_ast, document) do
+ # credo:disable-for-next-line Credo.Check.Design.TagTODO
+ # TODO: remove this function once we drop support for Elixir 1.14
+ old_modules = ModuleMappings.modules_in_file(document.path)
+ compile = fn -> safe_compile_quoted(quoted_ast, document.path) end
+
+ case capture_io(:stderr, compile) do
+ {captured_messages, {:error, {:exception, {exception, _inner_stack}, stack}}} ->
+ error = Build.Error.error_to_diagnostic(document, exception, stack, [])
+ diagnostics = Build.Error.message_to_diagnostic(document, captured_messages)
+
+ {:error, [error | diagnostics]}
+
+ {captured_messages, {:exception, exception, stack, quoted_ast}} ->
+ error = Build.Error.error_to_diagnostic(document, exception, stack, quoted_ast)
+ diagnostics = Build.Error.message_to_diagnostic(document, captured_messages)
+ refined = Build.Error.refine_diagnostics([error | diagnostics])
+
+ {:error, refined}
+
+ {"", {:ok, modules}} ->
+ purge_removed_modules(old_modules, modules)
+ {:ok, []}
+
+ {captured_warnings, {:ok, modules}} ->
+ purge_removed_modules(old_modules, modules)
+
+ diagnostics =
+ document
+ |> Build.Error.message_to_diagnostic(captured_warnings)
+ |> Build.Error.refine_diagnostics()
+
+ {:ok, diagnostics}
+ end
+ end
+
+ defp prepare_compile(path) do
+ # If we're compiling a mix.exs file, the after compile callback from
+ # `use Mix.Project` will blow up if we add the same project to the project stack
+ # twice. Preemptively popping it prevents that error from occurring.
+ if Path.basename(path) == "mix.exs" do
+ Mix.ProjectStack.pop()
+ end
+
+ Mix.Task.run(:loadconfig)
+ end
+
+ @dialyzer {:nowarn_function, compile_quoted_with_diagnostics: 2}
+
+ defp compile_quoted_with_diagnostics(quoted_ast, path) do
+ # Using apply to prevent a compile warning on elixir < 1.15
+ # credo:disable-for-next-line
+ apply(Code, :with_diagnostics, [fn -> safe_compile_quoted(quoted_ast, path) end])
+ end
+
+ defp safe_compile_quoted(quoted_ast, path) do
+ try do
+ {:ok, Code.compile_quoted(quoted_ast, path)}
+ rescue
+ exception ->
+ {filled_exception, stack} = Exception.blame(:error, exception, __STACKTRACE__)
+ {:exception, filled_exception, stack, quoted_ast}
+ end
+ end
+
+ defp purge_removed_modules(old_modules, new_modules) do
+ new_modules = MapSet.new(new_modules, fn {module, _bytecode} -> module end)
+ old_modules = MapSet.new(old_modules)
+
+ old_modules
+ |> MapSet.difference(new_modules)
+ |> Enum.each(fn to_remove ->
+ :code.purge(to_remove)
+ :code.delete(to_remove)
+ end)
+ end
+
+ defp replace_source(result, source) do
+ Map.put(result, :source, source)
+ end
+
+ @doc false
+ def wrap_top_level_forms({:__block__, meta, nodes}) do
+ {chunks, _vars} =
+ nodes
+ |> Enum.chunk_by(&should_wrap?/1)
+ |> Enum.with_index()
+ |> Enum.flat_map_reduce([], fn {[node | _] = nodes, i}, vars ->
+ if should_wrap?(node) do
+ {wrapped, vars} = wrap_nodes(nodes, vars, i)
+ {[wrapped], vars}
+ else
+ {nodes, vars}
+ end
+ end)
+
+ {:__block__, meta, chunks}
+ end
+
+ def wrap_top_level_forms(ast) do
+ wrap_top_level_forms({:__block__, [], [ast]})
+ end
+
+ defp wrap_nodes(nodes, vars, i) do
+ module_name = :"expert_wrapper_#{i}"
+ {nodes, new_vars} = suppress_and_extract_vars(nodes)
+
+ quoted =
+ quote do
+ defmodule unquote(module_name) do
+ def __expert_wrapper__([unquote_splicing(vars)]) do
+ (unquote_splicing(nodes))
+ end
+ end
+ end
+
+ {quoted, new_vars ++ vars}
+ end
+
+ @allowed_top_level [:defmodule, :alias, :import, :require, :use]
+ defp should_wrap?({allowed, _, _}) when allowed in @allowed_top_level, do: false
+ defp should_wrap?(_), do: true
+
+ @doc false
+ # This function replaces all unused variables with `_` in order
+ # to suppress warnings while accumulating those vars. The approach
+ # here is bottom-up, starting from the last expression and working
+ # back to the beginning:
+ #
+ # - If the expression is an assignment, collect vars from the LHS,
+ # replacing them with `_` if they haven't been referenced, then
+ # collect references from the RHS.
+ # - If the expression isn't an assignment, just collect references.
+ # - Note that pinned vars on the LHS of an assignment are references.
+ #
+ def suppress_and_extract_vars(quoted)
+
+ def suppress_and_extract_vars(list) when is_list(list) do
+ list
+ |> Enum.reverse()
+ |> do_suppress_and_extract_vars()
+ end
+
+ def suppress_and_extract_vars({:__block__, meta, nodes}) do
+ {nodes, vars} = suppress_and_extract_vars(nodes)
+ {{:__block__, meta, nodes}, vars}
+ end
+
+ def suppress_and_extract_vars(expr) do
+ {[expr], vars} = suppress_and_extract_vars([expr])
+ {expr, vars}
+ end
+
+ defp do_suppress_and_extract_vars(list, acc \\ [], references \\ [], vars \\ [])
+
+ defp do_suppress_and_extract_vars([expr | rest], acc, references, vars) do
+ {expr, new_vars} = suppress_and_extract_vars_from_expr(expr, references)
+ new_references = extract_references_from_expr(expr)
+
+ do_suppress_and_extract_vars(
+ rest,
+ [expr | acc],
+ new_references ++ references,
+ new_vars ++ vars
+ )
+ end
+
+ defp do_suppress_and_extract_vars([], acc, _references, vars) do
+ {acc, vars}
+ end
+
+ defp suppress_and_extract_vars_from_expr({:=, meta, [left, right]}, references) do
+ {left, left_vars} =
+ Ast.prewalk_vars(left, [], fn
+ {:^, _, _} = pinned, acc ->
+ {pinned, acc}
+
+ {name, meta, context} = var, acc ->
+ if Ast.has_var?(references, name, context) do
+ {var, [{name, [], context} | acc]}
+ else
+ {{:_, meta, nil}, [var | acc]}
+ end
+ end)
+
+ {right, right_vars} = suppress_and_extract_vars_from_expr(right, references)
+
+ {{:=, meta, [left, right]}, left_vars ++ right_vars}
+ end
+
+ defp suppress_and_extract_vars_from_expr(other, _references) do
+ {other, []}
+ end
+
+ defp extract_references_from_expr({:=, _, [left, right]}) do
+ {_, left_references} =
+ Ast.prewalk_vars(left, [], fn
+ {:^, _, [referenced_var]}, acc ->
+ {:ok, [referenced_var | acc]}
+
+ node, acc ->
+ {node, acc}
+ end)
+
+ right_references = extract_references_from_expr(right)
+
+ left_references ++ right_references
+ end
+
+ defp extract_references_from_expr(expr) do
+ {_, references} =
+ Ast.prewalk_vars(expr, [], fn
+ {:^, _, _}, acc ->
+ {:ok, acc}
+
+ var, acc ->
+ {:ok, [var | acc]}
+ end)
+
+ references
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/error.ex b/apps/engine/lib/engine/engine/build/error.ex
similarity index 98%
rename from apps/remote_control/lib/lexical/remote_control/build/error.ex
rename to apps/engine/lib/engine/engine/build/error.ex
index f1ae13fe..4e606173 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/error.ex
+++ b/apps/engine/lib/engine/engine/build/error.ex
@@ -1,8 +1,8 @@
-defmodule Lexical.RemoteControl.Build.Error do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic.Result
- alias Lexical.RemoteControl.Build.Error.Location
+defmodule Engine.Build.Error do
+ alias Engine.Build.Error.Location
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic.Result
alias Mix.Task.Compiler
require Logger
diff --git a/apps/engine/lib/engine/engine/build/error/location.ex b/apps/engine/lib/engine/engine/build/error/location.ex
new file mode 100644
index 00000000..4809e837
--- /dev/null
+++ b/apps/engine/lib/engine/engine/build/error/location.ex
@@ -0,0 +1,97 @@
+defmodule Engine.Build.Error.Location do
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Plugin.V1.Diagnostic.Result
+
+ require Logger
+
+ def stack_to_position([{_, target, _, _} | rest])
+ when target not in [:__FILE__, :__MODULE__] do
+ stack_to_position(rest)
+ end
+
+ def stack_to_position([{_, target, _, context} | _rest])
+ when target in [:__FILE__, :__MODULE__] do
+ context_to_position(context)
+ end
+
+ def stack_to_position([]) do
+ nil
+ end
+
+ def context_to_position(context) do
+ case {context[:line], context[:column]} do
+ {nil, nil} ->
+ Logger.error("Invalid context: #{inspect(context)}")
+ nil
+
+ {line, nil} ->
+ line
+
+ {line, column} ->
+ position(line, column)
+ end
+ end
+
+ def position(line) do
+ line
+ end
+
+ def position(line, column) do
+ {line, column}
+ end
+
+ def fetch_range(%Document{} = document, context) do
+ case {context[:end_line], context[:end_column]} do
+ {nil, _} ->
+ :error
+
+ {end_line, end_column} ->
+ {line, column} = {context[:line], context[:column]}
+ {:ok, range(document, line, column, end_line, end_column)}
+ end
+ end
+
+ def range(%Document{} = document, {line, column}, {end_line, end_column}) do
+ range(document, line, column, end_line, end_column)
+ end
+
+ def range(%Document{} = document, line, column, end_line, end_column) do
+ start_position = Position.new(document, line, column)
+ end_position = Position.new(document, end_line, end_column)
+ Range.new(start_position, end_position)
+ end
+
+ def uniq(diagnostics) do
+ exacts = Enum.filter(diagnostics, fn diagnostic -> match?(%Range{}, diagnostic.position) end)
+
+ extract_line = fn
+ %Result{position: {line, _column}} -> line
+ %Result{position: line} -> line
+ end
+
+ # Note: Sometimes error and warning appear on one line at the same time
+ # So we need to uniq by line and severity,
+ # and :error is always more important than :warning
+ extract_line_and_severity = &{extract_line.(&1), &1.severity}
+
+ filtered =
+ diagnostics
+ |> Enum.filter(fn diagnostic -> not match?(%Range{}, diagnostic.position) end)
+ |> Enum.sort_by(extract_line_and_severity)
+ |> Enum.uniq_by(extract_line)
+ |> reject_zeroth_line()
+
+ exacts ++ filtered
+ end
+
+ defp reject_zeroth_line(diagnostics) do
+ # Since 1.15, Elixir has some nonsensical error on line 0,
+ # e.g.: Can't compile this file
+ # We can simply ignore it, as there is a more accurate one
+ Enum.reject(diagnostics, fn diagnostic ->
+ diagnostic.position == 0
+ end)
+ end
+end
diff --git a/apps/engine/lib/engine/engine/build/error/parse.ex b/apps/engine/lib/engine/engine/build/error/parse.ex
new file mode 100644
index 00000000..8704b16f
--- /dev/null
+++ b/apps/engine/lib/engine/engine/build/error/parse.ex
@@ -0,0 +1,198 @@
+defmodule Engine.Build.Error.Parse do
+ alias Engine.Build.Error.Location
+ alias Forge.Document
+ alias Forge.Document.Range
+ alias Forge.Plugin.V1.Diagnostic.Result
+
+ @elixir_source "Elixir"
+
+ # Parse errors happen during Code.string_to_quoted and are raised as SyntaxErrors, and TokenMissingErrors.
+ def to_diagnostics(
+ %Document{} = source,
+ context,
+ {_error, detail} = message_info,
+ token
+ )
+ when is_binary(detail) do
+ # NOTE: mainly for `unexpected token` errors `< 1.16`,
+ # its details consist of multiple lines, so it is a tuple.
+ detail_diagnostics = detail_diagnostics(source, detail)
+ error = message_info_to_binary(message_info, token)
+ error_diagnostics = to_diagnostics(source, context, error, token)
+ Location.uniq(detail_diagnostics ++ error_diagnostics)
+ end
+
+ def to_diagnostics(%Document{} = source, context, message_info, token)
+ when is_exception(message_info) do
+ to_diagnostics(source, context, Exception.message(message_info), token)
+ end
+
+ def to_diagnostics(%Document{} = source, context, message_info, token) do
+ {start_line_fn, end_line_fn} =
+ if Features.details_in_context?() do
+ {&build_end_line_diagnostics_from_context/4, &build_start_line_diagnostics_from_context/4}
+ else
+ {&build_start_line_diagnostics/4, &build_end_line_diagnostics/4}
+ end
+
+ parse_error_diagnostic_functions = [
+ end_line_fn,
+ start_line_fn,
+ &build_hint_diagnostics/4
+ ]
+
+ parse_error_diagnostic_functions
+ |> Enum.flat_map(& &1.(source, context, message_info, token))
+ |> Location.uniq()
+ end
+
+ @missing_terminator_pattern ~r/missing terminator: \w+/
+ defp build_end_line_diagnostics_from_context(
+ %Document{} = source,
+ context,
+ message_info,
+ token
+ ) do
+ message =
+ cond do
+ String.starts_with?(message_info, "unexpected") ->
+ ~s/#{message_info}#{token}, expected `#{context[:expected_delimiter]}`/
+
+ Regex.match?(@missing_terminator_pattern, message_info) ->
+ [message] = Regex.run(@missing_terminator_pattern, message_info)
+ message
+
+ true ->
+ "#{message_info}#{token}"
+ end
+
+ case Location.fetch_range(source, context) do
+ {:ok, %Range{end: end_pos}} ->
+ [
+ Result.new(
+ source.uri,
+ {end_pos.line, end_pos.character},
+ message,
+ :error,
+ @elixir_source
+ )
+ ]
+
+ :error ->
+ []
+ end
+ end
+
+ defp build_end_line_diagnostics(%Document{} = source, context, message_info, token) do
+ [end_line_message | _] = String.split(message_info, "\n")
+
+ message =
+ if String.ends_with?(end_line_message, token) do
+ end_line_message
+ else
+ end_line_message <> token
+ end
+
+ diagnostic =
+ Result.new(source.uri, Location.context_to_position(context), message, :error, "Elixir")
+
+ [diagnostic]
+ end
+
+ defp build_start_line_diagnostics_from_context(
+ %Document{} = source,
+ context,
+ message_info,
+ token
+ ) do
+ opening_delimiter = context[:opening_delimiter]
+
+ if opening_delimiter do
+ build_opening_delimiter_diagnostics(source, context, opening_delimiter)
+ else
+ build_syntax_error_diagnostic(source, context, message_info, token)
+ end
+ end
+
+ defp build_opening_delimiter_diagnostics(%Document{} = source, context, opening_delimiter) do
+ message =
+ ~s/The `#{opening_delimiter}` here is missing terminator `#{context[:expected_delimiter]}`/
+
+ opening_delimiter_length = opening_delimiter |> Atom.to_string() |> String.length()
+
+ pos =
+ Location.range(
+ source,
+ context[:line],
+ context[:column],
+ context[:line],
+ context[:column] + opening_delimiter_length
+ )
+
+ result = Result.new(source.uri, pos, message, :error, @elixir_source)
+ [result]
+ end
+
+ defp build_syntax_error_diagnostic(%Document{} = source, context, message_info, token) do
+ message = "#{message_info}#{token}"
+ pos = Location.position(context[:line], context[:column])
+ result = Result.new(source.uri, pos, message, :error, @elixir_source)
+ [result]
+ end
+
+ @start_line_regex ~r/(\w+) \(for (.*) starting at line (\d+)\)/
+ defp build_start_line_diagnostics(%Document{} = source, _context, message_info, _token) do
+ case Regex.run(@start_line_regex, message_info) do
+ [_, missing, token, start_line] ->
+ message =
+ ~s[The #{format_token(token)} here is missing terminator #{format_token(missing)}]
+
+ position = String.to_integer(start_line)
+ result = Result.new(source.uri, position, message, :error, @elixir_source)
+ [result]
+
+ _ ->
+ []
+ end
+ end
+
+ @hint_regex ~r/(HINT:|hint:\e\[0m|hint:)( .*on line (\d+).*)/m
+ defp build_hint_diagnostics(%Document{} = source, _context, message_info, _token) do
+ case Regex.run(@hint_regex, message_info) do
+ [_whole_message, _hint, message, hint_line] ->
+ message = "HINT:" <> String.replace(message, ~r/on line \d+/, "here")
+ position = String.to_integer(hint_line)
+ result = Result.new(source.uri, position, message, :error, @elixir_source)
+ [result]
+
+ _ ->
+ []
+ end
+ end
+
+ defp message_info_to_binary({header, footer}, token) do
+ header <> token <> footer
+ end
+
+ @detail_location_re ~r/at line (\d+)/
+ defp detail_diagnostics(%Document{} = source, detail) do
+ case Regex.scan(@detail_location_re, detail) do
+ [[matched, line_number]] ->
+ line_number = String.to_integer(line_number)
+ message = String.replace(detail, matched, "here")
+ result = Result.new(source.uri, line_number, message, :error, @elixir_source)
+ [result]
+
+ _ ->
+ []
+ end
+ end
+
+ defp format_token(token) when is_binary(token) do
+ if String.contains?(token, "\"") do
+ String.replace(token, "\"", "`")
+ else
+ "`#{token}`"
+ end
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/isolation.ex b/apps/engine/lib/engine/engine/build/isolation.ex
similarity index 91%
rename from apps/remote_control/lib/lexical/remote_control/build/isolation.ex
rename to apps/engine/lib/engine/engine/build/isolation.ex
index 4bfd3e00..7139fba1 100644
--- a/apps/remote_control/lib/lexical/remote_control/build/isolation.ex
+++ b/apps/engine/lib/engine/engine/build/isolation.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Build.Isolation do
+defmodule Engine.Build.Isolation do
@moduledoc """
Runs functions in an isolated, monitored process
"""
diff --git a/apps/engine/lib/engine/engine/build/project.ex b/apps/engine/lib/engine/engine/build/project.ex
new file mode 100644
index 00000000..42df7d47
--- /dev/null
+++ b/apps/engine/lib/engine/engine/build/project.ex
@@ -0,0 +1,130 @@
+defmodule Engine.Build.Project do
+ alias Forge.Project
+
+ alias Engine.Build
+ alias Engine.Build.Isolation
+ alias Engine.Plugin
+ alias Mix.Task.Compiler.Diagnostic
+
+ use Engine.Progress
+ require Logger
+
+ def compile(%Project{} = project, initial?) do
+ Engine.Mix.in_project(fn _ ->
+ Mix.Task.clear()
+
+ prepare_for_project_build(initial?)
+
+ compile_fun = fn ->
+ Mix.Task.clear()
+
+ with_progress building_label(project), fn ->
+ result = compile_in_isolation()
+ Mix.Task.run(:loadpaths)
+ result
+ end
+ end
+
+ case compile_fun.() do
+ {:error, diagnostics} ->
+ diagnostics =
+ diagnostics
+ |> List.wrap()
+ |> Build.Error.refine_diagnostics()
+
+ {:error, diagnostics}
+
+ {status, diagnostics} when status in [:ok, :noop] ->
+ Logger.info(
+ "Compile completed with status #{status} " <>
+ "Produced #{length(diagnostics)} diagnostics " <>
+ inspect(diagnostics)
+ )
+
+ Build.Error.refine_diagnostics(diagnostics)
+ end
+ end)
+ end
+
+ defp compile_in_isolation do
+ compile_fun = fn -> Mix.Task.run(:compile, mix_compile_opts()) end
+
+ case Isolation.invoke(compile_fun) do
+ {:ok, result} ->
+ result
+
+ {:error, {exception, [{_mod, _fun, _arity, meta} | _]}} ->
+ diagnostic = %Diagnostic{
+ file: Keyword.get(meta, :file),
+ severity: :error,
+ message: Exception.message(exception),
+ compiler_name: "Elixir",
+ position: Keyword.get(meta, :line, 1)
+ }
+
+ {:error, [diagnostic]}
+ end
+ end
+
+ defp prepare_for_project_build(false = _initial?) do
+ :ok
+ end
+
+ defp prepare_for_project_build(true = _initial?) do
+ if connected_to_internet?() do
+ with_progress "mix local.hex", fn ->
+ Mix.Task.run("local.hex", ~w(--force --if-missing))
+ end
+
+ with_progress "mix local.rebar", fn ->
+ Mix.Task.run("local.rebar", ~w(--force --if-missing))
+ end
+
+ with_progress "mix deps.get", fn ->
+ Mix.Task.run("deps.get")
+ end
+ else
+ Logger.warning("Could not connect to hex.pm, dependencies will not be fetched")
+ end
+
+ with_progress "mix loadconfig", fn ->
+ Mix.Task.run(:loadconfig)
+ end
+
+ unless Elixir.Features.compile_keeps_current_directory?() do
+ with_progress "mix deps.compile", fn ->
+ Mix.Task.run("deps.safe_compile", ~w(--skip-umbrella-children))
+ end
+ end
+
+ with_progress "loading plugins", fn ->
+ Plugin.Discovery.run()
+ end
+ end
+
+ defp connected_to_internet? do
+ # While there's no perfect way to check if a computer is connected to the internet,
+ # it seems reasonable to gate pulling dependenices on a resolution check for hex.pm.
+ # Yes, it's entirely possible that the DNS server is local, and that the entry is in cache,
+ # but that's an edge case, and the build will just time out anyways.
+ case :inet_res.getbyname(~c"hex.pm", :a, 250) do
+ {:ok, _} -> true
+ _ -> false
+ end
+ end
+
+ def building_label(%Project{} = project) do
+ "Building #{Project.display_name(project)}"
+ end
+
+ defp mix_compile_opts do
+ ~w(
+ --return-errors
+ --ignore-module-conflict
+ --all-warnings
+ --docs
+ --debug-info
+ --no-protocol-consolidation
+ )
+ end
+end
diff --git a/apps/engine/lib/engine/engine/build/state.ex b/apps/engine/lib/engine/engine/build/state.ex
new file mode 100644
index 00000000..93a50d4f
--- /dev/null
+++ b/apps/engine/lib/engine/engine/build/state.ex
@@ -0,0 +1,260 @@
+defmodule Engine.Build.State do
+ alias Elixir.Features
+ alias Engine.Api.Messages
+ alias Engine.Build
+ alias Engine.Plugin
+ alias Forge.Document
+ alias Forge.Project
+ alias Forge.VM.Versions
+
+ require Logger
+
+ import Messages
+
+ use Engine.Progress
+
+ defstruct project: nil,
+ build_number: 0,
+ uri_to_document: %{},
+ project_compile: :none
+
+ def new(%Project{} = project) do
+ %__MODULE__{project: project}
+ end
+
+ def on_timeout(%__MODULE__{} = state) do
+ new_state =
+ case state.project_compile do
+ :none -> state
+ :force -> compile_project(state, true)
+ :normal -> compile_project(state, false)
+ end
+
+ # We need to compile the individual documents even after the project is
+ # compiled because they might have unsaved changes, and we want that state
+ # to be the latest state of the project.
+ new_state =
+ Enum.reduce(new_state.uri_to_document, state, fn {_uri, document}, state ->
+ compile_file(state, document)
+ end)
+
+ %__MODULE__{new_state | uri_to_document: %{}, project_compile: :none}
+ end
+
+ def on_file_compile(%__MODULE__{} = state, %Document{} = document) do
+ %__MODULE__{
+ state
+ | uri_to_document: Map.put(state.uri_to_document, document.uri, document)
+ }
+ end
+
+ def on_project_compile(%__MODULE__{} = state, force?) do
+ if force? do
+ %__MODULE__{state | project_compile: :force}
+ else
+ %__MODULE__{state | project_compile: :normal}
+ end
+ end
+
+ def ensure_build_directory(%__MODULE__{} = state) do
+ # If the project directory isn't there, for some reason the main build fails, so we create it here
+ # to ensure that the build will succeed.
+ project = state.project
+ build_path = Engine.Build.path(project)
+
+ unless Versions.compatible?(build_path) do
+ Logger.info("Build path #{build_path} was compiled on a previous erlang version. Deleting")
+
+ if File.exists?(build_path) do
+ File.rm_rf(build_path)
+ end
+ end
+
+ maybe_delete_old_builds(project)
+
+ unless File.exists?(build_path) do
+ File.mkdir_p!(build_path)
+ Versions.write(build_path)
+ end
+ end
+
+ defp compile_project(%__MODULE__{} = state, initial?) do
+ state = increment_build_number(state)
+ project = state.project
+
+ Build.with_lock(fn ->
+ compile_requested_message =
+ project_compile_requested(project: project, build_number: state.build_number)
+
+ Engine.broadcast(compile_requested_message)
+ {elapsed_us, result} = :timer.tc(fn -> Build.Project.compile(project, initial?) end)
+ elapsed_ms = to_ms(elapsed_us)
+
+ {compile_message, diagnostics} =
+ case result do
+ :ok ->
+ message = project_compiled(status: :success, project: project, elapsed_ms: elapsed_ms)
+
+ {message, []}
+
+ {:ok, diagnostics} ->
+ message = project_compiled(status: :success, project: project, elapsed_ms: elapsed_ms)
+
+ {message, List.wrap(diagnostics)}
+
+ {:error, diagnostics} ->
+ message = project_compiled(status: :error, project: project, elapsed_ms: elapsed_ms)
+
+ {message, List.wrap(diagnostics)}
+ end
+
+ diagnostics_message =
+ project_diagnostics(
+ project: project,
+ build_number: state.build_number,
+ diagnostics: diagnostics
+ )
+
+ Engine.broadcast(compile_message)
+ Engine.broadcast(diagnostics_message)
+ Plugin.diagnose(project, state.build_number)
+ end)
+
+ state
+ end
+
+ def compile_file(%__MODULE__{} = state, %Document{} = document) do
+ state = increment_build_number(state)
+ project = state.project
+
+ Build.with_lock(fn ->
+ Engine.broadcast(file_compile_requested(uri: document.uri))
+
+ safe_compile_func = fn ->
+ Engine.Mix.in_project(fn _ -> Build.Document.compile(document) end)
+ end
+
+ {elapsed_us, result} = :timer.tc(fn -> safe_compile_func.() end)
+
+ elapsed_ms = to_ms(elapsed_us)
+
+ {compile_message, diagnostics} =
+ case result do
+ {:ok, diagnostics} ->
+ message =
+ file_compiled(
+ project: project,
+ build_number: state.build_number,
+ status: :success,
+ uri: document.uri,
+ elapsed_ms: elapsed_ms
+ )
+
+ {message, diagnostics}
+
+ {:error, diagnostics} ->
+ message =
+ file_compiled(
+ project: project,
+ build_number: state.build_number,
+ status: :error,
+ uri: document.uri,
+ elapsed_ms: elapsed_ms
+ )
+
+ {message, diagnostics}
+ end
+
+ diagnostics =
+ file_diagnostics(
+ project: project,
+ build_number: state.build_number,
+ uri: document.uri,
+ diagnostics: List.wrap(diagnostics)
+ )
+
+ Engine.broadcast(compile_message)
+ Engine.broadcast(diagnostics)
+ Plugin.diagnose(project, state.build_number, document)
+ end)
+
+ state
+ end
+
+ def set_compiler_options do
+ Code.compiler_options(
+ parser_options: parser_options(),
+ tracers: [Engine.Compilation.Tracer]
+ )
+
+ :ok
+ end
+
+ def mix_compile_opts(initial?) do
+ opts = ~w(
+ --return-errors
+ --ignore-module-conflict
+ --all-warnings
+ --docs
+ --debug-info
+ --no-protocol-consolidation
+ )
+
+ if initial? do
+ ["--force " | opts]
+ else
+ opts
+ end
+ end
+
+ def building_label(%Project{} = project) do
+ "Building #{Project.display_name(project)}"
+ end
+
+ defp to_ms(microseconds) do
+ microseconds / 1000
+ end
+
+ defp parser_options do
+ [columns: true, token_metadata: true]
+ end
+
+ defp increment_build_number(%__MODULE__{} = state) do
+ %__MODULE__{state | build_number: state.build_number + 1}
+ end
+
+ @two_month_seconds 86_400 * 31 * 2
+ defp maybe_delete_old_builds(%Project{} = project) do
+ build_root = Project.build_path(project)
+ two_months_ago = System.system_time(:second) - @two_month_seconds
+
+ case File.ls(build_root) do
+ {:ok, entries} ->
+ for file_name <- entries,
+ absolute_path = Path.join(build_root, file_name),
+ File.dir?(absolute_path),
+ newest_beam_mtime(absolute_path) <=
+ two_months_ago do
+ File.rm_rf!(absolute_path)
+ end
+
+ _ ->
+ :ok
+ end
+ end
+
+ defp newest_beam_mtime(directory) do
+ directory
+ |> Path.join("**/*.beam")
+ |> Path.wildcard()
+ |> then(fn
+ [] ->
+ 0
+
+ beam_files ->
+ beam_files
+ |> Enum.map(&File.stat!(&1, time: :posix).mtime)
+ |> Enum.max()
+ end)
+ end
+end
diff --git a/apps/engine/lib/engine/engine/code_action.ex b/apps/engine/lib/engine/engine/code_action.ex
new file mode 100644
index 00000000..85b31588
--- /dev/null
+++ b/apps/engine/lib/engine/engine/code_action.ex
@@ -0,0 +1,66 @@
+defmodule Engine.CodeAction do
+ alias Engine.CodeAction.Diagnostic
+ alias Engine.CodeAction.Handlers
+ alias Forge.Document
+ alias Forge.Document.Changes
+ alias Forge.Document.Range
+
+ defstruct [:title, :kind, :changes, :uri]
+
+ @type code_action_kind ::
+ :empty
+ | :quick_fix
+ | :refactor
+ | :refactor_extract
+ | :refactor_inline
+ | :refactor_rewrite
+ | :source
+ | :source_organize_imports
+ | :source_fix_all
+
+ @type trigger_kind :: :invoked | :automatic
+
+ @type t :: %__MODULE__{
+ title: String.t(),
+ kind: code_action_kind,
+ changes: Changes.t(),
+ uri: Forge.uri()
+ }
+
+ @handlers [
+ Handlers.ReplaceRemoteFunction,
+ Handlers.ReplaceWithUnderscore,
+ Handlers.OrganizeAliases,
+ Handlers.AddAlias,
+ Handlers.RemoveUnusedAlias,
+ Handlers.Refactorex
+ ]
+
+ @spec new(Forge.uri(), String.t(), code_action_kind(), Changes.t()) :: t()
+ def new(uri, title, kind, changes) do
+ %__MODULE__{uri: uri, title: title, changes: changes, kind: kind}
+ end
+
+ @spec for_range(
+ Document.t(),
+ Range.t(),
+ [Diagnostic.t()],
+ [code_action_kind] | :all,
+ trigger_kind
+ ) :: [t()]
+ def for_range(%Document{} = doc, %Range{} = range, diagnostics, kinds, trigger_kind) do
+ Enum.flat_map(@handlers, fn handler ->
+ if handle_kinds?(handler, kinds) and handle_trigger_kind?(handler, trigger_kind) do
+ handler.actions(doc, range, diagnostics)
+ else
+ []
+ end
+ end)
+ end
+
+ defp handle_kinds?(_handler, :all), do: true
+ defp handle_kinds?(handler, kinds), do: kinds -- handler.kinds() != kinds
+
+ defp handle_trigger_kind?(handler, trigger_kind),
+ do: handler.trigger_kind() in [trigger_kind, :all]
+end
diff --git a/apps/engine/lib/engine/engine/code_action/diagnostic.ex b/apps/engine/lib/engine/engine/code_action/diagnostic.ex
new file mode 100644
index 00000000..8d4c3086
--- /dev/null
+++ b/apps/engine/lib/engine/engine/code_action/diagnostic.ex
@@ -0,0 +1,17 @@
+defmodule Engine.CodeAction.Diagnostic do
+ alias Forge.Document.Range
+
+ defstruct [:range, :message, :source]
+ @type message :: String.t()
+ @type source :: String.t()
+ @type t :: %__MODULE__{
+ range: Range.t(),
+ message: message() | nil,
+ source: source() | nil
+ }
+
+ @spec new(Range.t(), message(), source() | nil) :: t
+ def new(%Range{} = range, message, source) do
+ %__MODULE__{range: range, message: message, source: source}
+ end
+end
diff --git a/apps/engine/lib/engine/engine/code_action/handler.ex b/apps/engine/lib/engine/engine/code_action/handler.ex
new file mode 100644
index 00000000..bc352766
--- /dev/null
+++ b/apps/engine/lib/engine/engine/code_action/handler.ex
@@ -0,0 +1,10 @@
+defmodule Engine.CodeAction.Handler do
+ alias Engine.CodeAction
+ alias Engine.CodeAction.Diagnostic
+ alias Forge.Document
+ alias Forge.Document.Range
+
+ @callback actions(Document.t(), Range.t(), [Diagnostic.t()]) :: [CodeAction.t()]
+ @callback kinds() :: [CodeAction.code_action_kind()]
+ @callback trigger_kind() :: CodeAction.trigger_kind() | :all
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/add_alias.ex b/apps/engine/lib/engine/engine/code_action/handlers/add_alias.ex
similarity index 89%
rename from apps/remote_control/lib/lexical/remote_control/code_action/handlers/add_alias.ex
rename to apps/engine/lib/engine/engine/code_action/handlers/add_alias.ex
index 223fcb3b..c80bc54a 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/add_alias.ex
+++ b/apps/engine/lib/engine/engine/code_action/handlers/add_alias.ex
@@ -1,20 +1,20 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.AddAlias do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Alias
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Formats
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.RemoteControl.CodeIntelligence.Entity
- alias Lexical.RemoteControl.CodeMod
- alias Lexical.RemoteControl.Modules
- alias Lexical.RemoteControl.Search.Fuzzy
- alias Lexical.RemoteControl.Search.Indexer.Entry
+defmodule Engine.CodeAction.Handlers.AddAlias do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Alias
+ alias Forge.Document
+ alias Forge.Document.Changes
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Formats
+
+ alias Engine.Analyzer
+ alias Engine.CodeAction
+ alias Engine.CodeIntelligence.Entity
+ alias Engine.CodeMod
+ alias Engine.Modules
+ alias Engine.Search.Fuzzy
+ alias Engine.Search.Indexer.Entry
alias Mix.Tasks.Namespace
alias Sourceror.Zipper
@@ -170,7 +170,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.AddAlias do
{:elixir, split} = Ast.Module.safe_split(module)
alias_as = List.last(split)
subject_module = module
- RemoteControl.Module.Loader.ensure_loaded(subject_module)
+ Engine.Module.Loader.ensure_loaded(subject_module)
protocol_or_implementation? = function_exported?(module, :__impl__, 1)
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/organize_aliases.ex b/apps/engine/lib/engine/engine/code_action/handlers/organize_aliases.ex
similarity index 78%
rename from apps/remote_control/lib/lexical/remote_control/code_action/handlers/organize_aliases.ex
rename to apps/engine/lib/engine/engine/code_action/handlers/organize_aliases.ex
index ecd73773..83cdde13 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/organize_aliases.ex
+++ b/apps/engine/lib/engine/engine/code_action/handlers/organize_aliases.ex
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.OrganizeAliases do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.RemoteControl.CodeMod
+defmodule Engine.CodeAction.Handlers.OrganizeAliases do
+ alias Engine.CodeAction
+ alias Engine.CodeMod
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Document
+ alias Forge.Document.Changes
+ alias Forge.Document.Range
require Logger
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/refactorex.ex b/apps/engine/lib/engine/engine/code_action/handlers/refactorex.ex
similarity index 79%
rename from apps/remote_control/lib/lexical/remote_control/code_action/handlers/refactorex.ex
rename to apps/engine/lib/engine/engine/code_action/handlers/refactorex.ex
index 07579f10..a1df13ec 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/refactorex.ex
+++ b/apps/engine/lib/engine/engine/code_action/handlers/refactorex.ex
@@ -1,11 +1,10 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.Refactorex do
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.Document.Range
+defmodule Engine.CodeAction.Handlers.Refactorex do
+ alias Forge.Document
+ alias Forge.Document.Changes
+ alias Forge.Document.Range
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.RemoteControl.CodeMod
+ alias Engine.CodeAction
+ alias Engine.CodeMod
alias Refactorex.Refactor
@@ -49,7 +48,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.Refactorex do
defp map_kind(kind), do: :"#{String.replace(kind, ".", "_")}"
defp ast_to_changes(doc, ast) do
- {formatter, opts} = CodeMod.Format.formatter_for_file(RemoteControl.get_project(), doc.uri)
+ {formatter, opts} = CodeMod.Format.formatter_for_file(Engine.get_project(), doc.uri)
ast
|> Sourceror.to_string(
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/remove_unused_alias.ex b/apps/engine/lib/engine/engine/code_action/handlers/remove_unused_alias.ex
similarity index 94%
rename from apps/remote_control/lib/lexical/remote_control/code_action/handlers/remove_unused_alias.ex
rename to apps/engine/lib/engine/engine/code_action/handlers/remove_unused_alias.ex
index 7fd013e8..f407f155 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/remove_unused_alias.ex
+++ b/apps/engine/lib/engine/engine/code_action/handlers/remove_unused_alias.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAlias do
+defmodule Engine.CodeAction.Handlers.RemoveUnusedAlias do
@moduledoc """
A code action that removes an unused alias
@@ -21,16 +21,16 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAlias do
normalizer and possibly fix sourceror, so until then, this is what we have.
"""
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.Document.Edit
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.RemoteControl.CodeAction.Diagnostic
+ alias Engine.Analyzer
+ alias Engine.CodeAction
+ alias Engine.CodeAction.Diagnostic
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.Document.Changes
+ alias Forge.Document.Edit
+ alias Forge.Document.Position
+ alias Forge.Document.Range
alias Sourceror.Zipper
import Record
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_remote_function.ex b/apps/engine/lib/engine/engine/code_action/handlers/replace_remote_function.ex
similarity index 90%
rename from apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_remote_function.ex
rename to apps/engine/lib/engine/engine/code_action/handlers/replace_remote_function.ex
index f48333b6..ebe50719 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_remote_function.ex
+++ b/apps/engine/lib/engine/engine/code_action/handlers/replace_remote_function.ex
@@ -1,13 +1,13 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceRemoteFunction do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.Document.Edit
- alias Lexical.Document.Range
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.RemoteControl.CodeAction.Diagnostic
- alias Lexical.RemoteControl.Modules
+defmodule Engine.CodeAction.Handlers.ReplaceRemoteFunction do
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Document.Changes
+ alias Forge.Document.Edit
+ alias Forge.Document.Range
+
+ alias Engine.CodeAction
+ alias Engine.CodeAction.Diagnostic
+ alias Engine.Modules
alias Sourceror.Zipper
@behaviour CodeAction.Handler
@@ -63,7 +63,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceRemoteFunction do
|> Ast.traverse_line(line_number, [], fn
%Zipper{node: {{:., _, [{:__aliases__, _, module_alias}, ^function_atom]}, _, _}} = zipper,
patches ->
- case RemoteControl.Analyzer.expand_alias(module_alias, analysis, position) do
+ case Engine.Analyzer.expand_alias(module_alias, analysis, position) do
{:ok, ^module} ->
patch = Sourceror.Patch.rename_call(zipper.node, suggestion)
{zipper, [patch | patches]}
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_with_underscore.ex b/apps/engine/lib/engine/engine/code_action/handlers/replace_with_underscore.ex
similarity index 88%
rename from apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_with_underscore.ex
rename to apps/engine/lib/engine/engine/code_action/handlers/replace_with_underscore.ex
index d83bc573..716dcdbc 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_with_underscore.ex
+++ b/apps/engine/lib/engine/engine/code_action/handlers/replace_with_underscore.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceWithUnderscore do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.RemoteControl.CodeAction.Diagnostic
+defmodule Engine.CodeAction.Handlers.ReplaceWithUnderscore do
+ alias Engine.CodeAction
+ alias Engine.CodeAction.Diagnostic
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Document.Changes
+ alias Forge.Document.Range
alias Sourceror.Zipper
@behaviour CodeAction.Handler
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/definition.ex b/apps/engine/lib/engine/engine/code_intelligence/definition.ex
similarity index 92%
rename from apps/remote_control/lib/lexical/remote_control/code_intelligence/definition.ex
rename to apps/engine/lib/engine/engine/code_intelligence/definition.ex
index b854f00b..e4590def 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/definition.ex
+++ b/apps/engine/lib/engine/engine/code_intelligence/definition.ex
@@ -1,16 +1,16 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Definition do
+defmodule Engine.CodeIntelligence.Definition do
alias ElixirSense.Providers.Location, as: ElixirSenseLocation
+ alias Engine.CodeIntelligence.Entity
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.Document.Location
+ alias Forge.Document.Position
+ alias Forge.Formats
+ alias Forge.Text
alias Future.Code
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.Document.Location
- alias Lexical.Document.Position
- alias Lexical.Formats
- alias Lexical.RemoteControl.CodeIntelligence.Entity
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store
- alias Lexical.Text
require Logger
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/docs.ex b/apps/engine/lib/engine/engine/code_intelligence/docs.ex
similarity index 95%
rename from apps/remote_control/lib/lexical/remote_control/code_intelligence/docs.ex
rename to apps/engine/lib/engine/engine/code_intelligence/docs.ex
index 5fa5da56..6fb9bad2 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/docs.ex
+++ b/apps/engine/lib/engine/engine/code_intelligence/docs.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Docs do
+defmodule Engine.CodeIntelligence.Docs do
@moduledoc """
Utilities for fetching documentation for a compiled module.
"""
- alias Lexical.RemoteControl.CodeIntelligence.Docs.Entry
- alias Lexical.RemoteControl.Modules
+ alias Engine.CodeIntelligence.Docs.Entry
+ alias Engine.Modules
defstruct [:module, :doc, functions_and_macros: [], callbacks: [], types: []]
diff --git a/apps/engine/lib/engine/engine/code_intelligence/docs/entry.ex b/apps/engine/lib/engine/engine/code_intelligence/docs/entry.ex
new file mode 100644
index 00000000..359ae0f2
--- /dev/null
+++ b/apps/engine/lib/engine/engine/code_intelligence/docs/entry.ex
@@ -0,0 +1,56 @@
+defmodule Engine.CodeIntelligence.Docs.Entry do
+ @moduledoc """
+ A documentation entry for a named entity within a module.
+ """
+
+ defstruct [
+ :module,
+ :kind,
+ :name,
+ :arity,
+ :signature,
+ :doc,
+ :metadata,
+ defs: []
+ ]
+
+ @type t(kind) :: %__MODULE__{
+ module: module(),
+ kind: kind,
+ name: atom(),
+ arity: arity(),
+ signature: [String.t()],
+ doc: content(),
+ metadata: metadata(),
+ defs: [String.t()]
+ }
+
+ @type content :: String.t() | :none | :hidden
+
+ @type metadata :: %{
+ optional(:defaults) => pos_integer(),
+ optional(:since) => String.t(),
+ optional(:guard) => boolean(),
+ optional(:opaque) => boolean(),
+ optional(:deprecated) => boolean()
+ }
+
+ @known_metadata [:defaults, :since, :guard, :opaque, :deprecated]
+
+ @doc false
+ def from_docs_v1(module, {{kind, name, arity}, _anno, signature, doc, meta}) do
+ %__MODULE__{
+ module: module,
+ kind: kind,
+ name: name,
+ arity: arity,
+ signature: signature,
+ doc: parse_doc(doc),
+ metadata: Map.take(meta, @known_metadata)
+ }
+ end
+
+ @doc false
+ def parse_doc(%{"en" => doc}), do: doc
+ def parse_doc(atom) when atom in [:none, :hidden], do: atom
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/entity.ex b/apps/engine/lib/engine/engine/code_intelligence/entity.ex
similarity index 94%
rename from apps/remote_control/lib/lexical/remote_control/code_intelligence/entity.ex
rename to apps/engine/lib/engine/engine/code_intelligence/entity.ex
index 5f89024d..5ca52c8f 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/entity.ex
+++ b/apps/engine/lib/engine/engine/code_intelligence/entity.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
+defmodule Engine.CodeIntelligence.Entity do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Formats
alias Future.Code, as: Code
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Formats
- alias Lexical.RemoteControl
+
alias Sourceror.Zipper
require Logger
@@ -85,7 +85,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
when def in [:def, :defp, :defmacro, :defmacrop] ->
# This case handles resolving calls that come from zero-arg definitions in
# a module, like hovering in `def my_fun| do`
- {:ok, module} = RemoteControl.Analyzer.current_module(analysis, position)
+ {:ok, module} = Engine.Analyzer.current_module(analysis, position)
{:ok, {:call, module, maybe_fun, 0}, node_range}
{:ok, [{^maybe_fun, _, args} | _]} ->
@@ -152,7 +152,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
with {:ok, path} <- Ast.path_at(analysis, position),
arity = arity_at_position(path, position),
{module, ^fun, ^arity} <-
- RemoteControl.Analyzer.resolve_local_call(analysis, position, fun, arity) do
+ Engine.Analyzer.resolve_local_call(analysis, position, fun, arity) do
{:ok, {:call, module, fun, arity}, node_range}
else
_ ->
@@ -222,9 +222,9 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
end
defp maybe_prepend_ecto_schema(module_string, %Analysis{} = analysis, %Position{} = position) do
- with true <- Ecto.Schema in RemoteControl.Analyzer.uses_at(analysis, position),
+ with true <- Ecto.Schema in Engine.Analyzer.uses_at(analysis, position),
true <- in_inline_embed?(analysis, position),
- {:ok, parent_module} <- RemoteControl.Analyzer.current_module(analysis, position) do
+ {:ok, parent_module} <- Engine.Analyzer.current_module(analysis, position) do
parent_module
|> Module.concat(module_string)
|> Formats.module()
@@ -254,7 +254,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
defp maybe_prepend_phoenix_scope_module(module_string, analysis, position) do
with {:ok, scope_segments} <- fetch_phoenix_scope_alias_segments(analysis, position),
{:ok, scope_module} <-
- RemoteControl.Analyzer.expand_alias(scope_segments, analysis, position),
+ Engine.Analyzer.expand_alias(scope_segments, analysis, position),
cursor_module = Module.concat(scope_module, module_string),
true <-
phoenix_controller_module?(cursor_module) or phoenix_liveview_module?(cursor_module) do
@@ -376,7 +376,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
defp expand_alias(module, analysis, %Position{} = position) when is_binary(module) do
[module]
|> Module.concat()
- |> RemoteControl.Analyzer.expand_alias(analysis, position)
+ |> Engine.Analyzer.expand_alias(analysis, position)
end
defp expand_alias(_, _analysis, _position), do: :error
@@ -482,7 +482,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
defp fetch_module_for_imported_function(analysis, position, function_name, arity) do
analysis
- |> RemoteControl.Analyzer.imports_at(position)
+ |> Engine.Analyzer.imports_at(position)
|> Enum.find_value({:error, :not_found}, fn
{imported_module, ^function_name, ^arity} ->
{:ok, imported_module}
@@ -493,7 +493,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
end
defp fetch_module_for_local_function(analysis, position, function_name, arity) do
- with {:ok, current_module} <- RemoteControl.Analyzer.current_module(analysis, position),
+ with {:ok, current_module} <- Engine.Analyzer.current_module(analysis, position),
true <- function_exported?(current_module, function_name, arity) do
{:ok, current_module}
else
@@ -507,7 +507,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Entity do
end
defp current_module(%Analysis{} = analysis, %Position{} = position) do
- case RemoteControl.Analyzer.current_module(analysis, position) do
+ case Engine.Analyzer.current_module(analysis, position) do
{:ok, module} -> module
_ -> nil
end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/references.ex b/apps/engine/lib/engine/engine/code_intelligence/references.ex
similarity index 84%
rename from apps/remote_control/lib/lexical/remote_control/code_intelligence/references.ex
rename to apps/engine/lib/engine/engine/code_intelligence/references.ex
index 878c6b37..05727860 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/references.ex
+++ b/apps/engine/lib/engine/engine/code_intelligence/references.ex
@@ -1,14 +1,14 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.References do
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.Document.Location
- alias Lexical.Document.Position
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.CodeIntelligence.Entity
- alias Lexical.RemoteControl.CodeIntelligence.Variable
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store
- alias Lexical.RemoteControl.Search.Subject
+defmodule Engine.CodeIntelligence.References do
+ alias Engine.Analyzer
+ alias Engine.CodeIntelligence.Entity
+ alias Engine.CodeIntelligence.Variable
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store
+ alias Engine.Search.Subject
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.Document.Location
+ alias Forge.Document.Position
require Logger
diff --git a/apps/engine/lib/engine/engine/code_intelligence/structs.ex b/apps/engine/lib/engine/engine/code_intelligence/structs.ex
new file mode 100644
index 00000000..bc2cd664
--- /dev/null
+++ b/apps/engine/lib/engine/engine/code_intelligence/structs.ex
@@ -0,0 +1,26 @@
+defmodule Engine.CodeIntelligence.Structs do
+ alias Engine.Module.Loader
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store
+
+ def for_project do
+ if Mix.Project.get() do
+ {:ok, structs_from_index()}
+ else
+ Engine.Mix.in_project(fn _ -> structs_from_index() end)
+ end
+ end
+
+ defp structs_from_index do
+ case Store.exact(type: :struct, subtype: :definition) do
+ {:ok, entries} ->
+ for %Entry{subject: struct_module} <- entries,
+ Loader.ensure_loaded?(struct_module) do
+ struct_module
+ end
+
+ _ ->
+ []
+ end
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols.ex b/apps/engine/lib/engine/engine/code_intelligence/symbols.ex
similarity index 90%
rename from apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols.ex
rename to apps/engine/lib/engine/engine/code_intelligence/symbols.ex
index 2459e43e..1b110e53 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols.ex
+++ b/apps/engine/lib/engine/engine/code_intelligence/symbols.ex
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Symbols do
- alias Lexical.Document
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.CodeIntelligence.Symbols
- alias Lexical.RemoteControl.Search
- alias Lexical.RemoteControl.Search.Indexer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Extractors
+defmodule Engine.CodeIntelligence.Symbols do
+ alias Engine.CodeIntelligence.Symbols
+ alias Engine.Search
+ alias Engine.Search.Indexer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Extractors
+ alias Forge.Document
+ alias Forge.Document.Range
@block_types [
:ex_unit_describe,
diff --git a/apps/engine/lib/engine/engine/code_intelligence/symbols/document.ex b/apps/engine/lib/engine/engine/code_intelligence/symbols/document.ex
new file mode 100644
index 00000000..6aa680d8
--- /dev/null
+++ b/apps/engine/lib/engine/engine/code_intelligence/symbols/document.ex
@@ -0,0 +1,99 @@
+defmodule Engine.CodeIntelligence.Symbols.Document do
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Document
+ alias Forge.Formats
+
+ defstruct [:name, :type, :range, :detail_range, :detail, :original_type, :subject, children: []]
+
+ def from(%Document{} = document, %Entry{} = entry, children \\ []) do
+ case name_and_type(entry.type, entry, document) do
+ {name, type} ->
+ range = entry.block_range || entry.range
+
+ {:ok,
+ %__MODULE__{
+ name: name,
+ type: type,
+ range: range,
+ detail_range: entry.range,
+ children: children,
+ original_type: entry.type,
+ subject: entry.subject
+ }}
+
+ _ ->
+ :error
+ end
+ end
+
+ @do_regex ~r/\s*do\s*$/
+
+ defp name_and_type({:function, type}, %Entry{} = entry, %Document{} = document)
+ when type in [:public, :private, :delegate] do
+ fragment =
+ document
+ |> Document.fragment(entry.range.start, entry.range.end)
+ |> remove_line_breaks_and_multiple_spaces()
+
+ prefix =
+ case type do
+ :public -> "def "
+ :private -> "defp "
+ :delegate -> "defdelegate "
+ end
+
+ {prefix <> fragment, entry.type}
+ end
+
+ @ignored_attributes ~w[spec doc moduledoc derive impl tag]
+ @type_name_regex ~r/@type\s+[^\s]+/
+
+ defp name_and_type(:module_attribute, %Entry{} = entry, document) do
+ case String.split(entry.subject, "@") do
+ [_, name] when name in @ignored_attributes ->
+ nil
+
+ [_, "type"] ->
+ type_text = Document.fragment(document, entry.range.start, entry.range.end)
+
+ name =
+ case Regex.scan(@type_name_regex, type_text) do
+ [[match]] -> match
+ _ -> "@type ??"
+ end
+
+ {name, :type}
+
+ [_, name] ->
+ {"@#{name}", :module_attribute}
+ end
+ end
+
+ defp name_and_type(ex_unit, %Entry{} = entry, document)
+ when ex_unit in [:ex_unit_describe, :ex_unit_setup, :ex_unit_test] do
+ name =
+ document
+ |> Document.fragment(entry.range.start, entry.range.end)
+ |> String.trim()
+ |> String.replace(@do_regex, "")
+
+ {name, ex_unit}
+ end
+
+ defp name_and_type(:struct, %Entry{} = entry, _document) do
+ module_name = Formats.module(entry.subject)
+ {"%#{module_name}{}", :struct}
+ end
+
+ defp name_and_type(type, %Entry{subject: name}, _document) when is_atom(name) do
+ {Formats.module(name), type}
+ end
+
+ defp name_and_type(type, %Entry{} = entry, _document) do
+ {to_string(entry.subject), type}
+ end
+
+ defp remove_line_breaks_and_multiple_spaces(string) do
+ string |> String.split(~r/\s/) |> Enum.reject(&match?("", &1)) |> Enum.join(" ")
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols/workspace.ex b/apps/engine/lib/engine/engine/code_intelligence/symbols/workspace.ex
similarity index 77%
rename from apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols/workspace.ex
rename to apps/engine/lib/engine/engine/code_intelligence/symbols/workspace.ex
index a78a6679..64dc0e94 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols/workspace.ex
+++ b/apps/engine/lib/engine/engine/code_intelligence/symbols/workspace.ex
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Symbols.Workspace do
+defmodule Engine.CodeIntelligence.Symbols.Workspace do
defmodule Link do
defstruct [:uri, :range, :detail_range]
@type t :: %__MODULE__{
- uri: Lexical.uri(),
- range: Lexical.Document.Range.t(),
- detail_range: Lexical.Document.Range.t()
+ uri: Forge.uri(),
+ range: Forge.Document.Range.t(),
+ detail_range: Forge.Document.Range.t()
}
def new(uri, range, detail_range \\ nil) do
@@ -13,9 +13,9 @@ defmodule Lexical.RemoteControl.CodeIntelligence.Symbols.Workspace do
end
end
- alias Lexical.Document
- alias Lexical.Formats
- alias Lexical.RemoteControl.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Document
+ alias Forge.Formats
defstruct [:name, :type, :link, container_name: nil]
diff --git a/apps/engine/lib/engine/engine/code_intelligence/variable.ex b/apps/engine/lib/engine/engine/code_intelligence/variable.ex
new file mode 100644
index 00000000..4f59c7c8
--- /dev/null
+++ b/apps/engine/lib/engine/engine/code_intelligence/variable.ex
@@ -0,0 +1,249 @@
+defmodule Engine.CodeIntelligence.Variable do
+ alias Engine.Search.Indexer
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Ast.Analysis
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+
+ require Logger
+
+ @extractors [Indexer.Extractors.Variable]
+
+ @spec definition(Analysis.t(), Position.t(), atom()) :: {:ok, Entry.t()} | :error
+ def definition(%Analysis{} = analysis, %Position{} = position, variable_name) do
+ with {:ok, block_structure, entries} <- index_variables(analysis),
+ {:ok, %Entry{} = definition_entry} <-
+ do_find_definition(variable_name, block_structure, entries, position) do
+ {:ok, definition_entry}
+ else
+ _ ->
+ :error
+ end
+ end
+
+ @spec references(Analysis.t(), Position.t(), charlist(), boolean()) :: [Range.t()]
+ def references(
+ %Analysis{} = analysis,
+ %Position{} = position,
+ variable_name,
+ include_definitions? \\ false
+ ) do
+ with {:ok, block_structure, entries} <- index_variables(analysis),
+ {:ok, %Entry{} = definition_entry} <-
+ do_find_definition(variable_name, block_structure, entries, position) do
+ references = search_for_references(entries, definition_entry, block_structure)
+
+ entries =
+ if include_definitions? do
+ [definition_entry | references]
+ else
+ references
+ end
+
+ Enum.sort_by(entries, fn %Entry{} = entry ->
+ {entry.range.start.line, entry.range.start.character}
+ end)
+ else
+ _ ->
+ []
+ end
+ end
+
+ defp index_variables(%Analysis{} = analysis) do
+ with {:ok, entries} <- Indexer.Quoted.index(analysis, @extractors),
+ {[block_structure], entries} <- Enum.split_with(entries, &(&1.type == :metadata)) do
+ {:ok, block_structure.subject, entries}
+ end
+ end
+
+ defp do_find_definition(variable_name, block_structure, entries, position) do
+ with {:ok, entry} <- fetch_entry(entries, variable_name, position) do
+ search_for_definition(entries, entry, block_structure)
+ end
+ end
+
+ defp fetch_entry(entries, variable_name, position) do
+ entries
+ |> Enum.find(fn %Entry{} = entry ->
+ entry.subject == variable_name and entry.type == :variable and
+ Range.contains?(entry.range, position)
+ end)
+ |> case do
+ %Entry{} = entry ->
+ {:ok, entry}
+
+ _ ->
+ :error
+ end
+ end
+
+ defp search_for_references(entries, %Entry{} = definition_entry, block_structure) do
+ block_id_to_children = block_id_to_children(block_structure)
+
+ definition_children = Map.get(block_id_to_children, definition_entry.block_id, [])
+
+ # The algorithm here is to first clean up the entries so they either are definitions or references to a
+ # variable with the given name. We sort them by their occurrence in the file, working backwards on a line, so
+ # definitions earlier in the line shadow definitions later in the line.
+ # Then we start at the definition entry, and then for each entry after that,
+ # if it's a definition, we mark the state as being shadowed, but reset the state if the block
+ # id isn't in the children of the current block id. If we're not in a child of the current block
+ # id, then we're no longer shadowed
+ #
+ # Note, this algorithm doesn't work when we have a block definition whose result rebinds a variable.
+ # For example:
+ # entries = [4, 5, 6]
+ # entries =
+ # if something() do
+ # [1 | entries]
+ # else
+ # entries
+ # end
+ # Searching for the references to the initial variable won't find anything inside the block, but
+ # searching for the rebound variable will.
+
+ {entries, _, _} =
+ entries
+ |> Enum.filter(fn %Entry{} = entry ->
+ after_definition? = Position.compare(entry.range.start, definition_entry.range.end) == :gt
+
+ variable_type? = entry.type == :variable
+ correct_subject? = entry.subject == definition_entry.subject
+ child_of_definition_block? = entry.block_id in definition_children
+
+ variable_type? and correct_subject? and child_of_definition_block? and after_definition?
+ end)
+ |> Enum.sort_by(fn %Entry{} = entry ->
+ start = entry.range.start
+ {start.line, -start.character, entry.block_id}
+ end)
+ |> Enum.reduce({[], false, definition_entry.block_id}, fn
+ %Entry{subtype: :definition} = entry, {entries, _, _} ->
+ # we have a definition that's shadowing our definition entry
+ {entries, true, entry.block_id}
+
+ %Entry{subtype: :reference} = entry, {entries, true, current_block_id} ->
+ shadowed? = entry.block_id in Map.get(block_id_to_children, current_block_id, [])
+
+ entries =
+ if shadowed? do
+ entries
+ else
+ [entry | entries]
+ end
+
+ {entries, shadowed?, entry.block_id}
+
+ %Entry{} = entry, {entries, false, _} ->
+ # we're a reference and we're not being shadowed; collect it and move on.
+ {[entry | entries], false, entry.block_id}
+ end)
+
+ entries
+ end
+
+ defp search_for_definition(entries, %Entry{} = entry, block_structure) do
+ block_id_to_parents = collect_parents(block_structure)
+ block_path = Map.get(block_id_to_parents, entry.block_id)
+ entries_by_block_id = entries_by_block_id(entries)
+
+ Enum.reduce_while([entry.block_id | block_path], :error, fn block_id, _ ->
+ block_entries =
+ entries_by_block_id
+ |> Map.get(block_id, [])
+ |> then(fn entries ->
+ # In the current block, reject all entries that come after the entry whose definition
+ # we're searching for. This prevents us from finding definitions who are shadowing
+ # our entry. For example, the definition on the left of the equals in: `param = param + 1`.
+
+ if block_id == entry.block_id do
+ Enum.drop_while(entries, &(&1.id != entry.id))
+ else
+ entries
+ end
+ end)
+
+ case Enum.find(block_entries, &definition_of?(entry, &1)) do
+ %Entry{} = definition ->
+ {:halt, {:ok, definition}}
+
+ nil ->
+ {:cont, :error}
+ end
+ end)
+ end
+
+ defp definition_of?(%Entry{} = needle, %Entry{} = compare) do
+ compare.type == :variable and compare.subtype == :definition and
+ compare.subject == needle.subject
+ end
+
+ defp entries_by_block_id(entries) do
+ entries
+ |> Enum.reduce(%{}, fn %Entry{} = entry, acc ->
+ Map.update(acc, entry.block_id, [entry], &[entry | &1])
+ end)
+ |> Map.new(fn {block_id, entries} ->
+ entries =
+ Enum.sort_by(
+ entries,
+ fn %Entry{} = entry ->
+ {entry.range.start.line, -entry.range.start.character}
+ end,
+ :desc
+ )
+
+ {block_id, entries}
+ end)
+ end
+
+ def block_id_to_parents(hierarchy) do
+ hierarchy
+ |> flatten_hierarchy()
+ |> Enum.reduce(%{}, fn {parent_id, child_id}, acc ->
+ old_parents = [parent_id | Map.get(acc, parent_id, [])]
+ Map.update(acc, child_id, old_parents, &Enum.concat(&1, old_parents))
+ end)
+ |> Map.put(:root, [])
+ end
+
+ def block_id_to_children(hierarchy) do
+ # Note: Parent ids are included in their children list in order to simplify
+ # checks for "is this id in one of its children"
+
+ hierarchy
+ |> flatten_hierarchy()
+ |> Enum.reverse()
+ |> Enum.reduce(%{root: [:root]}, fn {parent_id, child_id}, current_mapping ->
+ current_children = [child_id | Map.get(current_mapping, child_id, [parent_id])]
+
+ current_mapping
+ |> Map.put_new(child_id, [child_id])
+ |> Map.update(parent_id, current_children, &Enum.concat(&1, current_children))
+ end)
+ end
+
+ def flatten_hierarchy(hierarchy) do
+ Enum.flat_map(hierarchy, fn
+ {k, v} when is_map(v) and map_size(v) > 0 ->
+ v
+ |> Map.keys()
+ |> Enum.map(&{k, &1})
+ |> Enum.concat(flatten_hierarchy(v))
+
+ _ ->
+ []
+ end)
+ end
+
+ defp collect_parents(block_structure) do
+ do_collect_parents(block_structure, %{}, [])
+ end
+
+ defp do_collect_parents(hierarchy, parent_map, path) do
+ Enum.reduce(hierarchy, parent_map, fn {block_id, children}, acc ->
+ parent_map = Map.put(acc, block_id, path)
+ do_collect_parents(children, parent_map, [block_id | path])
+ end)
+ end
+end
diff --git a/apps/engine/lib/engine/engine/code_mod/aliases.ex b/apps/engine/lib/engine/engine/code_mod/aliases.ex
new file mode 100644
index 00000000..5153eb62
--- /dev/null
+++ b/apps/engine/lib/engine/engine/code_mod/aliases.ex
@@ -0,0 +1,256 @@
+defmodule Engine.CodeMod.Aliases do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Alias
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Document
+ alias Forge.Document.Edit
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+
+ alias Sourceror.Zipper
+
+ @doc """
+ Returns the aliases that are in scope at the given range.
+ """
+ @spec in_scope(Analysis.t(), Range.t()) :: [Alias.t()]
+ def in_scope(%Analysis{} = analysis, %Range{} = range) do
+ analysis
+ |> Analysis.module_scope(range)
+ |> aliases_in_scope()
+ end
+
+ @doc """
+ Sorts the given aliases according to our rules
+ """
+ @spec sort(Enumerable.t(Alias.t())) :: [Alias.t()]
+ def sort(aliases) do
+ Enum.sort_by(aliases, fn %Alias{} = scope_alias ->
+ Enum.map(scope_alias.module, fn elem -> elem |> to_string() |> String.downcase() end)
+ end)
+ end
+
+ @doc """
+ Returns the position in the document where aliases should be inserted
+ Since a document can have multiple module definitions, the cursor position is used to
+ determine the initial starting point.
+
+ This function also returns a string that should be appended to the end of the
+ edits that are performed.
+ """
+ @spec insert_position(Analysis.t(), Position.t()) :: {Position.t(), String.t() | nil}
+ def insert_position(%Analysis{} = analysis, %Position{} = cursor_position) do
+ range = Range.new(cursor_position, cursor_position)
+ current_aliases = in_scope(analysis, range)
+ do_insert_position(analysis, current_aliases, range)
+ end
+
+ @doc """
+ Turns a list of aliases into aliases into edits
+ """
+ @spec to_edits([Alias.t()], Position.t(), trailer :: String.t() | nil) :: [Edit.t()]
+
+ def to_edits(aliases, position, trailer \\ nil)
+ def to_edits([], _, _), do: []
+
+ def to_edits(aliases, %Position{} = insert_position, trailer) do
+ aliases = sort(aliases)
+ initial_spaces = insert_position.character - 1
+
+ alias_text =
+ aliases
+ # get rid of duplicate aliases
+ |> Enum.uniq_by(& &1.module)
+ |> Enum.map_join("\n", fn %Alias{} = a ->
+ text =
+ if List.last(a.module) == a.as do
+ "alias #{join(a.module)}"
+ else
+ "alias #{join(a.module)}, as: #{join(List.wrap(a.as))}"
+ end
+
+ indent(text, initial_spaces)
+ end)
+ |> String.trim_trailing()
+
+ zeroed = put_in(insert_position.character, 1)
+ new_alias_range = Range.new(zeroed, zeroed)
+
+ alias_text =
+ if is_binary(trailer) do
+ alias_text <> trailer
+ else
+ alias_text
+ end
+
+ edits = remove_old_aliases(aliases)
+
+ edits ++
+ [Edit.new(alias_text, new_alias_range)]
+ end
+
+ defp aliases_in_scope(%Scope{} = scope) do
+ scope.aliases
+ |> Enum.filter(fn %Alias{} = scope_alias ->
+ scope_alias.explicit? and Range.contains?(scope.range, scope_alias.range.start)
+ end)
+ |> sort()
+ end
+
+ defp join(module) do
+ Enum.join(module, ".")
+ end
+
+ defp indent(text, spaces) do
+ String.duplicate(" ", spaces) <> text
+ end
+
+ defp remove_old_aliases(aliases) do
+ ranges =
+ aliases
+ # Reject new aliases that don't have a range
+ |> Enum.reject(&is_nil(&1.range))
+ # iterating back to start means we won't have prior edits
+ # clobber subsequent edits
+ |> Enum.sort_by(& &1.range.start.line, :desc)
+ |> Enum.uniq_by(& &1.range)
+ |> Enum.map(fn %Alias{} = alias ->
+ orig_range = alias.range
+
+ orig_range
+ |> put_in([:start, :character], 1)
+ |> update_in([:end], fn %Position{} = pos ->
+ %Position{pos | character: 1, line: pos.line + 1}
+ end)
+ end)
+
+ first_alias_index = length(ranges) - 1
+
+ ranges
+ |> Enum.with_index()
+ |> Enum.map(fn
+ {range, ^first_alias_index} ->
+ # add a new line where the first alias was to make space
+ # for the rewritten aliases
+ Edit.new("\n", range)
+
+ {range, _} ->
+ Edit.new("", range)
+ end)
+ |> merge_adjacent_edits()
+ end
+
+ defp merge_adjacent_edits([]), do: []
+ defp merge_adjacent_edits([_] = edit), do: edit
+
+ defp merge_adjacent_edits([edit | rest]) do
+ rest
+ |> Enum.reduce([edit], fn %Edit{} = current, [%Edit{} = last | rest] = edits ->
+ with {same_text, same_text} <- {last.text, current.text},
+ {same, same} <- {to_tuple(current.range.end), to_tuple(last.range.start)} do
+ collapsed = put_in(current.range.end, last.range.end)
+
+ [collapsed | rest]
+ else
+ _ ->
+ [current | edits]
+ end
+ end)
+ |> Enum.reverse()
+ end
+
+ defp to_tuple(%Position{} = position) do
+ {position.line, position.character}
+ end
+
+ defp do_insert_position(%Analysis{}, [%Alias{} | _] = aliases, _) do
+ first = Enum.min_by(aliases, &{&1.range.start.line, &1.range.start.character})
+ {first.range.start, nil}
+ end
+
+ defp do_insert_position(%Analysis{} = analysis, _, range) do
+ case Analysis.module_scope(analysis, range) do
+ %Scope{id: :global} = scope ->
+ {scope.range.start, "\n"}
+
+ %Scope{} = scope ->
+ scope_start = scope.range.start
+ # we use the end position here because the start position is right after
+ # the do for modules, which puts it well into the line. The end position
+ # is before the end, which is equal to the indent of the scope.
+
+ initial_position =
+ scope_start
+ |> put_in([:line], scope_start.line + 1)
+ |> put_in([:character], scope.range.end.character)
+ |> constrain_to_range(scope.range)
+
+ position =
+ case Ast.zipper_at(analysis.document, scope_start) do
+ {:ok, zipper} ->
+ {_, position} =
+ Zipper.traverse(zipper, initial_position, fn
+ %Zipper{node: {:@, _, [{:moduledoc, _, _}]}} = zipper, _acc ->
+ # If we detect a moduledoc node, place the alias after it
+ range = Sourceror.get_range(zipper.node)
+
+ {zipper, after_node(analysis.document, scope.range, range)}
+
+ zipper, acc ->
+ {zipper, acc}
+ end)
+
+ position
+
+ _ ->
+ initial_position
+ end
+
+ maybe_move_cursor_to_token_start(position, analysis)
+ end
+ end
+
+ defp after_node(%Document{} = document, %Range{} = scope_range, %{
+ start: start_pos,
+ end: end_pos
+ }) do
+ document
+ |> Position.new(end_pos[:line] + 1, start_pos[:column])
+ |> constrain_to_range(scope_range)
+ end
+
+ defp constrain_to_range(%Position{} = position, %Range{} = scope_range) do
+ cond do
+ position.line == scope_range.end.line ->
+ character = min(scope_range.end.character, position.character)
+ %Position{position | character: character}
+
+ position.line > scope_range.end.line ->
+ %Position{scope_range.end | character: 1}
+
+ true ->
+ position
+ end
+ end
+
+ defp maybe_move_cursor_to_token_start(%Position{} = position, %Analysis{} = analysis) do
+ project = Engine.get_project()
+
+ with {:ok, env} <- Ast.Env.new(project, analysis, position),
+ false <- String.last(env.prefix) in [" ", ""] do
+ # ` en|d` -> `2`
+ # `en|d` -> `2`
+ non_empty_characters_count = env.prefix |> String.trim_leading() |> String.length()
+
+ new_position = %Position{
+ position
+ | character: position.character - non_empty_characters_count
+ }
+
+ {new_position, "\n"}
+ else
+ _ ->
+ {position, "\n"}
+ end
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_mod/diff.ex b/apps/engine/lib/engine/engine/code_mod/diff.ex
similarity index 94%
rename from apps/remote_control/lib/lexical/remote_control/code_mod/diff.ex
rename to apps/engine/lib/engine/engine/code_mod/diff.ex
index 85a4a338..3524fa4f 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_mod/diff.ex
+++ b/apps/engine/lib/engine/engine/code_mod/diff.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.CodeMod.Diff do
- alias Lexical.CodeUnit
- alias Lexical.Document
- alias Lexical.Document.Edit
- alias Lexical.Document.Position
- alias Lexical.Document.Range
+defmodule Engine.CodeMod.Diff do
+ alias Forge.CodeUnit
+ alias Forge.Document
+ alias Forge.Document.Edit
+ alias Forge.Document.Position
+ alias Forge.Document.Range
@spec diff(Document.t(), String.t()) :: [Edit.t()]
def diff(%Document{} = document, dest) when is_binary(dest) do
diff --git a/apps/remote_control/lib/lexical/remote_control/code_mod/format.ex b/apps/engine/lib/engine/engine/code_mod/format.ex
similarity index 94%
rename from apps/remote_control/lib/lexical/remote_control/code_mod/format.ex
rename to apps/engine/lib/engine/engine/code_mod/format.ex
index e934b286..aa97515e 100644
--- a/apps/remote_control/lib/lexical/remote_control/code_mod/format.ex
+++ b/apps/engine/lib/engine/engine/code_mod/format.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.CodeMod.Format do
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.CodeMod.Diff
+defmodule Engine.CodeMod.Format do
+ alias Forge.Document
+ alias Forge.Document.Changes
+ alias Forge.Project
+
+ alias Engine.Build
+ alias Engine.CodeMod.Diff
require Logger
@@ -97,7 +97,7 @@ defmodule Lexical.RemoteControl.CodeMod.Format do
@spec edits(Document.t()) :: {:ok, Changes.t()} | {:error, any}
def edits(%Document{} = document) do
- project = RemoteControl.get_project()
+ project = Engine.get_project()
with :ok <- Build.compile_document(project, document),
{:ok, formatted} <- do_format(project, document) do
@@ -163,7 +163,7 @@ defmodule Lexical.RemoteControl.CodeMod.Format do
fetch_formatter = fn _ -> Mix.Tasks.Format.formatter_for_file(file_path) end
{formatter_function, opts} =
- if RemoteControl.project_node?() do
+ if Engine.project_node?() do
case mix_formatter_from_task(project, file_path) do
{:ok, result} ->
result
@@ -245,7 +245,7 @@ defmodule Lexical.RemoteControl.CodeMod.Format do
defp mix_formatter_from_task(%Project{} = project, file_path) do
try do
root_path = Project.root_path(project)
- deps_paths = RemoteControl.deps_paths()
+ deps_paths = Engine.deps_paths()
formatter_and_opts =
Mix.Tasks.Future.Format.formatter_for_file(file_path,
diff --git a/apps/remote_control/lib/lexical/remote_control/commands/reindex.ex b/apps/engine/lib/engine/engine/commands/reindex.ex
similarity index 87%
rename from apps/remote_control/lib/lexical/remote_control/commands/reindex.ex
rename to apps/engine/lib/engine/engine/commands/reindex.ex
index 610854b2..3927e87f 100644
--- a/apps/remote_control/lib/lexical/remote_control/commands/reindex.ex
+++ b/apps/engine/lib/engine/engine/commands/reindex.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Commands.Reindex do
+defmodule Engine.Commands.Reindex do
defmodule State do
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.ProcessCache
- alias Lexical.RemoteControl.Search
- alias Lexical.RemoteControl.Search.Indexer
+ alias Engine.Search
+ alias Engine.Search.Indexer
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.ProcessCache
require Logger
require ProcessCache
@@ -70,11 +70,11 @@ defmodule Lexical.RemoteControl.Commands.Reindex do
A simple genserver that prevents more than one reindexing job from running at the same time
"""
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api
- alias Lexical.RemoteControl.Search
+ alias Forge.Document
+ alias Forge.Project
+
+ alias Engine.Api
+ alias Engine.Search
use GenServer
import Api.Messages
@@ -89,7 +89,7 @@ defmodule Lexical.RemoteControl.Commands.Reindex do
end
def perform do
- perform(RemoteControl.get_project())
+ perform(Engine.get_project())
end
def perform(%Project{} = project) do
@@ -144,7 +144,7 @@ defmodule Lexical.RemoteControl.Commands.Reindex do
end
defp do_reindex(%Project{} = project) do
- RemoteControl.broadcast(project_reindex_requested(project: project))
+ Engine.broadcast(project_reindex_requested(project: project))
{elapsed_us, result} =
:timer.tc(fn ->
@@ -153,7 +153,7 @@ defmodule Lexical.RemoteControl.Commands.Reindex do
end
end)
- RemoteControl.broadcast(
+ Engine.broadcast(
project_reindexed(project: project, elapsed_ms: round(elapsed_us / 1000), status: :success)
)
diff --git a/apps/remote_control/lib/lexical/remote_control/compilation/tracer.ex b/apps/engine/lib/engine/engine/compilation/tracer.ex
similarity index 83%
rename from apps/remote_control/lib/lexical/remote_control/compilation/tracer.ex
rename to apps/engine/lib/engine/engine/compilation/tracer.ex
index e779be3d..d4b3a59d 100644
--- a/apps/remote_control/lib/lexical/remote_control/compilation/tracer.ex
+++ b/apps/engine/lib/engine/engine/compilation/tracer.ex
@@ -1,14 +1,13 @@
-defmodule Lexical.RemoteControl.Compilation.Tracer do
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Module.Loader
+defmodule Engine.Compilation.Tracer do
+ alias Engine.Build
+ alias Engine.Module.Loader
- import RemoteControl.Api.Messages
+ import Engine.Api.Messages
def trace({:on_module, module_binary, _filename}, %Macro.Env{} = env) do
message = extract_module_updated(env.module, module_binary, env.file)
maybe_report_progress(env.file)
- RemoteControl.broadcast(message)
+ Engine.broadcast(message)
:ok
end
@@ -60,7 +59,7 @@ defmodule Lexical.RemoteControl.Compilation.Tracer do
if Path.extname(file) == ".ex" do
file
|> progress_message()
- |> RemoteControl.broadcast()
+ |> Engine.broadcast()
end
end
@@ -75,7 +74,7 @@ defmodule Lexical.RemoteControl.Compilation.Tracer do
message = "compiling: " <> Path.join([base_dir, "...", file_name])
- label = Build.State.building_label(RemoteControl.get_project())
+ label = Build.State.building_label(Engine.get_project())
project_progress(label: label, message: message)
end
end
diff --git a/apps/engine/lib/engine/engine/completion.ex b/apps/engine/lib/engine/engine/completion.ex
new file mode 100644
index 00000000..26092c3b
--- /dev/null
+++ b/apps/engine/lib/engine/engine/completion.ex
@@ -0,0 +1,150 @@
+defmodule Engine.Completion do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Env
+ alias Forge.Document
+ alias Forge.Document.Position
+
+ alias Engine.CodeMod.Format
+ alias Engine.Completion.Candidate
+
+ import Document.Line
+ import Forge.Logging
+
+ def elixir_sense_expand(%Env{} = env) do
+ {doc_string, position} = strip_struct_operator(env)
+
+ line = position.line
+ character = position.character
+ hint = ElixirSense.Core.Source.prefix(doc_string, line, character)
+
+ if String.trim(hint) == "" do
+ []
+ else
+ {_formatter, opts} =
+ timed_log("formatter for file", fn ->
+ Format.formatter_for_file(env.project, env.document.path)
+ end)
+
+ locals_without_parens = Keyword.fetch!(opts, :locals_without_parens)
+
+ for suggestion <-
+ timed_log("ES suggestions", fn ->
+ ElixirSense.suggestions(doc_string, line, character)
+ end),
+ candidate =
+ timed_log("from_elixir_sense", fn ->
+ from_elixir_sense(suggestion, locals_without_parens)
+ end),
+ candidate != nil do
+ candidate
+ end
+ end
+ end
+
+ defp from_elixir_sense(suggestion, locals_without_parens) do
+ suggestion
+ |> Candidate.from_elixir_sense()
+ |> maybe_suppress_parens(locals_without_parens)
+ end
+
+ defp maybe_suppress_parens(%struct{} = candidate, locals_without_parens)
+ when struct in [Candidate.Function, Candidate.Macro, Candidate.Typespec] do
+ atom_name = String.to_atom(candidate.name)
+ suppress_parens? = local_without_parens?(atom_name, candidate.arity, locals_without_parens)
+
+ %{candidate | parens?: not suppress_parens?}
+ end
+
+ defp maybe_suppress_parens(candidate, _), do: candidate
+
+ defp local_without_parens?(fun, arity, locals_without_parens) do
+ arity > 0 and
+ Enum.any?(locals_without_parens, fn
+ {^fun, :*} -> true
+ {^fun, ^arity} -> true
+ _ -> false
+ end)
+ end
+
+ def struct_fields(%Analysis{} = analysis, %Position{} = position) do
+ container_struct_module =
+ analysis
+ |> Forge.Ast.cursor_path(position)
+ |> container_struct_module()
+
+ with {:ok, struct_module} <-
+ Engine.Analyzer.expand_alias(container_struct_module, analysis, position),
+ true <- function_exported?(struct_module, :__struct__, 0) do
+ struct_module
+ |> struct()
+ |> Map.from_struct()
+ |> Map.keys()
+ |> Enum.map(&Candidate.StructField.new(&1, struct_module))
+ else
+ _ -> []
+ end
+ end
+
+ defp container_struct_module(cursor_path) do
+ Enum.find_value(cursor_path, fn
+ # current module struct: `%__MODULE__{|}`
+ {:%, _, [{:__MODULE__, _, _} | _]} -> [:__MODULE__]
+ # struct leading by current module: `%__MODULE__.Struct{|}`
+ {:%, _, [{:__aliases__, _, [{:__MODULE__, _, _} | tail]} | _]} -> [:__MODULE__ | tail]
+ # Struct leading by alias or just a aliased Struct: `%Struct{|}`, `%Project.Struct{|}`
+ {:%, _, [{:__aliases__, _, aliases} | _]} -> aliases
+ _ -> nil
+ end)
+ end
+
+ # HACK: This fixes ElixirSense struct completions for certain cases.
+ # We should try removing when we update or remove ElixirSense.
+ defp strip_struct_operator(%Env{} = env) do
+ with true <- Env.in_context?(env, :struct_reference),
+ {:ok, completion_length} <- fetch_struct_completion_length(env) do
+ column = env.position.character
+ percent_position = column - (completion_length + 1)
+
+ new_line_start = String.slice(env.line, 0, percent_position - 1)
+ new_line_end = String.slice(env.line, percent_position..-1//1)
+ new_line = [new_line_start, new_line_end]
+ new_position = Position.new(env.document, env.position.line, env.position.character - 1)
+ line_to_replace = env.position.line
+
+ stripped_text =
+ env.document.lines
+ |> Enum.with_index(1)
+ |> Enum.reduce([], fn
+ {line(ending: ending), ^line_to_replace}, acc ->
+ [acc, new_line, ending]
+
+ {line(text: line_text, ending: ending), _}, acc ->
+ [acc, line_text, ending]
+ end)
+ |> IO.iodata_to_binary()
+
+ {stripped_text, new_position}
+ else
+ _ ->
+ doc_string = Document.to_string(env.document)
+ {doc_string, env.position}
+ end
+ end
+
+ defp fetch_struct_completion_length(env) do
+ case Code.Fragment.cursor_context(env.prefix) do
+ {:struct, {:dot, {:alias, struct_name}, []}} ->
+ # add one because of the trailing period
+ {:ok, length(struct_name) + 1}
+
+ {:struct, {:local_or_var, local_name}} ->
+ {:ok, length(local_name)}
+
+ {:struct, struct_name} ->
+ {:ok, length(struct_name)}
+
+ {:local_or_var, local_name} ->
+ {:ok, length(local_name)}
+ end
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/completion/candidate.ex b/apps/engine/lib/engine/engine/completion/candidate.ex
similarity index 98%
rename from apps/remote_control/lib/lexical/remote_control/completion/candidate.ex
rename to apps/engine/lib/engine/engine/completion/candidate.ex
index 93125200..00a5199a 100644
--- a/apps/remote_control/lib/lexical/remote_control/completion/candidate.ex
+++ b/apps/engine/lib/engine/engine/completion/candidate.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Completion.Candidate do
- alias Lexical.RemoteControl.Completion.Candidate.ArgumentNames
+defmodule Engine.Completion.Candidate do
+ alias Engine.Completion.Candidate.ArgumentNames
require Logger
defmodule Function do
diff --git a/apps/remote_control/lib/lexical/remote_control/completion/candidate/argument_names.ex b/apps/engine/lib/engine/engine/completion/candidate/argument_names.ex
similarity index 97%
rename from apps/remote_control/lib/lexical/remote_control/completion/candidate/argument_names.ex
rename to apps/engine/lib/engine/engine/completion/candidate/argument_names.ex
index 6152f557..d8d2e1b8 100644
--- a/apps/remote_control/lib/lexical/remote_control/completion/candidate/argument_names.ex
+++ b/apps/engine/lib/engine/engine/completion/candidate/argument_names.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Completion.Candidate.ArgumentNames do
+defmodule Engine.Completion.Candidate.ArgumentNames do
@moduledoc """
Elixir sense, for whatever reason returns all the argument names when asked to do a completion on a function.
This means that the arity of the function might differ from the argument names returned. Furthermore, the
diff --git a/apps/remote_control/lib/lexical/remote_control/dispatch.ex b/apps/engine/lib/engine/engine/dispatch.ex
similarity index 76%
rename from apps/remote_control/lib/lexical/remote_control/dispatch.ex
rename to apps/engine/lib/engine/engine/dispatch.ex
index 9cc3b3e8..be264d74 100644
--- a/apps/remote_control/lib/lexical/remote_control/dispatch.ex
+++ b/apps/engine/lib/engine/engine/dispatch.ex
@@ -1,15 +1,15 @@
-defmodule Lexical.RemoteControl.Dispatch do
+defmodule Engine.Dispatch do
@moduledoc """
- A global event dispatcher for lexical.
+ A global event dispatcher for expert.
Dispatch allows two recipients of its messages, processes and modules. A process must register
itself via a call to `register_listener`, while a process must implement the
- `Lexical.RemoteControl.Dispatch.Handler` behaviour and add the module to the @handlers module attribute.
+ `Engine.Dispatch.Handler` behaviour and add the module to the @handlers module attribute.
"""
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Dispatch.Handlers
- alias Lexical.RemoteControl.Dispatch.PubSub
- import Lexical.RemoteControl.Api.Messages
+
+ alias Engine.Dispatch.Handlers
+ alias Engine.Dispatch.PubSub
+ import Engine.Api.Messages
@handlers [PubSub, Handlers.Indexing]
@@ -77,8 +77,8 @@ defmodule Lexical.RemoteControl.Dispatch do
end
defp progress_pid do
- project = RemoteControl.get_project()
- manager_node_name = RemoteControl.manager_node_name(project)
- :rpc.call(manager_node_name, Lexical.Server.Project.Progress, :whereis, [project])
+ project = Engine.get_project()
+ manager_node_name = Engine.manager_node_name(project)
+ :rpc.call(manager_node_name, Expert.Project.Progress, :whereis, [project])
end
end
diff --git a/apps/engine/lib/engine/engine/dispatch/handler.ex b/apps/engine/lib/engine/engine/dispatch/handler.ex
new file mode 100644
index 00000000..47d4fcbc
--- /dev/null
+++ b/apps/engine/lib/engine/engine/dispatch/handler.ex
@@ -0,0 +1,105 @@
+defmodule Engine.Dispatch.Handler do
+ @moduledoc """
+ Defines a handler that selectively receives events emitted from a remote control node.
+
+ ## Usage
+
+ Define a handler, specifying the events to be handled and implementing `on_event/2`:
+
+ defmodule MyHandler do
+ alias Engine.Api.Messages
+ alias Engine.Dispatch.Handler
+
+ import Messages
+
+ use Handler, [project_compiled()]
+
+ def on_event(project_compiled(), state) do
+ ...do something with the message
+ {:ok, state}
+ end
+ end
+
+ Register the handler with dispatch:
+
+ # The second argument here will be passed to the `init/1` callback
+ Engine.Dispatch.add_handler(MyHandler, init_arg)
+
+ """
+ @type event :: tuple()
+ @type handler_state :: term()
+
+ @callback on_event(event(), handler_state) :: {:ok, handler_state} | {:error, any()}
+ @callback init(term()) :: {:ok, handler_state()}
+ @optional_callbacks init: 1
+
+ defmacro __using__(event_types) do
+ event_types = List.wrap(event_types)
+
+ handler_bodies =
+ if Enum.member?(event_types, :all) do
+ [all_handler()]
+ else
+ handler_bodies(event_types)
+ end
+
+ quote do
+ @behaviour unquote(__MODULE__)
+
+ def init(arg) do
+ {:ok, arg}
+ end
+
+ def on(event, state) do
+ {:ok, event, state}
+ end
+
+ def handle_call(_, state) do
+ {:ok, state}
+ end
+
+ def handle_info(_, state) do
+ {:ok, state}
+ end
+
+ unquote_splicing(handler_bodies)
+
+ # handlers only respond to on or info, not calls.
+ defoverridable init: 1, handle_info: 2, on: 2
+ end
+ end
+
+ def handler_bodies(event_types) do
+ results =
+ Enum.map(event_types, fn {event_name, _, _} ->
+ event_handler(event_name)
+ end)
+
+ results ++ [ignore_handler()]
+ end
+
+ defp event_handler(event_name) do
+ quote do
+ def handle_event(event, state)
+ when is_tuple(event) and elem(event, 0) == unquote(event_name) do
+ on_event(event, state)
+ end
+ end
+ end
+
+ defp all_handler do
+ quote do
+ def handle_event(event, state) do
+ on_event(event, state)
+ end
+ end
+ end
+
+ defp ignore_handler do
+ quote do
+ def handle_event(event, state) do
+ {:ok, state}
+ end
+ end
+ end
+end
diff --git a/apps/engine/lib/engine/engine/dispatch/handlers/indexing.ex b/apps/engine/lib/engine/engine/dispatch/handlers/indexing.ex
new file mode 100644
index 00000000..a6e6b74e
--- /dev/null
+++ b/apps/engine/lib/engine/engine/dispatch/handlers/indexing.ex
@@ -0,0 +1,36 @@
+defmodule Engine.Dispatch.Handlers.Indexing do
+ alias Engine.Api.Messages
+ alias Engine.Commands
+ alias Engine.Dispatch
+ alias Engine.Search
+ alias Forge.Document
+
+ require Logger
+ import Messages
+
+ use Dispatch.Handler, [file_compile_requested(), filesystem_event()]
+
+ def on_event(file_compile_requested(uri: uri), state) do
+ reindex(uri)
+ {:ok, state}
+ end
+
+ def on_event(filesystem_event(uri: uri, event_type: :deleted), state) do
+ delete_path(uri)
+ {:ok, state}
+ end
+
+ def on_event(filesystem_event(), state) do
+ {:ok, state}
+ end
+
+ defp reindex(uri) do
+ Commands.Reindex.uri(uri)
+ end
+
+ def delete_path(uri) do
+ uri
+ |> Document.Path.ensure_path()
+ |> Search.Store.clear()
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/dispatch/pub_sub.ex b/apps/engine/lib/engine/engine/dispatch/pub_sub.ex
similarity index 96%
rename from apps/remote_control/lib/lexical/remote_control/dispatch/pub_sub.ex
rename to apps/engine/lib/engine/engine/dispatch/pub_sub.ex
index 887cc46f..e85b3583 100644
--- a/apps/remote_control/lib/lexical/remote_control/dispatch/pub_sub.ex
+++ b/apps/engine/lib/engine/engine/dispatch/pub_sub.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.Dispatch.PubSub do
+defmodule Engine.Dispatch.PubSub do
@moduledoc """
A pubsub event handler for a gen_event controller.
"""
defmodule State do
- alias Lexical.Project
+ alias Forge.Project
defstruct [:registrations]
@@ -55,7 +55,7 @@ defmodule Lexical.RemoteControl.Dispatch.PubSub do
end
end
- alias Lexical.Project
+ alias Forge.Project
@behaviour :gen_event
diff --git a/apps/remote_control/lib/lexical/remote_control/mix.ex b/apps/engine/lib/engine/engine/mix.ex
similarity index 78%
rename from apps/remote_control/lib/lexical/remote_control/mix.ex
rename to apps/engine/lib/engine/engine/mix.ex
index e64f5230..b8757ed7 100644
--- a/apps/remote_control/lib/lexical/remote_control/mix.ex
+++ b/apps/engine/lib/engine/engine/mix.ex
@@ -1,10 +1,9 @@
-defmodule Lexical.RemoteControl.Mix do
- alias Lexical.Project
- alias Lexical.RemoteControl
+defmodule Engine.Mix do
+ alias Forge.Project
def in_project(fun) do
- if RemoteControl.project_node?() do
- in_project(RemoteControl.get_project(), fun)
+ if Engine.project_node?() do
+ in_project(Engine.get_project(), fun)
else
{:error, :not_project_node}
end
@@ -20,7 +19,7 @@ defmodule Lexical.RemoteControl.Mix do
try do
Mix.ProjectStack.post_config(prune_code_paths: false)
- build_path = RemoteControl.Build.path(project)
+ build_path = Engine.Build.path(project)
project_root = Project.root_path(project)
project
@@ -49,6 +48,6 @@ defmodule Lexical.RemoteControl.Mix do
end
defp with_lock(fun) do
- RemoteControl.with_lock(__MODULE__, fun)
+ Engine.with_lock(__MODULE__, fun)
end
end
diff --git a/apps/remote_control/lib/lexical/remote_control/mix.tasks.deps.safe_compile.ex b/apps/engine/lib/engine/engine/mix.tasks.deps.safe_compile.ex
similarity index 100%
rename from apps/remote_control/lib/lexical/remote_control/mix.tasks.deps.safe_compile.ex
rename to apps/engine/lib/engine/engine/mix.tasks.deps.safe_compile.ex
diff --git a/apps/remote_control/lib/lexical/remote_control/module/loader.ex b/apps/engine/lib/engine/engine/module/loader.ex
similarity index 95%
rename from apps/remote_control/lib/lexical/remote_control/module/loader.ex
rename to apps/engine/lib/engine/engine/module/loader.ex
index 149c3d06..b6fb1d59 100644
--- a/apps/remote_control/lib/lexical/remote_control/module/loader.ex
+++ b/apps/engine/lib/engine/engine/module/loader.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Module.Loader do
+defmodule Engine.Module.Loader do
@moduledoc """
Apparently, Code.ensure_loaded?/1 is pretty slow. I'm guessing because it has to do a
round trip to the code server for each check. This in turn slows down indexing, so the thought
diff --git a/apps/remote_control/lib/lexical/remote_control/module_mappings.ex b/apps/engine/lib/engine/engine/module_mappings.ex
similarity index 92%
rename from apps/remote_control/lib/lexical/remote_control/module_mappings.ex
rename to apps/engine/lib/engine/engine/module_mappings.ex
index bcb8ce71..c2090dee 100644
--- a/apps/remote_control/lib/lexical/remote_control/module_mappings.ex
+++ b/apps/engine/lib/engine/engine/module_mappings.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.ModuleMappings do
+defmodule Engine.ModuleMappings do
defmodule State do
defstruct module_to_file: %{}, file_to_modules: %{}
@@ -39,8 +39,7 @@ defmodule Lexical.RemoteControl.ModuleMappings do
end
end
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
+ alias Engine.Api.Messages
use GenServer
@@ -63,7 +62,7 @@ defmodule Lexical.RemoteControl.ModuleMappings do
@impl GenServer
def init(_) do
- RemoteControl.register_listener(self(), [module_updated()])
+ Engine.register_listener(self(), [module_updated()])
{:ok, State.new()}
end
diff --git a/apps/remote_control/lib/lexical/remote_control/modules.ex b/apps/engine/lib/engine/engine/modules.ex
similarity index 96%
rename from apps/remote_control/lib/lexical/remote_control/modules.ex
rename to apps/engine/lib/engine/engine/modules.ex
index 3b32e871..22b940f1 100644
--- a/apps/remote_control/lib/lexical/remote_control/modules.ex
+++ b/apps/engine/lib/engine/engine/modules.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Modules do
+defmodule Engine.Modules do
@moduledoc """
Utilities for dealing with modules on the remote control node
"""
+ alias Engine.Module.Loader
alias Future.Code.Typespec
- alias Lexical.RemoteControl.Module.Loader
@typedoc "Module documentation record as defined by EEP-48"
@type docs_v1 :: tuple()
@@ -13,7 +13,7 @@ defmodule Lexical.RemoteControl.Modules do
@type definition ::
{name :: atom(), arity :: arity(), formatted :: String.t(), quoted :: Macro.t()}
- @cache_timeout Application.compile_env(:remote_control, :modules_cache_expiry, {10, :second})
+ @cache_timeout Application.compile_env(:engine, :modules_cache_expiry, {10, :second})
@doc """
Ensure the given module is compiled, returning the BEAM object code if successful.
@@ -159,7 +159,7 @@ defmodule Lexical.RemoteControl.Modules do
You can optionally pass a predicate MFA to further select which modules are returned, but
it's important to understand that the predicate can only be a function reference to a function that
- exists on the `remote_control` node. I.e. you CANNOT pass anonymous functions to this module.
+ exists on the `engine` node. I.e. you CANNOT pass anonymous functions to this module.
Each module will be added as the first argument to the given list of args in the predicate,
for example:
diff --git a/apps/engine/lib/engine/engine/plugin.ex b/apps/engine/lib/engine/engine/plugin.ex
new file mode 100644
index 00000000..2f590c99
--- /dev/null
+++ b/apps/engine/lib/engine/engine/plugin.ex
@@ -0,0 +1,48 @@
+defmodule Engine.Plugin do
+ alias Forge.Document
+ alias Forge.Project
+
+ alias Engine.Api.Messages
+ alias Engine.Plugin.Runner
+
+ import Messages
+
+ def diagnose(%Project{} = project, build_number) do
+ on_complete = fn
+ [] ->
+ :ok
+
+ [_ | _] = diagnostics ->
+ message =
+ project_diagnostics(
+ project: project,
+ build_number: build_number,
+ diagnostics: diagnostics
+ )
+
+ Engine.broadcast(message)
+ end
+
+ Runner.diagnose(project, on_complete)
+ end
+
+ def diagnose(%Project{} = project, build_number, %Document{} = document) do
+ on_complete = fn
+ [] ->
+ :ok
+
+ [_ | _] = diagnostics ->
+ message =
+ file_diagnostics(
+ project: project,
+ build_number: build_number,
+ uri: document.uri,
+ diagnostics: diagnostics
+ )
+
+ Engine.broadcast(message)
+ end
+
+ Runner.diagnose(document, on_complete)
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/plugin/discovery.ex b/apps/engine/lib/engine/engine/plugin/discovery.ex
similarity index 82%
rename from apps/remote_control/lib/lexical/remote_control/plugin/discovery.ex
rename to apps/engine/lib/engine/engine/plugin/discovery.ex
index 4633218c..02d3e317 100644
--- a/apps/remote_control/lib/lexical/remote_control/plugin/discovery.ex
+++ b/apps/engine/lib/engine/engine/plugin/discovery.ex
@@ -1,22 +1,22 @@
-defmodule Lexical.RemoteControl.Plugin.Discovery do
+defmodule Engine.Plugin.Discovery do
@moduledoc """
Discovers any plugins in any loaded applications
- This module runs through the loaded applications and checks to see if any of them are lexical plugins.
+ This module runs through the loaded applications and checks to see if any of them are expert plugins.
If a plugin is found, the app is then registered with the plugin system.
If we're running in a namespaced build, we must apply the same namespace to the plugin's modules, or it
- will be looking for structs like `Lexical.Document`, and be passed in structs like `LXRelease.Document`,
+ will be looking for structs like `Forge.Document`, and be passed in structs like `XPRelease.Document`,
and the plugin will crash.
"""
- alias Lexical.RemoteControl.Module.Loader
- alias Lexical.RemoteControl.Plugin.Runner
+ alias Engine.Module.Loader
+ alias Engine.Plugin.Runner
alias Mix.Tasks.Namespace
require Logger
- @namespaced_document_module [:Lexical, :Document]
+ @namespaced_document_module [:Forge, :Document]
|> Module.concat()
|> Namespace.Module.apply()
diff --git a/apps/remote_control/lib/lexical/remote_control/plugin/runner.ex b/apps/engine/lib/engine/engine/plugin/runner.ex
similarity index 85%
rename from apps/remote_control/lib/lexical/remote_control/plugin/runner.ex
rename to apps/engine/lib/engine/engine/plugin/runner.ex
index 23af365d..18ca5033 100644
--- a/apps/remote_control/lib/lexical/remote_control/plugin/runner.ex
+++ b/apps/engine/lib/engine/engine/plugin/runner.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Plugin.Runner do
+defmodule Engine.Plugin.Runner do
@moduledoc false
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl.Module.Loader
- alias Lexical.RemoteControl.Plugin.Runner
+ alias Engine.Module.Loader
+ alias Engine.Plugin.Runner
+ alias Forge.Document
+ alias Forge.Project
require Logger
@@ -18,12 +18,12 @@ defmodule Lexical.RemoteControl.Plugin.Runner do
@doc false
def plugin_module?(module) when is_atom(module) do
- function_exported?(module, :__lexical_plugin__, 0)
+ function_exported?(module, :__expert_plugin__, 0)
end
@doc false
def plugin_app?(app_name) do
- Application.get_env(app_name, lexical_prefixed_atom("plugin"), false)
+ Application.get_env(app_name, expert_prefixed_atom("plugin"), false)
end
@doc false
@@ -111,8 +111,8 @@ defmodule Lexical.RemoteControl.Plugin.Runner do
end
end
- defp lexical_prefixed_atom(suffix) do
- ("lexical_" <> suffix)
+ defp expert_prefixed_atom(suffix) do
+ ("expert_" <> suffix)
|> String.to_atom()
end
end
diff --git a/apps/remote_control/lib/lexical/remote_control/plugin/runner/coordinator.ex b/apps/engine/lib/engine/engine/plugin/runner/coordinator.ex
similarity index 84%
rename from apps/remote_control/lib/lexical/remote_control/plugin/runner/coordinator.ex
rename to apps/engine/lib/engine/engine/plugin/runner/coordinator.ex
index 0b99ba34..6a2258ad 100644
--- a/apps/remote_control/lib/lexical/remote_control/plugin/runner/coordinator.ex
+++ b/apps/engine/lib/engine/engine/plugin/runner/coordinator.ex
@@ -1,8 +1,8 @@
-defmodule Lexical.RemoteControl.Plugin.Runner.Coordinator do
+defmodule Engine.Plugin.Runner.Coordinator do
@moduledoc false
- alias Lexical.Formats
- alias Lexical.RemoteControl.Plugin.Runner.Coordinator.State
+ alias Engine.Plugin.Runner.Coordinator.State
+ alias Forge.Formats
require Logger
use GenServer
diff --git a/apps/engine/lib/engine/engine/plugin/runner/coordinator/state.ex b/apps/engine/lib/engine/engine/plugin/runner/coordinator/state.ex
new file mode 100644
index 00000000..a8289f12
--- /dev/null
+++ b/apps/engine/lib/engine/engine/plugin/runner/coordinator/state.ex
@@ -0,0 +1,102 @@
+defmodule Engine.Plugin.Runner.Coordinator.State do
+ @moduledoc false
+
+ alias Engine.Plugin.Runner
+
+ defstruct tasks: [], failures: %{}
+
+ @max_plugin_errors 10
+
+ require Logger
+
+ def new do
+ %__MODULE__{}
+ end
+
+ def run_all(%__MODULE__{} = state, subject, plugin_type, timeout) do
+ tasks_to_plugin_modules =
+ plugin_type
+ |> Runner.plugins_of_type()
+ |> Enum.map(&Runner.Supervisor.async(&1, subject))
+ |> Map.new()
+
+ await_results(state, tasks_to_plugin_modules, timeout)
+ end
+
+ def failure_count(%__MODULE__{} = state, plugin_module) do
+ Map.get(state.failures, plugin_module, 0)
+ end
+
+ def remove_task(%__MODULE__{} = state, ref) do
+ new_tasks = Enum.reject(state.tasks, &(&1.ref == ref))
+ %__MODULE__{state | tasks: new_tasks}
+ end
+
+ defp await_results(%__MODULE__{} = state, tasks_to_plugin_modules, timeout) do
+ raw_result =
+ tasks_to_plugin_modules
+ |> Map.keys()
+ |> Task.yield_many(timeout)
+
+ {successes, failed} =
+ raw_result
+ |> Enum.reduce({[], []}, fn
+ {_task, {:ok, {:ok, results}}}, {successes, failures} when is_list(results) ->
+ {[results | successes], failures}
+
+ {task, {:ok, {:ok, _not_list}}}, {successes, failures} ->
+ reason = "it did not return a list of results"
+ failure = {:log, task, reason}
+ {successes, [failure | failures]}
+
+ {task, {:ok, reason}}, {successes, failures} ->
+ failure = {:log, task, reason}
+ {successes, [failure | failures]}
+
+ {task, {:exit, reason}}, {successes, failures} ->
+ failure = {:log, task, reason}
+ {successes, [failure | failures]}
+
+ {task, nil}, {successes, failures} ->
+ failure = {:shutdown, task}
+ {successes, [failure | failures]}
+ end)
+
+ new_state =
+ Enum.reduce(failed, state, fn
+ {:log, %Task{} = task, reason}, state ->
+ plugin_module = Map.get(tasks_to_plugin_modules, task)
+ Logger.error("Task #{plugin_module} failed because #{inspect(reason)}")
+ mark_failed(state, plugin_module)
+
+ {:shutdown, %Task{} = task}, state ->
+ plugin_module = Map.get(tasks_to_plugin_modules, task)
+ Logger.error("Task #{plugin_module} did not complete in #{timeout}ms ")
+ Task.shutdown(task, :brutal_kill)
+ mark_failed(state, plugin_module)
+ end)
+
+ results =
+ successes
+ |> Enum.reverse()
+ |> List.flatten()
+
+ {results, new_state}
+ end
+
+ defp mark_failed(%__MODULE__{} = state, plugin_module) do
+ new_failures = Map.update(state.failures, plugin_module, 1, &(&1 + 1))
+ maybe_shutdown(plugin_module, new_failures)
+ %__MODULE__{state | failures: new_failures}
+ end
+
+ defp maybe_shutdown(plugin_module, failure_map) do
+ case Map.get(failure_map, plugin_module, 0) do
+ count when count >= @max_plugin_errors ->
+ Runner.disable(plugin_module)
+
+ _ ->
+ :ok
+ end
+ end
+end
diff --git a/apps/engine/lib/engine/engine/plugin/supervisor.ex b/apps/engine/lib/engine/engine/plugin/supervisor.ex
new file mode 100644
index 00000000..b6fbf6f3
--- /dev/null
+++ b/apps/engine/lib/engine/engine/plugin/supervisor.ex
@@ -0,0 +1,20 @@
+defmodule Engine.Plugin.Runner.Supervisor do
+ @moduledoc false
+
+ def child_spec(_) do
+ %{
+ id: __MODULE__,
+ start: {Task.Supervisor, :start_link, [[name: name()]]}
+ }
+ end
+
+ @spec async(module(), term()) :: {Task.t(), module()}
+ def async(plugin_module, subject) do
+ task = Task.Supervisor.async_nolink(name(), plugin_module, :diagnose, [subject])
+ {task, plugin_module}
+ end
+
+ defp name do
+ Module.concat(__MODULE__, TaskSupervisor)
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/port.ex b/apps/engine/lib/engine/engine/port.ex
similarity index 87%
rename from apps/remote_control/lib/lexical/remote_control/port.ex
rename to apps/engine/lib/engine/engine/port.ex
index 05a210de..c6815a9c 100644
--- a/apps/remote_control/lib/lexical/remote_control/port.ex
+++ b/apps/engine/lib/engine/engine/port.ex
@@ -1,10 +1,9 @@
-defmodule Lexical.RemoteControl.Port do
+defmodule Engine.Port do
@moduledoc """
Utilities for launching ports in the context of a project
"""
- alias Lexical.Project
- alias Lexical.RemoteControl
+ alias Forge.Project
@type open_opt ::
{:env, list()}
@@ -18,11 +17,11 @@ defmodule Lexical.RemoteControl.Port do
Launches elixir in a port.
This function takes the project's context into account and looks for the executable via calling
- `RemoteControl.elixir_executable(project)`. Environment variables are also retrieved with that call.
+ `Engine.elixir_executable(project)`. Environment variables are also retrieved with that call.
"""
@spec open_elixir(Project.t(), open_opts()) :: port()
def open_elixir(%Project{} = project, opts) do
- {:ok, elixir_executable, environment_variables} = RemoteControl.elixir_executable(project)
+ {:ok, elixir_executable, environment_variables} = Engine.elixir_executable(project)
opts =
opts
@@ -64,7 +63,7 @@ defmodule Lexical.RemoteControl.Port do
def path({:unix, _}) do
with :non_existing <- :code.where_is_file(~c"port_wrapper.sh") do
- :remote_control
+ :engine
|> :code.priv_dir()
|> Path.join("port_wrapper.sh")
|> Path.expand()
diff --git a/apps/engine/lib/engine/engine/progress.ex b/apps/engine/lib/engine/engine/progress.ex
new file mode 100644
index 00000000..ab24e5e8
--- /dev/null
+++ b/apps/engine/lib/engine/engine/progress.ex
@@ -0,0 +1,66 @@
+defmodule Engine.Progress do
+ import Engine.Api.Messages
+
+ @type label :: String.t()
+ @type message :: String.t()
+
+ @type delta :: pos_integer()
+ @type on_complete_callback :: (-> any())
+ @type report_progress_callback :: (delta(), message() -> any())
+
+ defmacro __using__(_) do
+ quote do
+ import unquote(__MODULE__), only: [with_progress: 2]
+ end
+ end
+
+ @spec with_progress(label(), (-> any())) :: any()
+ def with_progress(label, func) when is_function(func, 0) do
+ on_complete = begin_progress(label)
+
+ try do
+ func.()
+ after
+ on_complete.()
+ end
+ end
+
+ @spec with_percent_progress(label(), pos_integer(), (report_progress_callback() -> any())) ::
+ any()
+ def with_percent_progress(label, max, func) when is_function(func, 1) do
+ {report_progress, on_complete} = begin_percent(label, max)
+
+ try do
+ func.(report_progress)
+ after
+ on_complete.()
+ end
+ end
+
+ @spec begin_progress(label :: label()) :: on_complete_callback()
+ def begin_progress(label) do
+ Engine.broadcast(project_progress(label: label, stage: :begin))
+
+ fn ->
+ Engine.broadcast(project_progress(label: label, stage: :complete))
+ end
+ end
+
+ @spec begin_percent(label(), pos_integer()) ::
+ {report_progress_callback(), on_complete_callback()}
+ def begin_percent(label, max) do
+ Engine.broadcast(percent_progress(label: label, max: max, stage: :begin))
+
+ report_progress = fn delta, message ->
+ Engine.broadcast(
+ percent_progress(label: label, message: message, delta: delta, stage: :report)
+ )
+ end
+
+ complete = fn ->
+ Engine.broadcast(percent_progress(label: label, stage: :complete))
+ end
+
+ {report_progress, complete}
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/project_node.ex b/apps/engine/lib/engine/engine/project_node.ex
similarity index 94%
rename from apps/remote_control/lib/lexical/remote_control/project_node.ex
rename to apps/engine/lib/engine/engine/project_node.ex
index e79e288f..2d9e6c24 100644
--- a/apps/remote_control/lib/lexical/remote_control/project_node.ex
+++ b/apps/engine/lib/engine/engine/project_node.ex
@@ -1,6 +1,5 @@
-defmodule Lexical.RemoteControl.ProjectNode do
- alias Lexical.Project
- alias Lexical.RemoteControl
+defmodule Engine.ProjectNode do
+ alias Forge.Project
require Logger
defmodule State do
@@ -40,7 +39,7 @@ defmodule Lexical.RemoteControl.ProjectNode do
| path_append_arguments(paths)
]
- port = RemoteControl.Port.open_elixir(state.project, args: args)
+ port = Engine.Port.open_elixir(state.project, args: args)
%{state | port: port, started_by: from}
end
@@ -104,8 +103,8 @@ defmodule Lexical.RemoteControl.ProjectNode do
end
end
- alias Lexical.Document
- alias Lexical.RemoteControl.ProjectNodeSupervisor
+ alias Engine.ProjectNodeSupervisor
+ alias Forge.Document
use GenServer
def start(project, paths) do
@@ -114,7 +113,7 @@ defmodule Lexical.RemoteControl.ProjectNode do
with {:ok, node_pid} <- ProjectNodeSupervisor.start_project_node(project),
:ok <- start_node(project, paths),
- :ok <- :rpc.call(node_name, RemoteControl.Bootstrap, :init, bootstrap_args) do
+ :ok <- :rpc.call(node_name, Engine.Bootstrap, :init, bootstrap_args) do
{:ok, node_pid}
end
end
diff --git a/apps/remote_control/lib/lexical/remote_control/project_node_supervisor.ex b/apps/engine/lib/engine/engine/project_node_supervisor.ex
similarity index 83%
rename from apps/remote_control/lib/lexical/remote_control/project_node_supervisor.ex
rename to apps/engine/lib/engine/engine/project_node_supervisor.ex
index f79cb9ae..cd7d3d13 100644
--- a/apps/remote_control/lib/lexical/remote_control/project_node_supervisor.ex
+++ b/apps/engine/lib/engine/engine/project_node_supervisor.ex
@@ -1,8 +1,9 @@
-defmodule Lexical.RemoteControl.ProjectNodeSupervisor do
- alias Lexical.Project
- alias Lexical.RemoteControl.ProjectNode
+defmodule Engine.ProjectNodeSupervisor do
use DynamicSupervisor
+ alias Engine.ProjectNode
+ alias Forge.Project
+
@dialyzer {:no_return, start_link: 1}
def child_spec(%Project{} = project) do
diff --git a/apps/remote_control/lib/lexical/remote_control/search/fuzzy.ex b/apps/engine/lib/engine/engine/search/fuzzy.ex
similarity index 95%
rename from apps/remote_control/lib/lexical/remote_control/search/fuzzy.ex
rename to apps/engine/lib/engine/engine/search/fuzzy.ex
index 43681828..f6a071d2 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/fuzzy.ex
+++ b/apps/engine/lib/engine/engine/search/fuzzy.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Search.Fuzzy do
+defmodule Engine.Search.Fuzzy do
@moduledoc """
A backend for fuzzy matching
@@ -11,10 +11,9 @@ defmodule Lexical.RemoteControl.Search.Fuzzy do
returned.
"""
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Search.Fuzzy.Scorer
- alias Lexical.RemoteControl.Search.Indexer.Entry
+ alias Engine.Search.Fuzzy.Scorer
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Project
import Record
defstruct subject_to_values: %{},
@@ -289,7 +288,7 @@ defmodule Lexical.RemoteControl.Search.Fuzzy do
end
defp stringify(mapped(type: :module, subject: module_name)) do
- Lexical.Formats.module(module_name)
+ Forge.Formats.module(module_name)
end
defp stringify(mapped(subject: string)) when is_binary(string) do
@@ -307,10 +306,10 @@ defmodule Lexical.RemoteControl.Search.Fuzzy do
defp stringify(atom) when is_atom(atom) do
cond do
function_exported?(atom, :__info__, 1) ->
- Lexical.Formats.module(atom)
+ Forge.Formats.module(atom)
function_exported?(atom, :module_info, 0) ->
- Lexical.Formats.module(atom)
+ Forge.Formats.module(atom)
true ->
inspect(atom)
@@ -340,7 +339,7 @@ defmodule Lexical.RemoteControl.Search.Fuzzy do
deps_roots()
else
{:ok, deps_roots} =
- RemoteControl.Mix.in_project(fn _ ->
+ Engine.Mix.in_project(fn _ ->
deps_roots()
end)
@@ -359,13 +358,13 @@ defmodule Lexical.RemoteControl.Search.Fuzzy do
end
defp deps_roots do
- deps_roots(RemoteControl.get_project())
+ deps_roots(Engine.get_project())
end
defp deps_roots(%Project{mix_project?: true} = project) do
# Note: This function assumes that the deps directories for all
# found projects is `deps`. Projects may override this directory
- # and lexical won't understand this. This was done because loading
+ # and expert won't understand this. This was done because loading
# each sub-project is expensive and changes our global directory.
[Project.root_path(project), "**", "mix.exs"]
diff --git a/apps/remote_control/lib/lexical/remote_control/search/fuzzy/scorer.ex b/apps/engine/lib/engine/engine/search/fuzzy/scorer.ex
similarity index 99%
rename from apps/remote_control/lib/lexical/remote_control/search/fuzzy/scorer.ex
rename to apps/engine/lib/engine/engine/search/fuzzy/scorer.ex
index 03b1903e..f5e31486 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/fuzzy/scorer.ex
+++ b/apps/engine/lib/engine/engine/search/fuzzy/scorer.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Search.Fuzzy.Scorer do
+defmodule Engine.Search.Fuzzy.Scorer do
@moduledoc """
Scores a match based on heuristics.
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer.ex b/apps/engine/lib/engine/engine/search/indexer.ex
similarity index 91%
rename from apps/remote_control/lib/lexical/remote_control/search/indexer.ex
rename to apps/engine/lib/engine/engine/search/indexer.ex
index 56ed87f3..7d5994bc 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer.ex
+++ b/apps/engine/lib/engine/engine/search/indexer.ex
@@ -1,11 +1,10 @@
-defmodule Lexical.RemoteControl.Search.Indexer do
- alias Lexical.Identifier
- alias Lexical.ProcessCache
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Progress
- alias Lexical.RemoteControl.Search.Indexer
- alias Lexical.RemoteControl.Search.Indexer.Entry
+defmodule Engine.Search.Indexer do
+ alias Engine.Progress
+ alias Engine.Search.Indexer
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Identifier
+ alias Forge.ProcessCache
+ alias Forge.Project
require ProcessCache
@@ -83,7 +82,7 @@ defmodule Lexical.RemoteControl.Search.Indexer do
end
end
- # 128 K blocks indexed lexical in 5.3 seconds
+ # 128 K blocks indexed expert in 5.3 seconds
@bytes_per_block 1024 * 128
defp async_chunks(file_paths, processor, timeout \\ :infinity) do
# this function tries to even out the amount of data processed by
@@ -201,14 +200,14 @@ defmodule Lexical.RemoteControl.Search.Indexer do
end
defp deps_dir do
- case RemoteControl.Mix.in_project(&Mix.Project.deps_path/0) do
+ case Engine.Mix.in_project(&Mix.Project.deps_path/0) do
{:ok, path} -> path
_ -> Mix.Project.deps_path()
end
end
defp build_dir do
- case RemoteControl.Mix.in_project(&Mix.Project.build_path/0) do
+ case Engine.Mix.in_project(&Mix.Project.build_path/0) do
{:ok, path} -> path
_ -> Mix.Project.build_path()
end
diff --git a/apps/engine/lib/engine/engine/search/indexer/entry.ex b/apps/engine/lib/engine/engine/search/indexer/entry.ex
new file mode 100644
index 00000000..928c894c
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/entry.ex
@@ -0,0 +1,155 @@
+defmodule Engine.Search.Indexer.Entry do
+ @type function_type :: :public | :private | :delegated | :usage
+ @type protocol_type :: :implementation | :definition
+
+ @type entry_type ::
+ :ex_unit_describe
+ | :ex_unit_test
+ | :module
+ | :module_attribute
+ | :struct
+ | :variable
+ | {:protocol, protocol_type()}
+ | {:function, function_type()}
+
+ @type subject :: String.t()
+ @type entry_subtype :: :reference | :definition
+ @type version :: String.t()
+ @type entry_id :: pos_integer() | nil
+ @type block_id :: pos_integer() | :root
+ @type subject_query :: subject() | :_
+ @type entry_type_query :: entry_type() | :_
+ @type entry_subtype_query :: entry_subtype() | :_
+ @type constraint :: {:type, entry_type_query()} | {:subtype, entry_subtype_query()}
+ @type constraints :: [constraint()]
+
+ defstruct [
+ :application,
+ :id,
+ :block_id,
+ :block_range,
+ :path,
+ :range,
+ :subject,
+ :subtype,
+ :type,
+ :metadata
+ ]
+
+ @type t :: %__MODULE__{
+ application: module(),
+ subject: subject(),
+ block_id: block_id(),
+ block_range: Forge.Document.Range.t() | nil,
+ path: Path.t(),
+ range: Forge.Document.Range.t(),
+ subtype: entry_subtype(),
+ type: entry_type(),
+ metadata: nil | map()
+ }
+ @type datetime_format :: :erl | :unix | :datetime
+ @type date_type :: :calendar.datetime() | integer() | DateTime.t()
+
+ alias Engine.Search.Indexer.Source.Block
+ alias Forge.Identifier
+ alias Forge.StructAccess
+
+ use StructAccess
+
+ defguard is_structure(entry) when entry.type == :metadata and entry.subtype == :block_structure
+ defguard is_block(entry) when entry.id == entry.block_id
+
+ @doc """
+ Creates a new entry by copying the passed-in entry.
+
+ The returned entry will have the same fields set as the one passed in,
+ but a different id.
+ You can also pass in a keyword list of overrides, which will overwrit values in
+ the returned struct.
+ """
+ def copy(%__MODULE__{} = orig, overrides \\ []) when is_list(overrides) do
+ %__MODULE__{orig | id: Identifier.next_global!()}
+ |> struct(overrides)
+ end
+
+ def block_structure(path, structure) do
+ %__MODULE__{
+ path: path,
+ subject: structure,
+ type: :metadata,
+ subtype: :block_structure
+ }
+ end
+
+ def reference(path, %Block{} = block, subject, type, range, application) do
+ new(path, Identifier.next_global!(), block.id, subject, type, :reference, range, application)
+ end
+
+ def definition(path, %Block{} = block, subject, type, range, application) do
+ new(path, Identifier.next_global!(), block.id, subject, type, :definition, range, application)
+ end
+
+ def block_definition(
+ path,
+ %Block{} = block,
+ subject,
+ type,
+ block_range,
+ detail_range,
+ application
+ ) do
+ definition =
+ definition(
+ path,
+ block.id,
+ block.parent_id,
+ subject,
+ type,
+ detail_range,
+ application
+ )
+
+ %__MODULE__{definition | block_range: block_range}
+ end
+
+ defp definition(path, id, block_id, subject, type, range, application) do
+ new(path, id, block_id, subject, type, :definition, range, application)
+ end
+
+ defp new(path, id, block_id, subject, type, subtype, range, application) do
+ %__MODULE__{
+ application: application,
+ block_id: block_id,
+ id: id,
+ path: path,
+ range: range,
+ subject: subject,
+ subtype: subtype,
+ type: type
+ }
+ end
+
+ def block?(%__MODULE__{} = entry) do
+ is_block(entry)
+ end
+
+ @spec updated_at(t()) :: date_type()
+ @spec updated_at(t(), datetime_format) :: date_type()
+ def updated_at(entry, format \\ :erl)
+
+ def updated_at(%__MODULE__{id: id} = entry, format) when is_integer(id) do
+ case format do
+ :erl -> Identifier.to_erl(entry.id)
+ :unix -> Identifier.to_unix(id)
+ :datetime -> Identifier.to_datetime(id)
+ end
+ end
+
+ def updated_at(%__MODULE__{}, _format) do
+ nil
+ end
+
+ def put_metadata(%__MODULE__{} = entry, metadata) do
+ %__MODULE__{entry | metadata: metadata}
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/ecto_schema.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/ecto_schema.ex
similarity index 89%
rename from apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/ecto_schema.ex
rename to apps/engine/lib/engine/engine/search/indexer/extractors/ecto_schema.ex
index fd7ff34b..40102425 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/ecto_schema.ex
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/ecto_schema.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.EctoSchema do
- alias Lexical.Ast
- alias Lexical.Document.Position
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Metadata
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
+defmodule Engine.Search.Indexer.Extractors.EctoSchema do
+ alias Engine.Analyzer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Metadata
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Forge.Ast
+ alias Forge.Document.Position
def extract(
{:schema, meta, [{:__block__, _, [_source]} | _]} = schema_block,
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/ex_unit.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/ex_unit.ex
similarity index 90%
rename from apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/ex_unit.ex
rename to apps/engine/lib/engine/engine/search/indexer/extractors/ex_unit.ex
index d36f73ab..4e1d4e29 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/ex_unit.ex
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/ex_unit.ex
@@ -1,13 +1,13 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.ExUnit do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Formats
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Metadata
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
+defmodule Engine.Search.Indexer.Extractors.ExUnit do
+ alias Engine.Analyzer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Metadata
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Formats
require Logger
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/function_definition.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/function_definition.ex
similarity index 91%
rename from apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/function_definition.ex
rename to apps/engine/lib/engine/engine/search/indexer/extractors/function_definition.ex
index 39631474..cafb61cc 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/function_definition.ex
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/function_definition.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionDefinition do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
- alias Lexical.RemoteControl.Search.Subject
+defmodule Engine.Search.Indexer.Extractors.FunctionDefinition do
+ alias Engine.Analyzer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Engine.Search.Subject
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
@function_definitions [:def, :defp]
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/function_reference.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/function_reference.ex
similarity index 90%
rename from apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/function_reference.ex
rename to apps/engine/lib/engine/engine/search/indexer/extractors/function_reference.ex
index aee640b2..801b3362 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/function_reference.ex
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/function_reference.ex
@@ -1,13 +1,12 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionReference do
- alias Lexical.Ast
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Extractors.FunctionDefinition
- alias Lexical.RemoteControl.Search.Indexer.Metadata
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
- alias Lexical.RemoteControl.Search.Subject
+defmodule Engine.Search.Indexer.Extractors.FunctionReference do
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Extractors.FunctionDefinition
+ alias Engine.Search.Indexer.Metadata
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Engine.Search.Subject
+ alias Forge.Ast
+ alias Forge.Document.Position
+ alias Forge.Document.Range
require Logger
@@ -64,7 +63,7 @@ defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionReference do
position = Reducer.position(reducer)
{module, _, _} =
- RemoteControl.Analyzer.resolve_local_call(reducer.analysis, position, fn_name, arity)
+ Engine.Analyzer.resolve_local_call(reducer.analysis, position, fn_name, arity)
entry = entry(reducer, end_metadata, arity_meta, module, fn_name, arity)
{:ok, entry, nil}
@@ -110,7 +109,7 @@ defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionReference do
Entry.reference(
analysis.document.path,
Reducer.current_block(reducer),
- Lexical.Formats.mfa(module, function_name, arity),
+ Forge.Formats.mfa(module, function_name, arity),
{:function, :usage},
Ast.Range.get(ast, analysis.document),
Application.get_application(module)
@@ -133,7 +132,7 @@ defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionReference do
position = Reducer.position(reducer)
{module, _, _} =
- RemoteControl.Analyzer.resolve_local_call(reducer.analysis, position, fn_name, arity)
+ Engine.Analyzer.resolve_local_call(reducer.analysis, position, fn_name, arity)
entry = entry(reducer, meta, meta, [module], fn_name, args)
@@ -159,7 +158,7 @@ defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionReference do
range =
get_reference_range(reducer.analysis.document, start_metadata, end_metadata, function_name)
- case RemoteControl.Analyzer.expand_alias(module, reducer.analysis, range.start) do
+ case Engine.Analyzer.expand_alias(module, reducer.analysis, range.start) do
{:ok, module} ->
mfa = Subject.mfa(module, function_name, arity)
diff --git a/apps/engine/lib/engine/engine/search/indexer/extractors/module.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/module.ex
new file mode 100644
index 00000000..4883a059
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/module.ex
@@ -0,0 +1,343 @@
+defmodule Engine.Search.Indexer.Extractors.Module do
+ @moduledoc """
+ Extracts module references and definitions from AST
+ """
+
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Metadata
+ alias Engine.Search.Indexer.Source.Block
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Engine.Search.Subject
+ alias Forge.Ast
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.ProcessCache
+
+ require Logger
+
+ @definition_mappings %{
+ defmodule: :module,
+ defprotocol: {:protocol, :definition}
+ }
+ @module_definitions Map.keys(@definition_mappings)
+
+ # extract a module definition
+ def extract(
+ {definition, defmodule_meta,
+ [{:__aliases__, module_name_meta, module_name}, module_block]} = defmodule_ast,
+ %Reducer{} = reducer
+ )
+ when definition in @module_definitions do
+ %Block{} = block = Reducer.current_block(reducer)
+
+ case resolve_alias(reducer, module_name) do
+ {:ok, aliased_module} ->
+ module_position = Metadata.position(module_name_meta)
+ detail_range = to_range(reducer, module_name, module_position)
+
+ entry =
+ Entry.block_definition(
+ reducer.analysis.document.path,
+ block,
+ Subject.module(aliased_module),
+ @definition_mappings[definition],
+ block_range(reducer.analysis.document, defmodule_ast),
+ detail_range,
+ Application.get_application(aliased_module)
+ )
+
+ module_name_meta = Reducer.skip(module_name_meta)
+
+ elem =
+ {:defmodule, defmodule_meta,
+ [{:__aliases__, module_name_meta, module_name}, module_block]}
+
+ {:ok, entry, elem}
+
+ _ ->
+ :ignored
+ end
+ end
+
+ # defimpl MyProtocol, for: MyStruct do ...
+ def extract(
+ {:defimpl, _, [{:__aliases__, _, module_name}, [for_block], _impl_body]} = defimpl_ast,
+ %Reducer{} = reducer
+ ) do
+ %Block{} = block = Reducer.current_block(reducer)
+
+ with {:ok, protocol_module} <- resolve_alias(reducer, module_name),
+ {:ok, for_target} <- resolve_for_block(reducer, for_block) do
+ detail_range = defimpl_range(reducer, defimpl_ast)
+ implemented_module = Module.concat(protocol_module, for_target)
+
+ implementation_entry =
+ Entry.block_definition(
+ reducer.analysis.document.path,
+ block,
+ Subject.module(protocol_module),
+ {:protocol, :implementation},
+ block_range(reducer.analysis.document, defimpl_ast),
+ detail_range,
+ Application.get_application(protocol_module)
+ )
+
+ module_entry =
+ Entry.copy(implementation_entry,
+ subject: Subject.module(implemented_module),
+ type: :module
+ )
+
+ {:ok, [implementation_entry, module_entry]}
+ else
+ _ ->
+ :ignored
+ end
+ end
+
+ # This matches an elixir module reference
+ def extract({:__aliases__, metadata, maybe_module}, %Reducer{} = reducer)
+ when is_list(maybe_module) do
+ case module(reducer, maybe_module) do
+ {:ok, module} ->
+ start = Metadata.position(metadata)
+ range = to_range(reducer, maybe_module, start)
+ %Block{} = current_block = Reducer.current_block(reducer)
+
+ entry =
+ Entry.reference(
+ reducer.analysis.document.path,
+ current_block,
+ Subject.module(module),
+ :module,
+ range,
+ Application.get_application(module)
+ )
+
+ {:ok, entry, nil}
+
+ _ ->
+ :ignored
+ end
+ end
+
+ @module_length String.length("__MODULE__")
+ # This matches __MODULE__ references
+ def extract({:__MODULE__, metadata, _} = ast, %Reducer{} = reducer) do
+ line = Sourceror.get_line(ast)
+ pos = Position.new(reducer.analysis.document, line - 1, 1)
+
+ case Engine.Analyzer.current_module(reducer.analysis, pos) do
+ {:ok, current_module} ->
+ {start_line, start_col} = Metadata.position(metadata)
+ start_pos = Position.new(reducer.analysis.document, start_line, start_col)
+
+ end_pos =
+ Position.new(
+ reducer.analysis.document,
+ start_line,
+ start_col + @module_length
+ )
+
+ range = Range.new(start_pos, end_pos)
+ %Block{} = current_block = Reducer.current_block(reducer)
+
+ entry =
+ Entry.reference(
+ reducer.analysis.document.path,
+ current_block,
+ Subject.module(current_module),
+ :module,
+ range,
+ Application.get_application(current_module)
+ )
+
+ {:ok, entry}
+
+ _ ->
+ :ignored
+ end
+ end
+
+ # This matches an erlang module, which is just an atom
+ def extract({:__block__, metadata, [atom_literal]}, %Reducer{} = reducer)
+ when is_atom(atom_literal) do
+ case module(reducer, atom_literal) do
+ {:ok, module} ->
+ start = Metadata.position(metadata)
+ %Block{} = current_block = Reducer.current_block(reducer)
+ range = to_range(reducer, module, start)
+
+ entry =
+ Entry.reference(
+ reducer.analysis.document.path,
+ current_block,
+ Subject.module(module),
+ :module,
+ range,
+ Application.get_application(module)
+ )
+
+ {:ok, entry}
+
+ :error ->
+ :ignored
+ end
+ end
+
+ # Function capture with arity: &OtherModule.foo/3
+ def extract(
+ {:&, _,
+ [
+ {:/, _,
+ [
+ {{:., _, [{:__aliases__, start_metadata, maybe_module}, _function_name]}, _, []},
+ _
+ ]}
+ ]},
+ %Reducer{} = reducer
+ ) do
+ case module(reducer, maybe_module) do
+ {:ok, module} ->
+ start = Metadata.position(start_metadata)
+ range = to_range(reducer, maybe_module, start)
+ %Block{} = current_block = Reducer.current_block(reducer)
+
+ entry =
+ Entry.reference(
+ reducer.analysis.document.path,
+ current_block,
+ Subject.module(module),
+ :module,
+ range,
+ Application.get_application(module)
+ )
+
+ {:ok, entry}
+
+ _ ->
+ :ignored
+ end
+ end
+
+ def extract(_, _) do
+ :ignored
+ end
+
+ defp defimpl_range(%Reducer{} = reducer, {_, protocol_meta, _} = protocol_ast) do
+ start = Sourceror.get_start_position(protocol_ast)
+ {finish_line, finish_column} = Metadata.position(protocol_meta, :do)
+ # add two to include the do
+ finish_column = finish_column + 2
+ document = reducer.analysis.document
+
+ Range.new(
+ Position.new(document, start[:line], start[:column]),
+ Position.new(document, finish_line, finish_column)
+ )
+ end
+
+ defp resolve_for_block(
+ %Reducer{} = reducer,
+ {{:__block__, _, [:for]}, {:__aliases__, _, for_target}}
+ ) do
+ resolve_alias(reducer, for_target)
+ end
+
+ defp resolve_for_block(_, _), do: :error
+
+ defp resolve_alias(%Reducer{} = reducer, unresolved_alias) do
+ position = Reducer.position(reducer)
+
+ Engine.Analyzer.expand_alias(unresolved_alias, reducer.analysis, position)
+ end
+
+ defp module(%Reducer{} = reducer, maybe_module) when is_list(maybe_module) do
+ with true <- Enum.all?(maybe_module, &module_part?/1),
+ {:ok, resolved} <- resolve_alias(reducer, maybe_module) do
+ {:ok, resolved}
+ else
+ _ ->
+ human_location = Reducer.human_location(reducer)
+
+ Logger.warning(
+ "Could not expand module #{inspect(maybe_module)}. Please report this (at #{human_location})"
+ )
+
+ :error
+ end
+ end
+
+ defp module(%Reducer{}, maybe_erlang_module) when is_atom(maybe_erlang_module) do
+ if available_module?(maybe_erlang_module) do
+ {:ok, maybe_erlang_module}
+ else
+ :error
+ end
+ end
+
+ defp module(_, _), do: :error
+
+ @protocol_module_attribue_names [:protocol, :for]
+
+ @starts_with_capital ~r/[A-Z]+/
+ defp module_part?(part) when is_atom(part) do
+ Regex.match?(@starts_with_capital, Atom.to_string(part))
+ end
+
+ defp module_part?({:@, _, [{type, _, _} | _]}) when type in @protocol_module_attribue_names,
+ do: true
+
+ defp module_part?({:__MODULE__, _, context}) when is_atom(context), do: true
+
+ defp module_part?(_), do: false
+
+ defp available_module?(potential_module) do
+ MapSet.member?(all_modules(), potential_module)
+ end
+
+ defp all_modules do
+ ProcessCache.trans(:all_modules, fn ->
+ MapSet.new(:code.all_available(), fn {module_charlist, _, _} ->
+ List.to_atom(module_charlist)
+ end)
+ end)
+ end
+
+ # handles @protocol and @for in defimpl blocks
+ defp to_range(%Reducer{} = reducer, [{:@, _, [{type, _, _} | _]} = attribute | segments], _)
+ when type in @protocol_module_attribue_names do
+ range = Sourceror.get_range(attribute)
+
+ document = reducer.analysis.document
+ module_length = segments |> Ast.Module.name() |> String.length()
+ # add one because we're off by the @ sign
+ end_column = range.end[:column] + module_length + 1
+
+ Range.new(
+ Position.new(document, range.start[:line], range.start[:column]),
+ Position.new(document, range.end[:line], end_column)
+ )
+ end
+
+ defp to_range(%Reducer{} = reducer, module_name, {line, column}) do
+ document = reducer.analysis.document
+
+ module_length =
+ module_name
+ |> Ast.Module.name()
+ |> String.length()
+
+ Range.new(
+ Position.new(document, line, column),
+ Position.new(document, line, column + module_length)
+ )
+ end
+
+ defp block_range(document, ast) do
+ case Ast.Range.fetch(ast, document) do
+ {:ok, range} -> range
+ _ -> nil
+ end
+ end
+end
diff --git a/apps/engine/lib/engine/engine/search/indexer/extractors/module_attribute.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/module_attribute.ex
new file mode 100644
index 00000000..af6ab2f9
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/module_attribute.ex
@@ -0,0 +1,107 @@
+defmodule Engine.Search.Indexer.Extractors.ModuleAttribute do
+ @moduledoc """
+ Extracts module attribute definitions and references from AST
+ """
+
+ alias Engine.Analyzer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Engine.Search.Subject
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+
+ require Logger
+
+ # Finds module attribute usages
+ def extract({:@, _, [{attr_name, _, nil}]}, %Reducer{} = reducer) do
+ block = Reducer.current_block(reducer)
+
+ case Analyzer.current_module(reducer.analysis, Reducer.position(reducer)) do
+ {:ok, current_module} ->
+ reference =
+ Entry.reference(
+ reducer.analysis.document.path,
+ block,
+ Subject.module_attribute(current_module, attr_name),
+ :module_attribute,
+ reference_range(reducer, attr_name),
+ Application.get_application(current_module)
+ )
+
+ {:ok, reference}
+
+ :error ->
+ :ignored
+ end
+ end
+
+ # an attribute being typed above an already existing attribute will have the name `@`, which we ignore
+ # example:
+ # @|
+ # @callback foo() :: :ok
+ def extract({:@, _, [{:@, _, _attr_value}]}, %Reducer{}) do
+ :ignored
+ end
+
+ # Finds module attribute definitions @foo 3
+ def extract({:@, _, [{attr_name, _, _attr_value}]} = attr, %Reducer{} = reducer) do
+ block = Reducer.current_block(reducer)
+
+ case Analyzer.current_module(reducer.analysis, Reducer.position(reducer)) do
+ {:ok, current_module} ->
+ definition =
+ Entry.definition(
+ reducer.analysis.document.path,
+ block,
+ Subject.module_attribute(current_module, attr_name),
+ :module_attribute,
+ definition_range(reducer, attr),
+ Application.get_application(current_module)
+ )
+
+ {:ok, definition}
+
+ _ ->
+ :ignored
+ end
+ end
+
+ def extract(_, _) do
+ :ignored
+ end
+
+ defp reference_range(%Reducer{} = reducer, attr_name) do
+ document = reducer.analysis.document
+
+ name_length =
+ attr_name
+ |> Atom.to_string()
+ |> String.length()
+
+ {start_line, start_column} = reducer.position
+
+ # add 1 to include the @ character
+ end_column = start_column + name_length + 1
+
+ Range.new(
+ Position.new(document, start_line, start_column),
+ Position.new(document, start_line, end_column)
+ )
+ end
+
+ defp definition_range(%Reducer{} = reducer, attr_ast) do
+ document = reducer.analysis.document
+
+ [line: start_line, column: start_column] = Sourceror.get_start_position(attr_ast)
+
+ end_line = Sourceror.get_end_line(attr_ast)
+ {:ok, line_text} = Forge.Document.fetch_text_at(document, end_line)
+ # add one because lsp positions are one-based
+ end_column = String.length(line_text) + 1
+
+ Range.new(
+ Position.new(document, start_line, start_column),
+ Position.new(document, end_line, end_column)
+ )
+ end
+end
diff --git a/apps/engine/lib/engine/engine/search/indexer/extractors/struct_definition.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/struct_definition.ex
new file mode 100644
index 00000000..c9b2fec0
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/struct_definition.ex
@@ -0,0 +1,35 @@
+defmodule Engine.Search.Indexer.Extractors.StructDefinition do
+ alias Engine.Analyzer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Forge.Ast
+
+ def extract({:defstruct, _, [_fields]} = definition, %Reducer{} = reducer) do
+ document = reducer.analysis.document
+ block = Reducer.current_block(reducer)
+
+ case Analyzer.current_module(reducer.analysis, Reducer.position(reducer)) do
+ {:ok, current_module} ->
+ range = Ast.Range.fetch!(definition, document)
+
+ entry =
+ Entry.definition(
+ document.path,
+ block,
+ current_module,
+ :struct,
+ range,
+ Application.get_application(current_module)
+ )
+
+ {:ok, entry}
+
+ _ ->
+ :ignored
+ end
+ end
+
+ def extract(_, _) do
+ :ignored
+ end
+end
diff --git a/apps/engine/lib/engine/engine/search/indexer/extractors/struct_reference.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/struct_reference.ex
new file mode 100644
index 00000000..5621cd6c
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/struct_reference.ex
@@ -0,0 +1,93 @@
+defmodule Engine.Search.Indexer.Extractors.StructReference do
+ alias Engine.Analyzer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Engine.Search.Subject
+ alias Forge.Ast
+
+ require Logger
+
+ @struct_fn_names [:struct, :struct!]
+
+ # Handles usages via an alias, e.g. x = %MyStruct{...} or %__MODULE__{...}
+ def extract(
+ {:%, _, [struct_alias, {:%{}, _, _struct_args}]} = reference,
+ %Reducer{} = reducer
+ ) do
+ case expand_alias(struct_alias, reducer) do
+ {:ok, struct_module} ->
+ {:ok, entry(reducer, struct_module, reference)}
+
+ _ ->
+ :ignored
+ end
+ end
+
+ # Call to Kernel.struct with a fully qualified module e.g. Kernel.struct(MyStruct, ...)
+ def extract(
+ {{:., _, [kernel_alias, struct_fn_name]}, _, [struct_alias | _]} = reference,
+ %Reducer{} = reducer
+ )
+ when struct_fn_name in @struct_fn_names do
+ with {:ok, Kernel} <- expand_alias(kernel_alias, reducer),
+ {:ok, struct_module} <- expand_alias(struct_alias, reducer) do
+ {:ok, entry(reducer, struct_module, reference)}
+ else
+ _ ->
+ :ignored
+ end
+ end
+
+ # handles calls to Kernel.struct e.g. struct(MyModule) or struct(MyModule, foo: 3)
+ def extract({struct_fn_name, _, [struct_alias | _] = args} = reference, %Reducer{} = reducer)
+ when struct_fn_name in @struct_fn_names do
+ reducer_position = Reducer.position(reducer)
+ imports = Analyzer.imports_at(reducer.analysis, reducer_position)
+ arity = length(args)
+
+ with true <- Enum.member?(imports, {Kernel, struct_fn_name, arity}),
+ {:ok, struct_module} <- expand_alias(struct_alias, reducer) do
+ {:ok, entry(reducer, struct_module, reference)}
+ else
+ _ ->
+ :ignored
+ end
+ end
+
+ def extract(_, _) do
+ :ignored
+ end
+
+ defp entry(%Reducer{} = reducer, struct_module, reference) do
+ document = reducer.analysis.document
+ block = Reducer.current_block(reducer)
+ subject = Subject.module(struct_module)
+
+ Entry.reference(
+ document.path,
+ block,
+ subject,
+ :struct,
+ Ast.Range.fetch!(reference, document),
+ Application.get_application(struct_module)
+ )
+ end
+
+ defp expand_alias({:__aliases__, _, struct_alias}, %Reducer{} = reducer) do
+ Analyzer.expand_alias(struct_alias, reducer.analysis, Reducer.position(reducer))
+ end
+
+ defp expand_alias({:__MODULE__, _, _}, %Reducer{} = reducer) do
+ Analyzer.current_module(reducer.analysis, Reducer.position(reducer))
+ end
+
+ defp expand_alias(alias, %Reducer{} = reducer) do
+ {line, column} = reducer.position
+
+ Logger.error(
+ "Could not expand alias: #{inspect(alias)} at #{reducer.analysis.document.path} #{line}:#{column}"
+ )
+
+ :error
+ end
+end
diff --git a/apps/engine/lib/engine/engine/search/indexer/extractors/variable.ex b/apps/engine/lib/engine/engine/search/indexer/extractors/variable.ex
new file mode 100644
index 00000000..41c32fe4
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/extractors/variable.ex
@@ -0,0 +1,249 @@
+defmodule Engine.Search.Indexer.Extractors.Variable do
+ alias Engine.Analyzer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Forge.Ast
+
+ @defs [:def, :defmacro, :defp, :defmacrop]
+
+ def extract(
+ {def, _, [{:when, _, [{_fn_name, _, params} | when_args]}, body]},
+ %Reducer{} = reducer
+ )
+ when def in @defs do
+ entries = extract_definitions(params, reducer) ++ extract_references(when_args, reducer)
+ {:ok, entries, body}
+ end
+
+ def extract({def, _, [{_fn_name, _, params}, body]}, %Reducer{} = reducer)
+ when def in @defs do
+ entries = extract_definitions(params, reducer)
+
+ {:ok, entries, body}
+ end
+
+ # Stab operator x -> body
+ def extract({:->, _, [params, body]}, %Reducer{} = reducer) do
+ entries = extract_definitions(params, reducer) ++ extract_in_definitions(params, reducer)
+
+ {:ok, entries, List.wrap(body)}
+ end
+
+ # with match operator. with {:ok, var} <- something()
+ def extract({:<-, _, [left, right]}, %Reducer{} = reducer) do
+ entries = extract_definitions(left, reducer)
+
+ {:ok, entries, List.wrap(right)}
+ end
+
+ # Match operator left = right
+ def extract({:=, _, [left, right]}, %Reducer{} = reducer) do
+ definitions = extract_definitions(left, reducer)
+
+ {:ok, definitions, List.wrap(right)}
+ end
+
+ # String interpolations "#{foo}"
+ def extract(
+ {:<<>>, _, [{:"::", _, [{{:., _, [Kernel, :to_string]}, _, body}, {:binary, _, _}]}]},
+ %Reducer{}
+ ) do
+ {:ok, [], body}
+ end
+
+ # Test declarations
+ def extract(
+ {:test, _metadata,
+ [
+ {:__block__, [delimiter: "\"", line: 3, column: 8], ["my test"]},
+ args,
+ body
+ ]},
+ %Reducer{} = reducer
+ ) do
+ entries = extract_definitions(args, reducer)
+ {:ok, entries, body}
+ end
+
+ def extract({:binary, _, _}, %Reducer{}) do
+ :ignored
+ end
+
+ def extract({:@, _, _}, %Reducer{}) do
+ {:ok, nil, nil}
+ end
+
+ # Generic variable reference
+ def extract({var_name, _, _} = ast, %Reducer{} = reducer) when is_atom(var_name) do
+ case extract_reference(ast, reducer, get_current_app(reducer)) do
+ %Entry{} = entry -> {:ok, entry}
+ _ -> :ignored
+ end
+ end
+
+ # Pin operator ^pinned_variable
+ def extract({:^, _, [reference]}, %Reducer{} = reducer) do
+ reference = extract_reference(reference, reducer, get_current_app(reducer))
+
+ {:ok, reference, nil}
+ end
+
+ def extract(_ast, _reducer) do
+ :ignored
+ end
+
+ defp extract_definitions(ast, reducer) do
+ current_app = get_current_app(reducer)
+
+ {_ast, entries} =
+ Macro.prewalk(ast, [], fn ast, acc ->
+ case extract_definition(ast, reducer, current_app) do
+ %Entry{} = entry ->
+ {ast, [entry | acc]}
+
+ {%Entry{} = entry, ast} ->
+ {ast, [entry | acc]}
+
+ {entries, ast} when is_list(entries) ->
+ {ast, entries ++ acc}
+
+ {_, ast} ->
+ {ast, acc}
+
+ _ ->
+ {ast, acc}
+ end
+ end)
+
+ Enum.reverse(entries)
+ end
+
+ # the pin operator is always on the left side of a pattern match, but it's
+ # not defining a variable, just referencing one.
+ defp extract_definition({:^, _, [reference]}, %Reducer{} = reducer, current_app) do
+ reference = extract_reference(reference, reducer, current_app)
+
+ {reference, nil}
+ end
+
+ # unquote(expression)
+ defp extract_definition({:unquote, _, [expr]}, %Reducer{} = reducer, current_app) do
+ reference = extract_reference(expr, reducer, current_app)
+ {reference, nil}
+ end
+
+ defp extract_definition({:@, _, _}, %Reducer{}, _current_app) do
+ {nil, []}
+ end
+
+ # when clauses actually contain parameters and references
+ defp extract_definition({:when, _, when_args}, %Reducer{} = reducer, _current_app) do
+ {definitions, references} =
+ Enum.split_with(when_args, fn {_, _, context} -> is_atom(context) end)
+
+ definitions = extract_definitions(definitions, reducer)
+ references = extract_references(references, reducer)
+
+ {Enum.reverse(definitions ++ references), nil}
+ end
+
+ # This is an effect of string interpolation
+ defp extract_definition({:binary, _metadata, nil}, _reducer, _current_app) do
+ nil
+ end
+
+ defp extract_definition({var_name, _metadata, nil} = ast, reducer, current_app) do
+ if used_variable?(var_name) do
+ document = reducer.analysis.document
+ block = Reducer.current_block(reducer)
+
+ Entry.definition(
+ document.path,
+ block,
+ var_name,
+ :variable,
+ Ast.Range.fetch!(ast, document),
+ current_app
+ )
+ end
+ end
+
+ defp extract_definition(_, _reducer, _current_app), do: nil
+
+ defp extract_references(ast, reducer) do
+ current_app = get_current_app(reducer)
+
+ {_ast, entries} =
+ Macro.prewalk(ast, [], fn ast, acc ->
+ case extract_reference(ast, reducer, current_app) do
+ %Entry{} = entry ->
+ {ast, [entry | acc]}
+
+ _ ->
+ {ast, acc}
+ end
+ end)
+
+ Enum.reverse(entries)
+ end
+
+ defp extract_reference({var_name, _metadata, nil} = ast, reducer, current_app) do
+ if used_variable?(var_name) do
+ document = reducer.analysis.document
+ block = Reducer.current_block(reducer)
+
+ Entry.reference(
+ document.path,
+ block,
+ var_name,
+ :variable,
+ Ast.Range.fetch!(ast, document),
+ current_app
+ )
+ end
+ end
+
+ defp extract_reference(_, _, _) do
+ nil
+ end
+
+ # extracts definitions like e in SomeException ->
+ defp extract_in_definitions(ast, %Reducer{} = reducer) do
+ current_app = get_current_app(reducer)
+
+ {_ast, entries} =
+ Macro.prewalk(ast, [], fn ast, acc ->
+ case extract_in_definition(ast, reducer, current_app) do
+ %Entry{} = entry ->
+ {ast, [entry | acc]}
+
+ _ ->
+ {ast, acc}
+ end
+ end)
+
+ Enum.reverse(entries)
+ end
+
+ defp extract_in_definition(
+ [[{:in, _, [definition, _right]}], _body],
+ %Reducer{} = reducer,
+ current_app
+ ) do
+ extract_definition(definition, reducer, current_app)
+ end
+
+ defp extract_in_definition(_ast, %Reducer{}, _current_app), do: nil
+
+ defp get_current_app(%Reducer{} = reducer) do
+ with {:ok, module} <- Analyzer.current_module(reducer.analysis, Reducer.position(reducer)) do
+ Application.get_application(module)
+ end
+ end
+
+ defp used_variable?(variable_name) do
+ not (variable_name
+ |> Atom.to_string()
+ |> String.starts_with?("_"))
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/metadata.ex b/apps/engine/lib/engine/engine/search/indexer/metadata.ex
similarity index 96%
rename from apps/remote_control/lib/lexical/remote_control/search/indexer/metadata.ex
rename to apps/engine/lib/engine/engine/search/indexer/metadata.ex
index e8453219..69dcc181 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/metadata.ex
+++ b/apps/engine/lib/engine/engine/search/indexer/metadata.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Metadata do
+defmodule Engine.Search.Indexer.Metadata do
@moduledoc """
Utilities for extracting location information from AST metadata nodes.
"""
diff --git a/apps/engine/lib/engine/engine/search/indexer/module.ex b/apps/engine/lib/engine/engine/search/indexer/module.ex
new file mode 100644
index 00000000..676728cf
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/module.ex
@@ -0,0 +1,27 @@
+defmodule Engine.Search.Indexer.Module do
+ alias Engine.Search.Indexer
+
+ def index(module) do
+ with true <- indexable?(module),
+ {:ok, path, source} <- source_file_path(module) do
+ Indexer.Source.index(path, source)
+ else
+ _ ->
+ nil
+ end
+ end
+
+ defp source_file_path(module) do
+ with {:ok, file_path} <- Keyword.fetch(module.__info__(:compile), :source),
+ {:ok, contents} <- File.read(file_path) do
+ {:ok, file_path, contents}
+ end
+ end
+
+ defp indexable?(Kernel.SpecialForms), do: false
+
+ defp indexable?(module) do
+ module_string = to_string(module)
+ String.starts_with?(module_string, "Elixir.")
+ end
+end
diff --git a/apps/engine/lib/engine/engine/search/indexer/quoted.ex b/apps/engine/lib/engine/engine/search/indexer/quoted.ex
new file mode 100644
index 00000000..b4679dbf
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/quoted.ex
@@ -0,0 +1,29 @@
+defmodule Engine.Search.Indexer.Quoted do
+ alias Engine.Search.Indexer.Source.Reducer
+ alias Forge.Ast.Analysis
+ alias Forge.ProcessCache
+
+ require ProcessCache
+
+ def index_with_cleanup(%Analysis{} = analysis) do
+ ProcessCache.with_cleanup do
+ index(analysis)
+ end
+ end
+
+ def index(analysis, extractors \\ nil)
+
+ def index(%Analysis{valid?: true} = analysis, extractors) do
+ {_, reducer} =
+ Macro.prewalk(analysis.ast, Reducer.new(analysis, extractors), fn elem, reducer ->
+ {reducer, elem} = Reducer.reduce(reducer, elem)
+ {elem, reducer}
+ end)
+
+ {:ok, Reducer.entries(reducer)}
+ end
+
+ def index(%Analysis{valid?: false}, _extractors) do
+ {:ok, []}
+ end
+end
diff --git a/apps/engine/lib/engine/engine/search/indexer/source.ex b/apps/engine/lib/engine/engine/search/indexer/source.ex
new file mode 100644
index 00000000..6abaf645
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/indexer/source.ex
@@ -0,0 +1,19 @@
+defmodule Engine.Search.Indexer.Source do
+ alias Engine.Search.Indexer
+ alias Forge.Ast
+ alias Forge.Document
+
+ require Logger
+
+ def index(path, source, extractors \\ nil) do
+ path
+ |> Document.new(source, 1)
+ |> index_document(extractors)
+ end
+
+ def index_document(%Document{} = document, extractors \\ nil) do
+ document
+ |> Ast.analyze()
+ |> Indexer.Quoted.index(extractors)
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/source/block.ex b/apps/engine/lib/engine/engine/search/indexer/source/block.ex
similarity index 77%
rename from apps/remote_control/lib/lexical/remote_control/search/indexer/source/block.ex
rename to apps/engine/lib/engine/engine/search/indexer/source/block.ex
index 7cc5cd56..b91f4a2d 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/source/block.ex
+++ b/apps/engine/lib/engine/engine/search/indexer/source/block.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Source.Block do
+defmodule Engine.Search.Indexer.Source.Block do
@moduledoc """
A struct that represents a block of source code
"""
defstruct [:starts_at, :ends_at, :id, :parent_id]
- alias Lexical.Identifier
+ alias Forge.Identifier
def root do
%__MODULE__{id: :root}
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/source/reducer.ex b/apps/engine/lib/engine/engine/search/indexer/source/reducer.ex
similarity index 93%
rename from apps/remote_control/lib/lexical/remote_control/search/indexer/source/reducer.ex
rename to apps/engine/lib/engine/engine/search/indexer/source/reducer.ex
index 9880f030..6c4580a8 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/source/reducer.ex
+++ b/apps/engine/lib/engine/engine/search/indexer/source/reducer.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Source.Reducer do
+defmodule Engine.Search.Indexer.Source.Reducer do
@moduledoc """
A module and struct that can reduce over elixir AST via Macro.prewalk/3
@@ -6,12 +6,12 @@ defmodule Lexical.RemoteControl.Search.Indexer.Source.Reducer do
with the AST's overall structure, and can focus on extracting content from it.
"""
- alias Lexical.Ast.Analysis
- alias Lexical.Document.Position
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Extractors
- alias Lexical.RemoteControl.Search.Indexer.Metadata
- alias Lexical.RemoteControl.Search.Indexer.Source.Block
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Extractors
+ alias Engine.Search.Indexer.Metadata
+ alias Engine.Search.Indexer.Source.Block
+ alias Forge.Ast.Analysis
+ alias Forge.Document.Position
defstruct [:analysis, :entries, :position, :blocks, :block_hierarchy, extractors: []]
diff --git a/apps/engine/lib/engine/engine/search/store.ex b/apps/engine/lib/engine/engine/search/store.ex
new file mode 100644
index 00000000..190ce2a2
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/store.ex
@@ -0,0 +1,294 @@
+defmodule Engine.Search.Store do
+ @moduledoc """
+ A persistent store for search entries
+ """
+
+ alias Forge.Project
+
+ alias Engine.Api
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store
+ alias Engine.Search.Store.State
+
+ @type index_state :: :empty | :stale
+ @type existing_entries :: [Entry.t()]
+ @type new_entries :: [Entry.t()]
+ @type updated_entries :: [Entry.t()]
+ @type paths_to_delete :: [Path.t()]
+ @typedoc """
+ A function that creates indexes when none is detected
+ """
+ @type create_index ::
+ (project :: Project.t() ->
+ {:ok, new_entries} | {:error, term()})
+
+ @typedoc """
+ A function that takes existing entries and refreshes them if necessary
+ """
+ @type refresh_index ::
+ (project :: Project.t(), entries :: existing_entries ->
+ {:ok, new_entries, paths_to_delete} | {:error, term()})
+
+ @backend Application.compile_env(:engine, :search_store_backend, Store.Backends.Ets)
+ @flush_interval_ms Application.compile_env(
+ :engine,
+ :search_store_quiescent_period_ms,
+ 2500
+ )
+
+ import Api.Messages
+ use GenServer
+ require Logger
+
+ def stop do
+ GenServer.stop(__MODULE__)
+ end
+
+ def loaded? do
+ GenServer.call(__MODULE__, :loaded?)
+ end
+
+ def replace(entries) do
+ GenServer.call(__MODULE__, {:replace, entries})
+ end
+
+ @spec exact(Entry.subject_query(), Entry.constraints()) :: {:ok, [Entry.t()]} | {:error, term()}
+ def exact(subject \\ :_, constraints) do
+ call_or_default({:exact, subject, constraints}, [])
+ end
+
+ @spec prefix(String.t(), Entry.constraints()) :: {:ok, [Entry.t()]} | {:error, term()}
+ def prefix(prefix, constraints) do
+ call_or_default({:prefix, prefix, constraints}, [])
+ end
+
+ @spec parent(Entry.t()) :: {:ok, Entry.t()} | {:error, term()}
+ def parent(%Entry{} = entry) do
+ call_or_default({:parent, entry}, nil)
+ end
+
+ @spec siblings(Entry.t()) :: {:ok, [Entry.t()]} | {:error, term()}
+ def siblings(%Entry{} = entry) do
+ call_or_default({:siblings, entry}, [])
+ end
+
+ @spec fuzzy(Entry.subject(), Entry.constraints()) :: {:ok, [Entry.t()]} | {:error, term()}
+ def fuzzy(subject, constraints) do
+ call_or_default({:fuzzy, subject, constraints}, [])
+ end
+
+ def clear(path) do
+ GenServer.call(__MODULE__, {:update, path, []})
+ end
+
+ def update(path, entries) do
+ GenServer.call(__MODULE__, {:update, path, entries})
+ end
+
+ def destroy do
+ GenServer.call(__MODULE__, :destroy)
+ end
+
+ def enable do
+ GenServer.call(__MODULE__, :enable)
+ end
+
+ @spec start_link(Project.t(), create_index, refresh_index, module()) :: GenServer.on_start()
+ def start_link(%Project{} = project, create_index, refresh_index, backend) do
+ GenServer.start_link(__MODULE__, [project, create_index, refresh_index, backend],
+ name: __MODULE__
+ )
+ end
+
+ def child_spec(init_args) when is_list(init_args) do
+ %{
+ id: __MODULE__,
+ start: {__MODULE__, :start_link, normalize_init_args(init_args)}
+ }
+ end
+
+ defp normalize_init_args([create_index, refresh_index]) do
+ normalize_init_args([Engine.get_project(), create_index, refresh_index])
+ end
+
+ defp normalize_init_args([%Project{} = project, create_index, refresh_index]) do
+ normalize_init_args([project, create_index, refresh_index, backend()])
+ end
+
+ defp normalize_init_args([%Project{}, create_index, refresh_index, backend] = args)
+ when is_function(create_index, 1) and is_function(refresh_index, 2) and is_atom(backend) do
+ args
+ end
+
+ @impl GenServer
+ def init([%Project{} = project, create_index, update_index, backend]) do
+ Process.flag(:fullsweep_after, 5)
+ schedule_gc()
+ # I've found that if indexing happens before the first compile, for some reason
+ # the compilation is 4x slower than if indexing happens after it. I was
+ # unable to figure out why this is the case, and I looked extensively, so instead
+ # we have this bandaid. We wait for the first compilation to complete, and then
+ # the search store enables itself, at which point we index the code.
+
+ Engine.register_listener(self(), project_compiled())
+ state = State.new(project, create_index, update_index, backend)
+ {:ok, state}
+ end
+
+ @impl GenServer
+ # enable ourselves when the project is force compiled
+ def handle_info(project_compiled(), %State{} = state) do
+ {:noreply, enable(state)}
+ end
+
+ def handle_info(project_compiled(), {_, _} = state) do
+ # we're already enabled, no need to do anything
+ {:noreply, state}
+ end
+
+ # handle the result from `State.async_load/1`
+ def handle_info({ref, result}, {update_ref, %State{async_load_ref: ref} = state}) do
+ {:noreply, {update_ref, State.async_load_complete(state, result)}}
+ end
+
+ def handle_info(:flush_updates, {_, %State{} = state}) do
+ {:ok, state} = State.flush_buffered_updates(state)
+ ref = schedule_flush()
+ {:noreply, {ref, state}}
+ end
+
+ def handle_info(:gc, state) do
+ :erlang.garbage_collect()
+ schedule_gc()
+ {:noreply, state}
+ end
+
+ def handle_info(_, state) do
+ {:noreply, state}
+ end
+
+ @impl GenServer
+ def handle_call(:enable, _from, %State{} = state) do
+ {:reply, :ok, enable(state)}
+ end
+
+ def handle_call(:enable, _from, state) do
+ {:reply, :ok, state}
+ end
+
+ def handle_call({:replace, entities}, _from, {ref, %State{} = state}) do
+ {reply, new_state} =
+ case State.replace(state, entities) do
+ {:ok, new_state} ->
+ {:ok, State.drop_buffered_updates(new_state)}
+
+ {:error, _} = error ->
+ {error, state}
+ end
+
+ {:reply, reply, {ref, new_state}}
+ end
+
+ def handle_call({:exact, subject, constraints}, _from, {ref, %State{} = state}) do
+ {:reply, State.exact(state, subject, constraints), {ref, state}}
+ end
+
+ def handle_call({:prefix, prefix, constraints}, _from, {ref, %State{} = state}) do
+ {:reply, State.prefix(state, prefix, constraints), {ref, state}}
+ end
+
+ def handle_call({:fuzzy, subject, constraints}, _from, {ref, %State{} = state}) do
+ {:reply, State.fuzzy(state, subject, constraints), {ref, state}}
+ end
+
+ def handle_call({:update, path, entries}, _from, {ref, %State{} = state}) do
+ {reply, new_ref, new_state} = do_update(state, ref, path, entries)
+
+ {:reply, reply, {new_ref, new_state}}
+ end
+
+ def handle_call({:parent, entry}, _from, {_, %State{} = state} = orig_state) do
+ parent = State.parent(state, entry)
+ {:reply, parent, orig_state}
+ end
+
+ def handle_call({:siblings, entry}, _from, {_, %State{} = state} = orig_state) do
+ siblings = State.siblings(state, entry)
+ {:reply, siblings, orig_state}
+ end
+
+ def handle_call(:on_stop, _, {ref, %State{} = state}) do
+ {:ok, state} = State.flush_buffered_updates(state)
+
+ State.drop(state)
+ {:reply, :ok, {ref, state}}
+ end
+
+ def handle_call(:loaded?, _, {ref, %State{loaded?: loaded?} = state}) do
+ {:reply, loaded?, {ref, state}}
+ end
+
+ def handle_call(:loaded?, _, %State{loaded?: loaded?} = state) do
+ # We're not enabled yet, but we can still reply to the query
+ {:reply, loaded?, state}
+ end
+
+ def handle_call(:destroy, _, {ref, %State{} = state}) do
+ new_state = State.destroy(state)
+ {:reply, :ok, {ref, new_state}}
+ end
+
+ def handle_call(message, _from, %State{} = state) do
+ Logger.warning("Received #{inspect(message)}, but the search store isn't enabled yet.")
+ {:reply, {:error, {:not_enabled, message}}, state}
+ end
+
+ @impl GenServer
+ def terminate(_reason, {_, state}) do
+ {:ok, state} = State.flush_buffered_updates(state)
+ {:noreply, state}
+ end
+
+ defp backend do
+ @backend
+ end
+
+ defp do_update(state, old_ref, path, entries) do
+ {:ok, schedule_flush(old_ref), State.buffer_updates(state, path, entries)}
+ end
+
+ defp schedule_flush(ref) when is_reference(ref) do
+ Process.cancel_timer(ref)
+ schedule_flush()
+ end
+
+ defp schedule_flush(_) do
+ schedule_flush()
+ end
+
+ defp schedule_flush do
+ Process.send_after(self(), :flush_updates, @flush_interval_ms)
+ end
+
+ defp enable(%State{} = state) do
+ state = State.async_load(state)
+ :persistent_term.put({__MODULE__, :enabled?}, true)
+ {nil, state}
+ end
+
+ defp schedule_gc do
+ Process.send_after(self(), :gc, :timer.seconds(5))
+ end
+
+ defp call_or_default(call, default) do
+ if enabled?() do
+ GenServer.call(__MODULE__, call)
+ else
+ default
+ end
+ end
+
+ defp enabled? do
+ :persistent_term.get({__MODULE__, :enabled?}, false)
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backend.ex b/apps/engine/lib/engine/engine/search/store/backend.ex
similarity index 95%
rename from apps/remote_control/lib/lexical/remote_control/search/store/backend.ex
rename to apps/engine/lib/engine/engine/search/store/backend.ex
index 042f4942..02ca0652 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backend.ex
+++ b/apps/engine/lib/engine/engine/search/store/backend.ex
@@ -1,9 +1,10 @@
-defmodule Lexical.RemoteControl.Search.Store.Backend do
+defmodule Engine.Search.Store.Backend do
@moduledoc """
A behaviour for search store backends
"""
- alias Lexical.Project
- alias Lexical.RemoteControl.Search.Indexer.Entry
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Project
+
@type version :: pos_integer()
@type priv_state :: term()
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets.ex b/apps/engine/lib/engine/engine/search/store/backends/ets.ex
similarity index 93%
rename from apps/remote_control/lib/lexical/remote_control/search/store/backends/ets.ex
rename to apps/engine/lib/engine/engine/search/store/backends/ets.ex
index 144fa2eb..bafd854f 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets.ex
+++ b/apps/engine/lib/engine/engine/search/store/backends/ets.ex
@@ -1,9 +1,8 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets do
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store.Backend
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.State
+defmodule Engine.Search.Store.Backends.Ets do
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store.Backend
+ alias Engine.Search.Store.Backends.Ets.State
+ alias Forge.Project
use GenServer
@@ -94,7 +93,7 @@ defmodule Lexical.RemoteControl.Search.Store.Backends.Ets do
end
def start_link do
- start_link(RemoteControl.get_project())
+ start_link(Engine.get_project())
end
def child_spec([%Project{}] = init_args) do
@@ -102,7 +101,7 @@ defmodule Lexical.RemoteControl.Search.Store.Backends.Ets do
end
def child_spec(_) do
- child_spec([RemoteControl.get_project()])
+ child_spec([Engine.get_project()])
end
@impl GenServer
@@ -179,7 +178,7 @@ defmodule Lexical.RemoteControl.Search.Store.Backends.Ets do
end
defp genserver_name do
- genserver_name(RemoteControl.get_project())
+ genserver_name(Engine.get_project())
end
defp genserver_name(%Project{} = project) do
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schema.ex b/apps/engine/lib/engine/engine/search/store/backends/ets/schema.ex
similarity index 96%
rename from apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schema.ex
rename to apps/engine/lib/engine/engine/search/store/backends/ets/schema.ex
index 0c9e7ad7..d7517a5b 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schema.ex
+++ b/apps/engine/lib/engine/engine/search/store/backends/ets/schema.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schema do
+defmodule Engine.Search.Store.Backends.Ets.Schema do
@moduledoc """
A use-able module that allows ETS schemas to be created and migrated.
@@ -16,7 +16,7 @@ defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schema do
quote do
@behaviour unquote(__MODULE__)
@version unquote(version)
- alias Lexical.Project
+ alias Forge.Project
import unquote(__MODULE__), only: [defkey: 2]
def version do
@@ -28,7 +28,7 @@ defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schema do
end
def table_name do
- :"lexical_search_v#{@version}"
+ :"expert_search_v#{@version}"
end
def table_options do
@@ -43,9 +43,9 @@ defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schema do
end
end
- alias Lexical.Project
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Wal
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store.Backends.Ets.Wal
+ alias Forge.Project
import Wal, only: :macros
diff --git a/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/legacy_v0.ex b/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/legacy_v0.ex
new file mode 100644
index 00000000..65c1155d
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/legacy_v0.ex
@@ -0,0 +1,19 @@
+defmodule Engine.Search.Store.Backends.Ets.Schemas.LegacyV0 do
+ @moduledoc """
+ A legacy version of the schema.
+
+ We pushed the initial indexer to main before we added schemas and versioning.
+ This represents that schema type, hence the non-versioned name.
+ """
+ alias Engine.Search.Store.Backends.Ets.Schema
+
+ use Schema, version: 0
+
+ def index_file_name do
+ "source.index.ets"
+ end
+
+ def to_rows(_) do
+ []
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v1.ex b/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v1.ex
similarity index 90%
rename from apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v1.ex
rename to apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v1.ex
index 5653a3ec..502f81d9 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v1.ex
+++ b/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v1.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schemas.V1 do
+defmodule Engine.Search.Store.Backends.Ets.Schemas.V1 do
@moduledoc """
This schema uses a bit of data duplication in order to achieve good performance.
@@ -9,8 +9,8 @@ defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schemas.V1 do
"""
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schema
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store.Backends.Ets.Schema
use Schema, version: 1
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v2.ex b/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v2.ex
similarity index 88%
rename from apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v2.ex
rename to apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v2.ex
index ce326b65..13fec328 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v2.ex
+++ b/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v2.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schemas.V2 do
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schema
+defmodule Engine.Search.Store.Backends.Ets.Schemas.V2 do
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store.Backends.Ets.Schema
require Entry
use Schema, version: 2
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v3.ex b/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v3.ex
similarity index 90%
rename from apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v3.ex
rename to apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v3.ex
index 98c36f77..19713ca2 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/v3.ex
+++ b/apps/engine/lib/engine/engine/search/store/backends/ets/schemas/v3.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schemas.V3 do
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schema
+defmodule Engine.Search.Store.Backends.Ets.Schemas.V3 do
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store.Backends.Ets.Schema
require Entry
use Schema, version: 3
diff --git a/apps/engine/lib/engine/engine/search/store/backends/ets/state.ex b/apps/engine/lib/engine/engine/search/store/backends/ets/state.ex
new file mode 100644
index 00000000..59ebb1ae
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/store/backends/ets/state.ex
@@ -0,0 +1,297 @@
+defmodule Engine.Search.Store.Backends.Ets.State do
+ @moduledoc """
+ An ETS based search backend
+
+ This backend uses an ETS table to store its data using a schema defined in the schemas submodule.
+
+ """
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store.Backends.Ets.Schema
+ alias Engine.Search.Store.Backends.Ets.Schemas
+ alias Engine.Search.Store.Backends.Ets.Wal
+ alias Forge.Project
+
+ @schema_order [
+ Schemas.LegacyV0,
+ Schemas.V1,
+ Schemas.V2,
+ Schemas.V3
+ ]
+
+ import Wal, only: :macros
+ import Entry, only: :macros
+
+ import Schemas.V3,
+ only: [
+ by_block_id: 1,
+ query_by_id: 1,
+ query_by_path: 1,
+ query_structure: 1,
+ query_by_subject: 1,
+ structure: 1,
+ to_subject: 1
+ ]
+
+ defstruct [:project, :table_name, :leader?, :leader_pid, :wal_state]
+
+ def new_leader(%Project{} = project) do
+ %__MODULE__{project: project, leader?: true, leader_pid: self()}
+ end
+
+ def new_follower(%Project{} = project, leader_pid) do
+ %__MODULE__{project: project, leader?: false, leader_pid: leader_pid}
+ end
+
+ def prepare(%__MODULE__{leader?: true} = state) do
+ {:ok, wal, table_name, result} = Schema.load(state.project, @schema_order)
+
+ {{:ok, result}, %__MODULE__{state | table_name: table_name, wal_state: wal}}
+ end
+
+ def prepare(%__MODULE__{leader?: false}) do
+ {:error, :not_leader}
+ end
+
+ def drop(%__MODULE__{leader?: true} = state) do
+ Wal.truncate(state.wal_state)
+ :ets.delete_all_objects(state.table_name)
+ end
+
+ def insert(%__MODULE__{leader?: true} = state, entries) do
+ rows = Schema.entries_to_rows(entries, current_schema())
+
+ with_wal state.wal_state do
+ true = :ets.insert(state.table_name, rows)
+ end
+
+ :ok
+ end
+
+ def reduce(%__MODULE__{} = state, acc, reducer_fun) do
+ ets_reducer = fn
+ {{:by_id, _, _, _}, entries}, acc when is_list(entries) ->
+ Enum.reduce(entries, acc, reducer_fun)
+
+ {{:by_id, _, _, _}, %Entry{} = entry}, acc ->
+ reducer_fun.(entry, acc)
+
+ _, acc ->
+ acc
+ end
+
+ :ets.foldl(ets_reducer, acc, state.table_name)
+ end
+
+ def find_by_subject(%__MODULE__{} = state, subject, type, subtype) do
+ match_pattern =
+ query_by_subject(
+ subject: to_subject(subject),
+ type: type,
+ subtype: subtype
+ )
+
+ state.table_name
+ |> :ets.match_object({match_pattern, :_})
+ |> Enum.flat_map(fn {_, id_keys} ->
+ id_keys
+ end)
+ |> MapSet.new()
+ |> Enum.flat_map(&:ets.lookup_element(state.table_name, &1, 2))
+ end
+
+ def find_by_prefix(%__MODULE__{} = state, subject, type, subtype) do
+ match_pattern =
+ query_by_subject(
+ subject: to_prefix(subject),
+ type: type,
+ subtype: subtype
+ )
+
+ state.table_name
+ |> :ets.select([{{match_pattern, :_}, [], [:"$_"]}])
+ |> Stream.flat_map(fn {_, id_keys} -> id_keys end)
+ |> Stream.uniq()
+ |> Enum.flat_map(&:ets.lookup_element(state.table_name, &1, 2))
+ end
+
+ @dialyzer {:nowarn_function, to_prefix: 1}
+
+ defp to_prefix(prefix) when is_binary(prefix) do
+ # what we really want to do here is convert the prefix to a improper list
+ # like this: `'abc' -> [97, 98, 99 | :_]`, it's different from `'abc' ++ [:_]`
+ # this is the required format for the `:ets.select` function.
+ {last_char, others} = prefix |> String.to_charlist() |> List.pop_at(-1)
+ others ++ [last_char | :_]
+ end
+
+ def siblings(%__MODULE__{} = state, %Entry{} = entry) do
+ key = by_block_id(block_id: entry.block_id, path: entry.path)
+
+ siblings =
+ state.table_name
+ |> :ets.lookup_element(key, 2)
+ |> Enum.map(&:ets.lookup_element(state.table_name, &1, 2))
+ |> List.flatten()
+ |> Enum.filter(fn sibling ->
+ case {is_block(entry), is_block(sibling)} do
+ {same, same} -> true
+ _ -> false
+ end
+ end)
+ |> Enum.sort_by(& &1.id)
+ |> Enum.uniq()
+
+ {:ok, siblings}
+ rescue
+ ArgumentError ->
+ :error
+ end
+
+ def parent(%__MODULE__{} = state, %Entry{} = entry) do
+ with {:ok, structure} <- structure_for_path(state, entry.path),
+ {:ok, child_path} <- child_path(structure, entry.block_id) do
+ child_path =
+ if is_block(entry) do
+ # if we're a block, finding the first block will find us, so pop
+ # our id off the path.
+ tl(child_path)
+ else
+ child_path
+ end
+
+ find_first_by_block_id(state, child_path)
+ end
+ end
+
+ def parent(%__MODULE__{}, :root) do
+ :error
+ end
+
+ def find_by_ids(%__MODULE__{} = state, ids, type, subtype)
+ when is_list(ids) do
+ for id <- ids,
+ match_pattern = match_id_key(id, type, subtype),
+ {_key, entry} <- :ets.match_object(state.table_name, match_pattern) do
+ entry
+ end
+ |> List.flatten()
+ end
+
+ def replace_all(%__MODULE__{leader?: true} = state, entries) do
+ rows = Schema.entries_to_rows(entries, current_schema())
+
+ {:ok, _, result} =
+ with_wal state.wal_state do
+ true = :ets.delete_all_objects(state.table_name)
+ true = :ets.insert(state.table_name, rows)
+ :ok
+ end
+
+ # When we replace everything, the old checkpoint is invalidated
+ # so it makes sense to force a new one.
+ Wal.checkpoint(state.wal_state)
+ result
+ end
+
+ def delete_by_path(%__MODULE__{leader?: true} = state, path) do
+ ids_to_delete =
+ state.table_name
+ |> :ets.match({query_by_path(path: path), :"$0"})
+ |> List.flatten()
+
+ with_wal state.wal_state do
+ :ets.match_delete(state.table_name, {query_by_subject(path: path), :_})
+ :ets.match_delete(state.table_name, {query_by_path(path: path), :_})
+ :ets.match_delete(state.table_name, {query_structure(path: path), :_})
+ end
+
+ Enum.each(ids_to_delete, fn id ->
+ with_wal state.wal_state do
+ :ets.delete(state.table_name, id)
+ end
+ end)
+
+ {:ok, ids_to_delete}
+ end
+
+ def destroy_all(%Project{} = project) do
+ Wal.destroy_all(project)
+ end
+
+ def destroy(%__MODULE__{leader?: true, wal_state: %Wal{}} = state) do
+ Wal.destroy(state.wal_state)
+ end
+
+ def destroy(%__MODULE__{leader?: true}) do
+ :ok
+ end
+
+ def terminate(%__MODULE__{wal_state: %Wal{}} = state) do
+ Wal.close(state.wal_state)
+ end
+
+ def terminate(%__MODULE__{}) do
+ :ok
+ end
+
+ defp child_path(structure, child_id) do
+ path =
+ Enum.reduce_while(structure, [], fn
+ {^child_id, _children}, children ->
+ {:halt, [child_id | children]}
+
+ {_, children}, path when map_size(children) == 0 ->
+ {:cont, path}
+
+ {current_id, children}, path ->
+ case child_path(children, child_id) do
+ {:ok, child_path} -> {:halt, [current_id | path] ++ Enum.reverse(child_path)}
+ :error -> {:cont, path}
+ end
+ end)
+
+ case path do
+ [] -> :error
+ path -> {:ok, Enum.reverse(path)}
+ end
+ end
+
+ defp find_first_by_block_id(%__MODULE__{} = state, block_ids) do
+ Enum.reduce_while(block_ids, :error, fn block_id, failure ->
+ case find_entry_by_id(state, block_id) do
+ {:ok, _} = success ->
+ {:halt, success}
+
+ _ ->
+ {:cont, failure}
+ end
+ end)
+ end
+
+ def find_entry_by_id(%__MODULE__{} = state, id) do
+ case find_by_ids(state, [id], :_, :_) do
+ [entry] -> {:ok, entry}
+ _ -> :error
+ end
+ end
+
+ def structure_for_path(%__MODULE__{} = state, path) do
+ key = structure(path: path)
+
+ case :ets.lookup_element(state.table_name, key, 2) do
+ [structure] -> {:ok, structure}
+ _ -> :error
+ end
+ rescue
+ ArgumentError ->
+ :error
+ end
+
+ defp match_id_key(id, type, subtype) do
+ {query_by_id(id: id, type: type, subtype: subtype), :_}
+ end
+
+ defp current_schema do
+ List.last(@schema_order)
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/wal.ex b/apps/engine/lib/engine/engine/search/store/backends/ets/wal.ex
similarity index 98%
rename from apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/wal.ex
rename to apps/engine/lib/engine/engine/search/store/backends/ets/wal.ex
index ef092838..401e6f5a 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/wal.ex
+++ b/apps/engine/lib/engine/engine/search/store/backends/ets/wal.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Wal do
+defmodule Engine.Search.Store.Backends.Ets.Wal do
@moduledoc """
A (hopefully) simple write-ahead log
"""
- alias Lexical.Identifier
- alias Lexical.Project
- alias Lexical.VM.Versions
+ alias Forge.Identifier
+ alias Forge.Project
+ alias Forge.VM.Versions
import Record
diff --git a/apps/engine/lib/engine/engine/search/store/state.ex b/apps/engine/lib/engine/engine/search/store/state.ex
new file mode 100644
index 00000000..5179d328
--- /dev/null
+++ b/apps/engine/lib/engine/engine/search/store/state.ex
@@ -0,0 +1,260 @@
+defmodule Engine.Search.Store.State do
+ alias Engine.Api.Messages
+ alias Engine.Dispatch
+ alias Engine.Search.Fuzzy
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Project
+
+ require Logger
+ import Messages
+
+ defstruct [
+ :project,
+ :backend,
+ :create_index,
+ :update_index,
+ :loaded?,
+ :fuzzy,
+ :async_load_ref,
+ :update_buffer
+ ]
+
+ def new(%Project{} = project, create_index, update_index, backend) do
+ %__MODULE__{
+ backend: backend,
+ create_index: create_index,
+ project: project,
+ loaded?: false,
+ update_index: update_index,
+ update_buffer: %{},
+ fuzzy: Fuzzy.from_entries([])
+ }
+ end
+
+ def drop(%__MODULE__{} = state) do
+ state.backend.drop()
+ end
+
+ def destroy(%__MODULE__{} = state) do
+ state.backend.destroy(state)
+ end
+
+ @doc """
+ Asynchronously loads the search state.
+
+ This function returns prior to creating or refreshing the index, which
+ occurs in a separate process. The caller should listen for a message
+ of the shape `{ref, result}`, where `ref` matches the state's
+ `:async_load_ref`. Once received, that result should be passed to
+ `async_load_complete/2`.
+ """
+ def async_load(%__MODULE__{loaded?: false, async_load_ref: nil} = state) do
+ {:ok, backend_result} = state.backend.new(state.project)
+ prepare_backend_async(state, backend_result)
+ end
+
+ def async_load(%__MODULE__{} = state) do
+ {:ok, state}
+ end
+
+ def async_load_complete(%__MODULE__{} = state, result) do
+ new_state = %__MODULE__{state | loaded?: true, async_load_ref: nil}
+
+ response =
+ case result do
+ {:create_index, result} ->
+ create_index_complete(new_state, result)
+
+ {:update_index, result} ->
+ update_index_complete(new_state, result)
+
+ :initialize_fuzzy ->
+ initialize_fuzzy(new_state)
+ end
+
+ Dispatch.broadcast(project_index_ready(project: state.project))
+ response
+ end
+
+ def replace(%__MODULE__{} = state, entries) do
+ with :ok <- state.backend.replace_all(entries),
+ :ok <- maybe_sync(state) do
+ {:ok, %__MODULE__{state | fuzzy: Fuzzy.from_backend(state.backend)}}
+ end
+ end
+
+ def exact(%__MODULE__{} = state, subject, constraints) do
+ type = Keyword.get(constraints, :type, :_)
+ subtype = Keyword.get(constraints, :subtype, :_)
+
+ case state.backend.find_by_subject(subject, type, subtype) do
+ l when is_list(l) -> {:ok, l}
+ error -> error
+ end
+ end
+
+ def prefix(%__MODULE__{} = state, prefix, constraints) do
+ type = Keyword.get(constraints, :type, :_)
+ subtype = Keyword.get(constraints, :subtype, :_)
+
+ case state.backend.find_by_prefix(prefix, type, subtype) do
+ l when is_list(l) ->
+ {:ok, l}
+
+ error ->
+ error
+ end
+ end
+
+ def fuzzy(%__MODULE__{} = state, subject, constraints) do
+ case Fuzzy.match(state.fuzzy, subject) do
+ [] ->
+ {:ok, []}
+
+ ids ->
+ type = Keyword.get(constraints, :type, :_)
+ subtype = Keyword.get(constraints, :subtype, :_)
+
+ case state.backend.find_by_ids(ids, type, subtype) do
+ l when is_list(l) -> {:ok, l}
+ error -> error
+ end
+ end
+ end
+
+ def siblings(%__MODULE__{} = state, entry) do
+ case state.backend.siblings(entry) do
+ l when is_list(l) -> {:ok, l}
+ error -> error
+ end
+ end
+
+ def parent(%__MODULE__{} = state, entry) do
+ case state.backend.parent(entry) do
+ %Entry{} = entry -> {:ok, entry}
+ error -> error
+ end
+ end
+
+ def buffer_updates(%__MODULE__{} = state, path, entries) do
+ %__MODULE__{state | update_buffer: Map.put(state.update_buffer, path, entries)}
+ end
+
+ def drop_buffered_updates(%__MODULE__{} = state) do
+ %__MODULE__{state | update_buffer: %{}}
+ end
+
+ def flush_buffered_updates(%__MODULE__{update_buffer: buffer} = state)
+ when map_size(buffer) == 0 do
+ maybe_sync(state)
+ {:ok, state}
+ end
+
+ def flush_buffered_updates(%__MODULE__{} = state) do
+ result =
+ Enum.reduce_while(state.update_buffer, state, fn {path, entries}, state ->
+ case update_nosync(state, path, entries) do
+ {:ok, new_state} ->
+ {:cont, new_state}
+
+ error ->
+ {:halt, error}
+ end
+ end)
+
+ with %__MODULE__{} = state <- result,
+ :ok <- maybe_sync(state) do
+ {:ok, drop_buffered_updates(state)}
+ end
+ end
+
+ def update_nosync(%__MODULE__{} = state, path, entries) do
+ with {:ok, deleted_ids} <- state.backend.delete_by_path(path),
+ :ok <- state.backend.insert(entries) do
+ fuzzy =
+ state.fuzzy
+ |> Fuzzy.drop_values(deleted_ids)
+ |> Fuzzy.add(entries)
+
+ {:ok, %__MODULE__{state | fuzzy: fuzzy}}
+ end
+ end
+
+ require Logger
+
+ defp prepare_backend_async(%__MODULE__{async_load_ref: nil} = state, backend_result) do
+ task =
+ Task.async(fn ->
+ case state.backend.prepare(backend_result) do
+ {:ok, :empty} ->
+ Logger.info("backend reports empty")
+ {:create_index, state.create_index.(state.project)}
+
+ {:ok, :stale} ->
+ Logger.info("backend reports stale")
+ {:update_index, state.update_index.(state.project, state.backend)}
+
+ {:error, :not_leader} ->
+ :initialize_fuzzy
+
+ error ->
+ Logger.error("Could not initialize index due to #{inspect(error)}")
+ error
+ end
+ end)
+
+ %__MODULE__{state | async_load_ref: task.ref}
+ end
+
+ defp create_index_complete(%__MODULE__{} = state, {:ok, entries}) do
+ case replace(state, entries) do
+ {:ok, state} ->
+ state
+
+ {:error, _} ->
+ Logger.warning("Could not replace entries")
+ state
+ end
+ end
+
+ defp create_index_complete(%__MODULE__{} = state, {:error, _} = error) do
+ Logger.warning("Could not create index, got: #{inspect(error)}")
+ state
+ end
+
+ defp update_index_complete(%__MODULE__{} = state, {:ok, updated_entries, deleted_paths}) do
+ starting_state = initialize_fuzzy(%__MODULE__{state | loaded?: true})
+
+ new_state =
+ updated_entries
+ |> Enum.group_by(& &1.path)
+ |> Enum.reduce(starting_state, fn {path, entry_list}, state ->
+ {:ok, new_state} = update_nosync(state, path, entry_list)
+ new_state
+ end)
+
+ Enum.reduce(deleted_paths, new_state, fn path, state ->
+ {:ok, new_state} = update_nosync(state, path, [])
+ new_state
+ end)
+ end
+
+ defp update_index_complete(%__MODULE__{} = state, {:error, _} = error) do
+ Logger.warning("Could not update index, got: #{inspect(error)}")
+ state
+ end
+
+ defp maybe_sync(%__MODULE__{} = state) do
+ if function_exported?(state.backend, :sync, 1) do
+ state.backend.sync(state.project)
+ else
+ :ok
+ end
+ end
+
+ defp initialize_fuzzy(%__MODULE__{} = state) do
+ fuzzy = Fuzzy.from_backend(state.backend)
+
+ %__MODULE__{state | fuzzy: fuzzy}
+ end
+end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/subject.ex b/apps/engine/lib/engine/engine/search/subject.ex
similarity index 80%
rename from apps/remote_control/lib/lexical/remote_control/search/subject.ex
rename to apps/engine/lib/engine/engine/search/subject.ex
index 1e701042..ce3659f4 100644
--- a/apps/remote_control/lib/lexical/remote_control/search/subject.ex
+++ b/apps/engine/lib/engine/engine/search/subject.ex
@@ -1,8 +1,8 @@
-defmodule Lexical.RemoteControl.Search.Subject do
+defmodule Engine.Search.Subject do
@moduledoc """
Functions for converting to a search entry's subject field
"""
- alias Lexical.Formats
+ alias Forge.Formats
def module(module) do
module
diff --git a/apps/remote_control/lib/mix/tasks/namespace.ex b/apps/engine/lib/mix/tasks/namespace.ex
similarity index 91%
rename from apps/remote_control/lib/mix/tasks/namespace.ex
rename to apps/engine/lib/mix/tasks/namespace.ex
index 00353b82..c409fdc7 100644
--- a/apps/remote_control/lib/mix/tasks/namespace.ex
+++ b/apps/engine/lib/mix/tasks/namespace.ex
@@ -1,6 +1,6 @@
defmodule Mix.Tasks.Namespace do
@moduledoc """
- This task is used after a release is assembled, and investigates the remote_control
+ This task is used after a release is assembled, and investigates the engine
app for its dependencies, at which point it applies transformers to various parts of the
app.
@@ -10,7 +10,7 @@ defmodule Mix.Tasks.Namespace do
This task takes a single argument, which is the full path to the release.
"""
- alias Lexical.Ast
+ alias Forge.Ast
alias Mix.Tasks.Namespace.Transform
use Mix.Task
@@ -20,13 +20,14 @@ defmodule Mix.Tasks.Namespace do
# by this task. Plugin discovery uses this task, which happens after
# namespacing.
@extra_apps %{
- "proto" => "Lexical",
- "protocol" => "Lexical",
- "remote_control" => "Lexical",
- "server" => "Lexical"
+ "proto" => "Expert",
+ "protocol" => "Expert",
+ "engine" => "Engine",
+ "expert" => "Expert",
+ "forge" => "Forge"
}
- @deps_apps Lexical.RemoteControl.MixProject.project()
+ @deps_apps Engine.MixProject.project()
|> Keyword.get(:deps)
|> Enum.map(&elem(&1, 0))
|> then(fn dep_names -> dep_names -- @dev_deps end)
@@ -75,7 +76,7 @@ defmodule Mix.Tasks.Namespace do
all_modules = app_modules(app_name)
case Enum.filter(all_modules, fn module -> length(safe_split_module(module)) == 1 end) do
- [] -> {app_name, [Lexical]}
+ [] -> {app_name, [Expert]}
root_modules -> {app_name, root_modules}
end
end)
@@ -91,7 +92,7 @@ defmodule Mix.Tasks.Namespace do
modules
_ ->
- [Lexical]
+ [Expert]
end
end
diff --git a/apps/remote_control/lib/mix/tasks/namespace/abstract.ex b/apps/engine/lib/mix/tasks/namespace/abstract.ex
similarity index 100%
rename from apps/remote_control/lib/mix/tasks/namespace/abstract.ex
rename to apps/engine/lib/mix/tasks/namespace/abstract.ex
diff --git a/apps/remote_control/lib/mix/tasks/namespace/code.ex b/apps/engine/lib/mix/tasks/namespace/code.ex
similarity index 100%
rename from apps/remote_control/lib/mix/tasks/namespace/code.ex
rename to apps/engine/lib/mix/tasks/namespace/code.ex
diff --git a/apps/engine/lib/mix/tasks/namespace/module.ex b/apps/engine/lib/mix/tasks/namespace/module.ex
new file mode 100644
index 00000000..214bddf7
--- /dev/null
+++ b/apps/engine/lib/mix/tasks/namespace/module.ex
@@ -0,0 +1,79 @@
+defmodule Mix.Tasks.Namespace.Module do
+ alias Mix.Tasks.Namespace
+
+ @namespace_prefix "XP"
+
+ def apply(module_name) do
+ cond do
+ prefixed?(module_name) ->
+ module_name
+
+ module_name in Namespace.app_names() ->
+ :"xp_#{module_name}"
+
+ true ->
+ module_name
+ |> Atom.to_string()
+ |> apply_namespace()
+ end
+ end
+
+ def prefixed?(module) when is_atom(module) do
+ module
+ |> Atom.to_string()
+ |> prefixed?()
+ end
+
+ def prefixed?("Elixir." <> rest),
+ do: prefixed?(rest)
+
+ def prefixed?(@namespace_prefix <> _),
+ do: true
+
+ def prefixed?("xp" <> _),
+ do: true
+
+ def prefixed?([?x, ?p, ?_ | _]), do: true
+ def prefixed?([?E, ?l, ?i, ?x, ?i, ?r, ?., ?X, ?P | _]), do: true
+ def prefixed?([?X, ?P | _]), do: true
+
+ def prefixed?(_),
+ do: false
+
+ defp apply_namespace("Elixir." <> rest) do
+ Namespace.root_modules()
+ |> Enum.map(fn module -> module |> Module.split() |> List.first() end)
+ |> Enum.reduce_while(rest, fn root_module, module ->
+ if has_root_module?(root_module, module) do
+ namespaced_module =
+ module
+ |> String.replace(root_module, namespace(root_module), global: false)
+ |> String.to_atom()
+
+ {:halt, namespaced_module}
+ else
+ {:cont, module}
+ end
+ end)
+ |> List.wrap()
+ |> Module.concat()
+ end
+
+ defp apply_namespace(erlang_module) do
+ String.to_atom(erlang_module)
+ end
+
+ defp has_root_module?(root_module, root_module), do: true
+
+ defp has_root_module?(root_module, candidate) do
+ String.contains?(candidate, append_trailing_period(root_module))
+ end
+
+ defp namespace(orig) do
+ @namespace_prefix <> orig
+ end
+
+ defp append_trailing_period(str) do
+ str <> "."
+ end
+end
diff --git a/apps/remote_control/lib/mix/tasks/namespace/path.ex b/apps/engine/lib/mix/tasks/namespace/path.ex
similarity index 100%
rename from apps/remote_control/lib/mix/tasks/namespace/path.ex
rename to apps/engine/lib/mix/tasks/namespace/path.ex
diff --git a/apps/remote_control/lib/mix/tasks/namespace/transform/app_directories.ex b/apps/engine/lib/mix/tasks/namespace/transform/app_directories.ex
similarity index 100%
rename from apps/remote_control/lib/mix/tasks/namespace/transform/app_directories.ex
rename to apps/engine/lib/mix/tasks/namespace/transform/app_directories.ex
diff --git a/apps/remote_control/lib/mix/tasks/namespace/transform/apps.ex b/apps/engine/lib/mix/tasks/namespace/transform/apps.ex
similarity index 97%
rename from apps/remote_control/lib/mix/tasks/namespace/transform/apps.ex
rename to apps/engine/lib/mix/tasks/namespace/transform/apps.ex
index c6de667f..a3c1e938 100644
--- a/apps/remote_control/lib/mix/tasks/namespace/transform/apps.ex
+++ b/apps/engine/lib/mix/tasks/namespace/transform/apps.ex
@@ -66,7 +66,7 @@ defmodule Mix.Tasks.Namespace.Transform.Apps do
end
defp visit({:description, desc}) do
- {:description, desc ++ ~c" namespaced by lexical."}
+ {:description, desc ++ ~c" namespaced by expert."}
end
defp visit({:mod, {module_name, args}}) do
diff --git a/apps/remote_control/lib/mix/tasks/namespace/transform/beams.ex b/apps/engine/lib/mix/tasks/namespace/transform/beams.ex
similarity index 100%
rename from apps/remote_control/lib/mix/tasks/namespace/transform/beams.ex
rename to apps/engine/lib/mix/tasks/namespace/transform/beams.ex
diff --git a/apps/remote_control/lib/mix/tasks/namespace/transform/boots.ex b/apps/engine/lib/mix/tasks/namespace/transform/boots.ex
similarity index 100%
rename from apps/remote_control/lib/mix/tasks/namespace/transform/boots.ex
rename to apps/engine/lib/mix/tasks/namespace/transform/boots.ex
diff --git a/apps/remote_control/lib/mix/tasks/namespace/transform/configs.ex b/apps/engine/lib/mix/tasks/namespace/transform/configs.ex
similarity index 100%
rename from apps/remote_control/lib/mix/tasks/namespace/transform/configs.ex
rename to apps/engine/lib/mix/tasks/namespace/transform/configs.ex
diff --git a/apps/remote_control/lib/mix/tasks/namespace/transform/erlang.ex b/apps/engine/lib/mix/tasks/namespace/transform/erlang.ex
similarity index 100%
rename from apps/remote_control/lib/mix/tasks/namespace/transform/erlang.ex
rename to apps/engine/lib/mix/tasks/namespace/transform/erlang.ex
diff --git a/apps/remote_control/lib/mix/tasks/namespace/transform/scripts.ex b/apps/engine/lib/mix/tasks/namespace/transform/scripts.ex
similarity index 97%
rename from apps/remote_control/lib/mix/tasks/namespace/transform/scripts.ex
rename to apps/engine/lib/mix/tasks/namespace/transform/scripts.ex
index 6947b9ce..37ea5271 100644
--- a/apps/remote_control/lib/mix/tasks/namespace/transform/scripts.ex
+++ b/apps/engine/lib/mix/tasks/namespace/transform/scripts.ex
@@ -22,7 +22,7 @@ defmodule Mix.Tasks.Namespace.Transform.Scripts do
end
end
- @script_names ~w(start.script start_clean.script lexical.rel)
+ @script_names ~w(start.script start_clean.script expert.rel)
defp find_scripts(base_directory) do
scripts_glob = "{" <> Enum.join(@script_names, ",") <> "}"
diff --git a/apps/engine/mix.exs b/apps/engine/mix.exs
new file mode 100644
index 00000000..ce4f0eb5
--- /dev/null
+++ b/apps/engine/mix.exs
@@ -0,0 +1,61 @@
+defmodule Engine.MixProject do
+ use Mix.Project
+ Code.require_file("../../mix_includes.exs")
+
+ def project do
+ [
+ app: :engine,
+ version: "0.7.2",
+ elixir: "~> 1.15",
+ start_permanent: Mix.env() == :prod,
+ deps: deps(),
+ dialyzer: Mix.Dialyzer.config(),
+ elixirc_paths: elixirc_paths(Mix.env()),
+ aliases: aliases(),
+ preferred_cli_env: [benchmark: :test]
+ ]
+ end
+
+ def application do
+ [
+ extra_applications: [:logger, :sasl, :eex, :path_glob],
+ mod: {Engine.Application, []}
+ ]
+ end
+
+ # cli/0 is new for elixir 1.15, prior, we need to set `preferred_cli_env` in the project
+ def cli do
+ [
+ preferred_envs: [benchmark: :test]
+ ]
+ end
+
+ defp elixirc_paths(:test) do
+ ~w(lib test/support)
+ end
+
+ defp elixirc_paths(_) do
+ ~w(lib)
+ end
+
+ defp deps do
+ [
+ {:benchee, "~> 1.3", only: :test},
+ {:forge, path: "../forge", env: Mix.env()},
+ Mix.Credo.dependency(),
+ Mix.Dialyzer.dependency(),
+ {:elixir_sense,
+ github: "elixir-lsp/elixir_sense", ref: "73ce7e0d239342fb9527d7ba567203e77dbb9b25"},
+ {:patch, "~> 0.15", only: [:dev, :test], optional: true, runtime: false},
+ {:path_glob, "~> 0.2", optional: true},
+ {:phoenix_live_view, "~> 1.0", only: [:test], optional: true, runtime: false},
+ {:sourceror, "~> 1.9"},
+ {:stream_data, "~> 1.1", only: [:test], runtime: false},
+ {:refactorex, "~> 0.1.51"}
+ ]
+ end
+
+ defp aliases do
+ [test: "test --no-start", benchmark: "run"]
+ end
+end
diff --git a/apps/remote_control/mix.lock b/apps/engine/mix.lock
similarity index 100%
rename from apps/remote_control/mix.lock
rename to apps/engine/mix.lock
diff --git a/apps/remote_control/priv/port_wrapper.sh b/apps/engine/priv/port_wrapper.sh
similarity index 100%
rename from apps/remote_control/priv/port_wrapper.sh
rename to apps/engine/priv/port_wrapper.sh
diff --git a/apps/engine/test/engine/analyzer/aliases_test.exs b/apps/engine/test/engine/analyzer/aliases_test.exs
new file mode 100644
index 00000000..d2c678b0
--- /dev/null
+++ b/apps/engine/test/engine/analyzer/aliases_test.exs
@@ -0,0 +1,730 @@
+defmodule Engine.Analyzer.AliasesTest do
+ alias Engine.Analyzer
+ alias Forge.Ast
+ alias Forge.Document
+
+ import Forge.Test.CursorSupport
+ import Forge.Test.CodeSigil
+ import Forge.Test.RangeSupport
+
+ use ExUnit.Case
+
+ def aliases_at_cursor(text) do
+ {position, document} = pop_cursor(text, as: :document)
+
+ document
+ |> Ast.analyze()
+ |> Analyzer.aliases_at(position)
+ end
+
+ defp scope_aliases(text) do
+ {position, document} = pop_cursor(text, as: :document)
+
+ aliases =
+ document
+ |> Ast.analyze()
+ |> Ast.Analysis.scopes_at(position)
+ |> Enum.flat_map(& &1.aliases)
+ |> Map.new(&{&1.as, &1})
+
+ {aliases, document}
+ end
+
+ describe "top level aliases" do
+ test "a useless alias" do
+ aliases =
+ ~q[
+ alias Foo
+ |
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Foo] == Foo
+ end
+
+ test "an alias outside of a module" do
+ aliases =
+ ~q[
+ alias Foo.Bar.Baz
+ defmodule Parent do
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Baz] == Foo.Bar.Baz
+ end
+
+ test "an alias inside the body of a module" do
+ aliases =
+ ~q[
+ defmodule Basic do
+ alias Foo.Bar
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases == %{__MODULE__: Basic, Bar: Foo.Bar, Basic: Basic}
+ end
+
+ test "an alias using as" do
+ aliases =
+ ~q[
+ defmodule TopLevel do
+ alias Foo.Bar, as: FooBar
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:__MODULE__] == TopLevel
+ assert aliases[:FooBar] == Foo.Bar
+ end
+
+ test "an alias using warn" do
+ aliases =
+ ~q[
+ defmodule TopLevel do
+ alias Foo.Bar, warn: false
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Bar] == Foo.Bar
+ end
+
+ test "an alias using warn and as" do
+ aliases =
+ ~q[
+ defmodule TopLevel do
+ alias Foo.Bar, warn: false, as: FooBar
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:FooBar] == Foo.Bar
+ end
+
+ test "multiple aliases off of single alias" do
+ aliases =
+ ~q[
+ defmodule TopLevel do
+ alias Foo.{First, Second, Third.Fourth}
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:First] == Foo.First
+ assert aliases[:Second] == Foo.Second
+ assert aliases[:Fourth] == Foo.Third.Fourth
+ end
+
+ test "multiple aliases off of nested alias" do
+ aliases =
+ ~q[
+ defmodule TopLevel do
+ alias Foo.Bar.{First, Second, Third.Fourth}
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:First] == Foo.Bar.First
+ assert aliases[:Second] == Foo.Bar.Second
+ assert aliases[:Fourth] == Foo.Bar.Third.Fourth
+ end
+
+ test "aliasing __MODULE__" do
+ aliases =
+ ~q[
+ defmodule Something.Is.Nested do
+ alias __MODULE__|
+
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Nested] == Something.Is.Nested
+ end
+
+ test "multiple aliases leading by current module" do
+ aliases =
+ ~q[
+ defmodule TopLevel do
+ alias __MODULE__.{First, Second}
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:First] == TopLevel.First
+ assert aliases[:Second] == TopLevel.Second
+ end
+
+ test "multiple aliases leading by current module's child" do
+ aliases =
+ ~q[
+ defmodule TopLevel do
+ alias __MODULE__.Child.{First, Second}
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:First] == TopLevel.Child.First
+ assert aliases[:Second] == TopLevel.Child.Second
+ end
+
+ test "aliases expanding other aliases" do
+ aliases =
+ ~q[
+ alias Foo.Bar.Baz
+ alias Baz.Quux|
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Baz] == Foo.Bar.Baz
+ assert aliases[:Quux] == Foo.Bar.Baz.Quux
+ end
+
+ test "aliases expanding current module" do
+ aliases = ~q[
+ defmodule TopLevel do
+ alias __MODULE__.Foo|
+ end
+ ] |> aliases_at_cursor()
+
+ assert aliases[:Foo] == TopLevel.Foo
+ end
+
+ test "aliases expanding current module using as" do
+ aliases = ~q[
+ defmodule TopLevel do
+ alias __MODULE__.Foo, as: OtherAlias|
+ end
+ ] |> aliases_at_cursor()
+
+ assert aliases[:OtherAlias] == TopLevel.Foo
+ end
+
+ test "can be overridden" do
+ aliases =
+ ~q[
+ alias Foo.Bar.Baz
+ alias Other.Baz
+ |
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Baz] == Other.Baz
+ end
+
+ test "can be accessed before being overridden" do
+ aliases =
+ ~q[
+ alias Foo.Bar.Baz
+ |
+ alias Other.Baz
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Baz] == Foo.Bar.Baz
+ end
+
+ test "aliases used to define a module" do
+ aliases =
+ ~q[
+ alias Something.Else
+ defmodule Else.Other do
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Else] == Something.Else
+ end
+
+ test "in a protocol implementation" do
+ aliases =
+ ~q[
+ defimpl MyProtocol, for: Atom do
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:"@protocol"] == MyProtocol
+ assert aliases[:"@for"] == Atom
+ end
+ end
+
+ describe "alias ranges" do
+ test "for a simple alias" do
+ {aliases, doc} =
+ ~q[
+ defmodule Parent do
+ alias Foo.Bar.Baz|
+ end
+ ]
+ |> scope_aliases()
+
+ assert decorate(doc, aliases[:Baz].range) =~ " «alias Foo.Bar.Baz»"
+ end
+
+ test "for a multiple alias on one line" do
+ {aliases, doc} =
+ ~q[
+ defmodule Parent do
+ alias Foo.Bar.{Baz, Quux}|
+ end
+ ]
+ |> scope_aliases()
+
+ assert decorate(doc, aliases[:Baz].range) =~ " «alias Foo.Bar.{Baz, Quux}»"
+ assert decorate(doc, aliases[:Quux].range) =~ " «alias Foo.Bar.{Baz, Quux}»"
+ end
+
+ test "for a multiple alias on multiple lines" do
+ {aliases, doc} =
+ ~q[
+ defmodule Parent do
+ alias Foo.Bar.{
+ Baz,
+ Quux,
+ Other
+ }|
+ end
+ ]
+ |> scope_aliases()
+
+ for name <- [:Baz, :Quux, :Other] do
+ assert decorate(doc, aliases[name].range) =~
+ " «alias Foo.Bar.{\n Baz,\n Quux,\n Other\n}»"
+ end
+ end
+
+ def column_after_do(%Document{} = doc, line) do
+ with {:ok, text} <- Document.fetch_text_at(doc, line),
+ {:ok, column} <- find_do_position(text, 0) do
+ column + 2
+ else
+ _ ->
+ :not_found
+ end
+ end
+
+ def find_do_position("do" <> _, position) do
+ {:ok, position}
+ end
+
+ def find_do_position(<<_c::utf8, rest::binary>>, position) do
+ find_do_position(rest, position + 1)
+ end
+
+ def find_do_position(<<>>, _) do
+ :not_found
+ end
+
+ test "__MODULE__ implicit aliases don't have a visible range" do
+ {aliases, doc} =
+ ~q[
+ defmodule MyModule do
+ |
+ end
+ ]
+ |> scope_aliases()
+
+ module_range = aliases[:__MODULE__].range
+
+ refute aliases[:__MODULE__].explicit?
+ assert module_range.start.line == 1
+ assert module_range.start.character == column_after_do(doc, 1)
+ assert module_range.start == module_range.end
+ end
+
+ test "implicit parent alias doesn't have a range" do
+ {aliases, doc} =
+ ~q[
+ defmodule Parent do
+ defmodule Child do
+ |
+ end
+ end
+ ]
+ |> scope_aliases()
+
+ parent_range = aliases[:Parent].range
+
+ refute aliases[:Parent].explicit?
+ assert parent_range.start.line == 1
+ assert parent_range.start.character == column_after_do(doc, 1)
+ assert parent_range.start == parent_range.end
+ end
+
+ test "protocol implicit aliases doesn't have a visible range" do
+ {aliases, doc} =
+ ~q[
+ defimpl MyThing, for: MyProtocol do
+ |
+ end
+ ]
+ |> scope_aliases()
+
+ # the implicit aliases don't have any text in their range
+
+ for_range = aliases[:"@for"].range
+ refute aliases[:"@for"].explicit?
+ assert for_range.start.line == 1
+ assert for_range.start.character == column_after_do(doc, 1)
+ assert for_range.start == for_range.end
+
+ protocol_range = aliases[:"@protocol"].range
+ refute aliases[:"@protocol"].explicit?
+ assert protocol_range.start.line == 1
+ assert protocol_range.start.character == column_after_do(doc, 1)
+ assert protocol_range.start == protocol_range.end
+ end
+ end
+
+ describe "nested modules" do
+ test "no aliases are defined for modules with dots" do
+ aliases =
+ ~q[
+ defmodule GrandParent.Parent.Child do
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ refute Map.has_key?(aliases, :Child)
+ end
+
+ test "with children get their parents name" do
+ aliases =
+ ~q[
+ defmodule Grandparent.Parent do
+ defmodule Child do
+ |
+ end
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Child] == Grandparent.Parent.Child
+ assert aliases[:__MODULE__] == Grandparent.Parent.Child
+ end
+
+ test "with a child that has an explicit parent" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ defmodule __MODULE__.Child do
+ |
+ end
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:__MODULE__] == Parent.Child
+ end
+ end
+
+ describe "alias scopes" do
+ test "aliases are removed when leaving a module" do
+ aliases =
+ ~q[
+ defmodule Basic do
+ alias Foo.Bar
+ end|
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases == %{Basic: Basic}
+ end
+
+ test "aliases inside of nested modules" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ alias Foo.Grandparent
+
+ defmodule Child do
+ alias Foo.Something
+ |
+ end
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Grandparent] == Foo.Grandparent
+ assert aliases[:Something] == Foo.Something
+ assert aliases[:__MODULE__] == Parent.Child
+ assert aliases[:Child] == Parent.Child
+ end
+
+ test "multiple nested module are aliased after definition" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ alias Foo.Grandparent
+
+ defmodule Child do
+ alias Foo.Something
+ end
+
+ defmodule AnotherChild do
+ alias Foo.Something
+ end
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:AnotherChild] == Parent.AnotherChild
+ assert aliases[:Child] == Parent.Child
+ end
+
+ test "an alias defined in a named function" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ def fun do
+ alias Foo.Parent
+ |
+ end
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Parent] == Foo.Parent
+ end
+
+ test "an alias defined in a named function doesn't leak" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ def fun do
+ alias Foo.Parent
+ end|
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Parent] == Parent
+ end
+
+ test "an alias defined in a private named function" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ defp fun do
+ alias Foo.Parent
+ |
+ end
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Parent] == Foo.Parent
+ end
+
+ test "an alias defined in a private named function doesn't leak" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ defp fun do
+ alias Foo.InFun
+ end|
+ end
+ ]
+ |> aliases_at_cursor()
+
+ refute aliases[:InFun]
+ end
+
+ test "an alias defined in a DSL" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ my_dsl do
+ alias Foo.Parent
+ |
+ end
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Parent] == Foo.Parent
+ end
+
+ test "an alias defined in a DSL does not leak" do
+ aliases =
+ ~q[
+ defmodule Parent do
+ my_dsl do
+ alias Foo.InDSL
+ end
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ refute aliases[InDsl]
+ end
+
+ test "an alias defined in an if statement" do
+ aliases =
+ ~q[
+ if test() do
+ alias Foo.Something
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Something]
+ end
+
+ test "an alias defined in an if statement does not leak" do
+ aliases =
+ ~q[
+ if test() do
+ alias Foo.Something
+ end
+ |
+ ]
+ |> aliases_at_cursor()
+
+ refute aliases[:Something]
+ end
+
+ test "an alias defined in an cond statement" do
+ aliases =
+ ~q[
+ cond do
+ something() ->
+ alias Foo.Something
+ |Else
+ true ->
+ :ok
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Something]
+ end
+
+ test "an alias defined in an cond statement shouldn't leak" do
+ aliases =
+ ~q[
+ cond do
+ something() ->
+ alias Foo.Something
+ true ->
+ |
+ :ok
+ end
+ ]
+ |> aliases_at_cursor()
+
+ refute aliases[:Something]
+
+ aliases =
+ ~q[
+ cond do
+ something() ->
+ alias Foo.Something
+ true ->
+ :ok
+ end
+ |
+ ]
+ |> aliases_at_cursor()
+
+ refute aliases[:Something]
+ end
+
+ test "an alias defined in an with statement" do
+ aliases =
+ ~q[
+ with {:ok, val} <- some_function() do
+ alias Foo.Something
+ |
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Something]
+ end
+
+ test "an alias defined in an with statement shouldn't leak" do
+ aliases =
+ ~q[
+ with {:ok, val} <- some_function() do
+ alias Foo.Something
+ end
+ |
+ ]
+ |> aliases_at_cursor()
+
+ refute aliases[:Something]
+ end
+
+ test "sibling modules with nested blocks" do
+ aliases =
+ ~q[
+ defmodule First do
+ defstuff do
+ field :x
+ end
+ end
+
+ defmodule Second do
+ defstuff do
+ field :y
+ end
+ end
+ |
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:First] == First
+ assert aliases[:Second] == Second
+ end
+
+ test "an alias defined in a anonymous function" do
+ aliases =
+ ~q[
+ fn x ->
+ alias Foo.Bar
+ Bar|
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Bar] == Foo.Bar
+ end
+
+ test "an alias defined in a anonymous function doesn't leak" do
+ aliases =
+ ~q[
+ fn
+ x ->
+ alias Foo.Bar
+ Bar.bar(x)
+ y ->
+ alias Baz.Buzz
+ |Buzz
+ end
+ ]
+ |> aliases_at_cursor()
+
+ assert aliases[:Buzz] == Baz.Buzz
+ refute aliases[:Bar]
+ end
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/analyzer/imports_test.exs b/apps/engine/test/engine/analyzer/imports_test.exs
similarity index 98%
rename from apps/remote_control/test/lexical/remote_control/analyzer/imports_test.exs
rename to apps/engine/test/engine/analyzer/imports_test.exs
index 7b84f6e3..e61548ed 100644
--- a/apps/remote_control/test/lexical/remote_control/analyzer/imports_test.exs
+++ b/apps/engine/test/engine/analyzer/imports_test.exs
@@ -37,12 +37,12 @@ defmodule WithSigils do
end
end
-defmodule Lexical.Ast.Analysis.ImportsTest do
- alias Lexical.Ast
- alias Lexical.RemoteControl.Analyzer
+defmodule Engine.Ast.Analysis.ImportsTest do
+ alias Engine.Analyzer
+ alias Forge.Ast
alias Parent.Child.ImportedModule
- import Lexical.Test.CursorSupport
- import Lexical.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Forge.Test.CodeSigil
use ExUnit.Case
diff --git a/apps/remote_control/test/lexical/remote_control/analyzer/requires_test.exs b/apps/engine/test/engine/analyzer/requires_test.exs
similarity index 91%
rename from apps/remote_control/test/lexical/remote_control/analyzer/requires_test.exs
rename to apps/engine/test/engine/analyzer/requires_test.exs
index b19a07a7..df31bfc2 100644
--- a/apps/remote_control/test/lexical/remote_control/analyzer/requires_test.exs
+++ b/apps/engine/test/engine/analyzer/requires_test.exs
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.Analyzer.RequiresTest do
- alias Lexical.Ast
- alias Lexical.RemoteControl.Analyzer
+defmodule Engine.Analyzer.RequiresTest do
+ alias Engine.Analyzer
+ alias Forge.Ast
- import Lexical.Test.CursorSupport
- import Lexical.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Forge.Test.CodeSigil
use ExUnit.Case
diff --git a/apps/remote_control/test/lexical/remote_control/analyzer/uses_test.exs b/apps/engine/test/engine/analyzer/uses_test.exs
similarity index 92%
rename from apps/remote_control/test/lexical/remote_control/analyzer/uses_test.exs
rename to apps/engine/test/engine/analyzer/uses_test.exs
index 19f342b7..7f5f4e8c 100644
--- a/apps/remote_control/test/lexical/remote_control/analyzer/uses_test.exs
+++ b/apps/engine/test/engine/analyzer/uses_test.exs
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.Analyzer.UsesTest do
- alias Lexical.Ast
- alias Lexical.RemoteControl.Analyzer
+defmodule Engine.Analyzer.UsesTest do
+ alias Engine.Analyzer
+ alias Forge.Ast
- import Lexical.Test.CursorSupport
- import Lexical.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Forge.Test.CodeSigil
use ExUnit.Case
diff --git a/apps/remote_control/test/lexical/remote_control/analyzer_test.exs b/apps/engine/test/engine/analyzer_test.exs
similarity index 97%
rename from apps/remote_control/test/lexical/remote_control/analyzer_test.exs
rename to apps/engine/test/engine/analyzer_test.exs
index 4b7ed6ff..aea7decf 100644
--- a/apps/remote_control/test/lexical/remote_control/analyzer_test.exs
+++ b/apps/engine/test/engine/analyzer_test.exs
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.AnalyzerTest do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.RemoteControl.Analyzer
+defmodule Engine.AnalyzerTest do
+ alias Engine.Analyzer
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
use ExUnit.Case, async: true
diff --git a/apps/remote_control/test/lexical/remote_control/api/proxy/buffering_state_test.exs b/apps/engine/test/engine/api/proxy/buffering_state_test.exs
similarity index 92%
rename from apps/remote_control/test/lexical/remote_control/api/proxy/buffering_state_test.exs
rename to apps/engine/test/engine/api/proxy/buffering_state_test.exs
index 851a7a94..49adc6cf 100644
--- a/apps/remote_control/test/lexical/remote_control/api/proxy/buffering_state_test.exs
+++ b/apps/engine/test/engine/api/proxy/buffering_state_test.exs
@@ -1,13 +1,13 @@
-defmodule Lexical.RemoteControl.Api.Proxy.BufferingStateStateTest do
- alias Lexical.Document
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Api.Proxy
- alias Lexical.RemoteControl.Api.Proxy.BufferingState
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Commands
-
- import Lexical.Test.Fixtures
+defmodule Engine.Api.Proxy.BufferingStateStateTest do
+ alias Forge.Document
+
+ alias Engine.Api.Messages
+ alias Engine.Api.Proxy
+ alias Engine.Api.Proxy.BufferingState
+ alias Engine.Build
+ alias Engine.Commands
+
+ import Engine.Test.Fixtures
import Messages
import Proxy.Records
@@ -134,7 +134,7 @@ defmodule Lexical.RemoteControl.Api.Proxy.BufferingStateStateTest do
mfa
message ->
- mfa(module: RemoteControl.Dispatch, function: :broadcast, arguments: [message])
+ mfa(module: Engine.Dispatch, function: :broadcast, arguments: [message])
end)
end
diff --git a/apps/remote_control/test/lexical/remote_control/api/proxy_test.exs b/apps/engine/test/engine/api/proxy_test.exs
similarity index 92%
rename from apps/remote_control/test/lexical/remote_control/api/proxy_test.exs
rename to apps/engine/test/engine/api/proxy_test.exs
index 51a6f770..69661b9c 100644
--- a/apps/remote_control/test/lexical/remote_control/api/proxy_test.exs
+++ b/apps/engine/test/engine/api/proxy_test.exs
@@ -1,26 +1,26 @@
-defmodule Lexical.RemoteControl.Api.ProxyTest do
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api
- alias Lexical.RemoteControl.Api.Proxy
- alias Lexical.RemoteControl.Api.Proxy.BufferingState
- alias Lexical.RemoteControl.Api.Proxy.DrainingState
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.CodeMod
- alias Lexical.RemoteControl.Commands
- alias Lexical.RemoteControl.Dispatch
+defmodule Engine.Api.ProxyTest do
+ alias Forge.Document
+ alias Forge.Document.Changes
+
+ alias Engine.Api
+ alias Engine.Api.Proxy
+ alias Engine.Api.Proxy.BufferingState
+ alias Engine.Api.Proxy.DrainingState
+ alias Engine.Build
+ alias Engine.CodeMod
+ alias Engine.Commands
+ alias Engine.Dispatch
use ExUnit.Case
use Patch
import Api.Messages
- import Lexical.Test.Fixtures
+ import Engine.Test.Fixtures
setup do
start_supervised!(Api.Proxy)
project = project()
- RemoteControl.set_project(project)
+ Engine.set_project(project)
{:ok, project: project}
end
diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/config_test.exs b/apps/engine/test/engine/build/document/compilers/config_test.exs
similarity index 94%
rename from apps/remote_control/test/lexical/remote_control/build/document/compilers/config_test.exs
rename to apps/engine/test/engine/build/document/compilers/config_test.exs
index e8fb49df..50e6f6c1 100644
--- a/apps/remote_control/test/lexical/remote_control/build/document/compilers/config_test.exs
+++ b/apps/engine/test/engine/build/document/compilers/config_test.exs
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.ConfigTest do
- alias Lexical.Document
- alias Lexical.RemoteControl.Build.Document.Compilers
+defmodule Engine.Build.Document.Compilers.ConfigTest do
+ alias Engine.Build.Document.Compilers
+ alias Forge.Document
use ExUnit.Case
- import Lexical.Test.CodeSigil
+ import Forge.Test.CodeSigil
import Compilers.Config
def document_with_path(left, right) do
@@ -126,7 +126,7 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.ConfigTest do
~q[
import Config
- System.fetch_env!("_LEXICAL_NON_EXISTING_ENV_VAR_")
+ System.fetch_env!("_EXPERT_NON_EXISTING_ENV_VAR_")
]
|> document()
|> compile()
diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/eex_test.exs b/apps/engine/test/engine/build/document/compilers/eex_test.exs
similarity index 90%
rename from apps/remote_control/test/lexical/remote_control/build/document/compilers/eex_test.exs
rename to apps/engine/test/engine/build/document/compilers/eex_test.exs
index 08e45da8..cc8b6dfa 100644
--- a/apps/remote_control/test/lexical/remote_control/build/document/compilers/eex_test.exs
+++ b/apps/engine/test/engine/build/document/compilers/eex_test.exs
@@ -1,19 +1,19 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.EExTest do
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic.Result
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.CaptureServer
- alias Lexical.RemoteControl.Build.Document.Compilers
- alias Lexical.RemoteControl.Dispatch
- alias Lexical.RemoteControl.ModuleMappings
+defmodule Engine.Build.Document.Compilers.EExTest do
+ alias Engine.Build
+ alias Engine.Build.CaptureServer
+ alias Engine.Build.Document.Compilers
+ alias Engine.Dispatch
+ alias Engine.ModuleMappings
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic.Result
use ExUnit.Case
import Compilers.EEx, only: [recognizes?: 1]
- import Lexical.Test.Quiet
- import Lexical.Test.CodeSigil
- import Lexical.Test.DiagnosticSupport
- import Lexical.Test.RangeSupport
+ import Forge.Test.Quiet
+ import Forge.Test.CodeSigil
+ import Forge.Test.DiagnosticSupport
+ import Forge.Test.RangeSupport
def with_capture_server(_) do
start_supervised!(CaptureServer)
diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/elixir_test.exs b/apps/engine/test/engine/build/document/compilers/elixir_test.exs
similarity index 86%
rename from apps/remote_control/test/lexical/remote_control/build/document/compilers/elixir_test.exs
rename to apps/engine/test/engine/build/document/compilers/elixir_test.exs
index b2dc3b95..2192ad5c 100644
--- a/apps/remote_control/test/lexical/remote_control/build/document/compilers/elixir_test.exs
+++ b/apps/engine/test/engine/build/document/compilers/elixir_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.ElixirTest do
- alias Lexical.Document
- alias Lexical.RemoteControl.Build.Document.Compilers
+defmodule Engine.Build.Document.Compilers.ElixirTest do
+ alias Engine.Build.Document.Compilers
+ alias Forge.Document
use ExUnit.Case
import Compilers.Elixir
diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs b/apps/engine/test/engine/build/document/compilers/heex_test.exs
similarity index 88%
rename from apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs
rename to apps/engine/test/engine/build/document/compilers/heex_test.exs
index 54e86376..d5f3907b 100644
--- a/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs
+++ b/apps/engine/test/engine/build/document/compilers/heex_test.exs
@@ -1,14 +1,14 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.HeexTest do
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic.Result
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.CaptureServer
- alias Lexical.RemoteControl.Build.Document.Compilers
- alias Lexical.RemoteControl.Dispatch
- alias Lexical.RemoteControl.ModuleMappings
-
- import Lexical.Test.CodeSigil
- import Lexical.Test.Quiet
+defmodule Engine.Build.Document.Compilers.HeexTest do
+ alias Engine.Build
+ alias Engine.Build.CaptureServer
+ alias Engine.Build.Document.Compilers
+ alias Engine.Dispatch
+ alias Engine.ModuleMappings
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic.Result
+
+ import Forge.Test.CodeSigil
+ import Forge.Test.Quiet
use ExUnit.Case
def with_capture_server(_) do
diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/quoted_test.exs b/apps/engine/test/engine/build/document/compilers/quoted_test.exs
similarity index 89%
rename from apps/remote_control/test/lexical/remote_control/build/document/compilers/quoted_test.exs
rename to apps/engine/test/engine/build/document/compilers/quoted_test.exs
index b11c2c6f..49f04acd 100644
--- a/apps/remote_control/test/lexical/remote_control/build/document/compilers/quoted_test.exs
+++ b/apps/engine/test/engine/build/document/compilers/quoted_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.QuotedTest do
- alias Lexical.RemoteControl.Build.Document.Compilers.Quoted
+defmodule Engine.Build.Document.Compilers.QuotedTest do
+ alias Engine.Build.Document.Compilers.Quoted
- import Lexical.Test.CodeSigil
+ import Forge.Test.CodeSigil
use ExUnit.Case, async: true
@@ -27,8 +27,8 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.QuotedTest do
|> parse!()
assert quoted |> Quoted.wrap_top_level_forms() |> Macro.to_string() == """
- defmodule :lexical_wrapper_0 do
- def __lexical_wrapper__([]) do
+ defmodule :expert_wrapper_0 do
+ def __expert_wrapper__([]) do
foo = 1
_ = foo + 1
end
@@ -40,8 +40,8 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.QuotedTest do
:ok
end
- defmodule :lexical_wrapper_2 do
- def __lexical_wrapper__([foo, bar]) do
+ defmodule :expert_wrapper_2 do
+ def __expert_wrapper__([foo, bar]) do
_ = bar + foo
end
end\
diff --git a/apps/remote_control/test/lexical/remote_control/build/error/parse_test.exs b/apps/engine/test/engine/build/error/parse_test.exs
similarity index 97%
rename from apps/remote_control/test/lexical/remote_control/build/error/parse_test.exs
rename to apps/engine/test/engine/build/error/parse_test.exs
index e1098767..4a2b8f09 100644
--- a/apps/remote_control/test/lexical/remote_control/build/error/parse_test.exs
+++ b/apps/engine/test/engine/build/error/parse_test.exs
@@ -1,12 +1,12 @@
-defmodule Lexical.RemoteControl.Build.Error.ParseTest do
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.RemoteControl.Build
-
- alias Lexical.RemoteControl.Build.CaptureServer
- alias Lexical.RemoteControl.Dispatch
- import Lexical.Test.RangeSupport
- import Lexical.Test.DiagnosticSupport
+defmodule Engine.Build.Error.ParseTest do
+ alias Engine.Build
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
+
+ alias Engine.Build.CaptureServer
+ alias Engine.Dispatch
+ import Forge.Test.RangeSupport
+ import Forge.Test.DiagnosticSupport
use ExUnit.Case, async: true
diff --git a/apps/remote_control/test/lexical/remote_control/build/error_test.exs b/apps/engine/test/engine/build/error_test.exs
similarity index 98%
rename from apps/remote_control/test/lexical/remote_control/build/error_test.exs
rename to apps/engine/test/engine/build/error_test.exs
index dd8d2f85..78fd77a2 100644
--- a/apps/remote_control/test/lexical/remote_control/build/error_test.exs
+++ b/apps/engine/test/engine/build/error_test.exs
@@ -1,17 +1,17 @@
-defmodule Lexical.RemoteControl.Build.ErrorTest do
- alias Lexical.Document
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.CaptureServer
- alias Lexical.RemoteControl.ModuleMappings
+defmodule Engine.Build.ErrorTest do
+ alias Engine.Build
+ alias Engine.Build.CaptureServer
+ alias Engine.ModuleMappings
+ alias Forge.Document
require Logger
- import Lexical.Test.DiagnosticSupport
- import Lexical.Test.RangeSupport
+ import Forge.Test.DiagnosticSupport
+ import Forge.Test.RangeSupport
use ExUnit.Case
use Patch
setup do
- start_supervised!(Lexical.RemoteControl.Dispatch)
+ start_supervised!(Engine.Dispatch)
start_supervised!(CaptureServer)
:ok
end
diff --git a/apps/engine/test/engine/build/state_test.exs b/apps/engine/test/engine/build/state_test.exs
new file mode 100644
index 00000000..8041e8bb
--- /dev/null
+++ b/apps/engine/test/engine/build/state_test.exs
@@ -0,0 +1,152 @@
+defmodule Engine.Build.StateTest do
+ alias Engine.Build
+ alias Engine.Build.State
+ alias Engine.Plugin
+ alias Forge.Document
+ alias Forge.Project
+
+ import Engine.Test.Fixtures
+
+ use ExUnit.Case, async: false
+ use Patch
+
+ setup do
+ start_supervised!(Engine.Dispatch)
+ start_supervised!(Engine.Api.Proxy)
+ start_supervised!(Build.CaptureServer)
+ start_supervised!(Engine.ModuleMappings)
+ start_supervised!(Plugin.Runner.Coordinator)
+ start_supervised!(Plugin.Runner.Supervisor)
+ :ok
+ end
+
+ def document(%State{} = state, filename \\ "file.ex", source_code) do
+ sequence = System.unique_integer([:monotonic, :positive])
+
+ uri =
+ state.project
+ |> Project.root_path()
+ |> Path.join(to_string(sequence))
+ |> Path.join(filename)
+ |> Document.Path.to_uri()
+
+ Document.new(uri, source_code, 0)
+ end
+
+ def with_project_state(project_name) do
+ test = self()
+
+ patch(Engine.Dispatch, :broadcast, &send(test, &1))
+
+ project_name = to_string(project_name)
+ fixture_dir = Path.join(fixtures_path(), project_name)
+ project = Project.new("file://#{fixture_dir}")
+ state = State.new(project)
+
+ Engine.set_project(project)
+ {:ok, state}
+ end
+
+ def with_metadata_project(_) do
+ {:ok, state} = with_project_state(:project_metadata)
+ {:ok, state: state}
+ end
+
+ def with_a_valid_document(%{state: state}) do
+ source = ~S[
+ defmodule Testing.ValidSource do
+ def add(a, b) do
+ a + b
+ end
+ end
+ ]
+
+ document = document(state, source)
+ {:ok, document: document}
+ end
+
+ def with_patched_compilation(_) do
+ patch(Build.Document, :compile, :ok)
+ patch(Build.Project, :compile, :ok)
+ :ok
+ end
+
+ describe "throttled document compilation" do
+ setup [:with_metadata_project, :with_a_valid_document, :with_patched_compilation]
+
+ test "it doesn't compile immediately", %{state: state, document: document} do
+ State.on_file_compile(state, document)
+
+ refute_called(Build.Document.compile(document))
+ refute_called(Build.Project.compile(_, _))
+ end
+
+ test "it compiles files when on_timeout is called", %{state: state, document: document} do
+ state
+ |> State.on_file_compile(document)
+ |> State.on_timeout()
+
+ assert_called(Build.Document.compile(document))
+ refute_called(Build.Project.compile(_, _))
+ end
+ end
+
+ describe "throttled project compilation" do
+ setup [:with_metadata_project, :with_a_valid_document, :with_patched_compilation]
+
+ test "doesn't compile immediately if forced", %{state: state} do
+ State.on_project_compile(state, true)
+ refute_called(Build.Project.compile(_, _))
+ end
+
+ test "doesn't compile immediately", %{state: state} do
+ State.on_project_compile(state, false)
+ refute_called(Build.Project.compile(_, _))
+ end
+
+ test "compiles if force is true after on_timeout is called", %{state: state} do
+ state
+ |> State.on_project_compile(true)
+ |> State.on_timeout()
+
+ assert_called(Build.Project.compile(_, true))
+ end
+
+ test "compiles after on_timeout is called", %{state: state} do
+ state
+ |> State.on_project_compile(false)
+ |> State.on_timeout()
+
+ assert_called(Build.Project.compile(_, false))
+ end
+ end
+
+ describe "mixed compilation" do
+ setup [:with_metadata_project, :with_a_valid_document, :with_patched_compilation]
+
+ test "doesn't compile if both documents and projects are added", %{
+ state: state,
+ document: document
+ } do
+ state
+ |> State.on_project_compile(false)
+ |> State.on_file_compile(document)
+
+ refute_called(Build.Document.compile(_))
+ refute_called(Build.Project.compile(_, _))
+ end
+
+ test "compiles when on_timeout is called if both documents and projects are added", %{
+ state: state,
+ document: document
+ } do
+ state
+ |> State.on_project_compile(false)
+ |> State.on_file_compile(document)
+ |> State.on_timeout()
+
+ assert_called(Build.Document.compile(_))
+ assert_called(Build.Project.compile(_, _))
+ end
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/build_test.exs b/apps/engine/test/engine/build_test.exs
similarity index 93%
rename from apps/remote_control/test/lexical/remote_control/build_test.exs
rename to apps/engine/test/engine/build_test.exs
index 5e35c7d2..ffda124a 100644
--- a/apps/remote_control/test/lexical/remote_control/build_test.exs
+++ b/apps/engine/test/engine/build_test.exs
@@ -1,16 +1,16 @@
-defmodule Lexical.BuildTest do
+defmodule Engine.BuildTest do
alias Elixir.Features
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.ProjectNodeSupervisor
+
+ alias Engine.Api.Messages
+ alias Engine.Build
+ alias Engine.ProjectNodeSupervisor
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
+ alias Forge.Project
import Messages
- import Lexical.Test.Fixtures
- import Lexical.Test.DiagnosticSupport
+ import Engine.Test.Fixtures
+ import Forge.Test.DiagnosticSupport
use ExUnit.Case
use Patch
@@ -29,7 +29,7 @@ defmodule Lexical.BuildTest do
end
source = Document.new(uri, source_code, 0)
- RemoteControl.call(project, Build, :force_compile_document, [source])
+ Engine.call(project, Build, :force_compile_document, [source])
end
def with_project(project_name) do
@@ -42,8 +42,8 @@ defmodule Lexical.BuildTest do
|> File.rm_rf()
{:ok, _} = start_supervised({ProjectNodeSupervisor, project})
- {:ok, _, _} = RemoteControl.start_link(project)
- RemoteControl.Api.register_listener(project, self(), [:all])
+ {:ok, _, _} = Engine.start_link(project)
+ Engine.Api.register_listener(project, self(), [:all])
{:ok, project}
end
@@ -72,7 +72,7 @@ defmodule Lexical.BuildTest do
describe "compiling a project" do
test "sends a message when complete " do
{:ok, project} = with_project(:project_metadata)
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.schedule_compile(project, true)
assert_receive project_compiled(status: :success)
assert_receive project_progress(label: "Building " <> project_name)
@@ -82,7 +82,7 @@ defmodule Lexical.BuildTest do
test "receives metadata about the defined modules" do
{:ok, project} = with_project(:project_metadata)
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.schedule_compile(project, true)
assert_receive module_updated(name: ProjectMetadata, functions: functions)
assert {:zero_arity, 0} in functions
@@ -94,7 +94,7 @@ defmodule Lexical.BuildTest do
describe "compiling an umbrella project" do
test "it sends a message when compilation is complete" do
{:ok, project} = with_project(:umbrella)
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.schedule_compile(project, true)
assert_receive project_compiled(status: :success)
assert_receive project_diagnostics(diagnostics: [])
@@ -119,7 +119,7 @@ defmodule Lexical.BuildTest do
describe "compiling a project that has errors" do
test "it reports the errors" do
{:ok, project} = with_project(:compilation_errors)
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.schedule_compile(project, true)
assert_receive project_compiled(status: :error)
assert_receive project_diagnostics(diagnostics: [%Diagnostic.Result{}])
@@ -132,7 +132,7 @@ defmodule Lexical.BuildTest do
@feature_condition span_in_diagnostic?: false
@tag execute_if(@feature_condition)
test "stuff", %{project: project} do
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.schedule_compile(project, true)
assert_receive project_compiled(status: :error)
assert_receive project_diagnostics(diagnostics: [%Diagnostic.Result{} = diagnostic])
@@ -144,7 +144,7 @@ defmodule Lexical.BuildTest do
@feature_condition span_in_diagnostic?: true
@tag execute_if(@feature_condition)
test "stuff when #{inspect(@feature_condition)}", %{project: project} do
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.schedule_compile(project, true)
assert_receive project_compiled(status: :error)
assert_receive project_diagnostics(diagnostics: [%Diagnostic.Result{} = diagnostic])
@@ -160,7 +160,7 @@ defmodule Lexical.BuildTest do
describe "when compiling a project that has warnings" do
test "it reports them" do
{:ok, project} = with_project(:compilation_warnings)
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.schedule_compile(project, true)
assert_receive project_compiled(status: :success)
assert_receive project_diagnostics(diagnostics: diagnostics)
@@ -174,8 +174,8 @@ defmodule Lexical.BuildTest do
end
def with_patched_state_timeout(_) do
- patch(Lexical.RemoteControl.Build.State, :should_compile?, true)
- patch(Lexical.RemoteControl.Build.State, :edit_window_millis, 50)
+ patch(Engine.Build.State, :should_compile?, true)
+ patch(Engine.Build.State, :edit_window_millis, 50)
:ok
end
@@ -565,7 +565,7 @@ defmodule Lexical.BuildTest do
end
def loaded?(project, module) do
- RemoteControl.call(project, Code, :ensure_loaded?, [module])
+ Engine.call(project, Code, :ensure_loaded?, [module])
end
describe "module sanitization" do
@@ -649,8 +649,8 @@ defmodule Lexical.BuildTest do
describe ".exs files" do
setup do
- start_supervised!(RemoteControl.Dispatch)
- start_supervised!(RemoteControl.ModuleMappings)
+ start_supervised!(Engine.Dispatch)
+ start_supervised!(Engine.ModuleMappings)
start_supervised!(Build.CaptureServer)
:ok
end
@@ -675,7 +675,7 @@ defmodule Lexical.BuildTest do
describe "exceptions during compilation" do
test "compiling a project with callback errors" do
{:ok, project} = with_project(:compilation_callback_errors)
- RemoteControl.Api.schedule_compile(project, false)
+ Engine.Api.schedule_compile(project, false)
assert_receive project_compiled(status: :error)
assert_receive project_diagnostics(diagnostics: [diagnostic])
diff --git a/apps/engine/test/engine/code_action/handlers/add_alias_test.exs b/apps/engine/test/engine/code_action/handlers/add_alias_test.exs
new file mode 100644
index 00000000..ed104f14
--- /dev/null
+++ b/apps/engine/test/engine/code_action/handlers/add_alias_test.exs
@@ -0,0 +1,306 @@
+defmodule Engine.CodeAction.Handlers.AddAliasTest do
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.CodeUnit
+ alias Forge.Document
+ alias Forge.Document.Line
+ alias Forge.Document.Range
+
+ alias Engine.CodeAction.Handlers.AddAlias
+ alias Engine.Search.Store
+
+ import Forge.Test.CursorSupport
+ import Forge.Test.CodeSigil
+
+ use Engine.Test.CodeMod.Case, enable_ast_conversion: false
+ use Patch
+
+ setup do
+ start_supervised!({Document.Store, derive: [analysis: &Forge.Ast.analyze/1]})
+ :ok
+ end
+
+ def apply_code_mod(text, _ast, options) do
+ range = options[:range]
+ uri = "file:///file.ex"
+ :ok = Document.Store.open(uri, text, 1)
+ {:ok, document} = Document.Store.fetch(uri)
+
+ edits =
+ case AddAlias.actions(document, range, []) do
+ [action] -> action.changes.edits
+ _ -> []
+ end
+
+ {:ok, edits}
+ end
+
+ def add_alias(original_text, modules_to_return) do
+ {position, stripped_text} = pop_cursor(original_text)
+ patch_fuzzy_search(modules_to_return)
+ range = Range.new(position, position)
+ modify(stripped_text, range: range)
+ end
+
+ def patch_fuzzy_search(modules_to_return) do
+ all_modules =
+ Enum.map(modules_to_return, fn module ->
+ {Atom.to_charlist(module), :code.which(module), :code.is_loaded(module)}
+ end)
+
+ patch(AddAlias, :all_modules, all_modules)
+ end
+
+ describe "in an existing module with no aliases" do
+ test "aliases are added at the top of the module" do
+ patch(Engine, :get_project, %Forge.Project{})
+
+ {:ok, added} =
+ ~q[
+ defmodule MyModule do
+ def my_fn do
+ Line|
+ end
+ end
+ ]
+ |> add_alias([Line])
+
+ expected = ~q[
+ defmodule MyModule do
+ alias Forge.Document.Line
+ def my_fn do
+ Line
+ end
+ end
+ ]t
+ assert added =~ expected
+ end
+ end
+
+ describe "in an existing module" do
+ end
+
+ describe "in the root context" do
+ end
+
+ describe "adding an alias" do
+ test "does nothing on an invalid document" do
+ {:ok, added} = add_alias("%Engine.Search.", [Engine.Search])
+
+ assert added == "%Engine.Search."
+ end
+
+ test "outside of a module with aliases" do
+ {:ok, added} =
+ ~q[
+ alias ZZ.XX.YY
+ Line|
+ ]
+ |> add_alias([Line])
+
+ expected = ~q[
+ alias Forge.Document.Line
+ alias ZZ.XX.YY
+ Line
+ ]t
+
+ assert added == expected
+ end
+
+ test "when a full module name is given" do
+ {:ok, added} =
+ ~q[
+ Engine.Search.Store.Backend|
+ ]
+ |> add_alias([Store.Backend])
+
+ expected = ~q[
+ alias Engine.Search.Store.Backend
+ Backend
+ ]t
+
+ assert added == expected
+ end
+
+ test "when a full module name is given in a module function" do
+ patch(Engine, :get_project, %Forge.Project{})
+
+ {:ok, added} =
+ ~q[
+ defmodule MyModule do
+ def my_fun do
+ result = Engine.Search.Store|
+ end
+ end
+ ]
+ |> add_alias([Store])
+
+ expected = ~q[
+ defmodule MyModule do
+ alias Engine.Search.Store
+ def my_fun do
+ result = Store
+ end
+ end
+ ]t
+
+ assert added =~ expected
+ end
+
+ test "outside of a module with no aliases" do
+ {:ok, added} =
+ ~q[Line|]
+ |> add_alias([Line])
+
+ expected = ~q[
+ alias Forge.Document.Line
+ Line
+ ]t
+
+ assert added == expected
+ end
+
+ test "in a module with no aliases" do
+ patch(Engine, :get_project, %Forge.Project{})
+
+ {:ok, added} =
+ ~q[
+ defmodule MyModule do
+ def my_fun do
+ Line|
+ end
+ end
+ ]
+ |> add_alias([Line])
+
+ expected = ~q[
+ defmodule MyModule do
+ alias Forge.Document.Line
+ def my_fun do
+ Line
+ end
+ end
+ ]t
+
+ assert added =~ expected
+ end
+
+ test "outside of functions" do
+ {:ok, added} =
+ ~q[
+ defmodule MyModule do
+ alias Something.Else
+ Line|
+ end
+ ]
+ |> add_alias([Line])
+
+ expected = ~q[
+ defmodule MyModule do
+ alias Forge.Document.Line
+ alias Something.Else
+ Line
+ end
+ ]
+
+ assert expected =~ added
+ end
+
+ test "inside a function" do
+ {:ok, added} =
+ ~q[
+ defmodule MyModule do
+ alias Something.Else
+ def my_fn do
+ Line|
+ end
+ end
+ ]
+ |> add_alias([Line])
+
+ expected = ~q[
+ defmodule MyModule do
+ alias Forge.Document.Line
+ alias Something.Else
+ def my_fn do
+ Line
+ end
+ end
+ ]
+ assert expected =~ added
+ end
+
+ test "inside a nested module" do
+ {:ok, added} =
+ ~q[
+ defmodule Parent do
+ alias Top.Level
+ defmodule Child do
+ alias Some.Other
+ Line|
+ end
+ end
+ ]
+ |> add_alias([Line])
+
+ expected = ~q[
+ defmodule Parent do
+ alias Top.Level
+ defmodule Child do
+ alias Forge.Document.Line
+ alias Some.Other
+ Line
+ end
+ end
+ ]t
+
+ assert added =~ expected
+ end
+
+ test "aliases for struct references don't include non-struct modules" do
+ {:ok, added} = add_alias("%Scope|{}", [Forge.Ast, Scope])
+
+ expected = ~q[
+ alias Forge.Ast.Analysis.Scope
+ %Scope
+ ]t
+
+ assert added =~ expected
+ end
+
+ test "only modules with a similarly named function will be included in aliases" do
+ {:ok, added} = add_alias("Document.fetch|", [Document, Engine])
+
+ expected = ~q[
+ alias Forge.Document
+ Document.fetch
+ ]t
+
+ assert added =~ expected
+ end
+
+ test "protocols are excluded" do
+ {:ok, added} = add_alias("Co|", [Collectable, CodeUnit])
+ expected = ~q[
+ alias Forge.CodeUnit
+ Co
+ ]t
+
+ assert added =~ expected
+ end
+
+ test "protocol implementations are excluded" do
+ {:ok, added} = add_alias("Lin|", [Forge.Document.Lines, Enumerable.Forge.Document.Lines])
+
+ expected = ~q[
+ alias Forge.Document.Lines
+ Lin
+ ]t
+ assert added =~ expected
+ end
+
+ test "erlang modules are excluded" do
+ {:ok, added} = add_alias(":ets|", [:ets])
+ assert added =~ ":ets"
+ end
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/code_action/handlers/organize_aliases_test.exs b/apps/engine/test/engine/code_action/handlers/organize_aliases_test.exs
similarity index 92%
rename from apps/remote_control/test/lexical/remote_control/code_action/handlers/organize_aliases_test.exs
rename to apps/engine/test/engine/code_action/handlers/organize_aliases_test.exs
index 34401889..499eff0d 100644
--- a/apps/remote_control/test/lexical/remote_control/code_action/handlers/organize_aliases_test.exs
+++ b/apps/engine/test/engine/code_action/handlers/organize_aliases_test.exs
@@ -1,17 +1,17 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.OrganizeAliasesTest do
- alias Lexical.Document
- alias Lexical.Document.Range
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeAction.Handlers.OrganizeAliases
+defmodule Engine.CodeAction.Handlers.OrganizeAliasesTest do
+ alias Forge.Document
+ alias Forge.Document.Range
- import Lexical.Test.CursorSupport
- import Lexical.Test.CodeSigil
+ alias Engine.CodeAction.Handlers.OrganizeAliases
- use Lexical.Test.CodeMod.Case, enable_ast_conversion: false
+ import Forge.Test.CursorSupport
+ import Forge.Test.CodeSigil
+
+ use Engine.Test.CodeMod.Case, enable_ast_conversion: false
use Patch
setup do
- start_supervised!({Document.Store, derive: [analysis: &Lexical.Ast.analyze/1]})
+ start_supervised!({Document.Store, derive: [analysis: &Forge.Ast.analyze/1]})
:ok
end
@@ -92,7 +92,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.OrganizeAliasesTest do
describe "at the top of a module" do
test "does nothing if there are no aliases" do
- patch(RemoteControl, :get_project, %Lexical.Project{})
+ patch(Engine, :get_project, %Forge.Project{})
{:ok, organized} =
~q[
diff --git a/apps/remote_control/test/lexical/remote_control/code_action/handlers/refactorex_test.exs b/apps/engine/test/engine/code_action/handlers/refactorex_test.exs
similarity index 84%
rename from apps/remote_control/test/lexical/remote_control/code_action/handlers/refactorex_test.exs
rename to apps/engine/test/engine/code_action/handlers/refactorex_test.exs
index a477df86..a52388f9 100644
--- a/apps/remote_control/test/lexical/remote_control/code_action/handlers/refactorex_test.exs
+++ b/apps/engine/test/engine/code_action/handlers/refactorex_test.exs
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.RefactorexTest do
- use Lexical.Test.CodeMod.Case
+defmodule Engine.CodeAction.Handlers.RefactorexTest do
+ use Engine.Test.CodeMod.Case
- alias Lexical.Document
- alias Lexical.RemoteControl.CodeAction.Handlers.Refactorex
+ alias Engine.CodeAction.Handlers.Refactorex
+ alias Forge.Document
- import Lexical.Test.RangeSupport
- import Lexical.Test.CodeSigil
+ import Forge.Test.RangeSupport
+ import Forge.Test.CodeSigil
def apply_code_mod(original_text, _ast, options) do
document = Document.new("file:///file.ex", original_text, 0)
@@ -26,7 +26,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RefactorexTest do
setup do
project = project()
- Lexical.RemoteControl.set_project(project)
+ Engine.set_project(project)
{:ok, project: project}
end
diff --git a/apps/remote_control/test/lexical/remote_control/code_action/handlers/remove_unused_alias_test.exs b/apps/engine/test/engine/code_action/handlers/remove_unused_alias_test.exs
similarity index 94%
rename from apps/remote_control/test/lexical/remote_control/code_action/handlers/remove_unused_alias_test.exs
rename to apps/engine/test/engine/code_action/handlers/remove_unused_alias_test.exs
index 7c1f619c..1ce0adbc 100644
--- a/apps/remote_control/test/lexical/remote_control/code_action/handlers/remove_unused_alias_test.exs
+++ b/apps/engine/test/engine/code_action/handlers/remove_unused_alias_test.exs
@@ -1,14 +1,14 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAliasTest do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.CodeAction.Diagnostic
- alias Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAlias
+defmodule Engine.CodeAction.Handlers.RemoveUnusedAliasTest do
+ alias Engine.CodeAction.Diagnostic
+ alias Engine.CodeAction.Handlers.RemoveUnusedAlias
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Document.Range
- import Lexical.Test.CursorSupport
- import Lexical.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Forge.Test.CodeSigil
- use Lexical.Test.CodeMod.Case, enable_ast_conversion: false
+ use Engine.Test.CodeMod.Case, enable_ast_conversion: false
def apply_code_mod(original_text, _ast, options) do
Document.Store.open("file:///file.ex", original_text, 1)
@@ -49,7 +49,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.RemoveUnusedAliasTest do
end
setup do
- start_supervised!({Document.Store, derive: [analysis: &Lexical.Ast.analyze/1]})
+ start_supervised!({Document.Store, derive: [analysis: &Forge.Ast.analyze/1]})
:ok
end
diff --git a/apps/remote_control/test/lexical/remote_control/code_action/handlers/replace_remote_function_test.exs b/apps/engine/test/engine/code_action/handlers/replace_remote_function_test.exs
similarity index 94%
rename from apps/remote_control/test/lexical/remote_control/code_action/handlers/replace_remote_function_test.exs
rename to apps/engine/test/engine/code_action/handlers/replace_remote_function_test.exs
index 5e5b51cd..baed29e9 100644
--- a/apps/remote_control/test/lexical/remote_control/code_action/handlers/replace_remote_function_test.exs
+++ b/apps/engine/test/engine/code_action/handlers/replace_remote_function_test.exs
@@ -1,12 +1,12 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceRemoteFunctionTest do
- alias Lexical.Document
- alias Lexical.RemoteControl.CodeAction.Diagnostic
- alias Lexical.RemoteControl.CodeAction.Handlers.ReplaceRemoteFunction
+defmodule Engine.CodeAction.Handlers.ReplaceRemoteFunctionTest do
+ alias Engine.CodeAction.Diagnostic
+ alias Engine.CodeAction.Handlers.ReplaceRemoteFunction
+ alias Forge.Document
- use Lexical.Test.CodeMod.Case
+ use Engine.Test.CodeMod.Case
setup do
- start_supervised!({Document.Store, derive: [analysis: &Lexical.Ast.analyze/1]})
+ start_supervised!({Document.Store, derive: [analysis: &Forge.Ast.analyze/1]})
:ok
end
@@ -46,7 +46,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceRemoteFunctionTest do
|> ReplaceRemoteFunction.actions(range, [diagnostic])
|> Enum.flat_map(& &1.changes.edits)
|> Enum.filter(fn
- %Lexical.Document.Edit{text: ^suggestion} -> true
+ %Forge.Document.Edit{text: ^suggestion} -> true
_ -> false
end)
diff --git a/apps/remote_control/test/lexical/remote_control/code_action/handlers/replace_with_underscore_test.exs b/apps/engine/test/engine/code_action/handlers/replace_with_underscore_test.exs
similarity index 96%
rename from apps/remote_control/test/lexical/remote_control/code_action/handlers/replace_with_underscore_test.exs
rename to apps/engine/test/engine/code_action/handlers/replace_with_underscore_test.exs
index d19b3856..e37e37b5 100644
--- a/apps/remote_control/test/lexical/remote_control/code_action/handlers/replace_with_underscore_test.exs
+++ b/apps/engine/test/engine/code_action/handlers/replace_with_underscore_test.exs
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceWithUnderscoreTest do
- alias Lexical.Document
- alias Lexical.RemoteControl.CodeAction.Diagnostic
- alias Lexical.RemoteControl.CodeAction.Handlers.ReplaceWithUnderscore
+defmodule Engine.CodeAction.Handlers.ReplaceWithUnderscoreTest do
+ alias Engine.CodeAction.Diagnostic
+ alias Engine.CodeAction.Handlers.ReplaceWithUnderscore
+ alias Forge.Document
- use Lexical.Test.CodeMod.Case
+ use Engine.Test.CodeMod.Case
def apply_code_mod(original_text, _ast, options) do
variable = Keyword.get(options, :variable, :unused)
diff --git a/apps/remote_control/test/lexical/remote_control/code_intelligence/definition_test.exs b/apps/engine/test/engine/code_intelligence/definition_test.exs
similarity index 93%
rename from apps/remote_control/test/lexical/remote_control/code_intelligence/definition_test.exs
rename to apps/engine/test/engine/code_intelligence/definition_test.exs
index fc286bf7..e4ba6399 100644
--- a/apps/remote_control/test/lexical/remote_control/code_intelligence/definition_test.exs
+++ b/apps/engine/test/engine/code_intelligence/definition_test.exs
@@ -1,14 +1,13 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.DefinitionTest do
- alias Lexical.Document
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.ProjectNodeSupervisor
- alias Lexical.RemoteControl.Search
-
- import Lexical.RemoteControl.Api.Messages
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
- import Lexical.Test.Fixtures
- import Lexical.Test.RangeSupport
+defmodule Engine.CodeIntelligence.DefinitionTest do
+ alias Engine.ProjectNodeSupervisor
+ alias Engine.Search
+ alias Forge.Document
+
+ import Engine.Api.Messages
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Engine.Test.Fixtures
+ import Forge.Test.RangeSupport
use ExUnit.Case, async: false
@@ -43,12 +42,12 @@ defmodule Lexical.RemoteControl.CodeIntelligence.DefinitionTest do
setup_all do
project = project(:navigations)
- start_supervised!({Document.Store, derive: [analysis: &Lexical.Ast.analyze/1]})
+ start_supervised!({Document.Store, derive: [analysis: &Forge.Ast.analyze/1]})
{:ok, _} = start_supervised({ProjectNodeSupervisor, project})
- {:ok, _, _} = RemoteControl.start_link(project)
+ {:ok, _, _} = Engine.start_link(project)
- RemoteControl.Api.register_listener(project, self(), [:all])
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.register_listener(project, self(), [:all])
+ Engine.Api.schedule_compile(project, true)
assert_receive project_compiled(), 5000
assert_receive project_index_ready(), 5000
@@ -453,7 +452,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.DefinitionTest do
{:ok, document} <- subject_module(project, code),
:ok <- index(project, referenced_uri),
{:ok, location} <-
- RemoteControl.Api.definition(project, document, position) do
+ Engine.Api.definition(project, document, position) do
if is_list(location) do
{:ok, Enum.map(location, &{&1.document.uri, decorate(&1.document, &1.range)})}
else
@@ -464,12 +463,12 @@ defmodule Lexical.RemoteControl.CodeIntelligence.DefinitionTest do
defp index(project, referenced_uris) when is_list(referenced_uris) do
entries = Enum.flat_map(referenced_uris, &do_index/1)
- RemoteControl.call(project, Search.Store, :replace, [entries])
+ Engine.call(project, Search.Store, :replace, [entries])
end
defp index(project, referenced_uri) do
entries = do_index(referenced_uri)
- RemoteControl.call(project, Search.Store, :replace, [entries])
+ Engine.call(project, Search.Store, :replace, [entries])
end
defp do_index(referenced_uri) do
diff --git a/apps/remote_control/test/lexical/remote_control/code_intelligence/entity_test.exs b/apps/engine/test/engine/code_intelligence/entity_test.exs
similarity index 97%
rename from apps/remote_control/test/lexical/remote_control/code_intelligence/entity_test.exs
rename to apps/engine/test/engine/code_intelligence/entity_test.exs
index b6d4a58b..1c80b538 100644
--- a/apps/remote_control/test/lexical/remote_control/code_intelligence/entity_test.exs
+++ b/apps/engine/test/engine/code_intelligence/entity_test.exs
@@ -1,12 +1,12 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.EntityTest do
- alias Lexical.Document
- alias Lexical.RemoteControl.CodeIntelligence.Entity
+defmodule Engine.CodeIntelligence.EntityTest do
+ alias Engine.CodeIntelligence.Entity
+ alias Forge.Document
import ExUnit.CaptureIO
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
- import Lexical.Test.Fixtures
- import Lexical.Test.RangeSupport
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Engine.Test.Fixtures
+ import Forge.Test.RangeSupport
use ExUnit.Case
use Patch
@@ -806,13 +806,13 @@ defmodule Lexical.RemoteControl.CodeIntelligence.EntityTest do
test "imported in module scope" do
code = ~q[
defmodule Parent do
- import Lexical.Ast
+ import Forge.Ast
def parse(doc), do: |from(doc)
end
]
- assert {:ok, {:call, Lexical.Ast, :from, 1}, resolved_range} = resolve(code)
+ assert {:ok, {:call, Forge.Ast, :from, 1}, resolved_range} = resolve(code)
assert resolved_range =~ ~S[«from»(doc)]
end
@@ -820,13 +820,13 @@ defmodule Lexical.RemoteControl.CodeIntelligence.EntityTest do
code = ~q[
defmodule Parent do
def parse(doc) do
- import Lexical.Ast
+ import Forge.Ast
|from(doc)
end
end
]
- assert {:ok, {:call, Lexical.Ast, :from, 1}, resolved_range} = resolve(code)
+ assert {:ok, {:call, Forge.Ast, :from, 1}, resolved_range} = resolve(code)
assert resolved_range =~ ~S[«from»(doc)]
end
@@ -834,7 +834,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.EntityTest do
code = ~q[
defmodule Parent do
def parse(doc) do
- import Lexical.Ast
+ import Forge.Ast
from(doc)
end
@@ -976,7 +976,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.EntityTest do
with {position, code} <- pop_cursor(code),
:ok <- maybe_evaluate(code, evaluate?),
document = subject_module(code),
- analysis = Lexical.Ast.analyze(document),
+ analysis = Forge.Ast.analyze(document),
{:ok, resolved, range} <- Entity.resolve(analysis, position) do
{:ok, resolved, decorate(document, range)}
end
diff --git a/apps/remote_control/test/lexical/remote_control/code_intelligence/references_test.exs b/apps/engine/test/engine/code_intelligence/references_test.exs
similarity index 93%
rename from apps/remote_control/test/lexical/remote_control/code_intelligence/references_test.exs
rename to apps/engine/test/engine/code_intelligence/references_test.exs
index 06932111..4ae8ab04 100644
--- a/apps/remote_control/test/lexical/remote_control/code_intelligence/references_test.exs
+++ b/apps/engine/test/engine/code_intelligence/references_test.exs
@@ -1,27 +1,27 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do
- alias Lexical.Document
- alias Lexical.Document.Location
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeIntelligence.References
- alias Lexical.RemoteControl.Search
- alias Lexical.RemoteControl.Search.Store.Backends
+defmodule Engine.CodeIntelligence.ReferencesTest do
+ alias Forge.Document
+ alias Forge.Document.Location
+
+ alias Engine.CodeIntelligence.References
+ alias Engine.Search
+ alias Engine.Search.Store.Backends
use ExUnit.Case, async: false
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
- import Lexical.Test.Fixtures
- import Lexical.Test.RangeSupport
- import Lexical.Test.EventualAssertions
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Engine.Test.Fixtures
+ import Forge.Test.RangeSupport
+ import Forge.Test.EventualAssertions
setup do
project = project()
Backends.Ets.destroy_all(project)
- RemoteControl.set_project(project)
+ Engine.set_project(project)
start_supervised!(Document.Store)
- start_supervised!(RemoteControl.Dispatch)
+ start_supervised!(Engine.Dispatch)
start_supervised!(Backends.Ets)
start_supervised!(
@@ -317,7 +317,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.ReferencesTest do
{:ok, entries} <- Search.Indexer.Source.index(document.path, code),
:ok <- Search.Store.replace(entries) do
referenced
- |> Lexical.Ast.analyze()
+ |> Forge.Ast.analyze()
|> References.references(position, include_definitions?)
end
end
diff --git a/apps/remote_control/test/lexical/remote_control/code_intelligence/symbols_test.exs b/apps/engine/test/engine/code_intelligence/symbols_test.exs
similarity index 97%
rename from apps/remote_control/test/lexical/remote_control/code_intelligence/symbols_test.exs
rename to apps/engine/test/engine/code_intelligence/symbols_test.exs
index 9150a636..afd1a656 100644
--- a/apps/remote_control/test/lexical/remote_control/code_intelligence/symbols_test.exs
+++ b/apps/engine/test/engine/code_intelligence/symbols_test.exs
@@ -1,14 +1,14 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.SymbolsTest do
- alias Lexical.Document
- alias Lexical.RemoteControl.CodeIntelligence.Symbols
- alias Lexical.RemoteControl.Search.Indexer.Extractors
- alias Lexical.RemoteControl.Search.Indexer.Source
+defmodule Engine.CodeIntelligence.SymbolsTest do
+ alias Engine.CodeIntelligence.Symbols
+ alias Engine.Search.Indexer.Extractors
+ alias Engine.Search.Indexer.Source
+ alias Forge.Document
use ExUnit.Case
use Patch
- import Lexical.Test.CodeSigil
- import Lexical.Test.RangeSupport
+ import Forge.Test.CodeSigil
+ import Forge.Test.RangeSupport
def document_symbols(code) do
doc = Document.new("file:///file.ex", code, 1)
@@ -30,7 +30,7 @@ defmodule Lexical.RemoteControl.CodeIntelligence.SymbolsTest do
])
entries = Enum.reject(entries, &(&1.type == :metadata))
- patch(Lexical.RemoteControl.Search.Store, :fuzzy, {:ok, entries})
+ patch(Engine.Search.Store, :fuzzy, {:ok, entries})
symbols = Symbols.for_workspace("")
{symbols, doc}
end
diff --git a/apps/engine/test/engine/code_intelligence/variable_test.exs b/apps/engine/test/engine/code_intelligence/variable_test.exs
new file mode 100644
index 00000000..0a12d36f
--- /dev/null
+++ b/apps/engine/test/engine/code_intelligence/variable_test.exs
@@ -0,0 +1,490 @@
+defmodule Engine.CodeIntelligence.VariableTest do
+ alias Engine.CodeIntelligence.Variable
+ alias Forge.Ast
+
+ use ExUnit.Case
+
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Forge.Test.RangeSupport
+
+ def find_definition(code) do
+ {position, document} = pop_cursor(code, as: :document)
+ analysis = Ast.analyze(document)
+ {:ok, {:local_or_var, var_name}} = Ast.cursor_context(analysis, position)
+
+ case Variable.definition(analysis, position, List.to_atom(var_name)) do
+ {:ok, entry} -> {:ok, entry.range, document}
+ error -> error
+ end
+ end
+
+ def find_references(code, include_definition? \\ false) do
+ {position, document} = pop_cursor(code, as: :document)
+ analysis = Ast.analyze(document)
+ {:ok, {:local_or_var, var_name}} = Ast.cursor_context(analysis, position)
+
+ ranges =
+ analysis
+ |> Variable.references(position, List.to_atom(var_name), include_definition?)
+ |> Enum.map(& &1.range)
+
+ {:ok, ranges, document}
+ end
+
+ describe "definitions in a single scope" do
+ test "are returned if it is selected" do
+ {:ok, range, doc} =
+ ~q[
+ def foo(param|) do
+ param
+ end
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "def foo(«param») do"
+ end
+
+ test "are found in a parameter" do
+ {:ok, range, doc} =
+ ~q[
+ def foo(param) do
+ param|
+ end
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "def foo(«param») do"
+ end
+
+ test "are found in a parameter list" do
+ {:ok, range, doc} =
+ ~q[
+ def foo(other_param, param) do
+ param|
+ end
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "def foo(other_param, «param») do"
+ end
+
+ test "are found when shadowed" do
+ {:ok, range, doc} =
+ ~q[
+ def foo(param) do
+ param = param + 1
+ param|
+ end
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "«param» = param + 1"
+ end
+
+ test "are found when shadowing a parameter" do
+ {:ok, range, doc} =
+ ~q[
+ def foo(param) do
+ param = param| + 1
+ end
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "def foo(«param») do"
+ end
+
+ test "when there are multiple definitions on one line" do
+ {:ok, range, doc} =
+ ~q[
+ param = 3
+ foo = param = param + 1
+ param|
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "= «param» = param + 1"
+ end
+
+ test "when the definition is in a map key" do
+ {:ok, range, doc} =
+ ~q[
+ %{key: value} = map
+ value|
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "%{key: «value»} = map"
+ end
+ end
+
+ describe "definitions across scopes" do
+ test "works in an if in a function" do
+ {:ok, range, doc} =
+ ~q[
+ def my_fun do
+ foo = 3
+ if something do
+ foo|
+ end
+ end
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "«foo» = 3"
+ end
+
+ test "works for variables defined in a module" do
+ {:ok, range, doc} =
+ ~q[
+ defmodule Parent do
+ x = 3
+ def fun do
+ unquote(x|)
+ end
+ end
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "«x» = 3"
+ end
+
+ test "works for variables defined outside module" do
+ {:ok, range, doc} =
+ ~q[
+ x = 3
+ defmodule Parent do
+ def fun do
+ unquote(x|)
+ end
+ end
+ ]
+ |> find_definition()
+
+ assert decorate(doc, range) =~ "«x» = 3"
+ end
+ end
+
+ describe "references" do
+ test "in a function parameter" do
+ {:ok, [range], doc} =
+ ~q[
+ def something(param|) do
+ param
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, range) =~ "«param»"
+ end
+
+ test "can include definitions" do
+ {:ok, [definition, reference], doc} =
+ ~q[
+ def something(param|) do
+ param
+ end
+ ]
+ |> find_references(true)
+
+ assert decorate(doc, definition) =~ "def something(«param») do"
+ assert decorate(doc, reference) =~ " «param»"
+ end
+
+ test "can be found via a usage" do
+ {:ok, [first, second, third], doc} =
+ ~q[
+ def something(param) do
+ y = param + 3
+ z = param + 4
+ param| + y + z
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, first) =~ " y = «param» + 3"
+ assert decorate(doc, second) =~ " z = «param» + 4"
+ assert decorate(doc, third) =~ " «param» + y + z"
+ end
+
+ test "are found in a function body" do
+ {:ok, [first, second, third, fourth, fifth], doc} =
+ ~q[
+ def something(param|) do
+ x = param + param + 3
+ y = param + x
+ z = 10 + param
+ x + y + z + param
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, first) =~ " x = «param» + param + 3"
+ assert decorate(doc, second) =~ " x = param + «param» + 3"
+ assert decorate(doc, third) =~ " y = «param» + x"
+ assert decorate(doc, fourth) =~ " z = 10 + «param»"
+ assert decorate(doc, fifth) =~ " x + y + z + «param»"
+ end
+
+ test "are constrained to their definition function" do
+ {:ok, [range], doc} =
+ ~q[
+ def something(param|) do
+ param
+ end
+
+ def other_fn(param) do
+ param + 1
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, range) =~ "«param»"
+ end
+
+ test "are visible across blocks" do
+ {:ok, [first, second], doc} =
+ ~q[
+ def something(param|) do
+ if something() do
+ param + 1
+ else
+ param + 2
+ end
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, first) =~ " «param» + 1"
+ assert decorate(doc, second) =~ " «param» + 2"
+ end
+
+ test "dont leak out of blocks" do
+ {:ok, [range], doc} =
+ ~q[
+ def something(param) do
+
+ if something() do
+ param| = 3
+ param + 1
+ end
+ param + 1
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, range) =~ "«param»"
+ end
+
+ test "are found in the head of a case statement" do
+ {:ok, [range], doc} =
+ ~q[
+ def something(param|) do
+ case param do
+ _ -> :ok
+ end
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, range) =~ " case «param» do"
+ end
+
+ test "are constrained to a single arm of a case statement" do
+ {:ok, [guard_range, usage_range], doc} =
+ ~q[
+ def something(param) do
+ case param do
+ param| when is_number(param) -> param + 1
+ param -> 0
+ end
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, guard_range) =~ " param when is_number(«param») -> param + 1"
+ assert decorate(doc, usage_range) =~ " param when is_number(param) -> «param» + 1"
+ end
+
+ test "are found in a module body" do
+ {:ok, [range], doc} =
+ ~q[
+ defmodule Outer do
+ something| = 3
+ def foo(unquote(something)) do
+ end
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, range) =~ "def foo(unquote(«something»)) do"
+ end
+
+ test "are found in anonymous function parameters" do
+ {:ok, [first, second], doc} =
+ ~q[
+ def outer do
+ fn param| ->
+ y = param + 1
+ x = param + 2
+ x + y
+ end
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, first) =~ "y = «param» + 1"
+ assert decorate(doc, second) =~ "x = «param» + 2"
+ end
+
+ test "are found in a pin operator" do
+ {:ok, [ref], doc} =
+ ~q[
+ def outer(param|) do
+ fn ^param ->
+ nil
+ end
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, ref) =~ "fn ^«param» ->"
+ end
+
+ test "are found inside of string interpolation" do
+ {:ok, [ref], doc} =
+ ~S[
+ name| = "Stinky"
+ "#{name} Stinkman"
+ ]
+ |> find_references()
+
+ assert decorate(doc, ref) =~ "\#{«name»} Stinkman"
+ end
+
+ # Note: This test needs to pass before we can implement renaming variables reliably
+ @tag :skip
+ test "works for variables defined outside of an if while being shadowed" do
+ {:ok, [first, second], doc} =
+ ~q{
+ entries| = [1, 2, 3]
+ entries =
+ if something() do
+ [4 | entries]
+ else
+ entries
+ end
+ }
+ |> find_references()
+
+ assert decorate(doc, first) =~ "[4 | «entries»]"
+ assert decorate(doc, second) =~ "«entries»"
+ end
+
+ test "finds variables defined in anonymous function arms" do
+ {:ok, [first, second], doc} =
+ ~q"
+ shadowed? = false
+ fn
+ {:foo, entries|} ->
+ if shadowed? do
+ [1, entries]
+ else
+ entries
+ end
+ {:bar, entries} ->
+ entries
+ end
+ "
+ |> find_references()
+
+ assert decorate(doc, first) =~ "[1, «entries»]"
+ assert decorate(doc, second) =~ "«entries»"
+ end
+ end
+
+ describe "reference shadowing" do
+ test "on a single line" do
+ {:ok, [], _doc} =
+ ~q[
+ def something(param) do
+ other = other = other| = param
+ end
+ ]
+ |> find_references()
+ end
+
+ test "in a function body" do
+ {:ok, [], _doc} =
+ ~q[
+ def something(param|) do
+ param = 3
+ param
+ end
+ ]
+ |> find_references()
+ end
+
+ test "in anonymous function arguments" do
+ {:ok, [], _doc} =
+ ~q[
+ def something(param|) do
+ fn param ->
+ param + 1
+ end
+ :ok
+ end
+ ]
+ |> find_references()
+ end
+
+ test "inside of a block" do
+ {:ok, [range], doc} =
+ ~q[
+ def something do
+ shadow| = 4
+ if true do
+ shadow = shadow + 1
+ shadow
+ end
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, range) == " shadow = «shadow» + 1"
+ end
+
+ test "exiting a block" do
+ {:ok, [range], doc} =
+ ~q[
+ def something do
+ shadow| = 4
+ if true do
+ shadow = :ok
+ shadow
+ end
+ shadow + 1
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, range) == " «shadow» + 1"
+ end
+
+ test "exiting nested blocks" do
+ {:ok, [range], doc} =
+ ~q[
+ def something(param| = arg) do
+ case arg do
+ param when is_number(n) ->
+ param + 4
+ end
+ param + 5
+ end
+ ]
+ |> find_references()
+
+ assert decorate(doc, range) == " «param» + 5"
+ end
+ end
+end
diff --git a/apps/engine/test/engine/code_mod/aliases_test.exs b/apps/engine/test/engine/code_mod/aliases_test.exs
new file mode 100644
index 00000000..1d9a8db2
--- /dev/null
+++ b/apps/engine/test/engine/code_mod/aliases_test.exs
@@ -0,0 +1,204 @@
+defmodule Engine.CodeMod.AliasesTest do
+ alias Forge.Ast
+
+ alias Engine.CodeMod.Aliases
+
+ import Forge.Test.CursorSupport
+ use Engine.Test.CodeMod.Case
+ use Patch
+
+ setup do
+ patch(Engine, :get_project, %Forge.Project{})
+ :ok
+ end
+
+ def insert_position(orig) do
+ {cursor, document} = pop_cursor(orig, as: :document)
+ analysis = Ast.analyze(document)
+ {position, _trailer} = Aliases.insert_position(analysis, cursor)
+
+ {:ok, document, position}
+ end
+
+ describe "insert_position" do
+ test "is directly after a module's definition if there are no aliases present" do
+ {:ok, document, position} =
+ ~q[
+ defmodule MyModule do|
+ end
+ ]
+ |> insert_position()
+
+ assert decorate_cursor(document, position) =~ ~q[
+ defmodule MyModule do
+ |end
+ ]
+ end
+
+ test "is after the moduledoc if no aliases are present" do
+ {:ok, document, position} =
+ ~q[
+ defmodule MyModule do|
+ @moduledoc """
+ This is my funny moduledoc
+ """
+ end
+ ]
+ |> insert_position()
+
+ assert decorate_cursor(document, position) =~ ~q[
+ defmodule MyModule do
+ @moduledoc """
+ This is my funny moduledoc
+ """
+ |end
+ ]
+ end
+
+ test "is before use statements" do
+ {:ok, document, position} =
+ ~q[
+ defmodule MyModule do|
+ use Something.That.Exists
+ end
+ ]
+ |> insert_position()
+
+ expected = ~q[
+ defmodule MyModule do
+ |use Something.That.Exists
+ end
+ ]
+ assert decorate_cursor(document, position) =~ expected
+ end
+
+ test "is before require statements" do
+ {:ok, document, position} =
+ ~q[
+ defmodule MyModule do|
+ require Something.That.Exists
+ end
+ ]
+ |> insert_position()
+
+ expected = ~q[
+ defmodule MyModule do
+ |require Something.That.Exists
+ end
+ ]
+ assert decorate_cursor(document, position) =~ expected
+ end
+
+ test "is before import statements" do
+ {:ok, document, position} =
+ ~q[
+ defmodule MyModule do|
+ import Something.That.Exists
+ end
+ ]
+ |> insert_position()
+
+ expected = ~q[
+ defmodule MyModule do
+ |import Something.That.Exists
+ end
+ ]
+ assert decorate_cursor(document, position) =~ expected
+ end
+
+ test "is where existing aliases are" do
+ {:ok, document, position} =
+ ~q[
+ defmodule MyModule do|
+ alias Something.That.Exists
+ end
+ ]
+ |> insert_position()
+
+ expected = ~q[
+ defmodule MyModule do
+ |alias Something.That.Exists
+ end
+ ]
+ assert decorate_cursor(document, position) =~ expected
+ end
+
+ test "in nested empty modules" do
+ {:ok, document, position} =
+ ~q[
+ defmodule Outer do
+ defmodule Inner do|
+ end
+ end
+ ]
+ |> insert_position()
+
+ expected = ~q[
+ defmodule Outer do
+ defmodule Inner do
+ |end
+ end
+ ]t
+
+ assert decorate_cursor(document, position) =~ expected
+ end
+
+ test "in nested modules that both have existing aliases" do
+ {:ok, document, position} =
+ ~q[
+ defmodule Outer do
+ alias First.Thing
+
+ defmodule Inner do|
+ alias Second.Person
+ end
+ end
+ ]
+ |> insert_position()
+
+ expected = ~q[
+ defmodule Outer do
+ alias First.Thing
+
+ defmodule Inner do
+ |alias Second.Person
+ end
+ end
+ ]t
+
+ assert decorate_cursor(document, position) =~ expected
+ end
+
+ test "is after moduledocs in nested modules" do
+ {:ok, document, position} =
+ ~q[
+ defmodule Outer do
+ alias First.Thing
+
+ defmodule Inner do|
+ @moduledoc """
+ This is my documentation, it
+ spans multiple lines
+ """
+ end
+ end
+ ]
+ |> insert_position()
+
+ expected = ~q[
+ defmodule Outer do
+ alias First.Thing
+
+ defmodule Inner do
+ @moduledoc """
+ This is my documentation, it
+ spans multiple lines
+ """
+ |end
+ end
+ ]t
+
+ assert decorate_cursor(document, position) =~ expected
+ end
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/code_mod/diff_test.exs b/apps/engine/test/engine/code_mod/diff_test.exs
similarity index 95%
rename from apps/remote_control/test/lexical/remote_control/code_mod/diff_test.exs
rename to apps/engine/test/engine/code_mod/diff_test.exs
index 59809921..5f3094e8 100644
--- a/apps/remote_control/test/lexical/remote_control/code_mod/diff_test.exs
+++ b/apps/engine/test/engine/code_mod/diff_test.exs
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.CodeMod.DiffTest do
- alias Lexical.Document
- alias Lexical.Document.Edit
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.CodeMod.Diff
-
- use Lexical.Test.CodeMod.Case
- use Lexical.Test.PositionSupport
+defmodule Engine.CodeMod.DiffTest do
+ alias Engine.CodeMod.Diff
+ alias Forge.Document
+ alias Forge.Document.Edit
+ alias Forge.Document.Range
+
+ use Engine.Test.CodeMod.Case
+ use Forge.Test.PositionSupport
def edit(start_line, start_code_unit, end_line, end_code_unit, replacement) do
Edit.new(
diff --git a/apps/remote_control/test/lexical/remote_control/code_mod/format_test.exs b/apps/engine/test/engine/code_mod/format_test.exs
similarity index 83%
rename from apps/remote_control/test/lexical/remote_control/code_mod/format_test.exs
rename to apps/engine/test/engine/code_mod/format_test.exs
index bf74c0df..6d60cc81 100644
--- a/apps/remote_control/test/lexical/remote_control/code_mod/format_test.exs
+++ b/apps/engine/test/engine/code_mod/format_test.exs
@@ -1,13 +1,12 @@
# credo:disable-for-this-file Credo.Check.Readability.RedundantBlankLines
-defmodule Lexical.RemoteControl.CodeMod.FormatTest do
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.CodeMod.Format
-
- use Lexical.Test.CodeMod.Case, enable_ast_conversion: false
+defmodule Engine.CodeMod.FormatTest do
+ alias Engine.Api.Messages
+ alias Engine.Build
+ alias Engine.CodeMod.Format
+ alias Forge.Document
+ alias Forge.Project
+
+ use Engine.Test.CodeMod.Case, enable_ast_conversion: false
use Patch
import Messages
@@ -54,9 +53,9 @@ defmodule Lexical.RemoteControl.CodeMod.FormatTest do
end
def with_real_project(%{project: project}) do
- {:ok, _} = start_supervised({RemoteControl.ProjectNodeSupervisor, project})
- {:ok, _, _} = RemoteControl.start_link(project)
- RemoteControl.Api.register_listener(project, self(), [:all])
+ {:ok, _} = start_supervised({Engine.ProjectNodeSupervisor, project})
+ {:ok, _, _} = Engine.start_link(project)
+ Engine.Api.register_listener(project, self(), [:all])
:ok
end
@@ -67,7 +66,7 @@ defmodule Lexical.RemoteControl.CodeMod.FormatTest do
setup do
project = project()
- RemoteControl.set_project(project)
+ Engine.set_project(project)
{:ok, project: project}
end
@@ -135,7 +134,7 @@ defmodule Lexical.RemoteControl.CodeMod.FormatTest do
end
]
document = document("file:///file.ex", text)
- RemoteControl.Api.format(project, document)
+ Engine.Api.format(project, document)
assert_receive file_diagnostics(diagnostics: [diagnostic]), 500
assert diagnostic.message =~ "syntax error"
diff --git a/apps/remote_control/test/lexical/remote_control/commands/reindex_test.exs b/apps/engine/test/engine/commands/reindex_test.exs
similarity index 87%
rename from apps/remote_control/test/lexical/remote_control/commands/reindex_test.exs
rename to apps/engine/test/engine/commands/reindex_test.exs
index c88b5366..eeddff64 100644
--- a/apps/remote_control/test/lexical/remote_control/commands/reindex_test.exs
+++ b/apps/engine/test/engine/commands/reindex_test.exs
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.Commands.ReindexTest do
- alias Lexical.Document
- alias Lexical.RemoteControl.Commands.Reindex
- alias Lexical.RemoteControl.Search
-
- import Lexical.Test.EventualAssertions
- import Lexical.Test.Fixtures
- import Lexical.Test.Entry.Builder
+defmodule Engine.Commands.ReindexTest do
+ alias Engine.Commands.Reindex
+ alias Engine.Search
+ alias Forge.Document
+
+ import Forge.Test.EventualAssertions
+ import Engine.Test.Fixtures
+ import Engine.Test.Entry.Builder
use ExUnit.Case
use Patch
diff --git a/apps/remote_control/test/lexical/remote_control/completion/candidate/argument_names_test.exs b/apps/engine/test/engine/completion/candidate/argument_names_test.exs
similarity index 95%
rename from apps/remote_control/test/lexical/remote_control/completion/candidate/argument_names_test.exs
rename to apps/engine/test/engine/completion/candidate/argument_names_test.exs
index a6a8bab9..3a871f10 100644
--- a/apps/remote_control/test/lexical/remote_control/completion/candidate/argument_names_test.exs
+++ b/apps/engine/test/engine/completion/candidate/argument_names_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Completion.Candidate.ArgumentNamesTest do
- alias Lexical.RemoteControl.Completion.Candidate.ArgumentNames
+defmodule Engine.Completion.Candidate.ArgumentNamesTest do
+ alias Engine.Completion.Candidate.ArgumentNames
use ExUnit.Case
import ArgumentNames
diff --git a/apps/engine/test/engine/completion_test.exs b/apps/engine/test/engine/completion_test.exs
new file mode 100644
index 00000000..2c5b232a
--- /dev/null
+++ b/apps/engine/test/engine/completion_test.exs
@@ -0,0 +1,225 @@
+defmodule Engine.CompletionTest do
+ alias Engine.Completion
+ alias Forge.Ast
+ alias Forge.Ast.Env
+ alias Forge.Document
+
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Engine.Test.Fixtures
+ import Forge.Test.Quiet
+
+ use ExUnit.Case, async: true
+ use Patch
+
+ describe "struct_fields/2" do
+ test "returns the field completion for current module" do
+ source = ~q<
+ defmodule Project.Issue do
+ defstruct [:message]
+ @type t :: %__MODULE__{|}
+ end
+ >
+
+ [message] = struct_fields(source)
+ assert message.name == :message
+ end
+
+ test "returns the field completions for aliased module" do
+ source = ~q<
+ defmodule Project.Issue do
+ alias Project.Issue
+ defstruct [:message]
+ @type t :: %Issue{|}
+ end
+ >
+
+ [message] = struct_fields(source)
+ assert message.name == :message
+ end
+
+ test "returns [] when cursor is not in a struct" do
+ source = ~q<
+ defmodule Project.Issue do
+ alias Project.Issue
+ defstruct [:message]
+ @type t :: %Issue{}|
+ end
+ >
+
+ assert struct_fields(source) == []
+ end
+
+ test "returns the field completions when cursor is in the current module child's arguments" do
+ source = ~q<
+ defmodule Project do
+ defmodule Issue do
+ @type t :: %Issue{}
+ defstruct [:message]
+ end
+
+ def message(%__MODULE__.Issue{|} = issue) do
+ issue.message
+ end
+ end
+ >
+
+ [message] = struct_fields(source)
+ assert message.name == :message
+ end
+
+ test "returns the field completion when cursor is in an alias child's arguments" do
+ source = ~q<
+ defmodule Project do
+ defmodule Issue do
+ defstruct [:message]
+ end
+ end
+
+ defmodule MyModule do
+ alias Project
+
+ def message(%Project.Issue{|} = issue) do
+ issue.message
+ end
+ end
+ >
+
+ [message] = struct_fields(source)
+ assert message.name == :message
+ end
+ end
+
+ def expose_strip_struct_operator(_) do
+ Patch.expose(Completion, strip_struct_operator: 1)
+ :ok
+ end
+
+ describe "strip_struct_operator/1" do
+ setup [:expose_strip_struct_operator]
+
+ test "with a reference followed by __" do
+ {doc, _position} =
+ "%__"
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == "__"
+ end
+
+ test "with a reference followed by a module name" do
+ {doc, _position} =
+ "%Module"
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == "Module"
+ end
+
+ test "with a reference followed by a module and a dot" do
+ {doc, _position} =
+ "%Module."
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == "Module."
+ end
+
+ test "with a reference followed by a nested module" do
+ {doc, _position} =
+ "%Module.Sub"
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == "Module.Sub"
+ end
+
+ test "with a reference followed by an alias" do
+ code = ~q[
+ alias Something.Else
+ %El|
+ ]t
+
+ {doc, _position} =
+ code
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == "alias Something.Else\nEl"
+ end
+
+ test "on a line with two references, replacing the first" do
+ {doc, _position} =
+ "%First{} = %Se"
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == "%First{} = Se"
+ end
+
+ test "on a line with two references, replacing the second" do
+ {doc, _position} =
+ "%Fir| = %Second{}"
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == "Fir = %Second{}"
+ end
+
+ test "with a plain module" do
+ env = new_env("Module")
+ {doc, _position} = private(Completion.strip_struct_operator(env))
+
+ assert doc == Document.to_string(env.document)
+ end
+
+ test "with a plain module strip_struct_reference a dot" do
+ env = new_env("Module.")
+ {doc, _position} = private(Completion.strip_struct_operator(env))
+
+ assert doc == Document.to_string(env.document)
+ end
+
+ test "leaves leading spaces in place" do
+ {doc, _position} =
+ " %Some"
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == " Some"
+ end
+
+ test "works in a function definition" do
+ {doc, _position} =
+ "def my_function(%Lo|)"
+ |> new_env()
+ |> private(Completion.strip_struct_operator())
+
+ assert doc == "def my_function(Lo)"
+ end
+ end
+
+ defp struct_fields(source) do
+ {position, document} = pop_cursor(source, as: :document)
+ text = Document.to_string(document)
+
+ quiet(:stderr, fn ->
+ Code.compile_string(text)
+ end)
+
+ analysis =
+ document
+ |> Ast.analyze()
+ |> Ast.reanalyze_to(position)
+
+ Completion.struct_fields(analysis, position)
+ end
+
+ def new_env(text) do
+ project = project()
+ {position, document} = pop_cursor(text, as: :document)
+ analysis = Ast.analyze(document)
+ {:ok, env} = Env.new(project, analysis, position)
+ env
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/dispatch/handler_test.exs b/apps/engine/test/engine/dispatch/handler_test.exs
similarity index 95%
rename from apps/remote_control/test/lexical/remote_control/dispatch/handler_test.exs
rename to apps/engine/test/engine/dispatch/handler_test.exs
index ddefecf3..927407fc 100644
--- a/apps/remote_control/test/lexical/remote_control/dispatch/handler_test.exs
+++ b/apps/engine/test/engine/dispatch/handler_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.Dispatch.HandlerTest do
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Dispatch
+defmodule Engine.Dispatch.HandlerTest do
+ alias Engine.Api.Messages
+ alias Engine.Dispatch
import Messages
use ExUnit.Case
diff --git a/apps/engine/test/engine/dispatch/handlers/indexer_test.exs b/apps/engine/test/engine/dispatch/handlers/indexer_test.exs
new file mode 100644
index 00000000..be21eb82
--- /dev/null
+++ b/apps/engine/test/engine/dispatch/handlers/indexer_test.exs
@@ -0,0 +1,146 @@
+defmodule Engine.Dispatch.Handlers.IndexingTest do
+ alias Forge.Document
+
+ alias Engine.Api
+ alias Engine.Commands
+ alias Engine.Dispatch.Handlers.Indexing
+ alias Engine.Search
+
+ import Api.Messages
+ import Forge.Test.CodeSigil
+ import Forge.Test.EventualAssertions
+ import Engine.Test.Fixtures
+
+ use ExUnit.Case
+ use Patch
+
+ setup do
+ project = project()
+ Engine.set_project(project)
+ create_index = &Search.Indexer.create_index/1
+ update_index = &Search.Indexer.update_index/2
+
+ start_supervised!(Engine.Dispatch)
+ start_supervised!(Commands.Reindex)
+ start_supervised!(Search.Store.Backends.Ets)
+ start_supervised!({Search.Store, [project, create_index, update_index]})
+ start_supervised!({Document.Store, derive: [analysis: &Forge.Ast.analyze/1]})
+
+ Search.Store.enable()
+ assert_eventually(Search.Store.loaded?(), 1500)
+
+ {:ok, state} = Indexing.init([])
+ {:ok, state: state, project: project}
+ end
+
+ def set_document!(source) do
+ uri = "file:///file.ex"
+
+ :ok =
+ case Document.Store.fetch(uri) do
+ {:ok, _} ->
+ Document.Store.update(uri, fn doc ->
+ edit = Document.Edit.new(source)
+ Document.apply_content_changes(doc, doc.version + 1, [edit])
+ end)
+
+ {:error, :not_open} ->
+ Document.Store.open(uri, source, 1)
+ end
+
+ {uri, source}
+ end
+
+ describe "handling file_quoted events" do
+ test "should add new entries to the store", %{state: state} do
+ {uri, _source} =
+ ~q[
+ defmodule NewModule do
+ end
+ ]
+ |> set_document!()
+
+ assert {:ok, _} = Indexing.on_event(file_compile_requested(uri: uri), state)
+
+ assert_eventually {:ok, [entry]} = Search.Store.exact("NewModule", [])
+
+ assert entry.subject == NewModule
+ end
+
+ test "should update entries in the store", %{state: state} do
+ {uri, source} =
+ ~q[
+ defmodule OldModule
+ end
+ ]
+ |> set_document!()
+
+ {:ok, _} = Search.Indexer.Source.index(uri, source)
+
+ {^uri, _source} =
+ ~q[
+ defmodule UpdatedModule do
+ end
+ ]
+ |> set_document!()
+
+ assert {:ok, _} = Indexing.on_event(file_compile_requested(uri: uri), state)
+
+ assert_eventually {:ok, [entry]} = Search.Store.exact("UpdatedModule", [])
+ assert entry.subject == UpdatedModule
+ assert {:ok, []} = Search.Store.exact("OldModule", [])
+ end
+
+ test "only updates entries if the version of the document is the same as the version in the document store",
+ %{state: state} do
+ Document.Store.open("file:///file.ex", "defmodule Newer do \nend", 3)
+
+ {uri, _source} =
+ ~q[
+ defmodule Stale do
+ end
+ ]
+ |> set_document!()
+
+ assert {:ok, _} = Indexing.on_event(file_compile_requested(uri: uri), state)
+ assert {:ok, []} = Search.Store.exact("Stale", [])
+ end
+ end
+
+ describe "a file is deleted" do
+ test "its entries should be deleted", %{project: project, state: state} do
+ {uri, source} =
+ ~q[
+ defmodule ToDelete do
+ end
+ ]
+ |> set_document!()
+
+ {:ok, entries} = Search.Indexer.Source.index(uri, source)
+ Search.Store.update(uri, entries)
+
+ assert_eventually {:ok, [_]} = Search.Store.exact("ToDelete", [])
+
+ Indexing.on_event(
+ filesystem_event(project: project, uri: uri, event_type: :deleted),
+ state
+ )
+
+ assert_eventually {:ok, []} = Search.Store.exact("ToDelete", [])
+ end
+ end
+
+ describe "a file is created" do
+ test "is a no op", %{project: project, state: state} do
+ spy(Search.Store)
+ spy(Search.Indexer)
+
+ event = filesystem_event(project: project, uri: "file:///another.ex", event_type: :created)
+
+ assert {:ok, _} = Indexing.on_event(event, state)
+
+ assert history(Search.Store) == []
+ assert history(Search.Indexer) == []
+ end
+ end
+end
diff --git a/apps/engine/test/engine/dispatch/handlers/indexing_test.exs b/apps/engine/test/engine/dispatch/handlers/indexing_test.exs
new file mode 100644
index 00000000..cf99a91e
--- /dev/null
+++ b/apps/engine/test/engine/dispatch/handlers/indexing_test.exs
@@ -0,0 +1,2 @@
+defmodule Engine.Dispatch.Handlers.IndexerTest do
+end
diff --git a/apps/remote_control/test/lexical/remote_control/dispatch/pub_sub_state_test.exs b/apps/engine/test/engine/dispatch/pub_sub_state_test.exs
similarity index 96%
rename from apps/remote_control/test/lexical/remote_control/dispatch/pub_sub_state_test.exs
rename to apps/engine/test/engine/dispatch/pub_sub_state_test.exs
index b9794dc4..5ff48b3d 100644
--- a/apps/remote_control/test/lexical/remote_control/dispatch/pub_sub_state_test.exs
+++ b/apps/engine/test/engine/dispatch/pub_sub_state_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.RemoteControl.Dispatch.PubSubStateTest do
+defmodule Engine.Dispatch.PubSubStateTest do
use ExUnit.Case
- alias Lexical.RemoteControl.Dispatch.PubSub.State
+ alias Engine.Dispatch.PubSub.State
setup do
state = State.new()
diff --git a/apps/remote_control/test/lexical/remote_control/dispatch_test.exs b/apps/engine/test/engine/dispatch_test.exs
similarity index 93%
rename from apps/remote_control/test/lexical/remote_control/dispatch_test.exs
rename to apps/engine/test/engine/dispatch_test.exs
index 949c9dd6..0ddb2688 100644
--- a/apps/remote_control/test/lexical/remote_control/dispatch_test.exs
+++ b/apps/engine/test/engine/dispatch_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.DispatchTest do
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Dispatch
+defmodule Engine.DispatchTest do
+ alias Engine.Api.Messages
+ alias Engine.Dispatch
import Messages
use ExUnit.Case
@@ -12,7 +12,7 @@ defmodule Lexical.RemoteControl.DispatchTest do
end
defmodule Forwarder do
- alias Lexical.RemoteControl.Dispatch
+ alias Engine.Dispatch
def start(message_types) do
test = self()
diff --git a/apps/remote_control/test/lexical/remote_control/module_mappings_test.exs b/apps/engine/test/engine/module_mappings_test.exs
similarity index 87%
rename from apps/remote_control/test/lexical/remote_control/module_mappings_test.exs
rename to apps/engine/test/engine/module_mappings_test.exs
index df7b30e3..3a4def9b 100644
--- a/apps/remote_control/test/lexical/remote_control/module_mappings_test.exs
+++ b/apps/engine/test/engine/module_mappings_test.exs
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.ModuleMappingsTest do
- alias Lexical.RemoteControl.Dispatch
- alias Lexical.RemoteControl.ModuleMappings
+defmodule Engine.ModuleMappingsTest do
+ alias Engine.Dispatch
+ alias Engine.ModuleMappings
use ExUnit.Case
- use Lexical.Test.EventualAssertions
+ use Forge.Test.EventualAssertions
- import Lexical.RemoteControl.Api.Messages
+ import Engine.Api.Messages
setup do
start_supervised!(Dispatch)
diff --git a/apps/remote_control/test/lexical/remote_control/modules_test.exs b/apps/engine/test/engine/modules_test.exs
similarity index 88%
rename from apps/remote_control/test/lexical/remote_control/modules_test.exs
rename to apps/engine/test/engine/modules_test.exs
index 62c959e4..0398b682 100644
--- a/apps/remote_control/test/lexical/remote_control/modules_test.exs
+++ b/apps/engine/test/engine/modules_test.exs
@@ -1,8 +1,8 @@
-defmodule Lexical.RemoteControl.ModulesTest do
- alias Lexical.RemoteControl.Modules
+defmodule Engine.ModulesTest do
+ alias Engine.Modules
use ExUnit.Case
- use Lexical.Test.EventualAssertions
+ use Forge.Test.EventualAssertions
describe "simple prefixes" do
test "specifying a prefix with a string" do
@@ -39,7 +39,7 @@ defmodule Lexical.RemoteControl.ModulesTest do
end
test "not finding anything" do
- assert [] = Modules.with_prefix("LexicalIsTheBest")
+ assert [] = Modules.with_prefix("ExpertIsTheBest")
end
end
diff --git a/apps/engine/test/engine/plugin/runner/coordinator/state_test.exs b/apps/engine/test/engine/plugin/runner/coordinator/state_test.exs
new file mode 100644
index 00000000..f934fb46
--- /dev/null
+++ b/apps/engine/test/engine/plugin/runner/coordinator/state_test.exs
@@ -0,0 +1,147 @@
+defmodule Engine.Plugin.Coordinator.StateTest do
+ alias Engine.Plugin.Runner
+ alias Engine.Plugin.Runner.Coordinator.State
+ alias Forge.Document
+ alias Forge.Plugin.V1
+
+ use ExUnit.Case
+
+ setup do
+ start_supervised!(Runner.Supervisor)
+
+ on_exit(fn ->
+ Runner.clear_config()
+ end)
+
+ {:ok, state: State.new()}
+ end
+
+ test "with no configured plugins" do
+ assert {[], _} = State.run_all(%State{}, nil, :diagnostic, 50)
+ end
+
+ defmodule FailsInit do
+ use V1.Diagnostic, name: :fails_init
+
+ def init do
+ {:error, :failed}
+ end
+
+ def diagnose(subject) do
+ {:ok, [subject]}
+ end
+ end
+
+ test "a plugin is deactivated if it fails to initialize" do
+ assert :error = Runner.register(FailsInit)
+ refute :fails_init in Runner.enabled_plugins()
+ end
+
+ defmodule Echo do
+ use V1.Diagnostic, name: :echo
+
+ def diagnose(subject) do
+ {:ok, [subject]}
+ end
+ end
+
+ defmodule MultipleResults do
+ use V1.Diagnostic, name: :multiple_results
+
+ def diagnose(subject) do
+ {:ok, [subject, subject]}
+ end
+ end
+
+ describe "plugins completing successfully" do
+ test "results are returned for a single plugin", %{state: state} do
+ Runner.register(Echo)
+ doc = %Document{}
+ assert {[^doc], _} = State.run_all(state, doc, :diagnostic, 50)
+ end
+
+ test "results are aggregated for multiple plugins", %{state: state} do
+ Runner.register(Echo)
+ Runner.register(MultipleResults)
+
+ doc = %Document{}
+ assert {[^doc, ^doc, ^doc], _} = State.run_all(state, doc, :diagnostic, 50)
+ end
+ end
+
+ describe "failure modes" do
+ defmodule TimesOut do
+ use V1.Diagnostic, name: :times_out
+
+ def diagnose(subject) do
+ Process.sleep(5000)
+ {:ok, [subject]}
+ end
+ end
+
+ defmodule Crashes do
+ use V1.Diagnostic, name: :crashes
+
+ def diagnose(subject) do
+ 45 = subject
+ {:ok, [subject]}
+ end
+ end
+
+ defmodule Errors do
+ use V1.Diagnostic, name: :errors
+
+ def diagnose(_) do
+ {:error, :invalid_subject}
+ end
+ end
+
+ defmodule BadReturn do
+ use V1.Diagnostic, name: :bad_return
+
+ def diagnose(subject) do
+ {:ok, subject}
+ end
+ end
+
+ test "timeouts are logged", %{state: state} do
+ Runner.register(TimesOut)
+
+ assert {[], state} = State.run_all(state, %Document{}, :diagnostic, 50)
+ assert State.failure_count(state, TimesOut) == 1
+ end
+
+ test "crashing plugins are logged", %{state: state} do
+ Runner.register(Crashes)
+ assert {[], state} = State.run_all(state, %Document{}, :diagnostic, 50)
+ assert State.failure_count(state, Crashes) == 1
+ end
+
+ test "a plugin that returns an error is logged", %{state: state} do
+ Runner.register(Errors)
+
+ assert {[], state} = State.run_all(state, %Document{}, :diagnostic, 50)
+ assert State.failure_count(state, Errors) == 1
+ end
+
+ test "a plugin that doesn't return a list is logged", %{state: state} do
+ Runner.register(BadReturn)
+
+ assert {[], state} = State.run_all(state, %Document{}, :diagnostic, 50)
+ assert State.failure_count(state, BadReturn) == 1
+ end
+
+ test "a plugin is disabled if it fails 10 times", %{state: state} do
+ Runner.register(Crashes)
+
+ assert :crashes in Runner.enabled_plugins()
+
+ Enum.reduce(1..10, state, fn _, state ->
+ {_, state} = State.run_all(state, %Document{}, :diagnostic, 50)
+ state
+ end)
+
+ refute :crashes in Runner.enabled_plugins()
+ end
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/plugin/runner/coordinator_test.exs b/apps/engine/test/engine/plugin/runner/coordinator_test.exs
similarity index 85%
rename from apps/remote_control/test/lexical/remote_control/plugin/runner/coordinator_test.exs
rename to apps/engine/test/engine/plugin/runner/coordinator_test.exs
index 89254857..c7adb3fc 100644
--- a/apps/remote_control/test/lexical/remote_control/plugin/runner/coordinator_test.exs
+++ b/apps/engine/test/engine/plugin/runner/coordinator_test.exs
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Plugin.Runner.CoordinatorTest do
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl.Plugin.Runner
+defmodule Engine.Plugin.Runner.CoordinatorTest do
+ alias Engine.Plugin.Runner
+ alias Forge.Document
+ alias Forge.Project
use ExUnit.Case, async: false
- import Lexical.Test.EventualAssertions
+ import Forge.Test.EventualAssertions
setup do
{:ok, _} = start_supervised(Runner.Supervisor)
@@ -18,10 +18,10 @@ defmodule Lexical.RemoteControl.Plugin.Runner.CoordinatorTest do
end
defmodule Echo do
- alias Lexical.Document
- alias Lexical.Project
+ alias Forge.Document
+ alias Forge.Project
- use Lexical.Plugin.V1.Diagnostic, name: :report_back
+ use Forge.Plugin.V1.Diagnostic, name: :report_back
def diagnose(%Document{} = doc) do
{:ok, [doc]}
@@ -87,7 +87,7 @@ defmodule Lexical.RemoteControl.Plugin.Runner.CoordinatorTest do
describe "handling exceptional conditions" do
defmodule Crashy do
- use Lexical.Plugin.V1.Diagnostic, name: :crashy
+ use Forge.Plugin.V1.Diagnostic, name: :crashy
def diagnose(_) do
raise "Bad"
@@ -95,7 +95,7 @@ defmodule Lexical.RemoteControl.Plugin.Runner.CoordinatorTest do
end
defmodule Slow do
- use Lexical.Plugin.V1.Diagnostic, name: :slow
+ use Forge.Plugin.V1.Diagnostic, name: :slow
def diagnose(_) do
Process.sleep(500)
@@ -104,7 +104,7 @@ defmodule Lexical.RemoteControl.Plugin.Runner.CoordinatorTest do
end
defmodule BadReturn do
- use Lexical.Plugin.V1.Diagnostic, name: :bad_return
+ use Forge.Plugin.V1.Diagnostic, name: :bad_return
def diagnose(_) do
{:ok, 34}
@@ -112,7 +112,7 @@ defmodule Lexical.RemoteControl.Plugin.Runner.CoordinatorTest do
end
defmodule Exits do
- use Lexical.Plugin.V1.Diagnostic, name: :exits
+ use Forge.Plugin.V1.Diagnostic, name: :exits
def diagnose(_) do
exit(:bad)
diff --git a/apps/engine/test/engine/progress_test.exs b/apps/engine/test/engine/progress_test.exs
new file mode 100644
index 00000000..6daa4ad6
--- /dev/null
+++ b/apps/engine/test/engine/progress_test.exs
@@ -0,0 +1,32 @@
+defmodule Engine.ProgressTest do
+ alias Engine.Progress
+
+ import Engine.Api.Messages
+
+ use ExUnit.Case
+ use Patch
+ use Progress
+
+ setup do
+ test_pid = self()
+ patch(Engine.Api.Proxy, :broadcast, &send(test_pid, &1))
+ :ok
+ end
+
+ test "it should send begin/complete event and return the result" do
+ result = with_progress "foo", fn -> :ok end
+
+ assert result == :ok
+ assert_received project_progress(label: "foo", stage: :begin)
+ assert_received project_progress(label: "foo", stage: :complete)
+ end
+
+ test "it should send begin/complete event even there is an exception" do
+ assert_raise(Mix.Error, fn ->
+ with_progress "compile", fn -> raise Mix.Error, "can't compile" end
+ end)
+
+ assert_received project_progress(label: "compile", stage: :begin)
+ assert_received project_progress(label: "compile", stage: :complete)
+ end
+end
diff --git a/apps/engine/test/engine/project_node_test.exs b/apps/engine/test/engine/project_node_test.exs
new file mode 100644
index 00000000..4a7fee02
--- /dev/null
+++ b/apps/engine/test/engine/project_node_test.exs
@@ -0,0 +1,43 @@
+defmodule Engine.ProjectNodeTest do
+ alias Engine.ProjectNode
+ alias Engine.ProjectNodeSupervisor
+
+ import Forge.Test.EventualAssertions
+ import Engine.Test.Fixtures
+
+ use ExUnit.Case, async: false
+
+ setup do
+ project = project()
+ start_supervised!({ProjectNodeSupervisor, project})
+ {:ok, %{project: project}}
+ end
+
+ test "it should be able to stop a project node and won't restart", %{project: project} do
+ {:ok, _node_name, _} = Engine.start_link(project)
+
+ project_alive? = project |> ProjectNode.name() |> Process.whereis() |> Process.alive?()
+
+ assert project_alive?
+ assert :ok = ProjectNode.stop(project, 1500)
+ assert Process.whereis(ProjectNode.name(project)) == nil
+ end
+
+ test "it should be stopped atomically when the startup process is dead", %{project: project} do
+ test_pid = self()
+
+ linked_node_process =
+ spawn(fn ->
+ {:ok, _node_name, _} = Engine.start_link(project)
+ send(test_pid, :started)
+ end)
+
+ assert_receive :started, 1500
+
+ node_process_name = ProjectNode.name(project)
+
+ assert node_process_name |> Process.whereis() |> Process.alive?()
+ Process.exit(linked_node_process, :kill)
+ assert_eventually Process.whereis(node_process_name) == nil, 50
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/search/fuzzy/scorer_test.exs b/apps/engine/test/engine/search/fuzzy/scorer_test.exs
similarity index 87%
rename from apps/remote_control/test/lexical/remote_control/search/fuzzy/scorer_test.exs
rename to apps/engine/test/engine/search/fuzzy/scorer_test.exs
index 2faecc5e..b10a5fa2 100644
--- a/apps/remote_control/test/lexical/remote_control/search/fuzzy/scorer_test.exs
+++ b/apps/engine/test/engine/search/fuzzy/scorer_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Search.Fuzzy.ScorerTest do
- alias Lexical.RemoteControl.Search.Fuzzy.Scorer
+defmodule Engine.Search.Fuzzy.ScorerTest do
+ alias Engine.Search.Fuzzy.Scorer
use ExUnit.Case
@@ -44,12 +44,12 @@ defmodule Lexical.RemoteControl.Search.Fuzzy.ScorerTest do
test "more complete matches are boosted" do
results =
score_and_sort(
- ~w(Lexical.Document.Range Something.Else.Lexical.Other.Type.Document.Thing Lexical.Document),
- "Lexical.Document"
+ ~w(Forge.Document.Range Something.Else.Expert.Other.Type.Document.Thing Forge.Document),
+ "Forge.Document"
)
assert results ==
- ~w(Lexical.Document Lexical.Document.Range Something.Else.Lexical.Other.Type.Document.Thing)
+ ~w(Forge.Document Forge.Document.Range Something.Else.Expert.Other.Type.Document.Thing)
end
test "matches at the beginning of the string are boosted" do
diff --git a/apps/remote_control/test/lexical/remote_control/search/fuzzy_test.exs b/apps/engine/test/engine/search/fuzzy_test.exs
similarity index 94%
rename from apps/remote_control/test/lexical/remote_control/search/fuzzy_test.exs
rename to apps/engine/test/engine/search/fuzzy_test.exs
index a027b866..3fc1683b 100644
--- a/apps/remote_control/test/lexical/remote_control/search/fuzzy_test.exs
+++ b/apps/engine/test/engine/search/fuzzy_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.Search.FuzzyTest do
- alias Lexical.RemoteControl.Search.Fuzzy
- import Lexical.Test.Entry.Builder
+defmodule Engine.Search.FuzzyTest do
+ alias Engine.Search.Fuzzy
+ import Engine.Test.Entry.Builder
use ExUnit.Case
setup do
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/ecto_schema_test.exs b/apps/engine/test/engine/search/indexer/extractors/ecto_schema_test.exs
similarity index 98%
rename from apps/remote_control/test/lexical/remote_control/search/indexer/extractors/ecto_schema_test.exs
rename to apps/engine/test/engine/search/indexer/extractors/ecto_schema_test.exs
index 2fad5375..f2b1f02f 100644
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/ecto_schema_test.exs
+++ b/apps/engine/test/engine/search/indexer/extractors/ecto_schema_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.EctoSchemaTest do
- use Lexical.Test.ExtractorCase
+defmodule Engine.Search.Indexer.Extractors.EctoSchemaTest do
+ use Engine.Test.ExtractorCase
def index(source) do
do_index(source, fn entry -> entry.type == :struct end)
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/ex_unit_test.exs b/apps/engine/test/engine/search/indexer/extractors/ex_unit_test.exs
similarity index 97%
rename from apps/remote_control/test/lexical/remote_control/search/indexer/extractors/ex_unit_test.exs
rename to apps/engine/test/engine/search/indexer/extractors/ex_unit_test.exs
index ea8b3958..087c4c27 100644
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/ex_unit_test.exs
+++ b/apps/engine/test/engine/search/indexer/extractors/ex_unit_test.exs
@@ -1,8 +1,8 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.ExUnitTest do
- alias Lexical.RemoteControl.Search.Indexer.Extractors
+defmodule Engine.Search.Indexer.Extractors.ExUnitTest do
+ alias Engine.Search.Indexer.Extractors
- use Lexical.Test.ExtractorCase
- import Lexical.Test.RangeSupport
+ use Engine.Test.ExtractorCase
+ import Forge.Test.RangeSupport
@test_types [
:ex_unit_setup,
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/function_definition_test.exs b/apps/engine/test/engine/search/indexer/extractors/function_definition_test.exs
similarity index 98%
rename from apps/remote_control/test/lexical/remote_control/search/indexer/extractors/function_definition_test.exs
rename to apps/engine/test/engine/search/indexer/extractors/function_definition_test.exs
index 7b2dea8a..3d7941c1 100644
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/function_definition_test.exs
+++ b/apps/engine/test/engine/search/indexer/extractors/function_definition_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionDefinitionTest do
- alias Lexical.RemoteControl.Search.Indexer.Entry
- use Lexical.Test.ExtractorCase
+defmodule Engine.Search.Indexer.Extractors.FunctionDefinitionTest do
+ alias Engine.Search.Indexer.Entry
+ use Engine.Test.ExtractorCase
def index(source) do
do_index(source, fn %Entry{type: type} = entry ->
@@ -197,8 +197,7 @@ defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionDefinitionTest
end
test "skip quoted function defined with defdelegate" do
- assert {:ok, [], _doc} =
- ~q[
+ assert {:ok, [], _doc} = ~q[
quote do
defdelegate unquote(symbol)(enumerable, other), to: Enum
end
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/function_reference_test.exs b/apps/engine/test/engine/search/indexer/extractors/function_reference_test.exs
similarity index 98%
rename from apps/remote_control/test/lexical/remote_control/search/indexer/extractors/function_reference_test.exs
rename to apps/engine/test/engine/search/indexer/extractors/function_reference_test.exs
index e49e5c29..344f6a38 100644
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/function_reference_test.exs
+++ b/apps/engine/test/engine/search/indexer/extractors/function_reference_test.exs
@@ -1,10 +1,10 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.FunctionReferenceTest do
- alias Lexical.Test.RangeSupport
+defmodule Engine.Search.Indexer.Extractors.FunctionReferenceTest do
+ alias Forge.Test.RangeSupport
- import Lexical.Test.CodeSigil
+ import Forge.Test.CodeSigil
import RangeSupport
- use Lexical.Test.ExtractorCase
+ use Engine.Test.ExtractorCase
def index(source) do
do_index(source, fn entry ->
diff --git a/apps/engine/test/engine/search/indexer/extractors/module_attribute_test.exs b/apps/engine/test/engine/search/indexer/extractors/module_attribute_test.exs
new file mode 100644
index 00000000..418a3b9b
--- /dev/null
+++ b/apps/engine/test/engine/search/indexer/extractors/module_attribute_test.exs
@@ -0,0 +1,194 @@
+defmodule Engine.Search.Indexer.Extractors.ModuleAttributeTest do
+ alias Engine.Search.Subject
+ use Engine.Test.ExtractorCase
+
+ def index(source) do
+ do_index(source, fn entry ->
+ entry.type == :module_attribute
+ end)
+ end
+
+ describe "indexing module attributes" do
+ test "finds definitions when defining scalars" do
+ {:ok, [attr], doc} =
+ ~q[
+ defmodule Root do
+ @attribute 32
+ end
+ ]
+ |> index()
+
+ assert attr.type == :module_attribute
+ assert attr.subtype == :definition
+ assert attr.subject == Subject.module_attribute(Root, :attribute)
+
+ assert decorate(doc, attr.range) =~ "«@attribute 32»"
+ end
+
+ test "in-progress module attributes are ignored" do
+ {:ok, [latter_attribute], _doc} =
+ ~q[
+ defmodule Root do
+ @
+ @callback foo() :: :ok
+ end
+ ]
+ |> index()
+
+ assert latter_attribute.subject == "@callback"
+ end
+
+ test "finds multiple definitions of the same attribute" do
+ {:ok, [first, second, third], doc} =
+ ~q[
+ defmodule Parent do
+ @tag 1
+ def first, do: 1
+
+ @tag 2
+ def second, do: 1
+
+ @tag 3
+ def third, do: 1
+ end
+ ]
+ |> index()
+
+ assert first.type == :module_attribute
+ assert first.subtype == :definition
+ assert first.subject == Subject.module_attribute(Parent, :tag)
+ assert decorate(doc, first.range) =~ "«@tag 1»"
+
+ assert second.type == :module_attribute
+ assert second.subtype == :definition
+ assert second.subject == Subject.module_attribute(Parent, :tag)
+ assert decorate(doc, second.range) =~ "«@tag 2»"
+
+ assert third.type == :module_attribute
+ assert third.subtype == :definition
+ assert third.subject == Subject.module_attribute(Parent, :tag)
+ assert decorate(doc, third.range) =~ "«@tag 3»"
+ end
+
+ test "finds definitions when the definition spans multiple lines" do
+ {:ok, [attr], doc} =
+ ~q[
+ defmodule Parent do
+ @number_strings 1..50
+ |> Enum.map(& &1 * 2)
+ |> Enum.map(&Integer.to_string/1)
+ end
+ ]
+ |> index()
+
+ assert attr.type == :module_attribute
+ assert attr.subtype == :definition
+ assert attr.subject == Subject.module_attribute(Parent, :number_strings)
+
+ expected =
+ """
+ «@number_strings 1..50
+ |> Enum.map(& &1 * 2)
+ |> Enum.map(&Integer.to_string/1)»
+ """
+ |> String.trim()
+
+ assert decorate(doc, attr.range) =~ expected
+ end
+
+ test "finds references in other definitions" do
+ {:ok, [_def1, def2, reference], doc} =
+ ~q[
+ defmodule Root do
+ @attr 23
+
+ @attr2 @attr + 1
+ end
+ ]
+ |> index()
+
+ assert def2.type == :module_attribute
+ assert def2.subtype == :definition
+ assert def2.subject == Subject.module_attribute(Root, :attr2)
+ assert decorate(doc, def2.range) =~ "«@attr2 @attr + 1»"
+
+ assert reference.type == :module_attribute
+ assert reference.subtype == :reference
+ assert reference.subject == Subject.module_attribute(Root, :attr)
+ assert decorate(doc, reference.range) =~ "@attr2 «@attr» + 1"
+ end
+
+ test "finds definitions in nested contexts" do
+ {:ok, [parent_def, child_def], doc} =
+ ~q[
+ defmodule Parent do
+ @in_parent true
+ defmodule Child do
+ @in_child true
+ end
+ end
+ ]
+ |> index()
+
+ assert parent_def.type == :module_attribute
+ assert parent_def.subtype == :definition
+ assert parent_def.subject == Subject.module_attribute(Parent, :in_parent)
+ assert decorate(doc, parent_def.range) =~ "«@in_parent true»"
+
+ assert child_def.type == :module_attribute
+ assert child_def.subtype == :definition
+ assert child_def.subject == Subject.module_attribute(Parent.Child, :in_child)
+ assert decorate(doc, child_def.range) =~ "«@in_child true»"
+ end
+
+ test "finds references in function arguments" do
+ {:ok, [_definition, reference], doc} =
+ ~q[
+ defmodule InArgs do
+ @age 95
+ def is_old?(@age), do: true
+ end
+ ]
+ |> index()
+
+ assert reference.type == :module_attribute
+ assert reference.subtype == :reference
+ assert reference.subject == Subject.module_attribute(InArgs, :age)
+ assert decorate(doc, reference.range) =~ " def is_old?(«@age»)"
+ end
+
+ test "finds references in map keys" do
+ {:ok, [_, key], doc} =
+ ~q[
+ defmodule InMapKey do
+ @foo 3
+ def something(%{@foo => 3}) do
+ end
+ end
+ ]
+ |> index()
+
+ assert key.type == :module_attribute
+ assert key.subtype == :reference
+ assert key.subject == Subject.module_attribute(InMapKey, :foo)
+ assert decorate(doc, key.range) =~ "%{«@foo» => 3}"
+ end
+
+ test "finds references in map values" do
+ {:ok, [_, value], doc} =
+ ~q[
+ defmodule InMapValue do
+ @foo 3
+ def something(%{foo: @foo}) do
+ end
+ end
+ ]
+ |> index()
+
+ assert value.type == :module_attribute
+ assert value.subtype == :reference
+ assert value.subject == Subject.module_attribute(InMapValue, :foo)
+ assert decorate(doc, value.range) =~ "%{foo: «@foo»}"
+ end
+ end
+end
diff --git a/apps/engine/test/engine/search/indexer/extractors/module_test.exs b/apps/engine/test/engine/search/indexer/extractors/module_test.exs
new file mode 100644
index 00000000..06a9b1ef
--- /dev/null
+++ b/apps/engine/test/engine/search/indexer/extractors/module_test.exs
@@ -0,0 +1,540 @@
+defmodule Engine.Search.Indexer.Extractors.ModuleTest do
+ use Engine.Test.ExtractorCase
+
+ def index(source) do
+ do_index(source, &(&1.type == :module))
+ end
+
+ describe "indexing modules" do
+ test "it doesn't confuse a list of atoms for a module" do
+ {:ok, [module], _} =
+ ~q(
+ defmodule Root do
+ @attr [:Some, :Other, :Module]
+ end
+ )
+ |> index()
+
+ assert module.type == :module
+ assert module.subject == Root
+ end
+
+ test "indexes a flat module with no aliases" do
+ {:ok, [entry], doc} =
+ ~q[
+ defmodule Simple do
+ end
+ ]
+ |> index()
+
+ assert entry.type == :module
+ assert entry.block_id == :root
+ assert entry.subject == Simple
+ assert decorate(doc, entry.range) =~ "defmodule «Simple» do"
+ end
+
+ test "indexes a flat module with a dotted name" do
+ {:ok, [entry], doc} =
+ ~q[
+ defmodule Simple.Module.Path do
+ end
+ ]
+ |> index()
+
+ assert entry.subject == Simple.Module.Path
+ assert entry.type == :module
+ assert entry.block_id == :root
+ assert decorate(doc, entry.range) =~ "defmodule «Simple.Module.Path» do"
+ end
+
+ test "indexes a flat module with an aliased name" do
+ {:ok, [_alias, entry], doc} =
+ ~q[
+ alias Something.Else
+ defmodule Else.Other do
+ end
+ ]
+ |> index()
+
+ assert entry.subject == Something.Else.Other
+ assert decorate(doc, entry.range) == "defmodule «Else.Other» do"
+ end
+
+ test "can detect an erlang module" do
+ {:ok, [module_def, erlang_module], doc} =
+ ~q[
+ defmodule Root do
+ @something :timer
+ end
+ ]
+ |> index()
+
+ assert erlang_module.type == :module
+ assert erlang_module.block_id == module_def.id
+ assert erlang_module.subject == :timer
+ assert decorate(doc, erlang_module.range) =~ " @something «:timer»"
+ end
+
+ test "can detect a module reference in a module attribute" do
+ {:ok, [module_def, attribute], doc} =
+ ~q[
+ defmodule Root do
+ @attr Some.Other.Module
+ end
+ ]
+ |> index()
+
+ assert attribute.type == :module
+ assert attribute.block_id == module_def.id
+ assert attribute.subject == Some.Other.Module
+ assert decorate(doc, attribute.range) =~ " @attr «Some.Other.Module»"
+ end
+
+ test "can detect __MODULE__ in a function" do
+ {:ok, [_module_def, module_ref], doc} =
+ ~q[
+ defmodule Root do
+ def something do
+ __MODULE__
+ end
+ end
+ ]
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Root
+ assert decorate(doc, module_ref.range) =~ " «__MODULE__»"
+ end
+
+ test "can detect a module reference on the left side of a pattern match" do
+ {:ok, [_module_def, module_ref], doc} =
+ ~q[
+ defmodule Root do
+ def my_fn(arg) do
+ Some.Module = arg
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ "«Some.Module» = arg"
+ end
+
+ test "can detect an aliased module reference on the left side of a pattern match" do
+ {:ok, [_module_def, _alias, module_ref], doc} =
+ ~q[
+ defmodule Root do
+ alias Some.Other.Thing
+ def my_fn(arg) do
+ Thing.Util = arg
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Other.Thing.Util
+ assert decorate(doc, module_ref.range) =~ " «Thing.Util» = arg"
+ end
+
+ test "can detect a module reference in a nested alias" do
+ {:ok, [_top_level, _foo, _first, second, fourth], doc} = ~q[
+ defmodule TopLevel do
+ alias Foo.{
+ First,
+ Second,
+ Third.Fourth
+ }
+ end] |> index()
+
+ assert second.subject == Foo.Second
+ assert decorate(doc, second.range) == " «Second»,"
+
+ assert fourth.subject == Foo.Third.Fourth
+ assert decorate(doc, fourth.range) == " «Third.Fourth»"
+ end
+
+ test "can detect a module reference on the right side of a pattern match" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule Root do
+ def my_fn(arg) do
+ arg = Some.Module
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ "arg = «Some.Module»"
+ end
+
+ test "can detect an aliased module reference on the right side of a pattern match" do
+ {:ok, [_module_def, _alias, module_ref], doc} =
+ ~q[
+ defmodule Root do
+ alias Some.Other.Thing
+ def my_fn(arg) do
+ arg = Thing.Util
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Other.Thing.Util
+ assert decorate(doc, module_ref.range) =~ " arg = «Thing.Util»"
+ end
+
+ test "can detect a module reference in a remote call" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule RemoteCall do
+ def my_fn do
+ Some.Module.function()
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ " «Some.Module».function()"
+ end
+
+ test "can detect a module reference in a remote captured function" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule Capture do
+ def my_fn do
+ &Some.Module.function/1
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ " &«Some.Module».function/1"
+ end
+
+ test "can detect a module reference in a remote captured function with multiple arities" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule Capture do
+ def my_fn do
+ &Some.Module.function(&1, 1)
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ " &«Some.Module».function(&1, 1)"
+ end
+
+ test "can detect a module reference in an aliased remote captured function" do
+ {:ok, [_module, _alias, _aliased, module_ref], doc} = ~q[
+ defmodule Capture do
+ alias First.Second, as: Third
+ def my_fn do
+ &Third.function/1
+ end
+ end
+ ]t |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == First.Second
+ assert decorate(doc, module_ref.range) =~ " &«Third».function/1"
+ end
+
+ test "can detect a module reference in a function call's arguments" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule FunCallArgs do
+ def my_fn do
+ function(Some.Module)
+ end
+
+ def function(_) do
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ " function(«Some.Module»)"
+ end
+
+ test "can detect a module reference in a function's pattern match arguments" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule FunCallArgs do
+ def my_fn(arg = Some.Module) do
+ arg
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ "def my_fn(arg = «Some.Module»)"
+ end
+
+ test "can detect a module reference in default parameters" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule FunCallArgs do
+ def my_fn(module \\ Some.Module) do
+ module.foo()
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ ~S[def my_fn(module \\ «Some.Module»)]
+ end
+
+ test "can detect a module reference in map keys" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule FunCallArgs do
+ def my_fn do
+ %{Some.Module => 1}
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ "%{«Some.Module» => 1}"
+ end
+
+ test "can detect a module reference in map values" do
+ {:ok, [_module, module_ref], doc} =
+ ~q[
+ defmodule FunCallArgs do
+ def my_fn do
+ %{invalid: Some.Module}
+ end
+ end
+ ]t
+ |> index()
+
+ assert module_ref.type == :module
+ assert module_ref.subject == Some.Module
+ assert decorate(doc, module_ref.range) =~ "%{invalid: «Some.Module»}"
+ end
+
+ test "can detect a module reference in an anonymous function call" do
+ {:ok, [parent, ref], doc} =
+ ~q[
+ defmodule Parent do
+ def outer_fn do
+ fn ->
+ Ref.To.Something
+ end
+ end
+ end
+ ]
+ |> index()
+
+ assert ref.type == :module
+ assert ref.subject == Ref.To.Something
+ refute ref.block_id == parent.id
+ assert decorate(doc, ref.range) =~ " «Ref.To.Something»"
+ end
+
+ test "can detect a @protocol module reference" do
+ {:ok, [protocol_def, protocol_ref, for_ref, proto_module_attr], doc} =
+ ~q[
+ defimpl MyProtocol, for: Atom do
+ def something do
+ @protocol.Something
+ end
+ end
+ ]
+ |> index()
+
+ assert protocol_def.type == :module
+ assert protocol_def.subtype == :definition
+ assert protocol_def.subject == MyProtocol.Atom
+ assert decorate(doc, protocol_def.range) == "«defimpl MyProtocol, for: Atom do»"
+
+ assert protocol_ref.type == :module
+ assert protocol_ref.subtype == :reference
+ assert protocol_ref.subject == MyProtocol
+ assert decorate(doc, protocol_ref.range) == "defimpl «MyProtocol», for: Atom do"
+
+ assert for_ref.type == :module
+ assert for_ref.subtype == :reference
+ assert for_ref.subject == Atom
+ assert decorate(doc, for_ref.range) == "defimpl MyProtocol, for: «Atom» do"
+
+ assert proto_module_attr.type == :module
+ assert proto_module_attr.subtype == :reference
+ assert proto_module_attr.subject == MyProtocol.Something
+ assert decorate(doc, proto_module_attr.range) == " «@protocol.Something»"
+ end
+
+ test "can detect an @for module reference" do
+ {:ok, [_, _, _, for_module_attr], doc} =
+ ~q[
+ defimpl MyProtocol, for: DataStructure do
+ def something do
+ @for.Something
+ end
+ end
+ ]
+ |> index()
+
+ assert for_module_attr.type == :module
+ assert for_module_attr.subtype == :reference
+ assert for_module_attr.subject == DataStructure.Something
+ assert decorate(doc, for_module_attr.range) == " «@for.Something»"
+ end
+ end
+
+ describe "multiple modules in one document" do
+ test "have different refs" do
+ {:ok, [first, second], _} =
+ ~q[
+ defmodule First do
+ end
+
+ defmodule Second do
+ end
+ ]
+ |> index()
+
+ assert first.block_id == :root
+ assert first.type == :module
+ assert first.subtype == :definition
+ assert first.subject == First
+
+ assert second.block_id == :root
+ assert second.type == :module
+ assert second.subtype == :definition
+ assert second.subject == Second
+
+ assert second.id != first.id
+ end
+
+ test "aren't nested" do
+ {:ok, [first, second, third, fourth], _} =
+ ~q[
+ defmodule A.B.C do
+ defstruct do
+ field(:ok, :boolean)
+ end
+ end
+
+ defmodule D.E.F do
+ defstruct do
+ field(:ok, :boolean)
+ end
+ end
+
+ defmodule G.H.I do
+ defstruct do
+ field(:ok, :boolean)
+ end
+ end
+
+ defmodule J.K.L do
+ defstruct do
+ field(:ok, :boolean)
+ end
+ end
+ ]
+ |> index()
+
+ assert first.subject == A.B.C
+ assert second.subject == D.E.F
+ assert third.subject == G.H.I
+ assert fourth.subject == J.K.L
+ end
+ end
+
+ describe "nested modules" do
+ test "have a parent/child relationship" do
+ {:ok, [parent, child], _} =
+ ~q[
+ defmodule Parent do
+ defmodule Child do
+ end
+ end
+ ]
+ |> index()
+
+ assert parent.block_id == :root
+ assert parent.type == :module
+ assert parent.subtype == :definition
+
+ assert child.block_id == parent.id
+ assert child.type == :module
+ assert child.subtype == :definition
+ end
+
+ test "Have aliases resolved correctly" do
+ {:ok, [_parent, _parent_alias, child, child_alias], _} =
+ ~q[
+ defmodule Parent do
+ alias Something.Else
+
+ defmodule Child do
+ alias Else.Other
+ end
+ end
+ ]
+ |> index()
+
+ assert child_alias.block_id == child.id
+ assert child_alias.type == :module
+ assert child_alias.subtype == :reference
+ assert child_alias.subject == Something.Else.Other
+ end
+
+ test "works with __MODULE__" do
+ {:ok, [parent, child], _} =
+ ~q[
+ defmodule Parent do
+ defmodule __MODULE__.Child do
+ end
+ end
+ ]
+ |> index()
+
+ assert parent.block_id == :root
+ assert parent.type == :module
+ assert parent.subtype == :definition
+
+ assert child.block_id == parent.id
+ assert child.type == :module
+ assert child.subtype == :definition
+ end
+
+ test "works with __MODULE__ alias concatenations" do
+ {:ok, [_, child], _} =
+ ~q[
+ defmodule Parent do
+ @child_module __MODULE__.Child
+ end
+ ]
+ |> index()
+
+ assert child.type == :module
+ assert child.subtype == :reference
+ assert child.subject == Parent.Child
+ end
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/protocol_test.exs b/apps/engine/test/engine/search/indexer/extractors/protocol_test.exs
similarity index 97%
rename from apps/remote_control/test/lexical/remote_control/search/indexer/extractors/protocol_test.exs
rename to apps/engine/test/engine/search/indexer/extractors/protocol_test.exs
index 4c0ac283..937e6e58 100644
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/protocol_test.exs
+++ b/apps/engine/test/engine/search/indexer/extractors/protocol_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.ProtocolTest do
- use Lexical.Test.ExtractorCase
+defmodule Engine.Search.Indexer.Extractors.ProtocolTest do
+ use Engine.Test.ExtractorCase
def index(source) do
do_index(source, &match?({:protocol, _}, &1.type))
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/struct_definition_test.exs b/apps/engine/test/engine/search/indexer/extractors/struct_definition_test.exs
similarity index 93%
rename from apps/remote_control/test/lexical/remote_control/search/indexer/extractors/struct_definition_test.exs
rename to apps/engine/test/engine/search/indexer/extractors/struct_definition_test.exs
index 10ea7270..39c43782 100644
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/struct_definition_test.exs
+++ b/apps/engine/test/engine/search/indexer/extractors/struct_definition_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.StructDefinitionTest do
- alias Lexical.RemoteControl.Search.Subject
- use Lexical.Test.ExtractorCase
+defmodule Engine.Search.Indexer.Extractors.StructDefinitionTest do
+ alias Engine.Search.Subject
+ use Engine.Test.ExtractorCase
def index(source) do
do_index(source, fn entry ->
diff --git a/apps/engine/test/engine/search/indexer/extractors/struct_reference_test.exs b/apps/engine/test/engine/search/indexer/extractors/struct_reference_test.exs
new file mode 100644
index 00000000..96f3774d
--- /dev/null
+++ b/apps/engine/test/engine/search/indexer/extractors/struct_reference_test.exs
@@ -0,0 +1,392 @@
+defmodule Engine.Search.Indexer.Extractors.StructReferenceTest do
+ alias Engine.Search.Subject
+ use Engine.Test.ExtractorCase
+
+ def index(source) do
+ do_index(source, fn entry ->
+ entry.type == :struct and entry.subtype == :reference
+ end)
+ end
+
+ describe "recognizing structs" do
+ test "in a naked reference" do
+ {:ok, [struct], doc} =
+ ~q[%MyStruct{}]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == "«%MyStruct{}»"
+ end
+
+ test "in a naked reference with fields" do
+ {:ok, [struct], doc} =
+ ~q[
+ %MyStruct{name: "stinky", height: 184}
+ ]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S(«%MyStruct{name: "stinky", height: 184}»)
+ end
+
+ test "in a struct on the left side of a match" do
+ {:ok, [struct], doc} =
+ ~q[%MyStruct{} = variable]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == "«%MyStruct{}» = variable"
+ end
+
+ test "in a struct on the right side of a match" do
+ {:ok, [struct], doc} =
+ ~q[variable = %MyStruct{}]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == "variable = «%MyStruct{}»"
+ end
+
+ test "in a struct reference in params" do
+ {:ok, [struct], doc} =
+ ~q[
+ def my_fn(%MyStruct{} = first) do
+ end
+ ]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S[def my_fn(«%MyStruct{}» = first) do]
+ end
+
+ test "in nested struct references" do
+ {:ok, [outer, inner], doc} =
+ ~q[
+ %OuterStruct{inner: %InnerStruct{}}
+ ]
+ |> index()
+
+ assert outer.type == :struct
+ assert outer.subtype == :reference
+ assert outer.subject == Subject.module(OuterStruct)
+ assert decorate(doc, outer.range) == ~S[«%OuterStruct{inner: %InnerStruct{}}»]
+
+ assert inner.type == :struct
+ assert inner.subtype == :reference
+ assert inner.subject == Subject.module(InnerStruct)
+ assert decorate(doc, inner.range) == ~S[%OuterStruct{inner: «%InnerStruct{}»}]
+ end
+
+ test "in map keys" do
+ {:ok, [struct], doc} =
+ ~q[%{%MyStruct{} => 3}]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S[%{«%MyStruct{}» => 3}]
+ end
+
+ test "in map values" do
+ {:ok, [struct], doc} =
+ ~q[%{cool_struct: %MyStruct{}}]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S[%{cool_struct: «%MyStruct{}»}]
+ end
+
+ test "in list elements" do
+ {:ok, [struct], doc} =
+ ~q([1, 2, %MyStruct{}])
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S([1, 2, «%MyStruct{}»])
+ end
+
+ test "in a imported call to struct/1 with an alias" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct(MyStruct)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct(MyStruct)»]
+ end
+
+ test "in a imported call to struct/1 with __MODULE__" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct(__MODULE__)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(Parent)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct(__MODULE__)»]
+ end
+
+ test "in a imported call to struct!/1 with __MODULE__" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct!(__MODULE__)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(Parent)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct!(__MODULE__)»]
+ end
+
+ test "in a imported call to struct/2 with an alias" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct(MyStruct, foo: 3)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct(MyStruct, foo: 3)»]
+ end
+
+ test "in a imported call to struct!/2 with an alias" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct!(MyStruct, foo: 3)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct!(MyStruct, foo: 3)»]
+ end
+
+ test "in a imported call to struct/2 with __MODULE__" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct(__MODULE__, foo: 3)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(Parent)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct(__MODULE__, foo: 3)»]
+ end
+
+ test "is ignored if struct isn't imported" do
+ assert {:ok, _, _} =
+ ~q{
+ defmodule Parent do
+
+ import Kernel, except: [struct: 1]
+ struct = struct(MyStruct)
+ end
+ }
+ |> index()
+ end
+
+ test "in a fully qualified call to Kernel.struct/1" do
+ {:ok, [struct], doc} = ~q[struct = Kernel.struct(MyStruct)] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S[struct = «Kernel.struct(MyStruct)»]
+ end
+
+ test "in a fully qualified call to Kernel.struct/2" do
+ {:ok, [struct], doc} = ~q[struct = Kernel.struct(MyStruct, foo: 3)] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyStruct)
+ assert decorate(doc, struct.range) == ~S[struct = «Kernel.struct(MyStruct, foo: 3)»]
+ end
+
+ test "other functions named struct are not counted" do
+ {:ok, [], _} = ~q[struct = Macro.struct(MyStruct)] |> index()
+ end
+ end
+
+ describe "handling __MODULE__" do
+ test "in a module attribute" do
+ {:ok, [struct], doc} =
+ ~q[
+ defmodule MyModule do
+ @attr %__MODULE__{}
+ end
+ ]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyModule)
+ assert decorate(doc, struct.range) == ~S( @attr «%__MODULE__{}»)
+ end
+
+ test "in handling a submodule" do
+ {:ok, [struct], doc} =
+ ~q[
+ defmodule MyModule do
+ @attr %__MODULE__.Submodule{}
+ end
+ ]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyModule.Submodule)
+ assert decorate(doc, struct.range) == ~S( @attr «%__MODULE__.Submodule{}»)
+ end
+
+ test "in a function definition" do
+ {:ok, [struct], doc} =
+ ~q[
+ defmodule MyModule do
+ def my_fn(%__MODULE__{}), do: :ok
+ end
+ ]
+ |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(MyModule)
+ assert decorate(doc, struct.range) == ~S[ def my_fn(«%__MODULE__{}»), do: :ok]
+ end
+
+ test "in a call to Kernel.struct/1" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct(__MODULE__)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(Parent)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct(__MODULE__)»]
+ end
+
+ test "in a call to Kernel.struct!/1" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct!(__MODULE__)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(Parent)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct!(__MODULE__)»]
+ end
+
+ test "in a call to Kernel.struct/2" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct(__MODULE__, foo: 3)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(Parent)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct(__MODULE__, foo: 3)»]
+ end
+
+ test "in a call to Kernel.struct!/2" do
+ {:ok, [struct], doc} = ~q[
+ defmodule Parent do
+ struct = struct!(__MODULE__, foo: 3)
+ end
+ ] |> index()
+
+ assert struct.type == :struct
+ assert struct.subtype == :reference
+ assert struct.subject == Subject.module(Parent)
+ assert decorate(doc, struct.range) == ~S[ struct = «struct!(__MODULE__, foo: 3)»]
+ end
+ end
+
+ describe "when aliases can't be expanded" do
+ test "a fully qualified call to Kernel.struct/1 is ignored" do
+ assert {:ok, [], _} = ~q[
+ defmodule Parent do
+ struct = Kernel.struct(unquote(__MODULE__))
+ end
+ ] |> index()
+ end
+
+ test "a fully qualified call to Kernel.struct/2 is ignored" do
+ assert {:ok, [], _} = ~q[
+ defmodule Parent do
+ struct = Kernel.struct(unquote(__MODULE__), foo: 3)
+ end
+ ] |> index()
+ end
+
+ test "a call to struct!/2 is ignored" do
+ assert {:ok, [], _} = ~q[
+ defmodule Parent do
+ struct = struct!(unquote(__MODULE__), foo: 3)
+ end
+ ] |> index()
+ end
+
+ test "a call to struct!/1 is ignored" do
+ assert {:ok, [], _} = ~q[
+ defmodule Parent do
+ struct = struct!(unquote(__MODULE__))
+ end
+ ] |> index()
+ end
+
+ test "a call to struct/1 is ignored" do
+ assert {:ok, [], _} = ~q[
+ defmodule Parent do
+ struct = struct(unquote(__MODULE__))
+ end
+ ] |> index()
+ end
+
+ test "a call to struct/2 is ignored" do
+ assert {:ok, [], _} = ~q[
+ defmodule Parent do
+ struct = struct(unquote(__MODULE__), foo: 3)
+ end
+ ] |> index()
+ end
+
+ test "a reference ignored" do
+ assert {:ok, [], _} = ~q[
+ defmodule Parent do
+ struct = %unquote(__MODULE__){}
+ end
+ ] |> index()
+ end
+ end
+end
diff --git a/apps/engine/test/engine/search/indexer/extractors/variable_test.exs b/apps/engine/test/engine/search/indexer/extractors/variable_test.exs
new file mode 100644
index 00000000..d30e5574
--- /dev/null
+++ b/apps/engine/test/engine/search/indexer/extractors/variable_test.exs
@@ -0,0 +1,1015 @@
+defmodule Engine.Search.Indexer.Extractors.VariableTest do
+ alias Engine.Search.Indexer.Extractors
+
+ use Engine.Test.ExtractorCase
+
+ def index_references(source) do
+ do_index(source, fn entry -> entry.type == :variable and entry.subtype == :reference end, [
+ Extractors.Variable
+ ])
+ end
+
+ def index_definitions(source) do
+ do_index(source, fn entry -> entry.type == :variable and entry.subtype == :definition end, [
+ Extractors.Variable
+ ])
+ end
+
+ def index(source) do
+ do_index(source, &(&1.type == :variable), [Extractors.Variable])
+ end
+
+ def assert_definition(entry, variable_name) do
+ assert entry.type == :variable
+ assert entry.subtype == :definition
+ assert entry.subject == variable_name
+ end
+
+ def assert_reference(entry, variable_name) do
+ assert entry.type == :variable
+ assert entry.subtype == :reference
+ assert entry.subject == variable_name
+ end
+
+ for def_type <- [:def, :defp, :defmacro, :defmacrop] do
+ describe "variable definitions in #{def_type} parameters are extracted" do
+ test "in a plain parameter" do
+ {:ok, [param], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun(var) do
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(param, :var)
+ assert decorate(doc, param.range) =~ "#{unquote(def_type)} my_fun(«var»)"
+ end
+
+ test "in a struct value" do
+ {:ok, [param], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun(%Pattern{foo: var}) do
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(param, :var)
+ assert decorate(doc, param.range) =~ "#{unquote(def_type)} my_fun(%Pattern{foo: «var»})"
+ end
+
+ test "on both sides of a pattern match" do
+ {:ok, [var_1, var_2], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun(%Pattern{foo: var} = var_2) do
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(var_1, :var)
+
+ assert decorate(doc, var_1.range) =~
+ "#{unquote(def_type)} my_fun(%Pattern{foo: «var»} = var_2)"
+
+ assert_definition(var_2, :var_2)
+
+ assert decorate(doc, var_2.range) =~
+ "#{unquote(def_type)} my_fun(%Pattern{foo: var} = «var_2»)"
+ end
+
+ test "in a struct module" do
+ {:ok, [var_1], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun(%my_module{}) do
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(var_1, :my_module)
+ assert decorate(doc, var_1.range) =~ "#{unquote(def_type)} my_fun(%«my_module»{})"
+ end
+
+ test "in a bitstrings" do
+ {:ok, [var], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun(<>) do
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(var, :foo)
+
+ assert decorate(doc, var.range) =~
+ "#{unquote(def_type)} my_fun(<<«foo»::binary-size(3)>>) do"
+ end
+
+ test "in list elements" do
+ {:ok, [var_1, var_2], doc} =
+ ~q{
+ #{unquote(def_type)} my_fun([var_1, var_2]) do
+ end
+ }
+ |> index_definitions()
+
+ assert_definition(var_1, :var_1)
+ assert decorate(doc, var_1.range) =~ "#{unquote(def_type)} my_fun([«var_1», var_2])"
+
+ assert_definition(var_2, :var_2)
+ assert decorate(doc, var_2.range) =~ "#{unquote(def_type)} my_fun([var_1, «var_2»])"
+ end
+
+ test "in the tail of a list" do
+ {:ok, [tail], doc} =
+ ~q{
+ #{unquote(def_type)} my_fun([_ | acc]) do
+ end
+ }
+ |> index_definitions()
+
+ assert_definition(tail, :acc)
+ assert decorate(doc, tail.range) =~ "#{unquote(def_type)} my_fun([_ | «acc»])"
+ end
+
+ test "unless it is an alias" do
+ {:ok, [], _} =
+ ~q[
+ #{unquote(def_type)} my_fun(%MyStruct{}) do
+ end
+ ]
+ |> index_definitions()
+ end
+
+ test "unless it begins with an underscore" do
+ {:ok, [], _} =
+ ~q[
+ #{unquote(def_type)} my_fun(_unused) do
+ end
+ ]
+ |> index_definitions()
+
+ {:ok, [], _} =
+ ~q[
+ #{unquote(def_type)} my_fun(_) do
+ end
+ ]
+ |> index_definitions()
+ end
+ end
+
+ describe "variable definitions in #{def_type} that contain references are extracted" do
+ test "when passed through" do
+ {:ok, [def, ref], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun(var) do
+ var
+ end
+ ]
+ |> index()
+
+ assert_definition(def, :var)
+ assert_reference(ref, :var)
+
+ assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun(«var») do"
+ assert decorate(doc, ref.range) =~ " «var»"
+ end
+
+ test "when wrapped in a list" do
+ {:ok, [def, ref], doc} =
+ ~q{
+ #{unquote(def_type)} my_fun([var]) do
+ [var]
+ end
+ }
+ |> index()
+
+ assert_definition(def, :var)
+ assert_reference(ref, :var)
+
+ assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun([«var»]) do"
+ assert decorate(doc, ref.range) =~ " [«var»]"
+ end
+
+ test "when it's a map value" do
+ {:ok, [def, ref], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun(%{key: var}) do
+ %{key: var}
+ end
+ ]
+ |> index()
+
+ assert_definition(def, :var)
+ assert_reference(ref, :var)
+
+ assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun(%{key: «var»}) do"
+ assert decorate(doc, ref.range) =~ " %{key: «var»}"
+ end
+
+ test "when it's a struct module" do
+ {:ok, [def, ref], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun(%{key: var}) do
+ %{key: var}
+ end
+ ]
+ |> index()
+
+ assert_definition(def, :var)
+ assert_reference(ref, :var)
+
+ assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun(%{key: «var»}) do"
+ assert decorate(doc, ref.range) =~ " %{key: «var»}"
+ end
+
+ test "when it's a tuple entry " do
+ {:ok, [def, ref], doc} =
+ ~q[
+ #{unquote(def_type)} my_fun({var}) do
+ {var}
+ end
+ ]
+ |> index()
+
+ assert_definition(def, :var)
+ assert_reference(ref, :var)
+
+ assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun({«var»}) do"
+ assert decorate(doc, ref.range) =~ " {«var»}"
+ end
+
+ test "when it utilizes a pin " do
+ {:ok, [first_def, second_def, first_pin, other_def, second_ref, other_ref], doc} =
+ ~q"
+ #{unquote(def_type)} my_fun({first, second}) do
+ [^first, other] = second
+ other
+ end
+ "
+ |> index()
+
+ assert_definition(first_def, :first)
+
+ assert decorate(doc, first_def.range) =~
+ "#{unquote(def_type)} my_fun({«first», second}) do"
+
+ assert_definition(second_def, :second)
+
+ assert decorate(doc, second_def.range) =~
+ "#{unquote(def_type)} my_fun({first, «second»}) do"
+
+ assert_reference(first_pin, :first)
+ assert decorate(doc, first_pin.range) =~ " [^«first», other]"
+
+ assert_definition(other_def, :other)
+ assert decorate(doc, other_def.range) =~ " [^first, «other»]"
+
+ assert_reference(second_ref, :second)
+ assert decorate(doc, second_ref.range) =~ " [^first, other] = «second»"
+
+ assert_reference(other_ref, :other)
+ assert decorate(doc, other_ref.range) =~ " «other»"
+ end
+ end
+ end
+
+ describe "variable definitions in anonymous function parameters are extracted" do
+ test "when definition on the right side of the equals" do
+ {:ok, [ref], doc} =
+ ~q[
+ fn 1 = a -> a end
+ ]
+ |> index_references()
+
+ assert decorate(doc, ref.range) =~ "fn 1 = a -> «a»"
+ end
+
+ test "in a plain parameter" do
+ {:ok, [param], doc} =
+ ~q[
+ fn var ->
+ nil
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(param, :var)
+ assert decorate(doc, param.range) =~ "fn «var» ->"
+ end
+
+ test "in a struct's values" do
+ {:ok, [param], doc} =
+ ~q[
+ fn %Pattern{foo: var} ->
+ nil
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(param, :var)
+ assert decorate(doc, param.range) =~ "fn %Pattern{foo: «var»} ->"
+ end
+
+ test "when they're pinned" do
+ {:ok, [param], doc} =
+ ~q[
+ fn ^pinned ->
+ nil
+ end
+ ]
+ |> index_references()
+
+ assert_reference(param, :pinned)
+ assert decorate(doc, param.range) =~ "fn ^«pinned» ->"
+ end
+
+ test "on both sides of a pattern match" do
+ {:ok, [var_1, var_2], doc} =
+ ~q[
+ fn %Pattern{foo: var} = var_2 ->
+ nil
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(var_1, :var)
+ assert decorate(doc, var_1.range) =~ "fn %Pattern{foo: «var»} = var_2 ->"
+
+ assert_definition(var_2, :var_2)
+ assert decorate(doc, var_2.range) =~ "fn %Pattern{foo: var} = «var_2» ->"
+ end
+
+ test "in a struct module" do
+ {:ok, [var_1], doc} =
+ ~q[
+ fn %my_module{} ->
+ nil
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(var_1, :my_module)
+ assert decorate(doc, var_1.range) =~ "fn %«my_module»{} ->"
+ end
+
+ test "in list elements" do
+ {:ok, [var_1, var_2], doc} =
+ ~q{
+ fn [var_1, var_2] ->
+ nil
+ end
+ }
+ |> index_definitions()
+
+ assert_definition(var_1, :var_1)
+ assert decorate(doc, var_1.range) =~ "fn [«var_1», var_2] ->"
+
+ assert_definition(var_2, :var_2)
+ assert decorate(doc, var_2.range) =~ "fn [var_1, «var_2»] ->"
+ end
+
+ test "in the tail of a list" do
+ {:ok, [tail], doc} =
+ ~q{
+ fn [_ | acc] ->
+ nil
+ end
+ }
+ |> index_definitions()
+
+ assert_definition(tail, :acc)
+ assert decorate(doc, tail.range) =~ "fn [_ | «acc»] ->"
+ end
+
+ test "unless it is an alias" do
+ {:ok, [], _} =
+ ~q[
+ fn %MyStruct{} ->
+ nil
+ end
+ ]
+ |> index_definitions()
+ end
+
+ test "unless it starts with an underscore" do
+ {:ok, [], _} =
+ ~q[
+ fn _unused ->
+ nil
+ end
+ ]
+ |> index_definitions()
+
+ {:ok, [], _} =
+ ~q[
+ fn _ ->
+ nil
+ end
+ ]
+ |> index_definitions()
+ end
+ end
+
+ describe "variable definitions in code are extracted" do
+ test "from full pattern matches" do
+ {:ok, [var], doc} = index_definitions(~q[var = 38])
+
+ assert_definition(var, :var)
+ assert decorate(doc, var.range) =~ "«var» = 38"
+ end
+
+ test "from tuples elements" do
+ {:ok, [first, second], doc} = index_definitions(~q({first, second} = foo))
+
+ assert_definition(first, :first)
+ assert decorate(doc, first.range) =~ "{«first», second} ="
+
+ assert_definition(second, :second)
+ assert decorate(doc, second.range) =~ "{first, «second»} ="
+ end
+
+ test "from list elements" do
+ {:ok, [first, second], doc} = index_definitions(~q([first, second] = foo))
+
+ assert_definition(first, :first)
+ assert decorate(doc, first.range) =~ "[«first», second] ="
+
+ assert_definition(second, :second)
+ assert decorate(doc, second.range) =~ "[first, «second»] ="
+ end
+
+ test "from map values" do
+ {:ok, [value], doc} = index_definitions(~q(%{key: value} = whatever))
+
+ assert_definition(value, :value)
+ assert decorate(doc, value.range) =~ "%{key: «value»} = whatever"
+ end
+
+ test "from struct values" do
+ {:ok, [value], doc} = index_definitions(~q(%MyStruct{key: value} = whatever))
+
+ assert_definition(value, :value)
+ assert decorate(doc, value.range) =~ "%MyStruct{key: «value»} = whatever"
+ end
+
+ test "from struct modules" do
+ {:ok, [module], doc} = index_definitions(~q(%struct_module{} = whatever))
+
+ assert_definition(module, :struct_module)
+ assert decorate(doc, module.range) =~ "%«struct_module»{} = whatever"
+ end
+
+ test "in an else block in a with" do
+ {:ok, [value], doc} =
+ ~q[
+ with true <- true do
+ :bad
+ else var ->
+ :ok
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(value, :var)
+ assert decorate(doc, value.range) =~ "else «var» ->"
+ end
+
+ test "from comprehensions" do
+ {:ok, [var, thing, field_1, field_2], doc} =
+ ~q[
+ for var <- things,
+ {:ok, thing} = var,
+ {:record, field_1, field_2} <- thing do
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(var, :var)
+ assert decorate(doc, var.range) =~ "for «var» <- things,"
+
+ assert_definition(thing, :thing)
+ assert decorate(doc, thing.range) =~ "{:ok, «thing»} = var,"
+
+ assert_definition(field_1, :field_1)
+ assert decorate(doc, field_1.range) =~ "{:record, «field_1», field_2} <- thing do"
+
+ assert_definition(field_2, :field_2)
+ assert decorate(doc, field_2.range) =~ "{:record, field_1, «field_2»} <- thing do"
+ end
+
+ test "in an else block in a try" do
+ {:ok, [value], doc} =
+ ~q[
+ try do
+ :ok
+ else failure ->
+ failure
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(value, :failure)
+ assert decorate(doc, value.range) =~ "else «failure» ->"
+ end
+
+ test "in a catch block in a try" do
+ {:ok, [value], doc} =
+ ~q[
+ try do
+ :ok
+ catch thrown ->
+ thrown
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(value, :thrown)
+ assert decorate(doc, value.range) =~ "catch «thrown» ->"
+ end
+
+ test "in a rescue block in a try" do
+ {:ok, [value], doc} =
+ ~q[
+ try do
+ :ok
+ rescue ex ->
+ ex
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(value, :ex)
+ assert decorate(doc, value.range) =~ "rescue «ex» ->"
+ end
+
+ test "in a rescue block in a try using in" do
+ {:ok, [value], doc} =
+ ~q[
+ try do
+ :ok
+ rescue ex in RuntimeError ->
+ ex
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(value, :ex)
+ assert decorate(doc, value.range) =~ "rescue «ex» in RuntimeError ->"
+ end
+
+ test "from complex, nested mappings" do
+ {:ok, [module, list_elem, tuple_first, tuple_second], doc} =
+ index_definitions(
+ ~q(%struct_module{key: [list_elem, {tuple_first, tuple_second}]} = whatever)
+ )
+
+ assert_definition(module, :struct_module)
+
+ assert decorate(doc, module.range) =~
+ "%«struct_module»{key: [list_elem, {tuple_first, tuple_second}]} = whatever"
+
+ assert_definition(list_elem, :list_elem)
+
+ assert decorate(doc, list_elem.range) =~
+ "%struct_module{key: [«list_elem», {tuple_first, tuple_second}]} = whatever"
+
+ assert_definition(tuple_first, :tuple_first)
+
+ assert decorate(doc, tuple_first.range) =~
+ "%struct_module{key: [list_elem, {«tuple_first», tuple_second}]} = whatever"
+
+ assert_definition(tuple_second, :tuple_second)
+
+ assert decorate(doc, tuple_second.range) =~
+ "%struct_module{key: [list_elem, {tuple_first, «tuple_second»}]} = whatever"
+ end
+
+ test "from test arguments" do
+ {:ok, [test_def], doc} =
+ ~q[
+ defmodule TestCase do
+ use ExUnit.Case
+ test "my test", %{var: var} do
+ var
+ end
+ end
+ ]
+ |> index_definitions()
+
+ assert_definition(test_def, :var)
+ assert decorate(doc, test_def.range) =~ "%{var: «var»} do"
+ end
+ end
+
+ describe "variable references are extracted" do
+ test "when by themselves" do
+ assert {:ok, [ref], doc} = index_references(~q[variable])
+
+ assert_reference(ref, :variable)
+ assert decorate(doc, ref.range) =~ "«variable»"
+ end
+
+ test "from pinned variables" do
+ {:ok, [ref], doc} = index_references("^pinned = 3")
+
+ assert_reference(ref, :pinned)
+ assert decorate(doc, ref.range) =~ "^«pinned» = 3"
+ end
+
+ test "from pinned variables in a function head" do
+ {:ok, [ref], doc} =
+ ~q{
+ fn [^pinned] ->
+ nil
+ end
+ }
+ |> index
+
+ assert_reference(ref, :pinned)
+ assert decorate(doc, ref.range) =~ "fn [^«pinned»] ->"
+ end
+
+ test "on the left side of operators" do
+ assert {:ok, [ref], doc} = index_references(~q[x + 3])
+
+ assert_reference(ref, :x)
+ assert decorate(doc, ref.range) =~ "«x» + 3"
+ end
+
+ test "on the right side of operators" do
+ assert {:ok, [ref], doc} = index_references(~q[3 + x])
+
+ assert_reference(ref, :x)
+ assert decorate(doc, ref.range) =~ "3 + «x»"
+ end
+
+ test "on the right of pattern matches" do
+ assert {:ok, [ref], doc} = index_references(~q[x = other_variable])
+
+ assert_reference(ref, :other_variable)
+ assert decorate(doc, ref.range) =~ "x = «other_variable»"
+ end
+
+ test "on the right side of pattern matches with dot notation" do
+ assert {:ok, [ref], doc} = index_references(~q[x = foo.bar.baz])
+
+ assert_reference(ref, :foo)
+ assert decorate(doc, ref.range) =~ "x = «foo».bar.baz"
+ end
+
+ test "on the right side of a pattern match in a function call" do
+ assert {:ok, [ref], doc} = index_references(~q[_ = foo(bar)])
+
+ assert_reference(ref, :bar)
+ assert decorate(doc, ref.range) =~ "_ = foo(«bar»)"
+ end
+
+ test "on the left of pattern matches via a pin" do
+ assert {:ok, [ref], doc} = index_references(~q[^pin = 49])
+
+ assert_reference(ref, :pin)
+ assert decorate(doc, ref.range) =~ "^«pin» = 49"
+ end
+
+ test "from function call arguments" do
+ assert {:ok, [ref], doc} = index_references(~q[pow(x, 3)])
+
+ assert_reference(ref, :x)
+ assert decorate(doc, ref.range) =~ "pow(«x», 3)"
+ end
+
+ test "when using access syntax" do
+ assert {:ok, [ref], doc} = index_references(~q{3 = foo[:bar]})
+
+ assert_reference(ref, :foo)
+ assert decorate(doc, ref.range) =~ "3 = «foo»[:bar]"
+ end
+
+ test "when inside brackets" do
+ assert {:ok, [ref, access_ref], doc} = index_references(~q{3 = foo[bar]})
+
+ assert_reference(ref, :foo)
+ assert decorate(doc, ref.range) =~ "3 = «foo»[bar]"
+
+ assert_reference(access_ref, :bar)
+ assert decorate(doc, access_ref.range) =~ "3 = foo[«bar»]"
+ end
+
+ test "inside string interpolations" do
+ quoted =
+ quote file: "foo.ex", line: 1 do
+ foo = 3
+ "#{foo}"
+ end
+
+ assert {:ok, [ref], doc} = index_references(quoted)
+
+ assert_reference(ref, :foo)
+ assert decorate(doc, ref.range) =~ ~S["#{«foo»}"]
+ end
+
+ test "inside string interpolations that have a statement" do
+ quoted =
+ quote file: "foo.ex", line: 1 do
+ foo = 3
+ "#{foo + 3}"
+ end
+
+ assert {:ok, [ref], doc} = index_references(quoted)
+
+ assert_reference(ref, :foo)
+ assert decorate(doc, ref.range) =~ ~S["#{«foo» + 3}"]
+ end
+
+ test "inside string interpolations that have a literal prefix" do
+ quoted =
+ quote file: "foo.ex", line: 1 do
+ foo = 3
+ "prefix #{foo}"
+ end
+
+ assert {:ok, [ref], doc} = index_references(quoted)
+
+ assert_reference(ref, :foo)
+ assert decorate(doc, ref.range) =~ ~S["prefix #{«foo»}"]
+ end
+
+ test "inside string interpolations that have a literal suffix" do
+ quoted =
+ quote file: "foo.ex", line: 1 do
+ foo = 3
+ "#{foo} suffix"
+ end
+
+ assert {:ok, [ref], doc} = index_references(quoted)
+
+ assert_reference(ref, :foo)
+ assert decorate(doc, ref.range) =~ ~S["#{«foo»} suffix"]
+ end
+
+ test "inside string interpolations that have a literal prefix and suffix" do
+ quoted =
+ quote file: "foo.ex", line: 1 do
+ foo = 3
+ "prefix #{foo} suffix"
+ end
+
+ assert {:ok, [ref], doc} = index_references(quoted)
+
+ assert_reference(ref, :foo)
+ assert decorate(doc, ref.range) =~ ~S["prefix #{«foo»} suffix"]
+ end
+
+ test "when inside a rescue block in a try" do
+ {:ok, [ref], doc} =
+ ~q[
+ try do
+ :ok
+ rescue e in Something ->
+ e
+ end
+ ]
+ |> index_references()
+
+ assert_reference(ref, :e)
+ assert decorate(doc, ref.range) =~ " «e»"
+ end
+
+ test "when inside a catch block in a try" do
+ {:ok, [ref], doc} =
+ ~q[
+ try do
+ :ok
+ catch thrown ->
+ thrown
+ end
+ ]
+ |> index_references()
+
+ assert_reference(ref, :thrown)
+ assert decorate(doc, ref.range) =~ " «thrown»"
+ end
+
+ test "when inside an after block in a try" do
+ {:ok, [ref], doc} =
+ ~q[
+ try do
+ :ok
+ after ->
+ x
+ end
+ ]
+ |> index_references()
+
+ assert_reference(ref, :x)
+ assert decorate(doc, ref.range) =~ " «x»"
+ end
+
+ test "when inside an else block in a with" do
+ {:ok, [ref], doc} =
+ ~q[
+ with :ok <- call() do
+ else other ->
+ other
+ end
+ ]
+ |> index_references()
+
+ assert_reference(ref, :other)
+ assert decorate(doc, ref.range) =~ " «other»"
+ end
+
+ test "when in the tail of a list" do
+ assert {:ok, [ref], doc} = index_references(~q{[3 | acc]})
+
+ assert_reference(ref, :acc)
+ assert decorate(doc, ref.range) =~ "[3 | «acc»]"
+ end
+
+ test "in the body of an anonymous function" do
+ {:ok, [ref], doc} =
+ ~q[
+ fn %Pattern{foo: var} ->
+ var
+ end
+ ]
+ |> index_references()
+
+ assert_reference(ref, :var)
+ assert decorate(doc, ref.range) =~ " «var»"
+ end
+
+ test "when unquote is used in a function definition" do
+ {:ok, [ref], doc} =
+ ~q[
+ def my_fun(unquote(other_var)) do
+ end
+ ]
+ |> index_references()
+
+ assert_reference(ref, :other_var)
+ assert decorate(doc, ref.range) =~ "def my_fun(unquote(«other_var»)) do"
+ end
+
+ test "unless it begins with underscore" do
+ assert {:ok, [], _} = index_references("_")
+ assert {:ok, [], _} = index_references("_unused")
+ assert {:ok, [], _} = index_references("_unused = 3")
+ assert {:ok, [], _} = index_references("_unused = foo()")
+ end
+ end
+
+ describe "variable and references are extracted" do
+ test "in a multiple match" do
+ {:ok, [foo_def, param_def, bar_def, other_ref], doc} =
+ ~q[
+ foo = param = bar = other
+ ]
+ |> index()
+
+ assert_definition(foo_def, :foo)
+ assert decorate(doc, foo_def.range) =~ "«foo» = param = bar = other"
+
+ assert_definition(param_def, :param)
+ assert decorate(doc, param_def.range) =~ "foo = «param» = bar = other"
+
+ assert_definition(bar_def, :bar)
+ assert decorate(doc, bar_def.range) =~ "foo = param = «bar» = other"
+
+ assert_reference(other_ref, :other)
+ assert decorate(doc, other_ref.range) =~ "foo = param = bar = «other»"
+ end
+
+ test "in an anoymous function" do
+ {:ok, [pin_param, var_param, first_def, pin_pin, var_ref, first_ref], doc} =
+ ~q{
+ fn pin, var ->
+ [first, ^pin] = var
+ first
+ end
+ }
+ |> index()
+
+ assert_definition(pin_param, :pin)
+ assert decorate(doc, pin_param.range) =~ "fn «pin», var ->"
+
+ assert_definition(var_param, :var)
+ assert decorate(doc, var_param.range) =~ "fn pin, «var» ->"
+
+ assert_definition(first_def, :first)
+ assert decorate(doc, first_def.range) =~ " [«first», ^pin] = var"
+
+ assert_reference(pin_pin, :pin)
+ assert decorate(doc, pin_pin.range) =~ " [first, ^«pin»] = var"
+
+ assert_reference(var_ref, :var)
+ assert decorate(doc, var_ref.range) =~ " [first, ^pin] = «var»"
+
+ assert_reference(first_ref, :first)
+ assert decorate(doc, first_ref.range) =~ " «first»"
+ end
+
+ test "in the match arms of a with" do
+ {:ok, [var_def, var_2_def, var_ref], doc} =
+ ~q[
+ with {:ok, var} <- something(),
+ {:ok, var_2} <- something_else(var) do
+ :bad
+ end
+ ]
+ |> index()
+
+ assert_definition(var_def, :var)
+ assert decorate(doc, var_def.range) =~ "{:ok, «var»} <- something(),"
+
+ assert_definition(var_2_def, :var_2)
+ assert decorate(doc, var_2_def.range) =~ " {:ok, «var_2»} <- something_else(var) do"
+
+ assert_reference(var_ref, :var)
+ assert decorate(doc, var_ref.range) =~ " {:ok, var_2} <- something_else(«var») do"
+ end
+
+ test "in the body of a with" do
+ {:ok, [_var_def, var_ref], doc} =
+ ~q[
+ with {:ok, var} <- something() do
+ var + 1
+ end
+ ]
+ |> index()
+
+ assert_reference(var_ref, :var)
+ assert decorate(doc, var_ref.range) =~ " «var» + 1"
+ end
+
+ test "in a comprehension" do
+ {:ok, extracted, doc} =
+ ~q[
+ for {:ok, var} <- list,
+ {:record, elem_1, elem_2} <- var do
+ {:ok, elem_1 + elem_2}
+ end
+ ]
+ |> index()
+
+ assert [var_def, list_ref, elem_1_def, elem_2_def, var_ref, elem_1_ref, elem_2_ref] =
+ extracted
+
+ assert_definition(var_def, :var)
+ assert decorate(doc, var_def.range) =~ "for {:ok, «var»} <- list,"
+
+ assert_reference(list_ref, :list)
+ assert decorate(doc, list_ref.range) =~ "for {:ok, var} <- «list»,"
+
+ assert_definition(elem_1_def, :elem_1)
+ assert decorate(doc, elem_1_def.range) =~ "{:record, «elem_1», elem_2} <- var do"
+
+ assert_definition(elem_2_def, :elem_2)
+ assert decorate(doc, elem_2_def.range) =~ "{:record, elem_1, «elem_2»} <- var do"
+
+ assert_reference(var_ref, :var)
+ assert decorate(doc, var_ref.range) =~ "{:record, elem_1, elem_2} <- «var» do"
+
+ assert_reference(elem_1_ref, :elem_1)
+ assert decorate(doc, elem_1_ref.range) =~ " {:ok, «elem_1» + elem_2}"
+
+ assert_reference(elem_2_ref, :elem_2)
+ assert decorate(doc, elem_2_ref.range) =~ " {:ok, elem_1 + «elem_2»}"
+ end
+
+ test "in guards in def functions" do
+ {:ok, [param_def, param_2_def, param_ref], doc} =
+ ~q[
+ def something(param, param_2) when param > 1 do
+ end
+ ]
+ |> index()
+
+ assert_definition(param_def, :param)
+ assert decorate(doc, param_def.range) =~ "def something(«param», param_2) when param > 1 do"
+
+ assert_definition(param_2_def, :param_2)
+
+ assert decorate(doc, param_2_def.range) =~
+ "def something(param, «param_2») when param > 1 do"
+
+ assert_reference(param_ref, :param)
+ assert decorate(doc, param_ref.range) =~ "def something(param, param_2) when «param» > 1 do"
+ end
+
+ test "in guards in anonymous functions" do
+ {:ok, [param_def, param_2_def, param_ref], doc} =
+ ~q[
+ fn param, param_2 when param > 1 -> :ok end
+ ]
+ |> index()
+
+ assert_definition(param_def, :param)
+ assert decorate(doc, param_def.range) =~ "fn «param», param_2 when param > 1 -> :ok end"
+
+ assert_definition(param_2_def, :param_2)
+ assert decorate(doc, param_2_def.range) =~ "fn param, «param_2» when param > 1 -> :ok end"
+
+ assert_reference(param_ref, :param)
+ assert decorate(doc, param_ref.range) =~ "fn param, param_2 when «param» > 1 -> :ok end"
+ end
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/metadata_test.exs b/apps/engine/test/engine/search/indexer/metadata_test.exs
similarity index 93%
rename from apps/remote_control/test/lexical/remote_control/search/indexer/metadata_test.exs
rename to apps/engine/test/engine/search/indexer/metadata_test.exs
index d2c1a1f0..5aabe584 100644
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/metadata_test.exs
+++ b/apps/engine/test/engine/search/indexer/metadata_test.exs
@@ -1,14 +1,14 @@
-defmodule Lexical.RemoteControl.Search.Indexer.MetadataTest do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.Search.Indexer.Metadata
+defmodule Engine.Search.Indexer.MetadataTest do
+ alias Engine.Search.Indexer.Metadata
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
use ExUnit.Case
- import Lexical.Test.RangeSupport
- import Lexical.Test.CodeSigil
+ import Forge.Test.RangeSupport
+ import Forge.Test.CodeSigil
describe "blocks in modules" do
test "finds a block in an empty module" do
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/structure_test.exs b/apps/engine/test/engine/search/indexer/structure_test.exs
similarity index 92%
rename from apps/remote_control/test/lexical/remote_control/search/indexer/structure_test.exs
rename to apps/engine/test/engine/search/indexer/structure_test.exs
index 0fdc84cb..e21fcbf9 100644
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/structure_test.exs
+++ b/apps/engine/test/engine/search/indexer/structure_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.RemoteControl.Search.Indexer.StructureTest do
- use Lexical.Test.ExtractorCase
+defmodule Engine.Search.Indexer.StructureTest do
+ use Engine.Test.ExtractorCase
def index(source) do
case do_index(source, fn entry -> entry.type != :metadata end) do
diff --git a/apps/engine/test/engine/search/indexer_test.exs b/apps/engine/test/engine/search/indexer_test.exs
new file mode 100644
index 00000000..29379b6c
--- /dev/null
+++ b/apps/engine/test/engine/search/indexer_test.exs
@@ -0,0 +1,166 @@
+defmodule Engine.Search.IndexerTest do
+ alias Engine.Dispatch
+ alias Engine.Search.Indexer
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Project
+
+ use ExUnit.Case
+ use Patch
+ import Engine.Test.Fixtures
+
+ defmodule FakeBackend do
+ def set_entries(entries) when is_list(entries) do
+ :persistent_term.put({__MODULE__, :entries}, entries)
+ end
+
+ def reduce(accumulator, reducer_fun) do
+ {__MODULE__, :entries}
+ |> :persistent_term.get([])
+ |> Enum.reduce(accumulator, fn
+ %{id: id} = entry, acc when is_integer(id) -> reducer_fun.(entry, acc)
+ _, acc -> acc
+ end)
+ end
+ end
+
+ setup do
+ project = project()
+ start_supervised(Dispatch)
+ {:ok, project: project}
+ end
+
+ describe "create_index/1" do
+ test "returns a list of entries", %{project: project} do
+ assert {:ok, entry_stream} = Indexer.create_index(project)
+ entries = Enum.to_list(entry_stream)
+ project_root = Project.root_path(project)
+
+ assert length(entries) > 0
+ assert Enum.all?(entries, fn entry -> String.starts_with?(entry.path, project_root) end)
+ end
+
+ test "entries are either .ex or .exs files", %{project: project} do
+ assert {:ok, entries} = Indexer.create_index(project)
+ assert Enum.all?(entries, fn entry -> Path.extname(entry.path) in [".ex", ".exs"] end)
+ end
+ end
+
+ @ephemeral_file_name "ephemeral.ex"
+
+ def with_an_ephemeral_file(%{project: project}, file_contents) do
+ file_path = Path.join([Project.root_path(project), "lib", @ephemeral_file_name])
+ File.write!(file_path, file_contents)
+
+ on_exit(fn ->
+ File.rm(file_path)
+ end)
+
+ {:ok, file_path: file_path}
+ end
+
+ def with_a_file_with_a_module(context) do
+ file_contents = ~s[
+ defmodule Ephemeral do
+ end
+ ]
+ with_an_ephemeral_file(context, file_contents)
+ end
+
+ def with_an_existing_index(%{project: project}) do
+ {:ok, entry_stream} = Indexer.create_index(project)
+ entries = Enum.to_list(entry_stream)
+ FakeBackend.set_entries(entries)
+ {:ok, entries: entries}
+ end
+
+ describe "update_index/2 encounters a new file" do
+ setup [:with_an_existing_index, :with_a_file_with_a_module]
+
+ test "the ephemeral file is not previously present in the index", %{entries: entries} do
+ refute Enum.any?(entries, fn entry -> Path.basename(entry.path) == @ephemeral_file_name end)
+ end
+
+ test "the ephemeral file is listed in the updated index", %{project: project} do
+ {:ok, entry_stream, []} = Indexer.update_index(project, FakeBackend)
+ assert [_structure, updated_entry] = Enum.to_list(entry_stream)
+ assert Path.basename(updated_entry.path) == @ephemeral_file_name
+ assert updated_entry.subject == Ephemeral
+ end
+ end
+
+ def with_an_ephemeral_empty_file(context) do
+ with_an_ephemeral_file(context, "")
+ end
+
+ describe "update_index/2 encounters a zero-length file" do
+ setup [:with_an_existing_index, :with_an_ephemeral_empty_file]
+
+ test "and does nothing", %{project: project} do
+ {:ok, entry_stream, []} = Indexer.update_index(project, FakeBackend)
+ assert [] = Enum.to_list(entry_stream)
+ end
+
+ test "there is no progress", %{project: project} do
+ # this ensures we don't emit progress with a total byte size of 0, which will
+ # cause an ArithmeticError
+
+ Dispatch.register_listener(self(), :all)
+ {:ok, entry_stream, []} = Indexer.update_index(project, FakeBackend)
+ assert [] = Enum.to_list(entry_stream)
+ refute_receive _
+ end
+ end
+
+ describe "update_index/2" do
+ setup [:with_a_file_with_a_module, :with_an_existing_index]
+
+ test "sees the ephemeral file", %{entries: entries} do
+ assert Enum.any?(entries, fn entry -> Path.basename(entry.path) == @ephemeral_file_name end)
+ end
+
+ test "returns the file paths of deleted files", %{project: project, file_path: file_path} do
+ File.rm(file_path)
+ assert {:ok, entry_stream, [^file_path]} = Indexer.update_index(project, FakeBackend)
+ assert [] = Enum.to_list(entry_stream)
+ end
+
+ test "updates files that have changed since the last index", %{
+ project: project,
+ entries: entries,
+ file_path: file_path
+ } do
+ entries = Enum.reject(entries, &is_nil(&1.id))
+ path_to_mtime = Map.new(entries, fn entry -> {entry.path, Entry.updated_at(entry)} end)
+
+ [entry | _] = entries
+ {{year, month, day}, hms} = Entry.updated_at(entry)
+ old_mtime = {{year - 1, month, day}, hms}
+
+ patch(Indexer, :stat, fn path ->
+ {ymd, {hour, minute, second}} =
+ Map.get_lazy(path_to_mtime, file_path, &:calendar.universal_time/0)
+
+ mtime =
+ if path == file_path do
+ {ymd, {hour, minute, second + 1}}
+ else
+ old_mtime
+ end
+
+ {:ok, %File.Stat{mtime: mtime}}
+ end)
+
+ new_contents = ~s[
+ defmodule Brand.Spanking.New do
+ end
+ ]
+
+ File.write!(file_path, new_contents)
+
+ assert {:ok, entry_stream, []} = Indexer.update_index(project, FakeBackend)
+ assert [_structure, entry] = Enum.to_list(entry_stream)
+ assert entry.path == file_path
+ assert entry.subject == Brand.Spanking.New
+ end
+ end
+end
diff --git a/apps/remote_control/test/lexical/remote_control/search/store/backends/ets/schema_test.exs b/apps/engine/test/engine/search/store/backends/ets/schema_test.exs
similarity index 94%
rename from apps/remote_control/test/lexical/remote_control/search/store/backends/ets/schema_test.exs
rename to apps/engine/test/engine/search/store/backends/ets/schema_test.exs
index e6ae8fea..67f1c82e 100644
--- a/apps/remote_control/test/lexical/remote_control/search/store/backends/ets/schema_test.exs
+++ b/apps/engine/test/engine/search/store/backends/ets/schema_test.exs
@@ -1,9 +1,9 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.SchemaTest do
- alias Lexical.Project
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schema
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Wal
+defmodule Engine.Search.Store.Backends.Ets.SchemaTest do
+ alias Engine.Search.Store.Backends.Ets.Schema
+ alias Engine.Search.Store.Backends.Ets.Wal
+ alias Forge.Project
- import Lexical.Test.Fixtures
+ import Engine.Test.Fixtures
import Wal, only: :macros
use ExUnit.Case
diff --git a/apps/remote_control/test/lexical/remote_control/search/store/backends/ets/wal_test.exs b/apps/engine/test/engine/search/store/backends/ets/wal_test.exs
similarity index 98%
rename from apps/remote_control/test/lexical/remote_control/search/store/backends/ets/wal_test.exs
rename to apps/engine/test/engine/search/store/backends/ets/wal_test.exs
index e5ea558f..ea812cec 100644
--- a/apps/remote_control/test/lexical/remote_control/search/store/backends/ets/wal_test.exs
+++ b/apps/engine/test/engine/search/store/backends/ets/wal_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.WalTest do
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Wal
+defmodule Engine.Search.Store.Backends.Ets.WalTest do
+ alias Engine.Search.Store.Backends.Ets.Wal
- import Lexical.Test.Fixtures
+ import Engine.Test.Fixtures
use ExUnit.Case
use Patch
diff --git a/apps/remote_control/test/lexical/remote_control/search/store/backends/ets_test.exs b/apps/engine/test/engine/search/store/backends/ets_test.exs
similarity index 95%
rename from apps/remote_control/test/lexical/remote_control/search/store/backends/ets_test.exs
rename to apps/engine/test/engine/search/store/backends/ets_test.exs
index dfb2d98b..786c3a88 100644
--- a/apps/remote_control/test/lexical/remote_control/search/store/backends/ets_test.exs
+++ b/apps/engine/test/engine/search/store/backends/ets_test.exs
@@ -1,11 +1,11 @@
-defmodule Lexical.RemoteControl.Search.Store.Backend.EtsTest do
- alias Lexical.Project
- alias Lexical.RemoteControl.Dispatch
- alias Lexical.RemoteControl.Search.Store
- alias Lexical.RemoteControl.Search.Store.Backends
- alias Lexical.Test.Entry
- alias Lexical.Test.EventualAssertions
- alias Lexical.Test.Fixtures
+defmodule Engine.Search.Store.Backend.EtsTest do
+ alias Engine.Dispatch
+ alias Engine.Search.Store
+ alias Engine.Search.Store.Backends
+ alias Engine.Test.Entry
+ alias Engine.Test.Fixtures
+ alias Forge.Project
+ alias Forge.Test.EventualAssertions
use ExUnit.Case, async: false
@@ -22,7 +22,7 @@ defmodule Lexical.RemoteControl.Search.Store.Backend.EtsTest do
# Removing the index at the end will also let other test cases
# start with a clean slate.
- Lexical.RemoteControl.set_project(project)
+ Engine.set_project(project)
delete_indexes(project, backend)
diff --git a/apps/engine/test/engine/search/store_test.exs b/apps/engine/test/engine/search/store_test.exs
new file mode 100644
index 00000000..7628e096
--- /dev/null
+++ b/apps/engine/test/engine/search/store_test.exs
@@ -0,0 +1,398 @@
+defmodule Engine.Search.StoreTest do
+ alias Engine.Dispatch
+ alias Engine.Search.Indexer
+ alias Engine.Search.Indexer.Entry
+ alias Engine.Search.Store
+ alias Engine.Search.Store.Backends.Ets
+ alias Engine.Test.Entry
+ alias Engine.Test.Fixtures
+ alias Forge.Test.EventualAssertions
+
+ use ExUnit.Case, async: false
+
+ import Entry.Builder
+ import EventualAssertions
+ import Fixtures
+ import Forge.Test.CodeSigil
+
+ @backends [Ets]
+
+ setup_all do
+ project = project()
+ Engine.set_project(project)
+ # These test cases require an clean slate going into them
+ # so we should remove the indexes once when the tests start,
+ # and again when tests end, so the next test has a clean slate.
+ # Removing the index at the end will also let other test cases
+ # start with a clean slate.
+
+ destroy_backends(project)
+
+ on_exit(fn ->
+ destroy_backends(project)
+ end)
+
+ {:ok, project: project}
+ end
+
+ def all_entries(backend) do
+ []
+ |> backend.reduce(fn entry, acc -> [entry | acc] end)
+ |> Enum.reverse()
+ end
+
+ for backend <- @backends,
+ backend_name = backend |> Module.split() |> List.last() do
+ describe "#{backend_name} :: replace/1" do
+ setup %{project: project} do
+ with_a_started_store(project, unquote(backend))
+ end
+
+ test "replaces the entire index" do
+ entries = [definition(subject: OtherModule)]
+
+ Store.replace(entries)
+ assert entries == all_entries(unquote(backend))
+ end
+ end
+
+ describe "#{backend_name} :: querying" do
+ setup %{project: project} do
+ with_a_started_store(project, unquote(backend))
+ end
+
+ test "matching can exclude on type" do
+ Store.replace([
+ definition(id: 1),
+ reference(id: 3)
+ ])
+
+ assert {:ok, [ref]} = Store.exact(subtype: :reference)
+ assert ref.subtype == :reference
+ end
+
+ test "matching with queries can exclude on type" do
+ Store.replace([
+ reference(subject: Foo.Bar.Baz),
+ reference(subject: Other.Module),
+ definition(subject: Foo.Bar.Baz)
+ ])
+
+ assert {:ok, [ref]} = Store.exact("Foo.Bar.Baz", subtype: :reference)
+
+ assert ref.subject == Foo.Bar.Baz
+ assert ref.type == :module
+ assert ref.subtype == :reference
+ end
+
+ test "matching exact tokens should work" do
+ Store.replace([
+ definition(id: 1, subject: Foo.Bar.Baz),
+ definition(id: 2, subject: Foo.Bar.Bak)
+ ])
+
+ assert {:ok, [entry]} = Store.exact("Foo.Bar.Baz", type: :module, subtype: :definition)
+
+ assert entry.subject == Foo.Bar.Baz
+ assert entry.id == 1
+ end
+
+ test "matching prefix tokens should work" do
+ Store.replace([
+ definition(id: 1, subject: Foo.Bar),
+ definition(id: 2, subject: Foo.Baa.Baa),
+ definition(id: 3, subject: Foo.Bar.Baz)
+ ])
+
+ assert {:ok, [entry1, entry3]} =
+ Store.prefix("Foo.Bar", type: :module, subtype: :definition)
+
+ assert entry1.subject == Foo.Bar
+ assert entry3.subject == Foo.Bar.Baz
+
+ assert entry1.id == 1
+ assert entry3.id == 3
+ end
+
+ test "matching fuzzy tokens works" do
+ Store.replace([
+ definition(id: 1, subject: Foo.Bar.Baz),
+ definition(id: 2, subject: Foo.Bar.Bak),
+ definition(id: 3, subject: Bad.Times.Now)
+ ])
+
+ assert {:ok, [entry_1, entry_2]} =
+ Store.fuzzy("Foo.Bar.B", type: :module, subtype: :definition)
+
+ assert entry_1.subject in [Foo.Bar.Baz, Foo.Bar.Bak]
+ assert entry_2.subject in [Foo.Bar.Baz, Foo.Bar.Bak]
+ end
+ end
+
+ describe "#{backend_name} :: updating entries in a file" do
+ setup %{project: project} do
+ with_a_started_store(project, unquote(backend))
+ end
+
+ test "old entries with the same path are deleted" do
+ path = "/path/to/file.ex"
+
+ Store.replace([
+ definition(id: 1, subject: Foo.Bar.Baz, path: path),
+ definition(id: 2, subject: Foo.Baz.Quux, path: path)
+ ])
+
+ updated = [
+ definition(id: 3, subject: Other.Thing.Entirely, path: path)
+ ]
+
+ Store.update(path, updated)
+
+ assert_eventually [remaining] = all_entries(unquote(backend))
+ refute remaining.id in [1, 2]
+ end
+
+ test "old entries with another path are kept" do
+ updated_path = "/path/to/file.ex"
+
+ Store.replace([
+ definition(id: 1, subject: Foo.Bar.Baz, path: updated_path),
+ definition(id: 2, subject: Foo.Bar.Baz.Quus, path: updated_path),
+ definition(id: 3, subject: Foo.Bar.Baz, path: "/path/to/another.ex")
+ ])
+
+ updated = [
+ definition(id: 4, subject: Other.Thing.Entirely, path: updated_path)
+ ]
+
+ Store.update(updated_path, updated)
+
+ assert_eventually [first, second] = all_entries(unquote(backend))
+
+ assert first.id in [3, 4]
+ assert second.id in [3, 4]
+ end
+
+ test "updated entries are not searchable" do
+ path = "/path/to/ex.ex"
+
+ Store.replace([
+ definition(id: 1, subject: Should.Be.Replaced, path: path)
+ ])
+
+ Store.update(path, [
+ definition(id: 2, subject: Present, path: path)
+ ])
+
+ assert_eventually {:ok, [found]} =
+ Store.fuzzy("Pres", type: :module, subtype: :definition)
+
+ assert found.id == 2
+ assert found.subject == Present
+ end
+ end
+
+ describe "#{backend_name} :: structure queries " do
+ setup %{project: project} do
+ with_a_started_store(project, unquote(backend))
+ end
+
+ test "finding siblings" do
+ entries =
+ ~q[
+ defmodule Parent do
+ def function do
+ First.Module
+ Second.Module
+ Third.Module
+ end
+ end
+ ]
+ |> entries()
+
+ subject_entry = Enum.find(entries, &(&1.subject == Third.Module))
+ assert {:ok, [first_ref, second_ref, ^subject_entry]} = Store.siblings(subject_entry)
+ assert first_ref.subject == First.Module
+ assert second_ref.subject == Second.Module
+ end
+
+ test "finding siblings of a function" do
+ entries =
+ ~q[
+ defmodule Parent do
+ def fun do
+ :ok
+ end
+
+ def fun2(arg) do
+ arg + 1
+ end
+
+ def fun3(arg, arg2) do
+ arg + arg2
+ end
+ end
+ ]
+ |> entries()
+
+ subject_entry = Enum.find(entries, &(&1.subject == "Parent.fun3/2"))
+
+ assert {:ok, siblings} = Store.siblings(subject_entry)
+ siblings = Enum.filter(siblings, &(&1.subtype == :definition))
+
+ assert [first_fun, second_fun, ^subject_entry] = siblings
+ assert first_fun.subject == "Parent.fun/0"
+ assert second_fun.subject == "Parent.fun2/1"
+ end
+
+ test "findidng siblings of a non-existent entry" do
+ assert :error = Store.siblings(%Indexer.Entry{})
+ end
+
+ test "finding a parent in a function" do
+ entries =
+ ~q[
+ defmodule Parent do
+ def function do
+ Module.Ref
+ end
+ end
+ ]
+ |> entries()
+
+ subject_entry = Enum.find(entries, &(&1.subject == Module.Ref))
+ {:ok, parent} = Store.parent(subject_entry)
+
+ assert parent.subject == "Parent.function/0"
+ assert parent.type == {:function, :public}
+ assert parent.subtype == :definition
+
+ assert {:ok, parent} = Store.parent(parent)
+ assert parent.subject == Parent
+
+ assert :error = Store.parent(parent)
+ end
+
+ test "finding a parent in a comprehension" do
+ entries =
+ ~q[
+ defmodule Parent do
+ def fun do
+ for n <- 1..10 do
+ Module.Ref
+ end
+ end
+ end
+ ]
+ |> entries()
+
+ subject_entry = Enum.find(entries, &(&1.subject == Module.Ref))
+ assert {:ok, parent} = Store.parent(subject_entry)
+ assert parent.subject == "Parent.fun/0"
+ end
+
+ test "finding parents in a file with multiple nested modules" do
+ entries =
+ ~q[
+ defmodule Parent do
+ defmodule Child do
+ def fun do
+ end
+ end
+ end
+
+ defmodule Parent2 do
+ defmodule Child2 do
+ def fun2 do
+ Module.Ref
+ end
+ end
+ end
+ ]
+ |> entries()
+
+ subject_entry = Enum.find(entries, &(&1.subject == Module.Ref))
+
+ assert {:ok, parent} = Store.parent(subject_entry)
+
+ assert parent.subject == "Parent2.Child2.fun2/0"
+ assert {:ok, parent} = Store.parent(parent)
+ assert parent.subject == Parent2.Child2
+
+ assert {:ok, parent} = Store.parent(parent)
+ assert parent.subject == Parent2
+ end
+
+ test "finding a non-existent entry" do
+ assert Store.parent(%Indexer.Entry{}) == :error
+ end
+ end
+ end
+
+ defp entries(source) do
+ document = Forge.Document.new("file:///file.ex", source, 1)
+
+ {:ok, entries} =
+ document
+ |> Forge.Ast.analyze()
+ |> Indexer.Quoted.index_with_cleanup()
+
+ Store.replace(entries)
+ entries
+ end
+
+ defp after_each_test(backend, project) do
+ destroy_backend(backend, project)
+ end
+
+ defp destroy_backends(project) do
+ Enum.each(@backends, &destroy_backend(&1, project))
+ end
+
+ defp destroy_backend(Ets, project) do
+ Ets.destroy_all(project)
+ end
+
+ defp destroy_backend(_, _) do
+ :ok
+ end
+
+ defp default_create(_project) do
+ {:ok, []}
+ end
+
+ defp default_update(_project, _entities) do
+ {:ok, [], []}
+ end
+
+ defp with_a_started_store(project, backend) do
+ destroy_backend(backend, project)
+
+ start_supervised!(Dispatch)
+ start_supervised!(backend)
+ start_supervised!({Store, [project, &default_create/1, &default_update/2, backend]})
+
+ assert_eventually alive?()
+
+ Store.enable()
+
+ assert_eventually ready?(), 1500
+
+ on_exit(fn ->
+ after_each_test(backend, project)
+ end)
+
+ {:ok, backend: backend}
+ end
+
+ def ready? do
+ alive?() and Store.loaded?()
+ end
+
+ def alive? do
+ case Process.whereis(Store) do
+ nil -> false
+ pid -> Process.alive?(pid)
+ end
+ end
+end
diff --git a/apps/lexical_credo/.formatter.exs b/apps/engine/test/fixtures/compilation_callback_errors/.formatter.exs
similarity index 100%
rename from apps/lexical_credo/.formatter.exs
rename to apps/engine/test/fixtures/compilation_callback_errors/.formatter.exs
diff --git a/apps/remote_control/test/fixtures/compilation_callback_errors/.gitignore b/apps/engine/test/fixtures/compilation_callback_errors/.gitignore
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_callback_errors/.gitignore
rename to apps/engine/test/fixtures/compilation_callback_errors/.gitignore
diff --git a/apps/remote_control/test/fixtures/compilation_callback_errors/lib/compile_callback_error.ex b/apps/engine/test/fixtures/compilation_callback_errors/lib/compile_callback_error.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_callback_errors/lib/compile_callback_error.ex
rename to apps/engine/test/fixtures/compilation_callback_errors/lib/compile_callback_error.ex
diff --git a/apps/remote_control/test/fixtures/compilation_callback_errors/mix.exs b/apps/engine/test/fixtures/compilation_callback_errors/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_callback_errors/mix.exs
rename to apps/engine/test/fixtures/compilation_callback_errors/mix.exs
diff --git a/apps/remote_control/test/fixtures/compilation_callback_errors/.formatter.exs b/apps/engine/test/fixtures/compilation_errors/.formatter.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_callback_errors/.formatter.exs
rename to apps/engine/test/fixtures/compilation_errors/.formatter.exs
diff --git a/apps/remote_control/test/fixtures/compilation_errors/.gitignore b/apps/engine/test/fixtures/compilation_errors/.gitignore
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_errors/.gitignore
rename to apps/engine/test/fixtures/compilation_errors/.gitignore
diff --git a/apps/remote_control/test/fixtures/compilation_errors/README.md b/apps/engine/test/fixtures/compilation_errors/README.md
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_errors/README.md
rename to apps/engine/test/fixtures/compilation_errors/README.md
diff --git a/apps/remote_control/test/fixtures/compilation_errors/lib/compilation_errors.ex b/apps/engine/test/fixtures/compilation_errors/lib/compilation_errors.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_errors/lib/compilation_errors.ex
rename to apps/engine/test/fixtures/compilation_errors/lib/compilation_errors.ex
diff --git a/apps/remote_control/test/fixtures/compilation_errors/mix.exs b/apps/engine/test/fixtures/compilation_errors/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_errors/mix.exs
rename to apps/engine/test/fixtures/compilation_errors/mix.exs
diff --git a/apps/remote_control/test/fixtures/compilation_errors/.formatter.exs b/apps/engine/test/fixtures/compilation_warnings/.formatter.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_errors/.formatter.exs
rename to apps/engine/test/fixtures/compilation_warnings/.formatter.exs
diff --git a/apps/remote_control/test/fixtures/compilation_warnings/.gitignore b/apps/engine/test/fixtures/compilation_warnings/.gitignore
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_warnings/.gitignore
rename to apps/engine/test/fixtures/compilation_warnings/.gitignore
diff --git a/apps/remote_control/test/fixtures/compilation_warnings/README.md b/apps/engine/test/fixtures/compilation_warnings/README.md
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_warnings/README.md
rename to apps/engine/test/fixtures/compilation_warnings/README.md
diff --git a/apps/remote_control/test/fixtures/compilation_warnings/lib/unused_variable.ex b/apps/engine/test/fixtures/compilation_warnings/lib/unused_variable.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_warnings/lib/unused_variable.ex
rename to apps/engine/test/fixtures/compilation_warnings/lib/unused_variable.ex
diff --git a/apps/remote_control/test/fixtures/compilation_warnings/mix.exs b/apps/engine/test/fixtures/compilation_warnings/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_warnings/mix.exs
rename to apps/engine/test/fixtures/compilation_warnings/mix.exs
diff --git a/apps/remote_control/test/fixtures/dependency/lib/dependency/structs.ex b/apps/engine/test/fixtures/dependency/lib/dependency/structs.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/dependency/lib/dependency/structs.ex
rename to apps/engine/test/fixtures/dependency/lib/dependency/structs.ex
diff --git a/apps/remote_control/test/fixtures/navigations/lib/macro_struct.ex b/apps/engine/test/fixtures/navigations/lib/macro_struct.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/navigations/lib/macro_struct.ex
rename to apps/engine/test/fixtures/navigations/lib/macro_struct.ex
diff --git a/apps/remote_control/test/fixtures/navigations/lib/multi_arity.ex b/apps/engine/test/fixtures/navigations/lib/multi_arity.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/navigations/lib/multi_arity.ex
rename to apps/engine/test/fixtures/navigations/lib/multi_arity.ex
diff --git a/apps/remote_control/test/fixtures/navigations/lib/my_definition.ex b/apps/engine/test/fixtures/navigations/lib/my_definition.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/navigations/lib/my_definition.ex
rename to apps/engine/test/fixtures/navigations/lib/my_definition.ex
diff --git a/apps/remote_control/test/fixtures/navigations/lib/struct.ex b/apps/engine/test/fixtures/navigations/lib/struct.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/navigations/lib/struct.ex
rename to apps/engine/test/fixtures/navigations/lib/struct.ex
diff --git a/apps/remote_control/test/fixtures/navigations/lib/uses.ex b/apps/engine/test/fixtures/navigations/lib/uses.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/navigations/lib/uses.ex
rename to apps/engine/test/fixtures/navigations/lib/uses.ex
diff --git a/apps/remote_control/test/fixtures/navigations/mix.exs b/apps/engine/test/fixtures/navigations/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/navigations/mix.exs
rename to apps/engine/test/fixtures/navigations/mix.exs
diff --git a/apps/remote_control/test/fixtures/parse_errors/lib/parse_errors.ex b/apps/engine/test/fixtures/parse_errors/lib/parse_errors.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/parse_errors/lib/parse_errors.ex
rename to apps/engine/test/fixtures/parse_errors/lib/parse_errors.ex
diff --git a/apps/remote_control/test/fixtures/parse_errors/mix.exs b/apps/engine/test/fixtures/parse_errors/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/parse_errors/mix.exs
rename to apps/engine/test/fixtures/parse_errors/mix.exs
diff --git a/apps/remote_control/test/fixtures/project/.formatter.exs b/apps/engine/test/fixtures/project/.formatter.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/project/.formatter.exs
rename to apps/engine/test/fixtures/project/.formatter.exs
diff --git a/apps/remote_control/test/fixtures/project/.gitignore b/apps/engine/test/fixtures/project/.gitignore
similarity index 100%
rename from apps/remote_control/test/fixtures/project/.gitignore
rename to apps/engine/test/fixtures/project/.gitignore
diff --git a/apps/remote_control/test/fixtures/project/README.md b/apps/engine/test/fixtures/project/README.md
similarity index 100%
rename from apps/remote_control/test/fixtures/project/README.md
rename to apps/engine/test/fixtures/project/README.md
diff --git a/apps/remote_control/test/fixtures/project/lib/behaviours.ex b/apps/engine/test/fixtures/project/lib/behaviours.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/project/lib/behaviours.ex
rename to apps/engine/test/fixtures/project/lib/behaviours.ex
diff --git a/apps/remote_control/test/fixtures/project/lib/default_args.ex b/apps/engine/test/fixtures/project/lib/default_args.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/project/lib/default_args.ex
rename to apps/engine/test/fixtures/project/lib/default_args.ex
diff --git a/apps/remote_control/test/fixtures/project/lib/functions.ex b/apps/engine/test/fixtures/project/lib/functions.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/project/lib/functions.ex
rename to apps/engine/test/fixtures/project/lib/functions.ex
diff --git a/apps/remote_control/test/fixtures/project/lib/macros.ex b/apps/engine/test/fixtures/project/lib/macros.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/project/lib/macros.ex
rename to apps/engine/test/fixtures/project/lib/macros.ex
diff --git a/apps/remote_control/test/fixtures/project/lib/other_modules.ex b/apps/engine/test/fixtures/project/lib/other_modules.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/project/lib/other_modules.ex
rename to apps/engine/test/fixtures/project/lib/other_modules.ex
diff --git a/apps/remote_control/test/fixtures/project/lib/project.ex b/apps/engine/test/fixtures/project/lib/project.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/project/lib/project.ex
rename to apps/engine/test/fixtures/project/lib/project.ex
diff --git a/apps/remote_control/test/fixtures/project/lib/structs.ex b/apps/engine/test/fixtures/project/lib/structs.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/project/lib/structs.ex
rename to apps/engine/test/fixtures/project/lib/structs.ex
diff --git a/apps/remote_control/test/fixtures/project/mix.exs b/apps/engine/test/fixtures/project/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/project/mix.exs
rename to apps/engine/test/fixtures/project/mix.exs
diff --git a/apps/remote_control/test/fixtures/compilation_warnings/.formatter.exs b/apps/engine/test/fixtures/project_metadata/.formatter.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/compilation_warnings/.formatter.exs
rename to apps/engine/test/fixtures/project_metadata/.formatter.exs
diff --git a/apps/remote_control/test/fixtures/project_metadata/.gitignore b/apps/engine/test/fixtures/project_metadata/.gitignore
similarity index 100%
rename from apps/remote_control/test/fixtures/project_metadata/.gitignore
rename to apps/engine/test/fixtures/project_metadata/.gitignore
diff --git a/apps/remote_control/test/fixtures/project_metadata/README.md b/apps/engine/test/fixtures/project_metadata/README.md
similarity index 100%
rename from apps/remote_control/test/fixtures/project_metadata/README.md
rename to apps/engine/test/fixtures/project_metadata/README.md
diff --git a/apps/remote_control/test/fixtures/project_metadata/config/config.exs b/apps/engine/test/fixtures/project_metadata/config/config.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/project_metadata/config/config.exs
rename to apps/engine/test/fixtures/project_metadata/config/config.exs
diff --git a/apps/remote_control/test/fixtures/project_metadata/lib/project_metadata.ex b/apps/engine/test/fixtures/project_metadata/lib/project_metadata.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/project_metadata/lib/project_metadata.ex
rename to apps/engine/test/fixtures/project_metadata/lib/project_metadata.ex
diff --git a/apps/remote_control/test/fixtures/project_metadata/mix.exs b/apps/engine/test/fixtures/project_metadata/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/project_metadata/mix.exs
rename to apps/engine/test/fixtures/project_metadata/mix.exs
diff --git a/apps/remote_control/test/fixtures/umbrella/.formatter.exs b/apps/engine/test/fixtures/umbrella/.formatter.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/.formatter.exs
rename to apps/engine/test/fixtures/umbrella/.formatter.exs
diff --git a/apps/remote_control/test/fixtures/umbrella/.gitignore b/apps/engine/test/fixtures/umbrella/.gitignore
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/.gitignore
rename to apps/engine/test/fixtures/umbrella/.gitignore
diff --git a/apps/remote_control/test/fixtures/umbrella/README.md b/apps/engine/test/fixtures/umbrella/README.md
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/README.md
rename to apps/engine/test/fixtures/umbrella/README.md
diff --git a/apps/remote_control/test/fixtures/project_metadata/.formatter.exs b/apps/engine/test/fixtures/umbrella/apps/first/.formatter.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/project_metadata/.formatter.exs
rename to apps/engine/test/fixtures/umbrella/apps/first/.formatter.exs
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/first/.gitignore b/apps/engine/test/fixtures/umbrella/apps/first/.gitignore
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/first/.gitignore
rename to apps/engine/test/fixtures/umbrella/apps/first/.gitignore
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/first/README.md b/apps/engine/test/fixtures/umbrella/apps/first/README.md
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/first/README.md
rename to apps/engine/test/fixtures/umbrella/apps/first/README.md
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/first/lib/umbrella/first.ex b/apps/engine/test/fixtures/umbrella/apps/first/lib/umbrella/first.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/first/lib/umbrella/first.ex
rename to apps/engine/test/fixtures/umbrella/apps/first/lib/umbrella/first.ex
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/first/mix.exs b/apps/engine/test/fixtures/umbrella/apps/first/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/first/mix.exs
rename to apps/engine/test/fixtures/umbrella/apps/first/mix.exs
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/first/.formatter.exs b/apps/engine/test/fixtures/umbrella/apps/second/.formatter.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/first/.formatter.exs
rename to apps/engine/test/fixtures/umbrella/apps/second/.formatter.exs
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/second/.gitignore b/apps/engine/test/fixtures/umbrella/apps/second/.gitignore
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/second/.gitignore
rename to apps/engine/test/fixtures/umbrella/apps/second/.gitignore
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/second/README.md b/apps/engine/test/fixtures/umbrella/apps/second/README.md
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/second/README.md
rename to apps/engine/test/fixtures/umbrella/apps/second/README.md
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/second/lib/umbrella/second.ex b/apps/engine/test/fixtures/umbrella/apps/second/lib/umbrella/second.ex
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/second/lib/umbrella/second.ex
rename to apps/engine/test/fixtures/umbrella/apps/second/lib/umbrella/second.ex
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/second/mix.exs b/apps/engine/test/fixtures/umbrella/apps/second/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/second/mix.exs
rename to apps/engine/test/fixtures/umbrella/apps/second/mix.exs
diff --git a/apps/remote_control/test/fixtures/umbrella/config/config.exs b/apps/engine/test/fixtures/umbrella/config/config.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/config/config.exs
rename to apps/engine/test/fixtures/umbrella/config/config.exs
diff --git a/apps/remote_control/test/fixtures/umbrella/mix.exs b/apps/engine/test/fixtures/umbrella/mix.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/mix.exs
rename to apps/engine/test/fixtures/umbrella/mix.exs
diff --git a/apps/engine/test/remote_control_test.exs b/apps/engine/test/remote_control_test.exs
new file mode 100644
index 00000000..fcb08046
--- /dev/null
+++ b/apps/engine/test/remote_control_test.exs
@@ -0,0 +1,43 @@
+defmodule EngineTest do
+ alias Forge.Document
+ alias Forge.Project
+
+ use ExUnit.Case
+ use Forge.Test.EventualAssertions
+ import Engine.Test.Fixtures
+
+ def start_project(%Project{} = project) do
+ start_supervised!({Engine.ProjectNodeSupervisor, project})
+ assert {:ok, _, _} = Engine.start_link(project)
+ :ok
+ end
+
+ def engine_cwd(project) do
+ Engine.call(project, File, :cwd!, [])
+ end
+
+ describe "detecting an umbrella app" do
+ test "it changes the directory to the root if it's started in a subapp" do
+ parent_project = project(:umbrella)
+
+ subapp_project =
+ [fixtures_path(), "umbrella", "apps", "first"]
+ |> Path.join()
+ |> Document.Path.to_uri()
+ |> Project.new()
+
+ start_project(subapp_project)
+
+ assert_eventually engine_cwd(subapp_project) == Project.root_path(parent_project),
+ 250
+ end
+
+ test "keeps the current directory if it's started in the parent app" do
+ parent_project = project(:umbrella)
+ start_project(parent_project)
+
+ assert_eventually engine_cwd(parent_project) == Project.root_path(parent_project),
+ 250
+ end
+ end
+end
diff --git a/apps/remote_control/test/support/lexical/test/code_mod_case.ex b/apps/engine/test/support/test/code_mod_case.ex
similarity index 89%
rename from apps/remote_control/test/support/lexical/test/code_mod_case.ex
rename to apps/engine/test/support/test/code_mod_case.ex
index c3f6e897..9e2f49ff 100644
--- a/apps/remote_control/test/support/lexical/test/code_mod_case.ex
+++ b/apps/engine/test/support/test/code_mod_case.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Test.CodeMod.Case do
- alias Lexical.Document
- alias Lexical.Test.CodeSigil
+defmodule Engine.Test.CodeMod.Case do
+ alias Forge.Document
+ alias Forge.Test.CodeSigil
use ExUnit.CaseTemplate
@@ -8,7 +8,7 @@ defmodule Lexical.Test.CodeMod.Case do
convert_to_ast? = Keyword.get(opts, :enable_ast_conversion, true)
quote do
- import Lexical.Test.Fixtures
+ import Engine.Test.Fixtures
import unquote(CodeSigil), only: [sigil_q: 2]
def apply_code_mod(_, _, _) do
@@ -25,7 +25,7 @@ defmodule Lexical.Test.CodeMod.Case do
end
defp maybe_convert_to_ast(code, options) do
- alias Lexical.Ast
+ alias Forge.Ast
if Keyword.get(options, :convert_to_ast, unquote(convert_to_ast?)) do
case Ast.from(code) do
diff --git a/apps/remote_control/test/support/lexical/test/entry/entry_builder.ex b/apps/engine/test/support/test/entry/entry_builder.ex
similarity index 80%
rename from apps/remote_control/test/support/lexical/test/entry/entry_builder.ex
rename to apps/engine/test/support/test/entry/entry_builder.ex
index 69eed838..82759c6e 100644
--- a/apps/remote_control/test/support/lexical/test/entry/entry_builder.ex
+++ b/apps/engine/test/support/test/entry/entry_builder.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.Test.Entry.Builder do
- alias Lexical.Document.Range
- alias Lexical.Identifier
- alias Lexical.RemoteControl.Search.Indexer.Entry
+defmodule Engine.Test.Entry.Builder do
+ alias Engine.Search.Indexer.Entry
+ alias Forge.Document.Range
+ alias Forge.Identifier
- import Lexical.Test.PositionSupport
+ import Forge.Test.PositionSupport
def entry(fields \\ []) do
defaults = [
@@ -13,7 +13,7 @@ defmodule Lexical.Test.Entry.Builder do
range: range(1, 1, 1, 5),
subject: Module,
type: :module,
- application: :remote_control
+ application: :engine
]
fields = Keyword.merge(defaults, fields)
diff --git a/apps/remote_control/test/support/lexical/test/extractor_case.ex b/apps/engine/test/support/test/extractor_case.ex
similarity index 82%
rename from apps/remote_control/test/support/lexical/test/extractor_case.ex
rename to apps/engine/test/support/test/extractor_case.ex
index 30bc9395..dc227055 100644
--- a/apps/remote_control/test/support/lexical/test/extractor_case.ex
+++ b/apps/engine/test/support/test/extractor_case.ex
@@ -1,14 +1,14 @@
-defmodule Lexical.Test.ExtractorCase do
- alias Lexical.Document
- alias Lexical.RemoteControl.Search.Indexer
+defmodule Engine.Test.ExtractorCase do
+ alias Engine.Search.Indexer
+ alias Forge.Document
use ExUnit.CaseTemplate
- import Lexical.Test.CodeSigil
+ import Forge.Test.CodeSigil
using do
quote do
- import Lexical.Test.CodeSigil
- import Lexical.Test.RangeSupport
+ import Forge.Test.CodeSigil
+ import Forge.Test.RangeSupport
import unquote(__MODULE__)
end
end
diff --git a/apps/engine/test/support/test/fixtures.ex b/apps/engine/test/support/test/fixtures.ex
new file mode 100644
index 00000000..e16e284a
--- /dev/null
+++ b/apps/engine/test/support/test/fixtures.ex
@@ -0,0 +1,37 @@
+defmodule Engine.Test.Fixtures do
+ alias Forge.Document
+ alias Forge.Project
+
+ use ExUnit.CaseTemplate
+
+ def fixtures_path do
+ [__ENV__.file, "..", "..", "..", "fixtures"]
+ |> Path.join()
+ |> Path.expand()
+ end
+
+ def project(project_name) do
+ [Path.dirname(__ENV__.file), "..", "..", "fixtures", to_string(project_name)]
+ |> Path.join()
+ |> Path.expand()
+ |> Forge.Document.Path.to_uri()
+ |> Project.new()
+ end
+
+ def project do
+ project(:project)
+ end
+
+ def file_path(%Project{} = project, path_relative_to_project) do
+ project
+ |> Project.project_path()
+ |> Path.join(path_relative_to_project)
+ |> Path.expand()
+ end
+
+ def file_uri(%Project{} = project, relative_path) do
+ project
+ |> file_path(relative_path)
+ |> Document.Path.ensure_uri()
+ end
+end
diff --git a/apps/remote_control/test/support/lexical/test/mfa_support.ex b/apps/engine/test/support/test/mfa_support.ex
similarity index 79%
rename from apps/remote_control/test/support/lexical/test/mfa_support.ex
rename to apps/engine/test/support/test/mfa_support.ex
index 88bded24..b4009714 100644
--- a/apps/remote_control/test/support/lexical/test/mfa_support.ex
+++ b/apps/engine/test/support/test/mfa_support.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Test.MfaSupport do
+defmodule Engine.Test.MfaSupport do
defmacro mfa(ast) do
{m, f, a} = Macro.decompose_call(ast)
diff --git a/apps/remote_control/test/test_helper.exs b/apps/engine/test/test_helper.exs
similarity index 90%
rename from apps/remote_control/test/test_helper.exs
rename to apps/engine/test/test_helper.exs
index 877e84ba..8a213117 100644
--- a/apps/remote_control/test/test_helper.exs
+++ b/apps/engine/test/test_helper.exs
@@ -8,7 +8,7 @@ with :nonode@nohost <- Node.self() do
:net_kernel.start(:"testing-#{random_number}@127.0.0.1", %{name_domain: :longnames})
end
-Lexical.RemoteControl.Module.Loader.start_link(nil)
+Engine.Module.Loader.start_link(nil)
ExUnit.configure(assert_receive_timeout: 1000)
ExUnit.start(exclude: [:skip])
diff --git a/apps/lexical_credo/.credo.exs b/apps/expert/.credo.exs
similarity index 100%
rename from apps/lexical_credo/.credo.exs
rename to apps/expert/.credo.exs
diff --git a/apps/expert/.formatter.exs b/apps/expert/.formatter.exs
new file mode 100644
index 00000000..f56e60d1
--- /dev/null
+++ b/apps/expert/.formatter.exs
@@ -0,0 +1,15 @@
+# Used by "mix format"
+imported_deps =
+ if Mix.env() == :test do
+ [:patch, :forge]
+ else
+ [:forge]
+ end
+
+locals_without_parens = [with_progress: 3]
+
+[
+ locals_without_parens: locals_without_parens,
+ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
+ import_deps: imported_deps
+]
diff --git a/apps/server/.gitignore b/apps/expert/.gitignore
similarity index 100%
rename from apps/server/.gitignore
rename to apps/expert/.gitignore
diff --git a/apps/expert/.iex.exs b/apps/expert/.iex.exs
new file mode 100644
index 00000000..6e4499ed
--- /dev/null
+++ b/apps/expert/.iex.exs
@@ -0,0 +1,15 @@
+alias Forge.Project
+
+
+other_project =
+ [
+ File.cwd!(),
+ "..",
+ "..",
+ "..",
+ "eakins"
+ ]
+ |> Path.join()
+ |> Path.expand()
+
+project = Forge.Project.new("file://#{other_project}")
diff --git a/apps/expert/README.md b/apps/expert/README.md
new file mode 100644
index 00000000..45e3aa4f
--- /dev/null
+++ b/apps/expert/README.md
@@ -0,0 +1,3 @@
+# Expert
+
+The Expert Language server implementation
diff --git a/apps/server/bin/activate_version_manager.sh b/apps/expert/bin/activate_version_manager.sh
similarity index 100%
rename from apps/server/bin/activate_version_manager.sh
rename to apps/expert/bin/activate_version_manager.sh
diff --git a/apps/server/bin/boot.exs b/apps/expert/bin/boot.exs
similarity index 78%
rename from apps/server/bin/boot.exs
rename to apps/expert/bin/boot.exs
index 031a6e98..551a64db 100644
--- a/apps/server/bin/boot.exs
+++ b/apps/expert/bin/boot.exs
@@ -20,10 +20,10 @@ end)
|> Code.append_path()
end)
-LXical.Server.Boot.start()
+XPExpert.Boot.start()
-if System.get_env("LX_HALT_AFTER_BOOT") do
+if System.get_env("XP_HALT_AFTER_BOOT") do
require Logger
- Logger.warning("Shutting down (LX_HALT_AFTER_BOOT)")
+ Logger.warning("Shutting down (XP_HALT_AFTER_BOOT)")
System.halt()
end
diff --git a/apps/server/bin/debug_shell.sh b/apps/expert/bin/debug_shell.sh
similarity index 90%
rename from apps/server/bin/debug_shell.sh
rename to apps/expert/bin/debug_shell.sh
index 9d33a804..4d4a80c8 100755
--- a/apps/server/bin/debug_shell.sh
+++ b/apps/expert/bin/debug_shell.sh
@@ -6,4 +6,4 @@ node_name=$(epmd -names | grep manager-"$project_name" | awk '{print $2}')
iex --name "shell@127.0.0.1" \
--remsh "${node_name}" \
--dot-iex .iex.namespaced.exs \
- --cookie lexical
+ --cookie expert
diff --git a/apps/expert/bin/start_expert.sh b/apps/expert/bin/start_expert.sh
new file mode 100755
index 00000000..ee813daf
--- /dev/null
+++ b/apps/expert/bin/start_expert.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+set -o pipefail
+
+script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
+
+# shellcheck disable=SC1091
+if ! . "$script_dir"/activate_version_manager.sh; then
+ echo >&2 "Could not activate a version manager. Trying system installation."
+fi
+
+case $1 in
+ iex)
+ elixir_command=iex
+ ;;
+ *)
+ elixir_command=elixir
+ ;;
+esac
+
+$elixir_command \
+ --cookie "expert" \
+ --no-halt \
+ "$script_dir/boot.exs"
diff --git a/apps/server/config/config.exs b/apps/expert/config/config.exs
similarity index 100%
rename from apps/server/config/config.exs
rename to apps/expert/config/config.exs
diff --git a/apps/server/config/dev.exs b/apps/expert/config/dev.exs
similarity index 100%
rename from apps/server/config/dev.exs
rename to apps/expert/config/dev.exs
diff --git a/apps/server/config/prod.exs b/apps/expert/config/prod.exs
similarity index 100%
rename from apps/server/config/prod.exs
rename to apps/expert/config/prod.exs
diff --git a/apps/server/config/runtime.exs b/apps/expert/config/runtime.exs
similarity index 76%
rename from apps/server/config/runtime.exs
rename to apps/expert/config/runtime.exs
index d1683e3c..f888c3b9 100644
--- a/apps/server/config/runtime.exs
+++ b/apps/expert/config/runtime.exs
@@ -1,13 +1,13 @@
import Config
if Code.ensure_loaded?(LoggerFileBackend) do
- log_directory = Path.join(File.cwd!(), ".lexical")
+ log_directory = Path.join(File.cwd!(), ".expert")
unless File.exists?(log_directory) do
File.mkdir_p(log_directory)
end
- log_file_name = Path.join(log_directory, "lexical.log")
+ log_file_name = Path.join(log_directory, "expert.log")
config :logger,
handle_sasl_reports: true,
diff --git a/apps/server/config/test.exs b/apps/expert/config/test.exs
similarity index 100%
rename from apps/server/config/test.exs
rename to apps/expert/config/test.exs
diff --git a/apps/expert/lib/convertibles/expert.plugin.diagnostic.result.ex b/apps/expert/lib/convertibles/expert.plugin.diagnostic.result.ex
new file mode 100644
index 00000000..1c750278
--- /dev/null
+++ b/apps/expert/lib/convertibles/expert.plugin.diagnostic.result.ex
@@ -0,0 +1,94 @@
+defimpl Forge.Convertible, for: Forge.Plugin.V1.Diagnostic.Result do
+ alias Expert.Protocol.Conversions
+ alias Expert.Protocol.Types
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Math
+ alias Forge.Plugin.V1.Diagnostic
+ alias Forge.Text
+
+ def to_lsp(%Diagnostic.Result{} = diagnostic) do
+ with {:ok, lsp_range} <- lsp_range(diagnostic) do
+ proto_diagnostic = %Types.Diagnostic{
+ message: diagnostic.message,
+ range: lsp_range,
+ severity: diagnostic.severity,
+ source: diagnostic.source
+ }
+
+ {:ok, proto_diagnostic}
+ end
+ end
+
+ def to_native(%Diagnostic.Result{} = diagnostic, _) do
+ {:ok, diagnostic}
+ end
+
+ defp lsp_range(%Diagnostic.Result{position: %Position{} = position}) do
+ with {:ok, lsp_start_pos} <- Conversions.to_lsp(position) do
+ range =
+ Types.Range.new(
+ start: lsp_start_pos,
+ end: Types.Position.new(line: lsp_start_pos.line + 1, character: 0)
+ )
+
+ {:ok, range}
+ end
+ end
+
+ defp lsp_range(%Diagnostic.Result{position: %Range{} = range}) do
+ Conversions.to_lsp(range)
+ end
+
+ defp lsp_range(%Diagnostic.Result{uri: uri} = diagnostic) when is_binary(uri) do
+ with {:ok, document} <- Document.Store.open_temporary(uri) do
+ position_to_range(document, diagnostic.position)
+ end
+ end
+
+ defp lsp_range(%Diagnostic.Result{}) do
+ {:error, :no_uri}
+ end
+
+ defp position_to_range(%Document{} = document, {start_line, start_column, end_line, end_column}) do
+ start_pos = Position.new(document, start_line, max(start_column, 1))
+ end_pos = Position.new(document, end_line, max(end_column, 1))
+
+ range = Range.new(start_pos, end_pos)
+ Conversions.to_lsp(range)
+ end
+
+ defp position_to_range(%Document{} = document, {line_number, column}) do
+ column = max(column, 1)
+
+ document
+ |> to_expert_range(line_number, column)
+ |> Conversions.to_lsp()
+ end
+
+ defp position_to_range(document, line_number) when is_integer(line_number) do
+ line_number = Math.clamp(line_number, 1, Document.size(document))
+
+ with {:ok, line_text} <- Document.fetch_text_at(document, line_number) do
+ column = Text.count_leading_spaces(line_text) + 1
+
+ document
+ |> to_expert_range(line_number, column)
+ |> Conversions.to_lsp()
+ end
+ end
+
+ defp position_to_range(document, nil) do
+ position_to_range(document, 1)
+ end
+
+ defp to_expert_range(%Document{} = document, line_number, column) do
+ line_number = Math.clamp(line_number, 1, Document.size(document) + 1)
+
+ Range.new(
+ Position.new(document, line_number, column),
+ Position.new(document, line_number + 1, 1)
+ )
+ end
+end
diff --git a/apps/expert/lib/expert.ex b/apps/expert/lib/expert.ex
new file mode 100644
index 00000000..67f0703f
--- /dev/null
+++ b/apps/expert/lib/expert.ex
@@ -0,0 +1,198 @@
+defmodule Expert do
+ alias Expert.Proto.Convert
+ alias Expert.Protocol.Notifications
+ alias Expert.Protocol.Requests
+ alias Expert.Provider.Handlers
+ alias Expert.State
+ alias Expert.TaskQueue
+
+ require Logger
+
+ use GenServer
+
+ @server_specific_messages [
+ Notifications.DidChange,
+ Notifications.DidChangeConfiguration,
+ Notifications.DidChangeWatchedFiles,
+ Notifications.DidClose,
+ Notifications.DidOpen,
+ Notifications.DidSave,
+ Notifications.Exit,
+ Notifications.Initialized,
+ Requests.Shutdown
+ ]
+
+ @dialyzer {:nowarn_function, apply_to_state: 2}
+
+ @spec server_request(
+ Requests.request(),
+ (Requests.request(), {:ok, any()} | {:error, term()} -> term())
+ ) :: :ok
+ def server_request(request, on_response) when is_function(on_response, 2) do
+ GenServer.call(__MODULE__, {:server_request, request, on_response})
+ end
+
+ @spec server_request(Requests.request()) :: :ok
+ def server_request(request) do
+ server_request(request, fn _, _ -> :ok end)
+ end
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, [], name: __MODULE__)
+ end
+
+ def protocol_message(message) do
+ GenServer.cast(__MODULE__, {:protocol_message, message})
+ end
+
+ def init(_) do
+ {:ok, State.new()}
+ end
+
+ def handle_call({:server_request, request, on_response}, _from, %State{} = state) do
+ new_state = State.add_request(state, request, on_response)
+ {:reply, :ok, new_state}
+ end
+
+ def handle_cast({:protocol_message, message}, %State{} = state) do
+ new_state =
+ case handle_message(message, state) do
+ {:ok, new_state} ->
+ new_state
+
+ error ->
+ Logger.error(
+ "Could not handle message #{inspect(message.__struct__)} #{inspect(error)}"
+ )
+
+ state
+ end
+
+ {:noreply, new_state}
+ end
+
+ def handle_cast(other, %State{} = state) do
+ Logger.info("got other: #{inspect(other)}")
+ {:noreply, state}
+ end
+
+ def handle_info(:default_config, %State{configuration: nil} = state) do
+ Logger.warning(
+ "Did not receive workspace/didChangeConfiguration notification after 5 seconds. " <>
+ "Using default settings."
+ )
+
+ {:ok, config} = State.default_configuration(state)
+ {:noreply, %State{state | configuration: config}}
+ end
+
+ def handle_info(:default_config, %State{} = state) do
+ {:noreply, state}
+ end
+
+ def handle_message(%Requests.Initialize{} = initialize, %State{} = state) do
+ Process.send_after(self(), :default_config, :timer.seconds(5))
+
+ case State.initialize(state, initialize) do
+ {:ok, _state} = success ->
+ success
+
+ error ->
+ {error, state}
+ end
+ end
+
+ def handle_message(%Requests.Cancel{} = cancel_request, %State{} = state) do
+ TaskQueue.cancel(cancel_request)
+ {:ok, state}
+ end
+
+ def handle_message(%Notifications.Cancel{} = cancel_notification, %State{} = state) do
+ TaskQueue.cancel(cancel_notification)
+ {:ok, state}
+ end
+
+ def handle_message(%message_module{} = message, %State{} = state)
+ when message_module in @server_specific_messages do
+ case apply_to_state(state, message) do
+ {:ok, _} = success ->
+ success
+
+ error ->
+ Logger.error("Failed to handle #{message.__struct__}, #{inspect(error)}")
+ end
+ end
+
+ def handle_message(nil, %State{} = state) do
+ # NOTE: This deals with the response after a request is requested by the server,
+ # such as the response of `CreateWorkDoneProgress`.
+ {:ok, state}
+ end
+
+ def handle_message(%_{} = request, %State{} = state) do
+ with {:ok, handler} <- fetch_handler(request),
+ {:ok, req} <- Convert.to_native(request) do
+ TaskQueue.add(request.id, {handler, :handle, [req, state.configuration]})
+ else
+ {:error, {:unhandled, _}} ->
+ Logger.info("Unhandled request: #{request.method}")
+
+ _ ->
+ :ok
+ end
+
+ {:ok, state}
+ end
+
+ def handle_message(%{} = response, %State{} = state) do
+ new_state = State.finish_request(state, response)
+
+ {:ok, new_state}
+ end
+
+ defp apply_to_state(%State{} = state, %{} = request_or_notification) do
+ case State.apply(state, request_or_notification) do
+ {:ok, new_state} -> {:ok, new_state}
+ :ok -> {:ok, state}
+ error -> {error, state}
+ end
+ end
+
+ # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
+ defp fetch_handler(%_{} = request) do
+ case request do
+ %Requests.FindReferences{} ->
+ {:ok, Handlers.FindReferences}
+
+ %Requests.Formatting{} ->
+ {:ok, Handlers.Formatting}
+
+ %Requests.CodeAction{} ->
+ {:ok, Handlers.CodeAction}
+
+ %Requests.CodeLens{} ->
+ {:ok, Handlers.CodeLens}
+
+ %Requests.Completion{} ->
+ {:ok, Handlers.Completion}
+
+ %Requests.GoToDefinition{} ->
+ {:ok, Handlers.GoToDefinition}
+
+ %Requests.Hover{} ->
+ {:ok, Handlers.Hover}
+
+ %Requests.ExecuteCommand{} ->
+ {:ok, Handlers.Commands}
+
+ %Requests.DocumentSymbols{} ->
+ {:ok, Handlers.DocumentSymbols}
+
+ %Requests.WorkspaceSymbol{} ->
+ {:ok, Handlers.WorkspaceSymbol}
+
+ %request_module{} ->
+ {:error, {:unhandled, request_module}}
+ end
+ end
+end
diff --git a/apps/expert/lib/expert/application.ex b/apps/expert/lib/expert/application.ex
new file mode 100644
index 00000000..f57086b7
--- /dev/null
+++ b/apps/expert/lib/expert/application.ex
@@ -0,0 +1,32 @@
+defmodule Expert.Application do
+ # See https://hexdocs.pm/elixir/Application.html
+ # for more information on OTP Applications
+ @moduledoc false
+
+ alias Forge.Document
+
+ alias Expert.TaskQueue
+ alias Expert.Transport
+
+ use Application
+
+ @impl true
+ def start(_type, _args) do
+ children = [
+ document_store_child_spec(),
+ Expert,
+ {DynamicSupervisor, Expert.Project.Supervisor.options()},
+ {Task.Supervisor, name: TaskQueue.task_supervisor_name()},
+ TaskQueue,
+ {Transport.StdIO, [:standard_io, &Expert.protocol_message/1]}
+ ]
+
+ opts = [strategy: :one_for_one, name: Expert.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+
+ @doc false
+ def document_store_child_spec do
+ {Document.Store, derive: [analysis: &Forge.Ast.analyze/1]}
+ end
+end
diff --git a/apps/server/lib/lexical/server/boot.ex b/apps/expert/lib/expert/boot.ex
similarity index 89%
rename from apps/server/lib/lexical/server/boot.ex
rename to apps/expert/lib/expert/boot.ex
index a076e275..9d5e6073 100644
--- a/apps/server/lib/lexical/server/boot.ex
+++ b/apps/expert/lib/expert/boot.ex
@@ -1,11 +1,11 @@
-defmodule Lexical.Server.Boot do
+defmodule Expert.Boot do
@moduledoc """
This module is called when the server starts by the start script.
Packaging will ensure that config.exs and runtime.exs will be visible to the `:code` module
"""
+ alias Forge.VM.Versions
alias Future.Code
- alias Lexical.VM.Versions
require Logger
# halt/1 will generate a "no local return" error, which is exactly right, but that's it's _job_
@@ -34,7 +34,7 @@ defmodule Lexical.Server.Boot do
|> halt()
end
- Application.ensure_all_started(:server)
+ Application.ensure_all_started(:expert)
end
@doc false
@@ -111,18 +111,18 @@ defmodule Lexical.Server.Boot do
errors = [
unless elixir_ok? do
"""
- FATAL: Lexical is not compatible with Elixir #{versions.elixir}
+ FATAL: Expert is not compatible with Elixir #{versions.elixir}
- Lexical is compatible with the following versions of Elixir:
+ Expert is compatible with the following versions of Elixir:
#{format_allowed_versions(@allowed_elixir)}
"""
end,
unless erlang_ok? do
"""
- FATAL: Lexical is not compatible with Erlang/OTP #{versions.erlang}
+ FATAL: Expert is not compatible with Erlang/OTP #{versions.erlang}
- Lexical is compatible with the following versions of Erlang/OTP:
+ Expert is compatible with the following versions of Erlang/OTP:
#{format_allowed_versions(@allowed_erlang)}
"""
diff --git a/apps/expert/lib/expert/code_intelligence/completion.ex b/apps/expert/lib/expert/code_intelligence/completion.ex
new file mode 100644
index 00000000..94f9299c
--- /dev/null
+++ b/apps/expert/lib/expert/code_intelligence/completion.ex
@@ -0,0 +1,368 @@
+defmodule Expert.CodeIntelligence.Completion do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.Builder
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Expert.Configuration
+ alias Expert.Project.Intelligence
+ alias Expert.Protocol.Types.Completion
+ alias Expert.Protocol.Types.InsertTextFormat
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Env
+ alias Forge.Document.Position
+ alias Forge.Project
+ alias Future.Code, as: Code
+ alias Mix.Tasks.Namespace
+
+ require InsertTextFormat
+ require Logger
+
+ @expert_deps Enum.map([:expert | Mix.Project.deps_apps()], &Atom.to_string/1)
+
+ @expert_dep_modules Enum.map(@expert_deps, &Macro.camelize/1)
+
+ def trigger_characters do
+ [".", "@", "&", "%", "^", ":", "!", "-", "~"]
+ end
+
+ @spec complete(Project.t(), Analysis.t(), Position.t(), Completion.Context.t()) ::
+ Completion.List.t()
+ def complete(
+ %Project{} = project,
+ %Analysis{} = analysis,
+ %Position{} = position,
+ %Completion.Context{} = context
+ ) do
+ case Env.new(project, analysis, position) do
+ {:ok, env} ->
+ completions = completions(project, env, context)
+ log_candidates(completions)
+ maybe_to_completion_list(completions)
+
+ {:error, _} = error ->
+ Logger.error("Failed to build completion env #{inspect(error)}")
+ maybe_to_completion_list()
+ end
+ end
+
+ defp log_candidates(candidates) do
+ log_iolist =
+ Enum.reduce(candidates, ["Emitting Completions: ["], fn %Completion.Item{} = completion,
+ acc ->
+ name = Map.get(completion, :name) || Map.get(completion, :label)
+ kind = completion |> Map.get(:kind, :unknown) |> to_string()
+
+ [acc, [kind, ":", name], " "]
+ end)
+
+ Logger.info([log_iolist, "]"])
+ end
+
+ defp completions(%Project{} = project, %Env{} = env, %Completion.Context{} = context) do
+ prefix_tokens = Env.prefix_tokens(env, 1)
+
+ cond do
+ prefix_tokens == [] or not should_emit_completions?(env) ->
+ []
+
+ should_emit_do_end_snippet?(env) ->
+ do_end_snippet = "do\n $0\nend"
+
+ env
+ |> Builder.snippet(
+ do_end_snippet,
+ label: "do/end block",
+ filter_text: "do"
+ )
+ |> List.wrap()
+
+ Env.in_context?(env, :struct_field_key) ->
+ project
+ |> Engine.Api.complete_struct_fields(env.analysis, env.position)
+ |> Enum.map(&Translatable.translate(&1, Builder, env))
+
+ true ->
+ project
+ |> Engine.Api.complete(env)
+ |> to_completion_items(project, env, context)
+ end
+ end
+
+ defp should_emit_completions?(%Env{} = env) do
+ if inside_comment?(env) or inside_string?(env) do
+ false
+ else
+ always_emit_completions?() or has_meaningful_completions?(env)
+ end
+ end
+
+ defp always_emit_completions? do
+ # If VS Code receives an empty completion list, it will never issue
+ # a new request, even if `is_incomplete: true` is specified.
+ # https://github.com/lexical-lsp/lexical/issues/400
+ Configuration.get().client_name == "Visual Studio Code"
+ end
+
+ defp has_meaningful_completions?(%Env{} = env) do
+ case Code.Fragment.cursor_context(env.prefix) do
+ :none ->
+ false
+
+ {:unquoted_atom, name} ->
+ length(name) > 1
+
+ {:local_or_var, name} ->
+ local_length = length(name)
+ surround_begin = max(1, env.position.character - local_length)
+
+ local_length > 1 or has_surround_context?(env.prefix, 1, surround_begin)
+
+ _ ->
+ true
+ end
+ end
+
+ defp inside_comment?(env) do
+ Env.in_context?(env, :comment)
+ end
+
+ defp inside_string?(env) do
+ Env.in_context?(env, :string)
+ end
+
+ defp has_surround_context?(fragment, line, column)
+ when is_binary(fragment) and line >= 1 and column >= 1 do
+ Code.Fragment.surround_context(fragment, {line, column}) != :none
+ end
+
+ # We emit a do/end snippet if the prefix token is the do operator or 'd', and
+ # there is a space before the token preceding it on the same line. This
+ # handles situations like `@do|` where a do/end snippet would be invalid.
+ defguardp valid_do_prefix(kind, value)
+ when (kind === :identifier and value === ~c"d") or
+ (kind === :operator and value === :do)
+
+ defguardp space_before_preceding_token(do_col, preceding_col)
+ when do_col - preceding_col > 1
+
+ defp should_emit_do_end_snippet?(%Env{} = env) do
+ prefix_tokens = Env.prefix_tokens(env, 2)
+
+ valid_prefix? =
+ match?(
+ [{kind, value, {line, do_col}}, {_, _, {line, preceding_col}}]
+ when space_before_preceding_token(do_col, preceding_col) and
+ valid_do_prefix(kind, value),
+ prefix_tokens
+ )
+
+ valid_prefix? and Env.empty?(env.suffix)
+ end
+
+ defp to_completion_items(
+ local_completions,
+ %Project{} = project,
+ %Env{} = env,
+ %Completion.Context{} = context
+ ) do
+ debug_local_completions(local_completions)
+
+ for result <- local_completions,
+ displayable?(project, result),
+ applies_to_context?(project, result, context),
+ applies_to_env?(env, result),
+ %Completion.Item{} = item <- to_completion_item(result, env) do
+ item
+ end
+ end
+
+ defp debug_local_completions(completions) do
+ completions_by_type =
+ Enum.group_by(completions, fn %candidate_module{} ->
+ candidate_module
+ |> Atom.to_string()
+ |> String.split(".")
+ |> List.last()
+ |> String.downcase()
+ end)
+
+ log_iodata =
+ Enum.reduce(completions_by_type, ["Local completions are: ["], fn {type, completions},
+ acc ->
+ names =
+ Enum.map_join(completions, ", ", fn candidate ->
+ Map.get(candidate, :name) || Map.get(candidate, :detail)
+ end)
+
+ [acc, [type, ": (", names], ") "]
+ end)
+
+ Logger.info([log_iodata, "]"])
+ end
+
+ defp to_completion_item(candidate, env) do
+ candidate
+ |> Translatable.translate(Builder, env)
+ |> List.wrap()
+ end
+
+ defp displayable?(%Project{} = project, result) do
+ suggested_module =
+ case result do
+ %_{full_name: full_name} when is_binary(full_name) -> full_name
+ %_{origin: origin} when is_binary(origin) -> origin
+ _ -> ""
+ end
+
+ cond do
+ Namespace.Module.prefixed?(suggested_module) ->
+ false
+
+ # If we're working on the dependency, we should include it!
+ Project.name(project) in @expert_deps ->
+ true
+
+ true ->
+ Enum.reduce_while(@expert_dep_modules, true, fn module, _ ->
+ if String.starts_with?(suggested_module, module) do
+ {:halt, false}
+ else
+ {:cont, true}
+ end
+ end)
+ end
+ end
+
+ defp applies_to_env?(%Env{} = env, %struct_module{} = result) do
+ cond do
+ Env.in_context?(env, :struct_reference) ->
+ struct_reference_completion?(result, env)
+
+ Env.in_context?(env, :bitstring) ->
+ struct_module in [Candidate.BitstringOption, Candidate.Variable]
+
+ Env.in_context?(env, :alias) ->
+ struct_module in [
+ Candidate.Behaviour,
+ Candidate.Module,
+ Candidate.Protocol,
+ Candidate.Struct
+ ]
+
+ Env.in_context?(env, :use) ->
+ # only allow modules that define __using__ in a use statement
+ usable?(env, result)
+
+ Env.in_context?(env, :impl) ->
+ # only allow behaviour modules after @impl
+ behaviour?(env, result)
+
+ Env.in_context?(env, :spec) or Env.in_context?(env, :type) ->
+ typespec_or_type_candidate?(result, env)
+
+ true ->
+ struct_module != Candidate.Typespec
+ end
+ end
+
+ defp usable?(%Env{} = env, completion) do
+ # returns true if the given completion is or is a parent of
+ # a module that defines __using__
+ case completion do
+ %{full_name: full_name} ->
+ with_prefix =
+ Engine.Api.modules_with_prefix(
+ env.project,
+ full_name,
+ {Kernel, :macro_exported?, [:__using__, 1]}
+ )
+
+ not Enum.empty?(with_prefix)
+
+ _ ->
+ false
+ end
+ end
+
+ defp behaviour?(%Env{} = env, completion) do
+ # returns true if the given completion is or is a parent of
+ # a module that is a behaviour
+
+ case completion do
+ %{full_name: full_name} ->
+ with_prefix =
+ Engine.Api.modules_with_prefix(
+ env.project,
+ full_name,
+ {Kernel, :function_exported?, [:behaviour_info, 1]}
+ )
+
+ not Enum.empty?(with_prefix)
+
+ _ ->
+ false
+ end
+ end
+
+ defp struct_reference_completion?(%Candidate.Struct{}, _) do
+ true
+ end
+
+ defp struct_reference_completion?(%Candidate.Module{} = module, %Env{} = env) do
+ Intelligence.defines_struct?(env.project, module.full_name, to: :great_grandchild)
+ end
+
+ defp struct_reference_completion?(%Candidate.Macro{name: "__MODULE__"}, _) do
+ true
+ end
+
+ defp struct_reference_completion?(_, _) do
+ false
+ end
+
+ defp typespec_or_type_candidate?(%struct_module{}, _)
+ when struct_module in [Candidate.Module, Candidate.Typespec, Candidate.ModuleAttribute] do
+ true
+ end
+
+ defp typespec_or_type_candidate?(%Candidate.Function{} = function, %Env{} = env) do
+ case Engine.Api.expand_alias(env.project, [:__MODULE__], env.analysis, env.position) do
+ {:ok, expanded} ->
+ expanded == function.origin
+
+ _error ->
+ false
+ end
+ end
+
+ defp typespec_or_type_candidate?(_, _) do
+ false
+ end
+
+ defp applies_to_context?(%Project{} = project, result, %Completion.Context{
+ trigger_kind: :trigger_character,
+ trigger_character: "%"
+ }) do
+ case result do
+ %Candidate.Module{} = result ->
+ Intelligence.defines_struct?(project, result.full_name, from: :child, to: :child)
+
+ %Candidate.Struct{} ->
+ true
+
+ _other ->
+ false
+ end
+ end
+
+ defp applies_to_context?(_project, _result, _context) do
+ true
+ end
+
+ defp maybe_to_completion_list(items \\ [])
+
+ defp maybe_to_completion_list([]) do
+ Completion.List.new(items: [], is_incomplete: true)
+ end
+
+ defp maybe_to_completion_list(items), do: items
+end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/builder.ex b/apps/expert/lib/expert/code_intelligence/completion/builder.ex
similarity index 91%
rename from apps/server/lib/lexical/server/code_intelligence/completion/builder.ex
rename to apps/expert/lib/expert/code_intelligence/completion/builder.ex
index e3e042c3..17c0afa8 100644
--- a/apps/server/lib/lexical/server/code_intelligence/completion/builder.ex
+++ b/apps/expert/lib/expert/code_intelligence/completion/builder.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Builder do
+defmodule Expert.CodeIntelligence.Completion.Builder do
@moduledoc """
Completion builder.
@@ -10,17 +10,17 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Builder do
replacement range will be determined by the preceding token.
"""
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.Protocol.Types.Completion
+ alias Expert.Protocol.Types.Markup.Content
+ alias Forge.Ast.Env
+ alias Forge.Document
+ alias Forge.Document.Edit
+ alias Forge.Document.Position
+ alias Forge.Document.Range
alias Future.Code, as: Code
- alias Lexical.Ast.Env
- alias Lexical.Document
- alias Lexical.Document.Edit
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Protocol.Types.Completion
- alias Lexical.Protocol.Types.Markup.Content
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
-
- @doc "Fields found in `t:Lexical.Protocol.Types.Completion.Item.t()`"
+
+ @doc "Fields found in `t:Expert.Protocol.Types.Completion.Item.t()`"
@type item_opts :: keyword()
@type t :: module()
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/sort_scope.ex b/apps/expert/lib/expert/code_intelligence/completion/sort_scope.ex
similarity index 91%
rename from apps/server/lib/lexical/server/code_intelligence/completion/sort_scope.ex
rename to apps/expert/lib/expert/code_intelligence/completion/sort_scope.ex
index 44f11da5..38dd439b 100644
--- a/apps/server/lib/lexical/server/code_intelligence/completion/sort_scope.ex
+++ b/apps/expert/lib/expert/code_intelligence/completion/sort_scope.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.SortScope do
+defmodule Expert.CodeIntelligence.Completion.SortScope do
@moduledoc """
Enumerated categories for sorting completion items.
@@ -12,7 +12,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.SortScope do
"""
@doc """
- Intended for module completions, such as `Lexical.` -> `Lexical.Completion`.
+ Intended for module completions, such as `Expert.` -> `Expert.Completion`.
"""
def module(local_priority \\ 1) do
"0" <> "0" <> local_priority(local_priority)
@@ -51,7 +51,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.SortScope do
@doc """
Aspirationally for declarations that could be auto-aliased into the user's
- immediate module (not yet a feature of Lexical).
+ immediate module (not yet a feature of Expert).
"""
def auto(deprecated? \\ false, local_priority \\ 1) do
"5" <> extra_order_fields(deprecated?, local_priority)
diff --git a/apps/expert/lib/expert/code_intelligence/completion/translatable.ex b/apps/expert/lib/expert/code_intelligence/completion/translatable.ex
new file mode 100644
index 00000000..1f647b39
--- /dev/null
+++ b/apps/expert/lib/expert/code_intelligence/completion/translatable.ex
@@ -0,0 +1,19 @@
+defprotocol Expert.CodeIntelligence.Completion.Translatable do
+ alias Forge.Ast.Env
+ alias Expert.Protocol.Types.Completion
+ alias Expert.CodeIntelligence.Completion.Builder
+
+ @type t :: any()
+
+ @type translated :: [Completion.Item.t()] | Completion.Item.t() | :skip
+
+ @fallback_to_any true
+ @spec translate(t, Builder.t(), Env.t()) :: translated
+ def translate(item, builder, env)
+end
+
+defimpl Expert.CodeIntelligence.Completion.Translatable, for: Any do
+ def translate(_any, _builder, _environment) do
+ :skip
+ end
+end
diff --git a/apps/expert/lib/expert/code_intelligence/completion/translations/bitstring_option.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/bitstring_option.ex
new file mode 100644
index 00000000..f055cb67
--- /dev/null
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/bitstring_option.ex
@@ -0,0 +1,25 @@
+defmodule Expert.CodeIntelligence.Completion.Translations.BitstringOption do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Expert.CodeIntelligence.Completion.Translations
+ alias Forge.Ast.Env
+
+ require Logger
+
+ defimpl Translatable, for: Candidate.BitstringOption do
+ def translate(option, builder, %Env{} = env) do
+ Translations.BitstringOption.translate(option, builder, env)
+ end
+ end
+
+ def translate(%Candidate.BitstringOption{} = option, builder, %Env{} = env) do
+ env
+ |> builder.plain_text(option.name,
+ filter_text: option.name,
+ kind: :unit,
+ label: option.name
+ )
+ |> builder.set_sort_scope(SortScope.global())
+ end
+end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/callable.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/callable.ex
similarity index 95%
rename from apps/server/lib/lexical/server/code_intelligence/completion/translations/callable.ex
rename to apps/expert/lib/expert/code_intelligence/completion/translations/callable.ex
index 5d4699af..dea684ee 100644
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/callable.ex
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/callable.ex
@@ -1,8 +1,8 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Callable do
- alias Lexical.Ast.Env
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.Builder
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
+defmodule Expert.CodeIntelligence.Completion.Translations.Callable do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.Builder
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Forge.Ast.Env
@callables [Candidate.Function, Candidate.Macro, Candidate.Callback, Candidate.Typespec]
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/callback.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/callback.ex
similarity index 87%
rename from apps/server/lib/lexical/server/code_intelligence/completion/translations/callback.ex
rename to apps/expert/lib/expert/code_intelligence/completion/translations/callback.ex
index e7a62d63..24129e98 100644
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/callback.ex
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/callback.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Callback do
- alias Lexical.Ast.Env
- alias Lexical.Document
- alias Lexical.RemoteControl.Completion.Candidate.Callback
- alias Lexical.Server.CodeIntelligence.Completion.Builder
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
+defmodule Expert.CodeIntelligence.Completion.Translations.Callback do
+ alias Engine.Completion.Candidate.Callback
+ alias Expert.CodeIntelligence.Completion.Builder
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Forge.Ast.Env
+ alias Forge.Document
defimpl Translatable, for: Callback do
def translate(callback, _builder, %Env{} = env) do
diff --git a/apps/expert/lib/expert/code_intelligence/completion/translations/function.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/function.ex
new file mode 100644
index 00000000..332b0719
--- /dev/null
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/function.ex
@@ -0,0 +1,16 @@
+defmodule Expert.CodeIntelligence.Completion.Translations.Function do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Expert.CodeIntelligence.Completion.Translations
+ alias Forge.Ast.Env
+
+ defimpl Translatable, for: Candidate.Function do
+ def translate(function, _builder, %Env{} = env) do
+ if Env.in_context?(env, :function_capture) do
+ Translations.Callable.capture_completions(function, env)
+ else
+ Translations.Callable.completion(function, env)
+ end
+ end
+ end
+end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/macro.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/macro.ex
similarity index 96%
rename from apps/server/lib/lexical/server/code_intelligence/completion/translations/macro.ex
rename to apps/expert/lib/expert/code_intelligence/completion/translations/macro.ex
index bc24ce97..fb3ece92 100644
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/macro.ex
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/macro.ex
@@ -1,14 +1,14 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Macro do
- alias Lexical.Ast
- alias Lexical.Ast.Env
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
- alias Lexical.Server.CodeIntelligence.Completion.Translations
- alias Lexical.Server.CodeIntelligence.Completion.Translations.Callable
- alias Lexical.Server.CodeIntelligence.Completion.Translations.Struct
+defmodule Expert.CodeIntelligence.Completion.Translations.Macro do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Expert.CodeIntelligence.Completion.Translations
+ alias Expert.CodeIntelligence.Completion.Translations.Callable
+ alias Expert.CodeIntelligence.Completion.Translations.Struct
+ alias Forge.Ast
+ alias Forge.Ast.Env
+ alias Forge.Document
+ alias Forge.Document.Position
@snippet_macros ~w(def defp defmacro defmacrop defimpl defmodule defprotocol defguard defguardp defexception test use)
@unhelpful_macros ~w(:: alias! in and or destructure)
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/map_field.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/map_field.ex
similarity index 75%
rename from apps/server/lib/lexical/server/code_intelligence/completion/translations/map_field.ex
rename to apps/expert/lib/expert/code_intelligence/completion/translations/map_field.ex
index 9b6a3529..840ac4c4 100644
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/map_field.ex
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/map_field.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.MapField do
- alias Lexical.Ast.Env
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
+defmodule Expert.CodeIntelligence.Completion.Translations.MapField do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Forge.Ast.Env
defimpl Translatable, for: Candidate.MapField do
def translate(%Candidate.MapField{} = map_field, builder, %Env{} = env) do
diff --git a/apps/expert/lib/expert/code_intelligence/completion/translations/module_attribute.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/module_attribute.ex
new file mode 100644
index 00000000..02dfea2a
--- /dev/null
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/module_attribute.ex
@@ -0,0 +1,178 @@
+defmodule Expert.CodeIntelligence.Completion.Translations.ModuleAttribute do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Expert.CodeIntelligence.Completion.Translations
+ alias Forge.Ast
+ alias Forge.Ast.Env
+ alias Forge.Document.Position
+
+ defimpl Translatable, for: Candidate.ModuleAttribute do
+ def translate(attribute, builder, %Env{} = env) do
+ Translations.ModuleAttribute.translate(attribute, builder, env)
+ end
+ end
+
+ def translate(%Candidate.ModuleAttribute{name: "@moduledoc"}, builder, env) do
+ doc_snippet = ~s'''
+ @moduledoc """
+ $0
+ """
+ '''
+
+ case fetch_range(env) do
+ {:ok, range} ->
+ with_doc =
+ builder.text_edit_snippet(env, doc_snippet, range,
+ detail: "Document public module",
+ kind: :property,
+ label: "@moduledoc"
+ )
+
+ without_doc =
+ builder.text_edit(env, "@moduledoc false", range,
+ detail: "Mark as private",
+ kind: :property,
+ label: "@moduledoc false"
+ )
+
+ [with_doc, without_doc]
+
+ :error ->
+ :skip
+ end
+ end
+
+ def translate(%Candidate.ModuleAttribute{name: "@doc"}, builder, env) do
+ doc_snippet = ~s'''
+ @doc """
+ $0
+ """
+ '''
+
+ case fetch_range(env) do
+ {:ok, range} ->
+ with_doc =
+ builder.text_edit_snippet(env, doc_snippet, range,
+ detail: "Document public function",
+ kind: :property,
+ label: "@doc"
+ )
+
+ without_doc =
+ builder.text_edit(env, "@doc false", range,
+ detail: "Mark as private",
+ kind: :property,
+ label: "@doc false"
+ )
+
+ [with_doc, without_doc]
+
+ :error ->
+ :skip
+ end
+ end
+
+ def translate(%Candidate.ModuleAttribute{name: "@spec"}, builder, env) do
+ case fetch_range(env) do
+ {:ok, range} ->
+ [
+ maybe_specialized_spec_snippet(builder, env, range),
+ basic_spec_snippet(builder, env, range)
+ ]
+
+ :error ->
+ :skip
+ end
+ end
+
+ def translate(%Candidate.ModuleAttribute{} = attribute, builder, env) do
+ case fetch_range(env) do
+ {:ok, range} ->
+ builder.text_edit(env, attribute.name, range,
+ detail: "module attribute",
+ kind: :constant,
+ label: attribute.name
+ )
+
+ :error ->
+ :skip
+ end
+ end
+
+ defp fetch_range(%Env{} = env) do
+ case fetch_at_op_on_same_line(env) do
+ {:ok, {:at_op, _, {_line, char}}} ->
+ {:ok, {char, env.position.character}}
+
+ _ ->
+ :error
+ end
+ end
+
+ defp fetch_at_op_on_same_line(%Env{} = env) do
+ Enum.reduce_while(Env.prefix_tokens(env), :error, fn
+ {:at_op, _, _} = at_op, _acc ->
+ {:halt, {:ok, at_op}}
+
+ {:eol, _, _}, _acc ->
+ {:halt, :error}
+
+ _, acc ->
+ {:cont, acc}
+ end)
+ end
+
+ defp maybe_specialized_spec_snippet(builder, %Env{} = env, range) do
+ with {:ok, %Position{} = position} <- Env.next_significant_position(env),
+ {:ok, [{maybe_def, _, [call, _]} | _]} when maybe_def in [:def, :defp] <-
+ Ast.path_at(env.analysis, position),
+ {function_name, _, args} <- call do
+ specialized_spec_snippet(builder, env, range, function_name, args)
+ else
+ _ -> nil
+ end
+ end
+
+ defp specialized_spec_snippet(builder, env, range, function_name, args) do
+ name = to_string(function_name)
+
+ args_snippet =
+ case args do
+ nil ->
+ ""
+
+ list ->
+ Enum.map_join(1..length(list), ", ", &"${#{&1}:term()}")
+ end
+
+ snippet = ~s"""
+ @spec #{name}(#{args_snippet}) :: ${0:term()}
+ """
+
+ env
+ |> builder.text_edit_snippet(snippet, range,
+ detail: "Typespec",
+ kind: :property,
+ label: "@spec #{name}"
+ )
+ |> builder.set_sort_scope(SortScope.global(false, 0))
+ end
+
+ defp basic_spec_snippet(builder, env, range) do
+ snippet = ~S"""
+ @spec ${1:function}(${2:term()}) :: ${3:term()}
+ def ${1:function}(${4:args}) do
+ $0
+ end
+ """
+
+ env
+ |> builder.text_edit_snippet(snippet, range,
+ detail: "Typespec",
+ kind: :property,
+ label: "@spec"
+ )
+ |> builder.set_sort_scope(SortScope.global(false, 1))
+ end
+end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/module_or_behaviour.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/module_or_behaviour.ex
similarity index 90%
rename from apps/server/lib/lexical/server/code_intelligence/completion/translations/module_or_behaviour.ex
rename to apps/expert/lib/expert/code_intelligence/completion/translations/module_or_behaviour.ex
index 57ee0140..e9f4e2e6 100644
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/module_or_behaviour.ex
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/module_or_behaviour.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleOrBehaviour do
- alias Lexical.Ast.Env
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
- alias Lexical.Server.CodeIntelligence.Completion.Translations
- alias Lexical.Server.Project.Intelligence
+defmodule Expert.CodeIntelligence.Completion.Translations.ModuleOrBehaviour do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Expert.CodeIntelligence.Completion.Translations
+ alias Expert.Project.Intelligence
+ alias Forge.Ast.Env
defimpl Translatable, for: Candidate.Module do
def translate(module, builder, %Env{} = env) do
@@ -139,7 +139,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleOrBehavi
defp strip_leading_period(<<".", rest::binary>>), do: rest
defp strip_leading_period(string_without_period), do: string_without_period
- defp immediate_descendent_struct_modules(%Lexical.Project{} = project, module_name) do
+ defp immediate_descendent_struct_modules(%Forge.Project{} = project, module_name) do
Intelligence.collect_struct_modules(project, module_name, to: :grandchild)
end
end
diff --git a/apps/expert/lib/expert/code_intelligence/completion/translations/struct.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/struct.ex
new file mode 100644
index 00000000..c55ed405
--- /dev/null
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/struct.ex
@@ -0,0 +1,93 @@
+defmodule Expert.CodeIntelligence.Completion.Translations.Struct do
+ alias Forge.Ast.Env
+ alias Forge.Formats
+ alias Future.Code, as: Code
+
+ def completion(%Env{} = _env, _builder, _module_name, _full_name, 0) do
+ nil
+ end
+
+ def completion(%Env{} = env, builder, module_name, full_name, more) when is_integer(more) do
+ singular = "${count} more struct"
+ plural = "${count} more structs"
+
+ builder_opts = [
+ kind: :module,
+ label: "#{module_name}...(#{Formats.plural(more, singular, plural)})",
+ detail: "#{full_name}."
+ ]
+
+ insert_text = "#{module_name}."
+ range = edit_range(env)
+
+ builder.text_edit_snippet(env, insert_text, range, builder_opts)
+ end
+
+ def completion(%Env{} = env, builder, struct_name, full_name) do
+ builder_opts = [
+ kind: :struct,
+ detail: "#{full_name}",
+ label: "#{struct_name}"
+ ]
+
+ range = edit_range(env)
+
+ insert_text =
+ if add_curlies?(env) do
+ struct_name <> "{$1}"
+ else
+ struct_name
+ end
+
+ builder.text_edit_snippet(env, insert_text, range, builder_opts)
+ end
+
+ defp add_curlies?(%Env{} = env) do
+ if Env.in_context?(env, :struct_reference) do
+ not String.contains?(env.suffix, "{")
+ else
+ false
+ end
+ end
+
+ defp edit_range(%Env{} = env) do
+ prefix_end = env.position.character
+
+ edit_begin =
+ case Code.Fragment.cursor_context(env.prefix) do
+ {:struct, {:dot, {:alias, _typed_module}, _rest}} ->
+ prefix_end
+
+ {:struct, typed_module_name} ->
+ beginning_of_edit(env, typed_module_name)
+
+ {:local_or_var, [?_ | _rest] = typed} ->
+ beginning_of_edit(env, typed)
+ end
+
+ {edit_begin, env.position.character}
+ end
+
+ defp beginning_of_edit(env, typed_module_name) do
+ case left_offset_of(typed_module_name, ?.) do
+ {:ok, offset} ->
+ env.position.character - offset
+
+ :error ->
+ env.position.character - length(typed_module_name)
+ end
+ end
+
+ defp left_offset_of(string, character) do
+ string
+ |> Enum.reverse()
+ |> Enum.with_index()
+ |> Enum.reduce_while(:error, fn
+ {^character, index}, _ ->
+ {:halt, {:ok, index}}
+
+ _, acc ->
+ {:cont, acc}
+ end)
+ end
+end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/struct_field.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/struct_field.ex
similarity index 79%
rename from apps/server/lib/lexical/server/code_intelligence/completion/translations/struct_field.ex
rename to apps/expert/lib/expert/code_intelligence/completion/translations/struct_field.ex
index 82f04504..52403125 100644
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/struct_field.ex
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/struct_field.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.StructField do
+defmodule Expert.CodeIntelligence.Completion.Translations.StructField do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Expert.CodeIntelligence.Completion.Translations
+ alias Forge.Ast.Env
alias Future.Code, as: Code
- alias Lexical.Ast.Env
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
- alias Lexical.Server.CodeIntelligence.Completion.Translations
defimpl Translatable, for: Candidate.StructField do
def translate(field, builder, %Env{} = env) do
diff --git a/apps/expert/lib/expert/code_intelligence/completion/translations/typespec.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/typespec.ex
new file mode 100644
index 00000000..44b0f9c0
--- /dev/null
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/typespec.ex
@@ -0,0 +1,12 @@
+defmodule Expert.CodeIntelligence.Completion.Translations.Typespec do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Expert.CodeIntelligence.Completion.Translations.Callable
+ alias Forge.Ast.Env
+
+ defimpl Translatable, for: Candidate.Typespec do
+ def translate(typespec, _builder, %Env{} = env) do
+ Callable.completion(typespec, env)
+ end
+ end
+end
diff --git a/apps/expert/lib/expert/code_intelligence/completion/translations/variable.ex b/apps/expert/lib/expert/code_intelligence/completion/translations/variable.ex
new file mode 100644
index 00000000..c19c5cca
--- /dev/null
+++ b/apps/expert/lib/expert/code_intelligence/completion/translations/variable.ex
@@ -0,0 +1,18 @@
+defmodule Expert.CodeIntelligence.Completion.Translations.Variable do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.CodeIntelligence.Completion.Translatable
+ alias Forge.Ast.Env
+
+ defimpl Translatable, for: Candidate.Variable do
+ def translate(variable, builder, %Env{} = env) do
+ env
+ |> builder.plain_text(variable.name,
+ detail: variable.name,
+ kind: :variable,
+ label: variable.name
+ )
+ |> builder.set_sort_scope(SortScope.variable())
+ end
+ end
+end
diff --git a/apps/server/lib/lexical/server/configuration.ex b/apps/expert/lib/expert/configuration.ex
similarity index 86%
rename from apps/server/lib/lexical/server/configuration.ex
rename to apps/expert/lib/expert/configuration.ex
index cb0dc1d6..a31c256f 100644
--- a/apps/server/lib/lexical/server/configuration.ex
+++ b/apps/expert/lib/expert/configuration.ex
@@ -1,17 +1,17 @@
-defmodule Lexical.Server.Configuration do
+defmodule Expert.Configuration do
@moduledoc """
- Encapsulates server configuration options and client capability support.
+ Encapsulates expert configuration options and client capability support.
"""
- alias Lexical.Project
- alias Lexical.Protocol.Id
- alias Lexical.Protocol.Notifications.DidChangeConfiguration
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Requests.RegisterCapability
- alias Lexical.Protocol.Types.ClientCapabilities
- alias Lexical.Protocol.Types.Registration
- alias Lexical.Server.Configuration.Support
- alias Lexical.Server.Dialyzer
+ alias Expert.Configuration.Support
+ alias Expert.Dialyzer
+ alias Expert.Protocol.Id
+ alias Expert.Protocol.Notifications.DidChangeConfiguration
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Requests.RegisterCapability
+ alias Expert.Protocol.Types.ClientCapabilities
+ alias Expert.Protocol.Types.Registration
+ alias Forge.Project
defstruct project: nil,
support: nil,
@@ -31,7 +31,7 @@ defmodule Lexical.Server.Configuration do
@dialyzer {:nowarn_function, set_dialyzer_enabled: 2}
- @spec new(Lexical.uri(), map(), String.t() | nil) :: t
+ @spec new(Forge.uri(), map(), String.t() | nil) :: t
def new(root_uri, %ClientCapabilities{} = client_capabilities, client_name) do
support = Support.new(client_capabilities)
project = Project.new(root_uri)
diff --git a/apps/expert/lib/expert/configuration/support.ex b/apps/expert/lib/expert/configuration/support.ex
new file mode 100644
index 00000000..53735c3f
--- /dev/null
+++ b/apps/expert/lib/expert/configuration/support.ex
@@ -0,0 +1,71 @@
+defmodule Expert.Configuration.Support do
+ @moduledoc false
+
+ alias Expert.Protocol.Types.ClientCapabilities
+
+ # To track a new client capability, add a new field and the path to the
+ # capability in the `Expert.Protocol.Types.ClientCapabilities` struct
+ # to this mapping:
+ @client_capability_mapping [
+ code_action_dynamic_registration: [
+ :text_document,
+ :code_action,
+ :dynamic_registration
+ ],
+ hierarchical_symbols: [
+ :text_document,
+ :document_symbol,
+ :hierarchical_document_symbol_support
+ ],
+ snippet: [
+ :text_document,
+ :completion,
+ :completion_item,
+ :snippet_support
+ ],
+ deprecated: [
+ :text_document,
+ :completion,
+ :completion_item,
+ :deprecated_support
+ ],
+ tags: [
+ :text_document,
+ :completion,
+ :completion_item,
+ :tag_support
+ ],
+ signature_help: [
+ :text_document,
+ :signature_help
+ ],
+ work_done_progress: [
+ :window,
+ :work_done_progress
+ ]
+ ]
+
+ defstruct code_action_dynamic_registration: false,
+ hierarchical_symbols: false,
+ snippet: false,
+ deprecated: false,
+ tags: false,
+ signature_help: false,
+ work_done_progress: false
+
+ @type t :: %__MODULE__{}
+
+ def new(%ClientCapabilities{} = client_capabilities) do
+ defaults =
+ for {key, path} <- @client_capability_mapping do
+ value = get_in(client_capabilities, path) || false
+ {key, value}
+ end
+
+ struct(__MODULE__, defaults)
+ end
+
+ def new do
+ %__MODULE__{}
+ end
+end
diff --git a/apps/expert/lib/expert/dialyzer.ex b/apps/expert/lib/expert/dialyzer.ex
new file mode 100644
index 00000000..fa7d575b
--- /dev/null
+++ b/apps/expert/lib/expert/dialyzer.ex
@@ -0,0 +1,5 @@
+defmodule Expert.Dialyzer do
+ def check_support do
+ :ok
+ end
+end
diff --git a/apps/server/lib/lexical/server/iex/helpers.ex b/apps/expert/lib/expert/iex/helpers.ex
similarity index 78%
rename from apps/server/lib/lexical/server/iex/helpers.ex
rename to apps/expert/lib/expert/iex/helpers.ex
index f1821be9..e31c2931 100644
--- a/apps/server/lib/lexical/server/iex/helpers.ex
+++ b/apps/expert/lib/expert/iex/helpers.ex
@@ -1,23 +1,22 @@
-defmodule Lexical.Server.IEx.Helpers do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Project
- alias Lexical.Protocol.Types.Completion
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Search
- alias Lexical.Server.CodeIntelligence
+defmodule Expert.IEx.Helpers do
+ alias Engine.Search
+ alias Expert.CodeIntelligence
+ alias Expert.Protocol.Types.Completion
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Project
defmacro __using__(_) do
quote do
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Search
+ alias Forge.Document
+ alias Forge.Document.Position
+
+ alias Engine.Search
import unquote(__MODULE__)
- RemoteControl.Module.Loader.start_link(nil)
- RemoteControl.Dispatch.start_link([])
+ Engine.Module.Loader.start_link(nil)
+ Engine.Dispatch.start_link([])
end
end
@@ -29,11 +28,11 @@ defmodule Lexical.Server.IEx.Helpers do
def observer(project) do
project
|> ensure_project()
- |> RemoteControl.call(:observer, :start)
+ |> Engine.call(:observer, :start)
end
def doc(text) do
- doc(:lexical, text)
+ doc(:expert, text)
end
def project_node(name) do
@@ -56,7 +55,7 @@ defmodule Lexical.Server.IEx.Helpers do
def search_store(project) do
project = ensure_project(project)
- RemoteControl.set_project(project)
+ Engine.set_project(project)
Search.Store.start_link(
project,
@@ -82,7 +81,7 @@ defmodule Lexical.Server.IEx.Helpers do
def compile_project(project) do
project
|> ensure_project()
- |> RemoteControl.Api.schedule_compile(true)
+ |> Engine.Api.schedule_compile(true)
end
def compile_file(project, source) when is_binary(source) do
@@ -94,7 +93,7 @@ defmodule Lexical.Server.IEx.Helpers do
def compile_file(project, %Document{} = document) do
project
|> ensure_project()
- |> RemoteControl.Api.compile_document(document)
+ |> Engine.Api.compile_document(document)
end
def complete(project, source, context \\ nil)
@@ -128,13 +127,13 @@ defmodule Lexical.Server.IEx.Helpers do
def connect do
manager_name = manager_name()
Node.start(:"r@127.0.0.1")
- Node.set_cookie(:lexical)
+ Node.set_cookie(:expert)
Node.connect(:"#{manager_name}@127.0.0.1")
end
@doc """
- Create a Lexical Project for an application in the same directory as
- Lexical.
+ Create an Expert Project for an application in the same directory as
+ Expert.
Alternatively, a project for one of our test fixtures can be created
using the `fixture: true` option.
@@ -142,28 +141,28 @@ defmodule Lexical.Server.IEx.Helpers do
## Examples
iex> project()
- %Lexical.Project{
- root_uri: "file:///.../lexical
+ %Forge.Project{
+ root_uri: "file:///.../expert
...
}
iex> project(:my_project)
- %Lexical.Project{
+ %Forge.Project{
root_uri: "file:///.../my_project"
...
}
iex> project(:navigations, fixture: true)
- %Lexical.Project{
- root_uri: "file:///.../lexical/apps/remote_control/test/fixtures/navigations"
+ %Forge.Project{
+ root_uri: "file:///.../expert/apps/engine/test/fixtures/navigations"
...
}
"""
- def project(project \\ :lexical, opts \\ []) do
+ def project(project \\ :expert, opts \\ []) do
project =
if opts[:fixture] do
- "lexical/apps/remote_control/test/fixtures/#{project}"
+ "expert/apps/engine/test/fixtures/#{project}"
else
project
end
@@ -177,7 +176,7 @@ defmodule Lexical.Server.IEx.Helpers do
|> Path.expand()
project_uri = "file://#{project_path}"
- Lexical.Project.new(project_uri)
+ Forge.Project.new(project_uri)
end)
end
@@ -190,13 +189,13 @@ defmodule Lexical.Server.IEx.Helpers do
def stop_project(project) do
project
|> ensure_project()
- |> Lexical.Server.Project.Supervisor.stop()
+ |> Expert.Project.Supervisor.stop()
end
def start_project(project) do
project
|> ensure_project()
- |> Lexical.Server.Project.Supervisor.start()
+ |> Expert.Project.Supervisor.start()
end
def time(fun) when is_function(fun, 0) do
@@ -204,7 +203,7 @@ defmodule Lexical.Server.IEx.Helpers do
IO.puts([
IO.ANSI.format([:cyan, :bright, "Time: "]),
- Lexical.Formats.time(elapsed_us)
+ Forge.Formats.time(elapsed_us)
])
result
diff --git a/apps/server/lib/lexical/server/project/diagnostics.ex b/apps/expert/lib/expert/project/diagnostics.ex
similarity index 86%
rename from apps/server/lib/lexical/server/project/diagnostics.ex
rename to apps/expert/lib/expert/project/diagnostics.ex
index 1c97d981..dd6f00e8 100644
--- a/apps/server/lib/lexical/server/project/diagnostics.ex
+++ b/apps/expert/lib/expert/project/diagnostics.ex
@@ -1,11 +1,10 @@
-defmodule Lexical.Server.Project.Diagnostics do
- alias Lexical.Formats
- alias Lexical.Project
- alias Lexical.Protocol.Notifications.PublishDiagnostics
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.Server.Project.Diagnostics.State
- alias Lexical.Server.Transport
+defmodule Expert.Project.Diagnostics do
+ alias Engine.Api.Messages
+ alias Expert.Project.Diagnostics.State
+ alias Expert.Protocol.Notifications.PublishDiagnostics
+ alias Expert.Transport
+ alias Forge.Formats
+ alias Forge.Project
import Messages
require Logger
@@ -26,7 +25,7 @@ defmodule Lexical.Server.Project.Diagnostics do
@impl GenServer
def init([%Project{} = project]) do
- RemoteControl.Api.register_listener(project, self(), [
+ Engine.Api.register_listener(project, self(), [
file_diagnostics(),
project_compile_requested(),
project_compiled(),
diff --git a/apps/expert/lib/expert/project/diagnostics/state.ex b/apps/expert/lib/expert/project/diagnostics/state.ex
new file mode 100644
index 00000000..5eaa41a2
--- /dev/null
+++ b/apps/expert/lib/expert/project/diagnostics/state.ex
@@ -0,0 +1,103 @@
+defmodule Expert.Project.Diagnostics.State do
+ defmodule Entry do
+ defstruct build_number: 0, diagnostics: []
+
+ def new(build_number) when is_integer(build_number) do
+ %__MODULE__{build_number: build_number}
+ end
+
+ def new(build_number, diagnostic) do
+ %__MODULE__{build_number: build_number, diagnostics: MapSet.new([diagnostic])}
+ end
+
+ def add(%__MODULE__{} = entry, build_number, diagnostic) do
+ cond do
+ build_number < entry.build_number ->
+ entry
+
+ build_number > entry.build_number ->
+ new(build_number, diagnostic)
+
+ true ->
+ %__MODULE__{entry | diagnostics: MapSet.put(entry.diagnostics, diagnostic)}
+ end
+ end
+
+ def diagnostics(%__MODULE__{} = entry) do
+ Enum.to_list(entry.diagnostics)
+ end
+ end
+
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
+ alias Forge.Project
+
+ defstruct project: nil, entries_by_uri: %{}
+
+ require Logger
+
+ def new(%Project{} = project) do
+ %__MODULE__{project: project}
+ end
+
+ def get(%__MODULE__{} = state, source_uri) do
+ entry = Map.get(state.entries_by_uri, source_uri, Entry.new(0))
+ Entry.diagnostics(entry)
+ end
+
+ def clear(%__MODULE__{} = state, source_uri) do
+ new_entries = Map.put(state.entries_by_uri, source_uri, Entry.new(0))
+
+ %__MODULE__{state | entries_by_uri: new_entries}
+ end
+
+ @doc """
+ Only clear diagnostics if they've been synced to disk
+ It's possible that the diagnostic presented by typing is still correct, and the file
+ that exists on the disk is actually an older copy of the file in memory.
+ """
+ def clear_all_flushed(%__MODULE__{} = state) do
+ cleared =
+ Map.new(state.entries_by_uri, fn {uri, %Entry{} = entry} ->
+ with true <- Document.Store.open?(uri),
+ {:ok, %Document{} = document} <- Document.Store.fetch(uri),
+ true <- keep_diagnostics?(document) do
+ {uri, entry}
+ else
+ _ ->
+ {uri, Entry.new(0)}
+ end
+ end)
+
+ %__MODULE__{state | entries_by_uri: cleared}
+ end
+
+ def add(%__MODULE__{} = state, build_number, %Diagnostic.Result{} = diagnostic) do
+ entries_by_uri =
+ Map.update(
+ state.entries_by_uri,
+ diagnostic.uri,
+ Entry.new(build_number, diagnostic),
+ fn entry ->
+ Entry.add(entry, build_number, diagnostic)
+ end
+ )
+
+ %__MODULE__{state | entries_by_uri: entries_by_uri}
+ end
+
+ def add(%__MODULE__{} = state, _build_number, other) do
+ Logger.error("Invalid diagnostic: #{inspect(other)}")
+ state
+ end
+
+ defp keep_diagnostics?(%Document{} = document) do
+ # Keep any diagnostics for script files, which aren't compiled)
+ # or dirty files, which have been modified after compilation has occurrend
+ document.dirty? or script_file?(document)
+ end
+
+ defp script_file?(document) do
+ Path.extname(document.path) == ".exs"
+ end
+end
diff --git a/apps/server/lib/lexical/server/project/intelligence.ex b/apps/expert/lib/expert/project/intelligence.ex
similarity index 97%
rename from apps/server/lib/lexical/server/project/intelligence.ex
rename to apps/expert/lib/expert/project/intelligence.ex
index 968f1ce5..3bc82fd6 100644
--- a/apps/server/lib/lexical/server/project/intelligence.ex
+++ b/apps/expert/lib/expert/project/intelligence.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Server.Project.Intelligence do
+defmodule Expert.Project.Intelligence do
defmodule State do
- alias Lexical.Formats
- alias Lexical.Project
+ alias Forge.Formats
+ alias Forge.Project
defstruct project: nil, struct_modules: MapSet.new()
@@ -58,8 +58,8 @@ defmodule Lexical.Server.Project.Intelligence do
end
end
- alias Lexical.Project
- alias Lexical.RemoteControl.Api
+ alias Engine.Api
+ alias Forge.Project
use GenServer
import Api.Messages
diff --git a/apps/server/lib/lexical/server/project/node.ex b/apps/expert/lib/expert/project/node.ex
similarity index 85%
rename from apps/server/lib/lexical/server/project/node.ex
rename to apps/expert/lib/expert/project/node.ex
index 82c6a3fa..69121482 100644
--- a/apps/server/lib/lexical/server/project/node.ex
+++ b/apps/expert/lib/expert/project/node.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Server.Project.Node do
+defmodule Expert.Project.Node do
@moduledoc """
A genserver responsibile for starting the remote node and cleaning up the build directory if it crashes
"""
@@ -11,9 +11,9 @@ defmodule Lexical.Server.Project.Node do
end
end
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.Server.Project.Progress
+ alias Forge.Project
+
+ alias Expert.Project.Progress
require Logger
@@ -60,7 +60,7 @@ defmodule Lexical.Server.Project.Node do
@impl GenServer
def handle_continue(:trigger_build, %State{} = state) do
- RemoteControl.Api.schedule_compile(state.project, true)
+ Engine.Api.schedule_compile(state.project, true)
{:noreply, state}
end
@@ -71,7 +71,7 @@ defmodule Lexical.Server.Project.Node do
@impl GenServer
def handle_cast(:trigger_build, %State{} = state) do
- RemoteControl.Api.schedule_compile(state.project, true)
+ Engine.Api.schedule_compile(state.project, true)
{:noreply, state}
end
@@ -91,14 +91,14 @@ defmodule Lexical.Server.Project.Node do
# private api
def start_node(%Project{} = project) do
- with {:ok, node, node_pid} <- RemoteControl.start_link(project) do
+ with {:ok, node, node_pid} <- Engine.start_link(project) do
Node.monitor(node, true)
{:ok, State.new(project, node, node_pid)}
end
end
defp delete_build_artifacts(%Project{} = project) do
- build_path = RemoteControl.Build.path(project)
+ build_path = Engine.Build.path(project)
case File.rm_rf(build_path) do
{:ok, _deleted} -> :ok
diff --git a/apps/expert/lib/expert/project/progress.ex b/apps/expert/lib/expert/project/progress.ex
new file mode 100644
index 00000000..bfa9f151
--- /dev/null
+++ b/apps/expert/lib/expert/project/progress.ex
@@ -0,0 +1,46 @@
+defmodule Expert.Project.Progress do
+ alias Expert.Project.Progress.State
+ alias Forge.Project
+
+ import Engine.Api.Messages
+
+ use GenServer
+
+ def start_link(%Project{} = project) do
+ GenServer.start_link(__MODULE__, [project], name: name(project))
+ end
+
+ def child_spec(%Project{} = project) do
+ %{
+ id: {__MODULE__, Project.name(project)},
+ start: {__MODULE__, :start_link, [project]}
+ }
+ end
+
+ # GenServer callbacks
+
+ @impl GenServer
+ def init([project]) do
+ {:ok, State.new(project)}
+ end
+
+ @impl true
+ def handle_info(project_progress(stage: stage) = message, %State{} = state) do
+ new_state = apply(State, stage, [state, message])
+ {:noreply, new_state}
+ end
+
+ def handle_info(percent_progress(stage: stage) = message, %State{} = state) do
+ new_state = apply(State, stage, [state, message])
+
+ {:noreply, new_state}
+ end
+
+ def name(%Project{} = project) do
+ :"#{Project.name(project)}::progress"
+ end
+
+ def whereis(%Project{} = project) do
+ project |> name() |> Process.whereis()
+ end
+end
diff --git a/apps/server/lib/lexical/server/project/progress/percentage.ex b/apps/expert/lib/expert/project/progress/percentage.ex
similarity index 91%
rename from apps/server/lib/lexical/server/project/progress/percentage.ex
rename to apps/expert/lib/expert/project/progress/percentage.ex
index aa6b5ede..61da3ce7 100644
--- a/apps/server/lib/lexical/server/project/progress/percentage.ex
+++ b/apps/expert/lib/expert/project/progress/percentage.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.Server.Project.Progress.Percentage do
+defmodule Expert.Project.Progress.Percentage do
@moduledoc """
The backing data structure for percentage based progress reports
"""
- alias Lexical.Math
- alias Lexical.Protocol.Notifications
- alias Lexical.Protocol.Types.WorkDone
+ alias Expert.Protocol.Notifications
+ alias Expert.Protocol.Types.WorkDone
+ alias Forge.Math
@enforce_keys [:token, :kind, :max]
defstruct [:token, :kind, :title, :message, :max, current: 0]
diff --git a/apps/expert/lib/expert/project/progress/state.ex b/apps/expert/lib/expert/project/progress/state.ex
new file mode 100644
index 00000000..c6d96cca
--- /dev/null
+++ b/apps/expert/lib/expert/project/progress/state.ex
@@ -0,0 +1,106 @@
+defmodule Expert.Project.Progress.State do
+ alias Expert.Configuration
+ alias Expert.Project.Progress.Percentage
+ alias Expert.Project.Progress.Value
+ alias Expert.Protocol.Id
+ alias Expert.Protocol.Requests
+ alias Expert.Transport
+ alias Forge.Project
+
+ import Engine.Api.Messages
+
+ defstruct project: nil, progress_by_label: %{}
+
+ def new(%Project{} = project) do
+ %__MODULE__{project: project}
+ end
+
+ def begin(%__MODULE__{} = state, project_progress(label: label)) do
+ progress = Value.begin(label)
+ progress_by_label = Map.put(state.progress_by_label, label, progress)
+
+ write_work_done(progress.token)
+ write(progress)
+
+ %__MODULE__{state | progress_by_label: progress_by_label}
+ end
+
+ def begin(%__MODULE__{} = state, percent_progress(label: label, max: max)) do
+ progress = Percentage.begin(label, max)
+ progress_by_label = Map.put(state.progress_by_label, label, progress)
+ write_work_done(progress.token)
+ write(progress)
+
+ %__MODULE__{state | progress_by_label: progress_by_label}
+ end
+
+ def report(%__MODULE__{} = state, project_progress(label: label, message: message)) do
+ {progress, progress_by_label} =
+ Map.get_and_update(state.progress_by_label, label, fn old_value ->
+ new_value = Value.report(old_value, message)
+ {new_value, new_value}
+ end)
+
+ write(progress)
+ %__MODULE__{state | progress_by_label: progress_by_label}
+ end
+
+ def report(
+ %__MODULE__{} = state,
+ percent_progress(label: label, message: message, delta: delta)
+ ) do
+ {progress, progress_by_label} =
+ Map.get_and_update(state.progress_by_label, label, fn old_percentage ->
+ new_percentage = Percentage.report(old_percentage, delta, message)
+ {new_percentage, new_percentage}
+ end)
+
+ write(progress)
+ %__MODULE__{state | progress_by_label: progress_by_label}
+ end
+
+ def complete(%__MODULE__{} = state, project_progress(label: label, message: message)) do
+ {progress, progress_by_label} =
+ Map.get_and_update(state.progress_by_label, label, fn _ -> :pop end)
+
+ case progress do
+ %Value{} = progress ->
+ progress |> Value.complete(message) |> write
+
+ _ ->
+ :ok
+ end
+
+ %__MODULE__{state | progress_by_label: progress_by_label}
+ end
+
+ def complete(%__MODULE__{} = state, percent_progress(label: label, message: message)) do
+ {progress, progress_by_label} =
+ Map.get_and_update(state.progress_by_label, label, fn _ -> :pop end)
+
+ case progress do
+ %Percentage{} = progress ->
+ progress |> Percentage.complete(message) |> write()
+
+ nil ->
+ :ok
+ end
+
+ %__MODULE__{state | progress_by_label: progress_by_label}
+ end
+
+ defp write_work_done(token) do
+ if Configuration.client_supports?(:work_done_progress) do
+ progress = Requests.CreateWorkDoneProgress.new(id: Id.next(), token: token)
+ Transport.write(progress)
+ end
+ end
+
+ defp write(%progress_module{token: token} = progress) when not is_nil(token) do
+ if Configuration.client_supports?(:work_done_progress) do
+ progress |> progress_module.to_protocol() |> Transport.write()
+ end
+ end
+
+ defp write(_), do: :ok
+end
diff --git a/apps/expert/lib/expert/project/progress/support.ex b/apps/expert/lib/expert/project/progress/support.ex
new file mode 100644
index 00000000..bcd590ba
--- /dev/null
+++ b/apps/expert/lib/expert/project/progress/support.ex
@@ -0,0 +1,42 @@
+defmodule Expert.Project.Progress.Support do
+ alias Expert.Project.Progress
+ alias Forge.Project
+
+ import Engine.Api.Messages
+
+ defmacro __using__(_) do
+ quote do
+ import unquote(__MODULE__), only: [with_progress: 3]
+ end
+ end
+
+ def with_progress(project, label, func) when is_function(func, 0) do
+ dest = Progress.name(project)
+
+ try do
+ send(dest, project_progress(label: label, stage: :begin))
+ func.()
+ after
+ send(dest, project_progress(label: label, stage: :complete))
+ end
+ end
+
+ def with_percentage_progress(%Project{} = project, label, max, func)
+ when is_function(func, 1) do
+ dest = Progress.name(project)
+
+ report_progress = fn delta, message ->
+ message =
+ percent_progress(label: label, max: max, message: message, delta: delta, stage: :report)
+
+ send(dest, message)
+ end
+
+ try do
+ send(dest, percent_progress(label: label, max: max, stage: :begin))
+ func.(report_progress)
+ after
+ send(dest, percent_progress(label: label, stage: :complete))
+ end
+ end
+end
diff --git a/apps/server/lib/lexical/server/project/progress/value.ex b/apps/expert/lib/expert/project/progress/value.ex
similarity index 89%
rename from apps/server/lib/lexical/server/project/progress/value.ex
rename to apps/expert/lib/expert/project/progress/value.ex
index 9d505e71..c4a18b28 100644
--- a/apps/server/lib/lexical/server/project/progress/value.ex
+++ b/apps/expert/lib/expert/project/progress/value.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Server.Project.Progress.Value do
- alias Lexical.Protocol.Notifications
- alias Lexical.Protocol.Types.WorkDone
+defmodule Expert.Project.Progress.Value do
+ alias Expert.Protocol.Notifications
+ alias Expert.Protocol.Types.WorkDone
@enforce_keys [:token, :kind]
defstruct [:token, :kind, :title, :message]
diff --git a/apps/server/lib/lexical/server/project/search_listener.ex b/apps/expert/lib/expert/project/search_listener.ex
similarity index 79%
rename from apps/server/lib/lexical/server/project/search_listener.ex
rename to apps/expert/lib/expert/project/search_listener.ex
index 2af99b77..f069b9da 100644
--- a/apps/server/lib/lexical/server/project/search_listener.ex
+++ b/apps/expert/lib/expert/project/search_listener.ex
@@ -1,11 +1,10 @@
-defmodule Lexical.Server.Project.SearchListener do
- alias Lexical.Formats
- alias Lexical.Project
- alias Lexical.Protocol.Id
- alias Lexical.Protocol.Requests
- alias Lexical.RemoteControl.Api
- alias Lexical.Server
- alias Lexical.Server.Window
+defmodule Expert.Project.SearchListener do
+ alias Engine.Api
+ alias Expert.Protocol.Id
+ alias Expert.Protocol.Requests
+ alias Expert.Window
+ alias Forge.Formats
+ alias Forge.Project
import Api.Messages
@@ -50,6 +49,6 @@ defmodule Lexical.Server.Project.SearchListener do
defp send_code_lens_refresh do
request = Requests.CodeLensRefresh.new(id: Id.next())
- Server.server_request(request)
+ Expert.server_request(request)
end
end
diff --git a/apps/expert/lib/expert/project/supervisor.ex b/apps/expert/lib/expert/project/supervisor.ex
new file mode 100644
index 00000000..b2c2980f
--- /dev/null
+++ b/apps/expert/lib/expert/project/supervisor.ex
@@ -0,0 +1,53 @@
+defmodule Expert.Project.Supervisor do
+ alias Engine.ProjectNodeSupervisor
+ alias Expert.Project.Diagnostics
+ alias Expert.Project.Intelligence
+ alias Expert.Project.Node
+ alias Expert.Project.Progress
+ alias Expert.Project.SearchListener
+ alias Forge.Project
+
+ use Supervisor
+
+ def dynamic_supervisor_name do
+ Expert.ProjectSupervisor
+ end
+
+ def options do
+ [name: dynamic_supervisor_name(), strategy: :one_for_one]
+ end
+
+ def start_link(%Project{} = project) do
+ Supervisor.start_link(__MODULE__, project, name: supervisor_name(project))
+ end
+
+ def init(%Project{} = project) do
+ children = [
+ {Progress, project},
+ {ProjectNodeSupervisor, project},
+ {Node, project},
+ {Diagnostics, project},
+ {Intelligence, project},
+ {SearchListener, project}
+ ]
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+
+ def start(%Project{} = project) do
+ DynamicSupervisor.start_child(dynamic_supervisor_name(), {__MODULE__, project})
+ end
+
+ def stop(%Project{} = project) do
+ pid =
+ project
+ |> supervisor_name()
+ |> Process.whereis()
+
+ DynamicSupervisor.terminate_child(dynamic_supervisor_name(), pid)
+ end
+
+ defp supervisor_name(%Project{} = project) do
+ :"#{Project.name(project)}::supervisor"
+ end
+end
diff --git a/apps/expert/lib/expert/provider/handlers/code_action.ex b/apps/expert/lib/expert/provider/handlers/code_action.ex
new file mode 100644
index 00000000..0731511a
--- /dev/null
+++ b/apps/expert/lib/expert/provider/handlers/code_action.ex
@@ -0,0 +1,42 @@
+defmodule Expert.Provider.Handlers.CodeAction do
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types
+ alias Expert.Protocol.Types.Workspace
+
+ alias Engine.CodeAction
+ alias Expert.Configuration
+
+ require Logger
+
+ def handle(%Requests.CodeAction{} = request, %Configuration{} = config) do
+ diagnostics = Enum.map(request.context.diagnostics, &to_code_action_diagnostic/1)
+
+ code_actions =
+ Engine.Api.code_actions(
+ config.project,
+ request.document,
+ request.range,
+ diagnostics,
+ request.context.only || :all,
+ request.context.trigger_kind
+ )
+
+ results = Enum.map(code_actions, &to_result/1)
+ reply = Responses.CodeAction.new(request.id, results)
+
+ {:reply, reply}
+ end
+
+ defp to_code_action_diagnostic(%Types.Diagnostic{} = diagnostic) do
+ CodeAction.Diagnostic.new(diagnostic.range, diagnostic.message, diagnostic.source)
+ end
+
+ defp to_result(%CodeAction{} = action) do
+ Types.CodeAction.new(
+ title: action.title,
+ kind: action.kind,
+ edit: Workspace.Edit.new(changes: %{action.uri => action.changes})
+ )
+ end
+end
diff --git a/apps/expert/lib/expert/provider/handlers/code_lens.ex b/apps/expert/lib/expert/provider/handlers/code_lens.ex
new file mode 100644
index 00000000..f8c7ae07
--- /dev/null
+++ b/apps/expert/lib/expert/provider/handlers/code_lens.ex
@@ -0,0 +1,57 @@
+defmodule Expert.Provider.Handlers.CodeLens do
+ alias Expert.Configuration
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types.CodeLens
+ alias Expert.Provider.Handlers
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Project
+
+ import Document.Line
+ require Logger
+
+ def handle(%Requests.CodeLens{} = request, %Configuration{} = config) do
+ lenses =
+ case reindex_lens(config.project, request.document) do
+ nil -> []
+ lens -> List.wrap(lens)
+ end
+
+ response = Responses.CodeLens.new(request.id, lenses)
+ {:reply, response}
+ end
+
+ defp reindex_lens(%Project{} = project, %Document{} = document) do
+ if show_reindex_lens?(project, document) do
+ range = def_project_range(document)
+ command = Handlers.Commands.reindex_command(project)
+
+ CodeLens.new(command: command, range: range)
+ end
+ end
+
+ @project_regex ~r/def\s+project\s/
+ defp def_project_range(%Document{} = document) do
+ # returns the line in mix.exs where `def project` occurs
+ Enum.reduce_while(document.lines, nil, fn
+ line(text: line_text, line_number: line_number), _ ->
+ if String.match?(line_text, @project_regex) do
+ start_pos = Position.new(document, line_number, 1)
+ end_pos = Position.new(document, line_number, String.length(line_text))
+ range = Range.new(start_pos, end_pos)
+ {:halt, range}
+ else
+ {:cont, nil}
+ end
+ end)
+ end
+
+ defp show_reindex_lens?(%Project{} = project, %Document{} = document) do
+ document_path = Path.expand(document.path)
+
+ document_path == Project.mix_exs_path(project) and
+ not Engine.Api.index_running?(project)
+ end
+end
diff --git a/apps/server/lib/lexical/server/provider/handlers/commands.ex b/apps/expert/lib/expert/provider/handlers/commands.ex
similarity index 77%
rename from apps/server/lib/lexical/server/provider/handlers/commands.ex
rename to apps/expert/lib/expert/provider/handlers/commands.ex
index 42d64fbc..eac449b7 100644
--- a/apps/server/lib/lexical/server/provider/handlers/commands.ex
+++ b/apps/expert/lib/expert/provider/handlers/commands.ex
@@ -1,12 +1,11 @@
-defmodule Lexical.Server.Provider.Handlers.Commands do
- alias Lexical.Project
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types
- alias Lexical.Protocol.Types.ErrorCodes
- alias Lexical.RemoteControl
- alias Lexical.Server.Configuration
- alias Lexical.Server.Window
+defmodule Expert.Provider.Handlers.Commands do
+ alias Expert.Configuration
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types
+ alias Expert.Protocol.Types.ErrorCodes
+ alias Expert.Window
+ alias Forge.Project
require ErrorCodes
require Logger
@@ -42,7 +41,7 @@ defmodule Lexical.Server.Provider.Handlers.Commands do
end
defp reindex(%Project{} = project, request_id) do
- case RemoteControl.Api.reindex(project) do
+ case Engine.Api.reindex(project) do
:ok ->
Responses.ExecuteCommand.new(request_id, "ok")
diff --git a/apps/expert/lib/expert/provider/handlers/completion.ex b/apps/expert/lib/expert/provider/handlers/completion.ex
new file mode 100644
index 00000000..3e249270
--- /dev/null
+++ b/apps/expert/lib/expert/provider/handlers/completion.ex
@@ -0,0 +1,37 @@
+defmodule Expert.Provider.Handlers.Completion do
+ alias Expert.CodeIntelligence
+ alias Expert.Configuration
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types.Completion
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Document.Position
+
+ require Logger
+
+ def handle(%Requests.Completion{} = request, %Configuration{} = config) do
+ completions =
+ CodeIntelligence.Completion.complete(
+ config.project,
+ document_analysis(request.document, request.position),
+ request.position,
+ request.context || Completion.Context.new(trigger_kind: :invoked)
+ )
+
+ response = Responses.Completion.new(request.id, completions)
+ {:reply, response}
+ end
+
+ defp document_analysis(%Document{} = document, %Position{} = position) do
+ case Document.Store.fetch(document.uri, :analysis) do
+ {:ok, %Document{}, %Ast.Analysis{} = analysis} ->
+ Ast.reanalyze_to(analysis, position)
+
+ _ ->
+ document
+ |> Ast.analyze()
+ |> Ast.reanalyze_to(position)
+ end
+ end
+end
diff --git a/apps/server/lib/lexical/server/provider/handlers/document_symbols.ex b/apps/expert/lib/expert/provider/handlers/document_symbols.ex
similarity index 77%
rename from apps/server/lib/lexical/server/provider/handlers/document_symbols.ex
rename to apps/expert/lib/expert/provider/handlers/document_symbols.ex
index 5805ff26..ba638709 100644
--- a/apps/server/lib/lexical/server/provider/handlers/document_symbols.ex
+++ b/apps/expert/lib/expert/provider/handlers/document_symbols.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.Server.Provider.Handlers.DocumentSymbols do
- alias Lexical.Document
- alias Lexical.Protocol.Requests.DocumentSymbols
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types.Document.Symbol
- alias Lexical.Protocol.Types.Symbol.Kind, as: SymbolKind
- alias Lexical.RemoteControl.Api
- alias Lexical.RemoteControl.CodeIntelligence.Symbols
- alias Lexical.Server.Configuration
+defmodule Expert.Provider.Handlers.DocumentSymbols do
+ alias Engine.Api
+ alias Engine.CodeIntelligence.Symbols
+ alias Expert.Configuration
+ alias Expert.Protocol.Requests.DocumentSymbols
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types.Document.Symbol
+ alias Expert.Protocol.Types.Symbol.Kind, as: SymbolKind
+ alias Forge.Document
require SymbolKind
diff --git a/apps/expert/lib/expert/provider/handlers/find_references.ex b/apps/expert/lib/expert/provider/handlers/find_references.ex
new file mode 100644
index 00000000..21f37647
--- /dev/null
+++ b/apps/expert/lib/expert/provider/handlers/find_references.ex
@@ -0,0 +1,26 @@
+defmodule Expert.Provider.Handlers.FindReferences do
+ alias Engine.Api
+ alias Expert.Configuration
+ alias Expert.Protocol.Requests.FindReferences
+ alias Expert.Protocol.Responses
+ alias Forge.Ast
+ alias Forge.Document
+
+ require Logger
+
+ def handle(%FindReferences{} = request, %Configuration{} = config) do
+ include_declaration? = !!request.context.include_declaration
+
+ locations =
+ case Document.Store.fetch(request.document.uri, :analysis) do
+ {:ok, _document, %Ast.Analysis{} = analysis} ->
+ Api.references(config.project, analysis, request.position, include_declaration?)
+
+ _ ->
+ nil
+ end
+
+ response = Responses.FindReferences.new(request.id, locations)
+ {:reply, response}
+ end
+end
diff --git a/apps/expert/lib/expert/provider/handlers/formatting.ex b/apps/expert/lib/expert/provider/handlers/formatting.ex
new file mode 100644
index 00000000..3d439835
--- /dev/null
+++ b/apps/expert/lib/expert/provider/handlers/formatting.ex
@@ -0,0 +1,23 @@
+defmodule Expert.Provider.Handlers.Formatting do
+ alias Expert.Configuration
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Responses
+ alias Forge.Document.Changes
+
+ require Logger
+
+ def handle(%Requests.Formatting{} = request, %Configuration{} = config) do
+ document = request.document
+
+ case Engine.Api.format(config.project, document) do
+ {:ok, %Changes{} = document_edits} ->
+ response = Responses.Formatting.new(request.id, document_edits)
+ Logger.info("Response #{inspect(response)}")
+ {:reply, response}
+
+ {:error, reason} ->
+ Logger.error("Formatter failed #{inspect(reason)}")
+ {:reply, Responses.Formatting.new(request.id, nil)}
+ end
+ end
+end
diff --git a/apps/expert/lib/expert/provider/handlers/go_to_definition.ex b/apps/expert/lib/expert/provider/handlers/go_to_definition.ex
new file mode 100644
index 00000000..130500dd
--- /dev/null
+++ b/apps/expert/lib/expert/provider/handlers/go_to_definition.ex
@@ -0,0 +1,19 @@
+defmodule Expert.Provider.Handlers.GoToDefinition do
+ alias Expert.Protocol.Requests.GoToDefinition
+ alias Expert.Protocol.Responses
+
+ alias Expert.Configuration
+
+ require Logger
+
+ def handle(%GoToDefinition{} = request, %Configuration{} = config) do
+ case Engine.Api.definition(config.project, request.document, request.position) do
+ {:ok, native_location} ->
+ {:reply, Responses.GoToDefinition.new(request.id, native_location)}
+
+ {:error, reason} ->
+ Logger.error("GoToDefinition failed: #{inspect(reason)}")
+ {:reply, Responses.GoToDefinition.new(request.id, nil)}
+ end
+ end
+end
diff --git a/apps/expert/lib/expert/provider/handlers/hover.ex b/apps/expert/lib/expert/provider/handlers/hover.ex
new file mode 100644
index 00000000..17a0fe50
--- /dev/null
+++ b/apps/expert/lib/expert/provider/handlers/hover.ex
@@ -0,0 +1,236 @@
+defmodule Expert.Provider.Handlers.Hover do
+ alias Engine.CodeIntelligence.Docs
+ alias Expert.Configuration
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types.Hover
+ alias Expert.Provider.Markdown
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Project
+
+ require Logger
+
+ def handle(%Requests.Hover{} = request, %Configuration{} = config) do
+ maybe_hover =
+ with {:ok, _document, %Ast.Analysis{} = analysis} <-
+ Document.Store.fetch(request.document.uri, :analysis),
+ {:ok, entity, range} <- resolve_entity(config.project, analysis, request.position),
+ {:ok, markdown} <- hover_content(entity, config.project) do
+ content = Markdown.to_content(markdown)
+ %Hover{contents: content, range: range}
+ else
+ error ->
+ Logger.warning("Could not resolve hover request, got: #{inspect(error)}")
+ nil
+ end
+
+ {:reply, Responses.Hover.new(request.id, maybe_hover)}
+ end
+
+ defp resolve_entity(%Project{} = project, %Analysis{} = analysis, %Position{} = position) do
+ Engine.Api.resolve_entity(project, analysis, position)
+ end
+
+ defp hover_content({kind, module}, %Project{} = project) when kind in [:module, :struct] do
+ case Engine.Api.docs(project, module, exclude_hidden: false) do
+ {:ok, %Docs{} = module_docs} ->
+ header = module_header(kind, module_docs)
+ types = module_header_types(kind, module_docs)
+
+ additional_sections = [
+ module_doc(module_docs.doc),
+ module_footer(kind, module_docs)
+ ]
+
+ if Enum.all?([types | additional_sections], &empty?/1) do
+ {:error, :no_doc}
+ else
+ header_block = "#{header}\n\n#{types}" |> String.trim() |> Markdown.code_block()
+ {:ok, Markdown.join_sections([header_block | additional_sections])}
+ end
+
+ _ ->
+ {:error, :no_doc}
+ end
+ end
+
+ defp hover_content({:call, module, fun, arity}, %Project{} = project) do
+ with {:ok, %Docs{} = module_docs} <- Engine.Api.docs(project, module),
+ {:ok, entries} <- Map.fetch(module_docs.functions_and_macros, fun) do
+ sections =
+ entries
+ |> Enum.sort_by(& &1.arity)
+ |> Enum.filter(&(&1.arity >= arity))
+ |> Enum.map(&entry_content/1)
+
+ {:ok, Markdown.join_sections(sections, Markdown.separator())}
+ end
+ end
+
+ defp hover_content({:type, module, type, arity}, %Project{} = project) do
+ with {:ok, %Docs{} = module_docs} <- Engine.Api.docs(project, module),
+ {:ok, entries} <- Map.fetch(module_docs.types, type) do
+ case Enum.find(entries, &(&1.arity == arity)) do
+ %Docs.Entry{} = entry ->
+ {:ok, entry_content(entry)}
+
+ _ ->
+ {:error, :no_type}
+ end
+ end
+ end
+
+ defp hover_content(type, _) do
+ {:error, {:unsupported, type}}
+ end
+
+ defp module_header(:module, %Docs{module: module}) do
+ Ast.Module.name(module)
+ end
+
+ defp module_header(:struct, %Docs{module: module}) do
+ "%#{Ast.Module.name(module)}{}"
+ end
+
+ defp module_header_types(:module, %Docs{}), do: ""
+
+ defp module_header_types(:struct, %Docs{} = docs) do
+ docs.types
+ |> Map.get(:t, [])
+ |> sort_entries()
+ |> Enum.flat_map(& &1.defs)
+ |> Enum.join("\n\n")
+ end
+
+ defp module_doc(s) when is_binary(s), do: s
+ defp module_doc(_), do: nil
+
+ defp module_footer(:module, docs) do
+ callbacks = format_callbacks(docs.callbacks)
+
+ unless empty?(callbacks) do
+ Markdown.section(callbacks, header: "Callbacks")
+ end
+ end
+
+ defp module_footer(:struct, _docs), do: nil
+
+ defp entry_content(%Docs.Entry{kind: fn_or_macro} = entry)
+ when fn_or_macro in [:function, :macro] do
+ call_header = call_header(entry)
+ specs = Enum.map_join(entry.defs, "\n", &("@spec " <> &1))
+
+ header =
+ [call_header, specs]
+ |> Markdown.join_sections()
+ |> String.trim()
+ |> Markdown.code_block()
+
+ Markdown.join_sections([header, entry_doc_content(entry.doc)])
+ end
+
+ defp entry_content(%Docs.Entry{kind: :type} = entry) do
+ header =
+ Markdown.code_block("""
+ #{call_header(entry)}
+
+ #{type_defs(entry)}\
+ """)
+
+ Markdown.join_sections([header, entry_doc_content(entry.doc)])
+ end
+
+ @one_line_header_cutoff 50
+
+ defp call_header(%Docs.Entry{kind: :type} = entry) do
+ module_name = Ast.Module.name(entry.module)
+
+ one_line_header = "#{module_name}.#{entry.name}/#{entry.arity}"
+
+ two_line_header =
+ "#{last_module_name(module_name)}.#{entry.name}/#{entry.arity}\n#{module_name}"
+
+ if String.length(one_line_header) >= @one_line_header_cutoff do
+ two_line_header
+ else
+ one_line_header
+ end
+ end
+
+ defp call_header(%Docs.Entry{kind: maybe_macro} = entry) do
+ [signature | _] = entry.signature
+ module_name = Ast.Module.name(entry.module)
+
+ macro_prefix =
+ if maybe_macro == :macro do
+ "(macro) "
+ else
+ ""
+ end
+
+ one_line_header = "#{macro_prefix}#{module_name}.#{signature}"
+
+ two_line_header =
+ "#{macro_prefix}#{last_module_name(module_name)}.#{signature}\n#{module_name}"
+
+ if String.length(one_line_header) >= @one_line_header_cutoff do
+ two_line_header
+ else
+ one_line_header
+ end
+ end
+
+ defp last_module_name(module_name) do
+ module_name
+ |> String.split(".")
+ |> List.last()
+ end
+
+ defp type_defs(%Docs.Entry{metadata: %{opaque: true}} = entry) do
+ Enum.map_join(entry.defs, "\n", fn def ->
+ def
+ |> String.split("::", parts: 2)
+ |> List.first()
+ |> String.trim()
+ end)
+ end
+
+ defp type_defs(%Docs.Entry{} = entry) do
+ Enum.join(entry.defs, "\n")
+ end
+
+ defp format_callbacks(callbacks) do
+ callbacks
+ |> Map.values()
+ |> List.flatten()
+ |> sort_entries()
+ |> Enum.map_join("\n", fn %Docs.Entry{} = entry ->
+ header =
+ entry.defs
+ |> Enum.map_join("\n", &("@callback " <> &1))
+ |> Markdown.code_block()
+
+ if is_binary(entry.doc) do
+ """
+ #{header}
+ #{entry_doc_content(entry.doc)}
+ """
+ else
+ header
+ end
+ end)
+ end
+
+ defp entry_doc_content(s) when is_binary(s), do: String.trim(s)
+ defp entry_doc_content(_), do: nil
+
+ defp sort_entries(entries) do
+ Enum.sort_by(entries, &{&1.name, &1.arity})
+ end
+
+ defp empty?(empty) when empty in [nil, "", []], do: true
+ defp empty?(_), do: false
+end
diff --git a/apps/expert/lib/expert/provider/handlers/workspace_symbol.ex b/apps/expert/lib/expert/provider/handlers/workspace_symbol.ex
new file mode 100644
index 00000000..b996484a
--- /dev/null
+++ b/apps/expert/lib/expert/provider/handlers/workspace_symbol.ex
@@ -0,0 +1,57 @@
+defmodule Expert.Provider.Handlers.WorkspaceSymbol do
+ alias Engine.Api
+ alias Engine.CodeIntelligence.Symbols
+ alias Expert.Configuration
+ alias Expert.Protocol.Requests.WorkspaceSymbol
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types.Location
+ alias Expert.Protocol.Types.Symbol.Kind, as: SymbolKind
+ alias Expert.Protocol.Types.Workspace.Symbol
+
+ require SymbolKind
+
+ require Logger
+
+ def handle(%WorkspaceSymbol{} = request, %Configuration{} = config) do
+ symbols =
+ if String.length(request.query) > 1 do
+ config.project
+ |> Api.workspace_symbols(request.query)
+ |> tap(fn symbols -> Logger.info("syms #{inspect(Enum.take(symbols, 5))}") end)
+ |> Enum.map(&to_response/1)
+ else
+ []
+ end
+
+ response = Responses.WorkspaceSymbol.new(request.id, symbols)
+ {:reply, response}
+ end
+
+ def to_response(%Symbols.Workspace{} = root) do
+ Symbol.new(
+ kind: to_kind(root.type),
+ location: to_location(root.link),
+ name: root.name,
+ container_name: root.container_name
+ )
+ end
+
+ defp to_location(%Symbols.Workspace.Link{} = link) do
+ Location.new(uri: link.uri, range: link.detail_range)
+ end
+
+ defp to_kind(:struct), do: :struct
+ defp to_kind(:module), do: :module
+ defp to_kind({:protocol, _}), do: :module
+ defp to_kind({:xp_protocol, _}), do: :module
+ defp to_kind(:variable), do: :variable
+ defp to_kind({:function, _}), do: :function
+ defp to_kind(:module_attribute), do: :constant
+ defp to_kind(:ex_unit_test), do: :method
+ defp to_kind(:ex_unit_describe), do: :method
+ defp to_kind(:ex_unit_setup), do: :method
+ defp to_kind(:ex_unit_setup_all), do: :method
+ defp to_kind(:type), do: :type_parameter
+ defp to_kind(:spec), do: :interface
+ defp to_kind(:file), do: :file
+end
diff --git a/apps/server/lib/lexical/server/provider/markdown.ex b/apps/expert/lib/expert/provider/markdown.ex
similarity index 95%
rename from apps/server/lib/lexical/server/provider/markdown.ex
rename to apps/expert/lib/expert/provider/markdown.ex
index 51879b8b..a3b4d96f 100644
--- a/apps/server/lib/lexical/server/provider/markdown.ex
+++ b/apps/expert/lib/expert/provider/markdown.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.Server.Provider.Markdown do
+defmodule Expert.Provider.Markdown do
@moduledoc """
Utilities for formatting Markdown content.
"""
- alias Lexical.Protocol.Types.Markup
+ alias Expert.Protocol.Types.Markup
@type markdown :: String.t()
diff --git a/apps/expert/lib/expert/state.ex b/apps/expert/lib/expert/state.ex
new file mode 100644
index 00000000..9de5ee12
--- /dev/null
+++ b/apps/expert/lib/expert/state.ex
@@ -0,0 +1,318 @@
+defmodule Expert.State do
+ alias Expert.Protocol.Id
+ alias Expert.Protocol.Notifications
+ alias Expert.Protocol.Notifications.DidChange
+ alias Expert.Protocol.Notifications.DidChangeConfiguration
+ alias Expert.Protocol.Notifications.DidClose
+ alias Expert.Protocol.Notifications.DidOpen
+ alias Expert.Protocol.Notifications.DidSave
+ alias Expert.Protocol.Notifications.Exit
+ alias Expert.Protocol.Notifications.Initialized
+ alias Expert.Protocol.Requests.Initialize
+ alias Expert.Protocol.Requests.RegisterCapability
+ alias Expert.Protocol.Requests.Shutdown
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types
+ alias Expert.Protocol.Types.CodeAction
+ alias Expert.Protocol.Types.CodeLens
+ alias Expert.Protocol.Types.Completion
+ alias Expert.Protocol.Types.DidChangeWatchedFiles
+ alias Expert.Protocol.Types.ExecuteCommand
+ alias Expert.Protocol.Types.FileEvent
+ alias Expert.Protocol.Types.FileSystemWatcher
+ alias Expert.Protocol.Types.Registration
+ alias Expert.Protocol.Types.TextDocument
+
+ alias Engine.Api
+ alias Expert.CodeIntelligence
+ alias Expert.Configuration
+ alias Expert.Project
+ alias Expert.Provider.Handlers
+ alias Expert.Transport
+ alias Forge.Document
+
+ require CodeAction.Kind
+ require Logger
+
+ import Api.Messages
+
+ defstruct configuration: nil,
+ initialized?: false,
+ shutdown_received?: false,
+ in_flight_requests: %{}
+
+ @supported_code_actions [
+ :quick_fix,
+ :refactor,
+ :refactor_extract,
+ :refactor_inline,
+ :refactor_rewrite,
+ :source,
+ :source_fix_all,
+ :source_organize_imports
+ ]
+
+ def new do
+ %__MODULE__{}
+ end
+
+ def initialize(%__MODULE__{initialized?: false} = state, %Initialize{
+ lsp: %Initialize.LSP{} = event
+ }) do
+ client_name =
+ case event.client_info do
+ %{name: name} -> name
+ _ -> nil
+ end
+
+ config = Configuration.new(event.root_uri, event.capabilities, client_name)
+ new_state = %__MODULE__{state | configuration: config, initialized?: true}
+ Logger.info("Starting project at uri #{config.project.root_uri}")
+
+ event.id
+ |> initialize_result()
+ |> Transport.write()
+
+ Transport.write(registrations())
+
+ Project.Supervisor.start(config.project)
+ {:ok, new_state}
+ end
+
+ def initialize(%__MODULE__{initialized?: true}, %Initialize{}) do
+ {:error, :already_initialized}
+ end
+
+ def in_flight?(%__MODULE__{} = state, request_id) do
+ Map.has_key?(state.in_flight_requests, request_id)
+ end
+
+ def add_request(%__MODULE__{} = state, request, callback) do
+ Transport.write(request)
+
+ in_flight_requests = Map.put(state.in_flight_requests, request.id, {request, callback})
+
+ %__MODULE__{state | in_flight_requests: in_flight_requests}
+ end
+
+ def finish_request(%__MODULE__{} = state, response) do
+ %{"id" => response_id} = response
+
+ case Map.pop(state.in_flight_requests, response_id) do
+ {{%request_module{} = request, callback}, in_flight_requests} ->
+ case request_module.parse_response(response) do
+ {:ok, response} ->
+ callback.(request, {:ok, response.result})
+
+ error ->
+ Logger.info("failed to parse response for #{request_module}, #{inspect(error)}")
+ callback.(request, error)
+ end
+
+ %__MODULE__{state | in_flight_requests: in_flight_requests}
+
+ _ ->
+ state
+ end
+ end
+
+ def default_configuration(%__MODULE__{configuration: config}) do
+ Configuration.default(config)
+ end
+
+ def apply(%__MODULE__{initialized?: false}, request) do
+ Logger.error("Received #{request.method} before server was initialized")
+ {:error, :not_initialized}
+ end
+
+ def apply(%__MODULE__{shutdown_received?: true} = state, %Exit{}) do
+ Logger.warning("Received an Exit notification. Halting the server in 150ms")
+ :timer.apply_after(50, System, :halt, [0])
+ {:ok, state}
+ end
+
+ def apply(%__MODULE__{shutdown_received?: true}, request) do
+ Logger.error("Received #{request.method} after shutdown. Ignoring")
+ {:error, :shutting_down}
+ end
+
+ def apply(%__MODULE__{} = state, %DidChangeConfiguration{} = event) do
+ case Configuration.on_change(state.configuration, event) do
+ {:ok, config} ->
+ {:ok, %__MODULE__{state | configuration: config}}
+
+ {:ok, config, response} ->
+ Transport.write(response)
+ {:ok, %__MODULE__{state | configuration: config}}
+ end
+
+ {:ok, state}
+ end
+
+ def apply(%__MODULE__{} = state, %DidChange{lsp: event}) do
+ uri = event.text_document.uri
+ version = event.text_document.version
+ project = state.configuration.project
+
+ case Document.Store.get_and_update(
+ uri,
+ &Document.apply_content_changes(&1, version, event.content_changes)
+ ) do
+ {:ok, updated_source} ->
+ updated_message =
+ file_changed(
+ uri: updated_source.uri,
+ open?: true,
+ from_version: version,
+ to_version: updated_source.version
+ )
+
+ Api.broadcast(project, updated_message)
+ Api.compile_document(state.configuration.project, updated_source)
+ {:ok, state}
+
+ error ->
+ error
+ end
+ end
+
+ def apply(%__MODULE__{} = state, %DidOpen{} = did_open) do
+ %TextDocument.Item{
+ text: text,
+ uri: uri,
+ version: version,
+ language_id: language_id
+ } = did_open.lsp.text_document
+
+ case Document.Store.open(uri, text, version, language_id) do
+ :ok ->
+ Logger.info("opened #{uri}")
+ {:ok, state}
+
+ error ->
+ Logger.error("Could not open #{uri} #{inspect(error)}")
+ error
+ end
+ end
+
+ def apply(%__MODULE__{} = state, %DidClose{lsp: event}) do
+ uri = event.text_document.uri
+
+ case Document.Store.close(uri) do
+ :ok ->
+ {:ok, state}
+
+ error ->
+ Logger.warning(
+ "Received textDocument/didClose for a file that wasn't open. URI was #{uri}"
+ )
+
+ error
+ end
+ end
+
+ def apply(%__MODULE__{} = state, %DidSave{lsp: event}) do
+ uri = event.text_document.uri
+
+ case Document.Store.save(uri) do
+ :ok ->
+ Api.schedule_compile(state.configuration.project, false)
+ {:ok, state}
+
+ error ->
+ Logger.error("Save failed for uri #{uri} error was #{inspect(error)}")
+ error
+ end
+ end
+
+ def apply(%__MODULE__{} = state, %Initialized{}) do
+ Logger.info("Expert Initialized")
+ {:ok, %__MODULE__{state | initialized?: true}}
+ end
+
+ def apply(%__MODULE__{} = state, %Shutdown{} = shutdown) do
+ Transport.write(Responses.Shutdown.new(id: shutdown.id))
+ Logger.error("Shutting down")
+
+ {:ok, %__MODULE__{state | shutdown_received?: true}}
+ end
+
+ def apply(%__MODULE__{} = state, %Notifications.DidChangeWatchedFiles{lsp: event}) do
+ project = state.configuration.project
+
+ Enum.each(event.changes, fn %FileEvent{} = change ->
+ event = filesystem_event(project: Project, uri: change.uri, event_type: change.type)
+ Engine.Api.broadcast(project, event)
+ end)
+
+ {:ok, state}
+ end
+
+ def apply(%__MODULE__{} = state, msg) do
+ Logger.error("Ignoring unhandled message: #{inspect(msg)}")
+ {:ok, state}
+ end
+
+ defp registrations do
+ RegisterCapability.new(id: Id.next(), registrations: [file_watcher_registration()])
+ end
+
+ @did_changed_watched_files_id "-42"
+ @watched_extensions ~w(ex exs)
+ defp file_watcher_registration do
+ extension_glob = "{" <> Enum.join(@watched_extensions, ",") <> "}"
+
+ watchers = [
+ FileSystemWatcher.new(glob_pattern: "**/mix.lock"),
+ FileSystemWatcher.new(glob_pattern: "**/*.#{extension_glob}")
+ ]
+
+ Registration.new(
+ id: @did_changed_watched_files_id,
+ method: "workspace/didChangeWatchedFiles",
+ register_options: DidChangeWatchedFiles.Registration.Options.new(watchers: watchers)
+ )
+ end
+
+ def initialize_result(event_id) do
+ sync_options =
+ TextDocument.Sync.Options.new(open_close: true, change: :incremental, save: true)
+
+ code_action_options =
+ CodeAction.Options.new(code_action_kinds: @supported_code_actions, resolve_provider: false)
+
+ code_lens_options = CodeLens.Options.new(resolve_provider: false)
+
+ command_options = ExecuteCommand.Registration.Options.new(commands: Handlers.Commands.names())
+
+ completion_options =
+ Completion.Options.new(trigger_characters: CodeIntelligence.Completion.trigger_characters())
+
+ server_capabilities =
+ Types.ServerCapabilities.new(
+ code_action_provider: code_action_options,
+ code_lens_provider: code_lens_options,
+ completion_provider: completion_options,
+ definition_provider: true,
+ document_formatting_provider: true,
+ document_symbol_provider: true,
+ execute_command_provider: command_options,
+ hover_provider: true,
+ references_provider: true,
+ text_document_sync: sync_options,
+ workspace_symbol_provider: true
+ )
+
+ result =
+ Types.Initialize.Result.new(
+ capabilities: server_capabilities,
+ server_info:
+ Types.Initialize.Result.ServerInfo.new(
+ name: "Expert",
+ version: "0.0.1"
+ )
+ )
+
+ Responses.InitializeResult.new(event_id, result)
+ end
+end
diff --git a/apps/server/lib/lexical/server/task_queue.ex b/apps/expert/lib/expert/task_queue.ex
similarity index 94%
rename from apps/server/lib/lexical/server/task_queue.ex
rename to apps/expert/lib/expert/task_queue.ex
index 9ecb1671..518fb4f9 100644
--- a/apps/server/lib/lexical/server/task_queue.ex
+++ b/apps/expert/lib/expert/task_queue.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.Server.TaskQueue do
+defmodule Expert.TaskQueue do
defmodule State do
- alias Lexical.Proto.Convert
- alias Lexical.Proto.LspTypes.ResponseError
- alias Lexical.Server.Transport
- import Lexical.Logging
+ alias Expert.Proto.Convert
+ alias Expert.Proto.LspTypes.ResponseError
+ alias Expert.Transport
+ import Forge.Logging
require Logger
defstruct ids_to_tasks: %{}, pids_to_ids: %{}
@@ -142,7 +142,7 @@ defmodule Lexical.Server.TaskQueue do
elapsed = System.system_time(:microsecond) - ts
Logger.warning(
- "Task #{m}.#{f}/#{length(a)} ran for #{Lexical.Formats.time(elapsed)}. Result #{inspect(result)}"
+ "Task #{m}.#{f}/#{length(a)} ran for #{Forge.Formats.time(elapsed)}. Result #{inspect(result)}"
)
end
diff --git a/apps/expert/lib/expert/transport.ex b/apps/expert/lib/expert/transport.ex
new file mode 100644
index 00000000..4110bd1a
--- /dev/null
+++ b/apps/expert/lib/expert/transport.ex
@@ -0,0 +1,12 @@
+defmodule Expert.Transport do
+ @moduledoc """
+ A behaviour for a LSP transport
+ """
+ @callback write(Jason.Encoder.t()) :: Jason.Encoder.t()
+
+ alias Expert.Transport.StdIO
+
+ @implementation Application.compile_env(:expert, :transport, StdIO)
+
+ defdelegate write(message), to: @implementation
+end
diff --git a/apps/server/lib/lexical/server/transport/std_io.ex b/apps/expert/lib/expert/transport/std_io.ex
similarity index 94%
rename from apps/server/lib/lexical/server/transport/std_io.ex
rename to apps/expert/lib/expert/transport/std_io.ex
index 97bf0a76..a8316ab3 100644
--- a/apps/server/lib/lexical/server/transport/std_io.ex
+++ b/apps/expert/lib/expert/transport/std_io.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.Server.Transport.StdIO do
- alias Lexical.Protocol.JsonRpc
+defmodule Expert.Transport.StdIO do
+ alias Expert.Protocol.JsonRpc
require Logger
- @behaviour Lexical.Server.Transport
+ @behaviour Expert.Transport
def start_link(device, callback) do
pid = :proc_lib.spawn_link(__MODULE__, :init, [{callback, device}])
@@ -22,7 +22,7 @@ defmodule Lexical.Server.Transport.StdIO do
def write(io_device \\ :stdio, payload)
def write(io_device, %_{} = payload) do
- with {:ok, lsp} <- Lexical.Proto.Convert.to_lsp(payload),
+ with {:ok, lsp} <- Expert.Proto.Convert.to_lsp(payload),
{:ok, json} <- Jason.encode(lsp) do
write(io_device, json)
end
diff --git a/apps/server/lib/lexical/server/window.ex b/apps/expert/lib/expert/window.ex
similarity index 83%
rename from apps/server/lib/lexical/server/window.ex
rename to apps/expert/lib/expert/window.ex
index 37a06a8f..c73de27e 100644
--- a/apps/server/lib/lexical/server/window.ex
+++ b/apps/expert/lib/expert/window.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.Server.Window do
- alias Lexical.Protocol.Id
- alias Lexical.Protocol.Notifications.LogMessage
- alias Lexical.Protocol.Notifications.ShowMessage
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Types
- alias Lexical.Server.Transport
+defmodule Expert.Window do
+ alias Expert.Protocol.Id
+ alias Expert.Protocol.Notifications.LogMessage
+ alias Expert.Protocol.Notifications.ShowMessage
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Types
+ alias Expert.Transport
@type level :: :error | :warning | :info | :log
@type message_result :: {:errory, term()} | {:ok, nil} | {:ok, Types.Message.ActionItem.t()}
@@ -37,7 +37,7 @@ defmodule Lexical.Server.Window do
@spec show_message(level(), message()) :: :ok
def show_message(level, message) do
request = Requests.ShowMessageRequest.new(id: Id.next(), message: message, type: level)
- Lexical.Server.server_request(request)
+ Expert.server_request(request)
end
for level <- @levels,
@@ -62,7 +62,7 @@ defmodule Lexical.Server.Window do
Displays a message to the user in the UI and waits for a response.
The result type handed to the callback function is a
- `Lexical.Protocol.Types.Message.ActionItem` or nil if there was no response
+ `Expert.Protocol.Types.Message.ActionItem` or nil if there was no response
from the user.
The strings passed in as the `actions` command are displayed to the user, and when
@@ -84,6 +84,6 @@ defmodule Lexical.Server.Window do
type: level
)
- Lexical.Server.server_request(request, fn _request, response -> on_response.(response) end)
+ Expert.server_request(request, fn _request, response -> on_response.(response) end)
end
end
diff --git a/apps/server/lib/mix/tasks/package.ex b/apps/expert/lib/mix/tasks/package.ex
similarity index 92%
rename from apps/server/lib/mix/tasks/package.ex
rename to apps/expert/lib/mix/tasks/package.ex
index 522aa723..f8fbf6e9 100644
--- a/apps/server/lib/mix/tasks/package.ex
+++ b/apps/expert/lib/mix/tasks/package.ex
@@ -1,11 +1,11 @@
defmodule Mix.Tasks.Package do
@moduledoc """
- Creates the Lexical application's artifacts
+ Creates the Expert application's artifacts
- Lexical does some strange things to its own code via namespacing, but because it does so, we can't
+ Expert does some strange things to its own code via namespacing, but because it does so, we can't
use standard build tooling. The app names of its modules and dependencies are changed, so `mix install`
won't realize that the correct apps are installed. It uses two different VMs, so making an `escript`
- will fail, as the second VM needs to find lexical's modules _somewhere. Releases seem ideal, and we
+ will fail, as the second VM needs to find Expert's modules _somewhere. Releases seem ideal, and we
used them for a while, but they need to match the _exact_ Elixir and Erlang versions they were compiled on,
right down to the patch level. This is much, much too strict for a project that needs to be able to run
on a variety of elixir / erlang versions.
@@ -25,12 +25,12 @@ defmodule Mix.Tasks.Package do
scripts are copied to a `bin` directory.
The end result is a release-like filesystem, but without a lot of the erlang booting stuff. Bootstrapping
- accomplished by simple scripts that reside in the project's `/bin` directory, and the `Lexical.Server.Boot`
+ accomplished by simple scripts that reside in the project's `/bin` directory, and the `Expert.Boot`
module, which loads applications and their modules.
## Command line options
- * `--path` - The package will be written to the path given. Defaults to `./build/dev/lexical`. If the
+ * `--path` - The package will be written to the path given. Defaults to `./build/dev/expert`. If the
`--zip` option is specified, The name of the zip file is determined by the last entry of the path.
For example, if the `--path` option is `_build/dev/output`, then the name of the zip file will be
`output.zip`.
@@ -41,12 +41,12 @@ defmodule Mix.Tasks.Package do
## Directory structure
```text
bin/
- start_lexical.sh
+ start_expert.sh
debug_shell.sh
lib/
- lx_common.sh
- lx_remote_control.sh
- lx_server.ez
+ xp_forge.sh
+ xp_engine.sh
+ xp_expert.ez
...
config/
config.exs
@@ -66,7 +66,7 @@ defmodule Mix.Tasks.Package do
to the code search path with the `-pa` argument.
"""
- alias Lexical.VM.Versions
+ alias Forge.VM.Versions
alias Mix.Tasks.Namespace
@options [
@@ -80,7 +80,7 @@ defmodule Mix.Tasks.Package do
def run(args) do
{opts, _, _} = OptionParser.parse(args, @options)
- default_path = Path.join([Mix.Project.build_path(), "package", "lexical"])
+ default_path = Path.join([Mix.Project.build_path(), "package", "expert"])
package_root = Keyword.get(opts, :path, default_path)
rebuild_on_version_change(package_root)
@@ -232,12 +232,12 @@ defmodule Mix.Tasks.Package do
defp server_deps do
deps_apps =
- if Mix.Project.get() == Lexical.Server.MixProject do
+ if Mix.Project.get() == Expert.MixProject do
Mix.Project.deps_apps()
else
- server_path = Mix.Project.deps_paths()[:server]
+ server_path = Mix.Project.deps_paths()[:expert]
- Mix.Project.in_project(:server, server_path, fn _ ->
+ Mix.Project.in_project(:expert, server_path, fn _ ->
Mix.Project.deps_apps()
end)
end
@@ -250,7 +250,7 @@ defmodule Mix.Tasks.Package do
end)
server_dep =
- :server
+ :expert
|> Namespace.Module.apply()
|> to_string()
@@ -270,7 +270,7 @@ defmodule Mix.Tasks.Package do
Namespace.Transform.Configs.apply_to_all(config_dest)
end
- @priv_apps [:remote_control]
+ @priv_apps [:engine]
defp copy_priv_files(package_root) do
priv_dest_dir = priv_path(package_root)
diff --git a/apps/expert/mix.exs b/apps/expert/mix.exs
new file mode 100644
index 00000000..e309ac58
--- /dev/null
+++ b/apps/expert/mix.exs
@@ -0,0 +1,57 @@
+defmodule Expert.MixProject do
+ use Mix.Project
+ Code.require_file("../../mix_includes.exs")
+
+ def project do
+ [
+ app: :expert,
+ version: "0.7.2",
+ elixir: "~> 1.15",
+ start_permanent: Mix.env() == :prod,
+ deps: deps(),
+ dialyzer: Mix.Dialyzer.config(add_apps: [:jason, :proto]),
+ aliases: aliases(),
+ elixirc_paths: elixirc_paths(Mix.env())
+ ]
+ end
+
+ def application do
+ [
+ extra_applications: [:logger, :runtime_tools, :kernel, :erts],
+ mod: {Expert.Application, []}
+ ]
+ end
+
+ def aliases do
+ [
+ compile: "compile --docs --debug-info",
+ docs: "docs --html",
+ test: "test --no-start"
+ ]
+ end
+
+ defp elixirc_paths(:test) do
+ ["lib", "test/support"]
+ end
+
+ defp elixirc_paths(_) do
+ ["lib"]
+ end
+
+ defp deps do
+ [
+ {:forge, path: "../forge", env: Mix.env()},
+ Mix.Credo.dependency(),
+ Mix.Dialyzer.dependency(),
+ {:elixir_sense,
+ github: "elixir-lsp/elixir_sense", ref: "73ce7e0d239342fb9527d7ba567203e77dbb9b25"},
+ {:jason, "~> 1.4"},
+ {:logger_file_backend, "~> 0.0", only: [:dev, :prod]},
+ {:patch, "~> 0.15", runtime: false, only: [:dev, :test]},
+ {:path_glob, "~> 0.2"},
+ {:protocol, path: "../protocol", env: Mix.env()},
+ {:engine, path: "../engine", env: Mix.env()},
+ {:sourceror, "~> 1.9"}
+ ]
+ end
+end
diff --git a/apps/server/mix.lock b/apps/expert/mix.lock
similarity index 100%
rename from apps/server/mix.lock
rename to apps/expert/mix.lock
diff --git a/apps/expert/test/convertibles/expert.plugin.diagnostic.result_test.exs b/apps/expert/test/convertibles/expert.plugin.diagnostic.result_test.exs
new file mode 100644
index 00000000..afb0e895
--- /dev/null
+++ b/apps/expert/test/convertibles/expert.plugin.diagnostic.result_test.exs
@@ -0,0 +1,106 @@
+defmodule Expert.Convertibles.Forge.Plugin.V1.Diagnostic.ResultTest do
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ import Forge.Test.CodeSigil
+
+ defp plugin_diagnostic(file_path, position) do
+ file_path
+ |> Document.Path.ensure_uri()
+ |> Diagnostic.Result.new(position, "Broken!", :error, "Elixir")
+ end
+
+ def open_file_contents do
+ ~q[
+ defmodule UnderTest do
+ def fun_one do
+ end
+
+ def fun_two do
+ "🎸hello"
+ end
+ end
+ ]t
+ end
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "it should translate a diagnostic with a line as a position", %{uri: uri} do
+ assert {:ok, %Types.Diagnostic{} = converted} = to_lsp(plugin_diagnostic(uri, 1), uri)
+
+ assert converted.message == "Broken!"
+ assert converted.severity == :error
+ assert converted.source == "Elixir"
+ assert converted.range == range(:lsp, position(:lsp, 0, 0), position(:lsp, 1, 0))
+ end
+
+ test "it should translate a diagnostic with a line and a column", %{uri: uri} do
+ assert {:ok, %Types.Diagnostic{} = converted} = to_lsp(plugin_diagnostic(uri, {1, 1}), uri)
+
+ assert converted.message == "Broken!"
+ assert converted.range == range(:lsp, position(:lsp, 0, 0), position(:lsp, 1, 0))
+ end
+
+ test "it should translate a diagnostic with a four-elements tuple position", %{uri: uri} do
+ assert {:ok, %Types.Diagnostic{} = converted} =
+ to_lsp(plugin_diagnostic(uri, {2, 5, 2, 8}), uri)
+
+ assert converted.message == "Broken!"
+ assert converted.range == range(:lsp, position(:lsp, 1, 4), position(:lsp, 1, 7))
+
+ assert {:ok, %Types.Diagnostic{} = converted} =
+ to_lsp(plugin_diagnostic(uri, {1, 0, 3, 0}), uri)
+
+ assert converted.message == "Broken!"
+ assert converted.range == range(:lsp, position(:lsp, 0, 0), position(:lsp, 2, 0))
+ end
+
+ test "it should translate a diagnostic line that is out of bounds (elixir can do this)", %{
+ uri: uri
+ } do
+ assert {:ok, %Types.Diagnostic{} = converted} = to_lsp(plugin_diagnostic(uri, 9), uri)
+
+ assert converted.message == "Broken!"
+ assert converted.range == range(:lsp, position(:lsp, 7, 0), position(:lsp, 8, 0))
+ end
+
+ test "it can translate a diagnostic of a file that isn't open", %{uri: uri} do
+ assert {:ok, %Types.Diagnostic{}} = to_lsp(plugin_diagnostic(__ENV__.file, 2), uri)
+ end
+
+ test "it can translate a diagnostic that starts after an emoji", %{uri: uri} do
+ assert {:ok, %Types.Diagnostic{} = converted} = to_lsp(plugin_diagnostic(uri, {6, 10}), uri)
+
+ assert converted.range == range(:lsp, position(:lsp, 5, 10), position(:lsp, 6, 0))
+ end
+
+ test "it converts expert positions", %{uri: uri, document: document} do
+ assert {:ok, %Types.Diagnostic{} = converted} =
+ to_lsp(plugin_diagnostic(uri, Document.Position.new(document, 1, 1)), uri)
+
+ assert converted.range == %Types.Range{
+ start: %Types.Position{line: 0, character: 0},
+ end: %Types.Position{line: 1, character: 0}
+ }
+ end
+
+ test "it converts expert ranges", %{uri: uri, document: document} do
+ expert_range =
+ Document.Range.new(
+ Document.Position.new(document, 2, 5),
+ Document.Position.new(document, 2, 8)
+ )
+
+ assert {:ok, %Types.Diagnostic{} = converted} =
+ to_lsp(plugin_diagnostic(uri, expert_range), uri)
+
+ assert %Types.Range{start: start_pos, end: end_pos} = converted.range
+ assert start_pos.line == 1
+ assert start_pos.character == 4
+ assert end_pos.line == 1
+ assert end_pos.character == 7
+ end
+ end
+end
diff --git a/apps/server/test/document_test.exs b/apps/expert/test/document_test.exs
similarity index 97%
rename from apps/server/test/document_test.exs
rename to apps/expert/test/document_test.exs
index c5f5e75a..39d4271f 100644
--- a/apps/server/test/document_test.exs
+++ b/apps/expert/test/document_test.exs
@@ -1,21 +1,21 @@
-defmodule Lexical.DocumentTest do
- alias Lexical.Document
- alias Lexical.Protocol.Types.Position
- alias Lexical.Protocol.Types.Range
- alias Lexical.Protocol.Types.TextEdit
+defmodule Forge.DocumentTest do
+ alias Expert.Protocol.Types.Position
+ alias Expert.Protocol.Types.Range
+ alias Expert.Protocol.Types.TextEdit
+ alias Forge.Document
- alias Lexical.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent,
+ alias Expert.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent,
as: RangedContentChange
- alias Lexical.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent1,
+ alias Expert.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent1,
as: TextOnlyContentChange
use ExUnit.Case
use ExUnitProperties
- import Lexical.Document, except: [to_string: 1]
+ import Forge.Document, except: [to_string: 1]
- def text(%Lexical.Document{} = doc) do
+ def text(%Forge.Document{} = doc) do
Document.to_string(doc)
end
@@ -529,7 +529,7 @@ defmodule Lexical.DocumentTest do
test "works with a content change event" do
orig = """
- defmodule LanguageServer.Experimental.Server.Test do
+ defmodule LanguageServer.Experimental.Expert.Test do
def foo do
{"🎸", "other"}
end
@@ -551,7 +551,7 @@ defmodule Lexical.DocumentTest do
test "deleting a line with a multi-byte character" do
orig = """
- defmodule LanguageServer.Experimental.Server.Test do
+ defmodule LanguageServer.Experimental.Expert.Test do
def foo do
{"🎸", "other"}
end
diff --git a/apps/server/test/lexical/server/boot_test.exs b/apps/expert/test/expert/boot_test.exs
similarity index 76%
rename from apps/server/test/lexical/server/boot_test.exs
rename to apps/expert/test/expert/boot_test.exs
index 4b718fb5..61765003 100644
--- a/apps/server/test/lexical/server/boot_test.exs
+++ b/apps/expert/test/expert/boot_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.Server.BootTest do
- alias Lexical.Server.Boot
- alias Lexical.VM.Versions
+defmodule Expert.BootTest do
+ alias Expert.Boot
+ alias Forge.VM.Versions
use ExUnit.Case
use Patch
@@ -18,7 +18,7 @@ defmodule Lexical.Server.BootTest do
patch_compiled_versions("1.13.4", "24.3.4")
assert [error] = Boot.detect_errors()
- assert error =~ "FATAL: Lexical is not compatible with Elixir 1.12.0"
+ assert error =~ "FATAL: Expert is not compatible with Elixir 1.12.0"
end
test "includes error when runtime erlang is incompatible" do
@@ -26,7 +26,7 @@ defmodule Lexical.Server.BootTest do
patch_compiled_versions("1.15.8", "23.0")
assert [error] = Boot.detect_errors()
- assert error =~ "FATAL: Lexical is not compatible with Erlang/OTP 23.0.0"
+ assert error =~ "FATAL: Expert is not compatible with Erlang/OTP 23.0.0"
end
test "includes multiple errors when runtime elixir and erlang are incompatible" do
@@ -34,8 +34,8 @@ defmodule Lexical.Server.BootTest do
patch_compiled_versions("1.15.8", "26.1")
assert [elixir_error, erlang_error] = Boot.detect_errors()
- assert elixir_error =~ "FATAL: Lexical is not compatible with Elixir 1.15.2"
- assert erlang_error =~ "FATAL: Lexical is not compatible with Erlang/OTP 26.0.0"
+ assert elixir_error =~ "FATAL: Expert is not compatible with Elixir 1.15.2"
+ assert erlang_error =~ "FATAL: Expert is not compatible with Erlang/OTP 26.0.0"
end
end
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/builder_test.exs b/apps/expert/test/expert/code_intelligence/completion/builder_test.exs
similarity index 84%
rename from apps/server/test/lexical/server/code_intelligence/completion/builder_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/builder_test.exs
index 5857ab81..279ce85b 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/builder_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/builder_test.exs
@@ -1,14 +1,14 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.BuilderTest do
- alias Lexical.Ast
- alias Lexical.Ast.Env
- alias Lexical.Protocol.Types.Completion.Item, as: CompletionItem
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
+defmodule Expert.CodeIntelligence.Completion.BuilderTest do
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.Protocol.Types.Completion.Item, as: CompletionItem
+ alias Forge.Ast
+ alias Forge.Ast.Env
use ExUnit.Case, async: true
- import Lexical.Server.CodeIntelligence.Completion.Builder
- import Lexical.Test.CursorSupport
- import Lexical.Test.Fixtures
+ import Expert.CodeIntelligence.Completion.Builder
+ import Forge.Test.CursorSupport
+ import Engine.Test.Fixtures
def new_env(text) do
project = project()
@@ -30,7 +30,7 @@ defmodule Lexical.Server.CodeIntelligence.Completion.BuilderTest do
end
setup do
- start_supervised!(Lexical.Server.Application.document_store_child_spec())
+ start_supervised!(Expert.Application.document_store_child_spec())
:ok
end
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/bitstring_option_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/bitstring_option_test.exs
similarity index 97%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/bitstring_option_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/bitstring_option_test.exs
index 195ea6f7..7a86db13 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/bitstring_option_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/bitstring_option_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.BitstringOptionsTest do
- use Lexical.Test.Server.CompletionCase
+defmodule Expert.CodeIntelligence.Completion.Translations.BitstringOptionsTest do
+ use Expert.Test.Expert.CompletionCase
describe "bitstring options" do
test "offers completions after ::", %{project: project} do
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/callback_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/callback_test.exs
similarity index 92%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/callback_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/callback_test.exs
index 8025b6c3..2e2a91c6 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/callback_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/callback_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.CallbackTest do
- use Lexical.Test.Server.CompletionCase
+defmodule Expert.CodeIntelligence.Completion.Translations.CallbackTest do
+ use Expert.Test.Expert.CompletionCase
describe "callback completions" do
test "suggest callbacks", %{project: project} do
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/function_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/function_test.exs
similarity index 98%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/function_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/function_test.exs
index d5ed719e..b5bcffa5 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/function_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/function_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.FunctionTest do
- use Lexical.Test.Server.CompletionCase
+defmodule Expert.CodeIntelligence.Completion.Translations.FunctionTest do
+ use Expert.Test.Expert.CompletionCase
describe "function completions" do
test "deprecated functions are marked", %{project: project} do
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/interpolation_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/interpolation_test.exs
similarity index 92%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/interpolation_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/interpolation_test.exs
index 1fa303a1..38a12254 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/interpolation_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/interpolation_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.InterpolationTest do
- use Lexical.Test.Server.CompletionCase
+defmodule Expert.CodeIntelligence.Completion.Translations.InterpolationTest do
+ use Expert.Test.Expert.CompletionCase
test "variables are completed inside strings", %{project: project} do
source =
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/macro_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/macro_test.exs
similarity index 99%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/macro_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/macro_test.exs
index f5137076..0f0eb79a 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/macro_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/macro_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.MacroTest do
- alias Lexical.Protocol.Types.Completion
+defmodule Expert.CodeIntelligence.Completion.Translations.MacroTest do
+ alias Expert.Protocol.Types.Completion
- use Lexical.Test.Server.CompletionCase
+ use Expert.Test.Expert.CompletionCase
describe "Kernel* macros" do
test "do/end only has a single completion", %{project: project} do
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/map_field_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/map_field_test.exs
similarity index 87%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/map_field_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/map_field_test.exs
index 3a422bb6..137086a6 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/map_field_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/map_field_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.MapFieldTest do
- use Lexical.Test.Server.CompletionCase
+defmodule Expert.CodeIntelligence.Completion.Translations.MapFieldTest do
+ use Expert.Test.Expert.CompletionCase
test "a map's fields are completed", %{project: project} do
source = ~q[
diff --git a/apps/expert/test/expert/code_intelligence/completion/translations/module_attribute_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/module_attribute_test.exs
new file mode 100644
index 00000000..2deb6438
--- /dev/null
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/module_attribute_test.exs
@@ -0,0 +1,283 @@
+defmodule Expert.CodeIntelligence.Completion.Translations.ModuleAttributeTest do
+ use Expert.Test.Expert.CompletionCase
+
+ describe "module attributes" do
+ test "@moduledoc completions", %{project: project} do
+ source = ~q[
+ defmodule Docs do
+ @modu|
+ end
+ ]
+
+ assert [snippet_completion, empty_completion] = complete(project, source)
+
+ assert snippet_completion.detail
+ assert snippet_completion.label == "@moduledoc"
+
+ # note: indentation should be correctly adjusted by editor
+ assert apply_completion(snippet_completion) == ~q[
+ defmodule Docs do
+ @moduledoc """
+ $0
+ """
+ end
+ ]
+
+ assert empty_completion.detail
+ assert empty_completion.label == "@moduledoc false"
+
+ assert apply_completion(empty_completion) == ~q[
+ defmodule Docs do
+ @moduledoc false
+ end
+ ]
+ end
+
+ test "@doc completions", %{project: project} do
+ source = ~q[
+ defmodule MyModule do
+ @d|
+ def other_thing do
+ end
+ end
+ ]
+
+ assert {:ok, [snippet_completion, empty_completion]} =
+ project
+ |> complete(source)
+ |> fetch_completion(kind: :property)
+
+ assert snippet_completion.detail
+ assert snippet_completion.label == "@doc"
+ assert snippet_completion.kind == :property
+
+ # note: indentation should be correctly adjusted by editor
+ assert apply_completion(snippet_completion) == ~q[
+ defmodule MyModule do
+ @doc """
+ $0
+ """
+ def other_thing do
+ end
+ end
+ ]
+
+ assert empty_completion.detail
+ assert empty_completion.label == "@doc false"
+ assert empty_completion.kind == :property
+
+ assert apply_completion(empty_completion) == ~q[
+ defmodule MyModule do
+ @doc false
+ def other_thing do
+ end
+ end
+ ]
+ end
+
+ # This is a limitation of ElixirSense, which does not return @doc as
+ # a suggestion when the prefix is `@do`. It does for both `@d` and `@doc`.
+ @tag :skip
+ test "@doc completion with do prefix", %{project: project} do
+ source = ~q[
+ defmodule MyModule do
+ @do|
+ def other_thing do
+ end
+ end
+ ]
+
+ assert {:ok, [_snippet_completion, empty_completion]} =
+ project
+ |> complete(source)
+ |> fetch_completion(kind: :property)
+
+ assert empty_completion.detail
+ assert empty_completion.label == "@doc"
+ assert empty_completion.kind == :property
+
+ assert apply_completion(empty_completion) == ~q[
+ defmodule MyModule do
+ @doc false
+ def other_thing do
+ end
+ end
+ ]
+ end
+
+ test "local attribute completion with prefix", %{project: project} do
+ source = ~q[
+ defmodule Attr do
+ @my_attribute :foo
+ @my_|
+ end
+ ]
+
+ assert [completion] = complete(project, source)
+ assert completion.label == "@my_attribute"
+
+ assert apply_completion(completion) == ~q[
+ defmodule Attr do
+ @my_attribute :foo
+ @my_attribute
+ end
+ ]
+ end
+
+ test "local attribute completion immediately after @", %{project: project} do
+ source = ~q[
+ defmodule Attr do
+ @my_attribute :foo
+ @|
+ end
+ ]
+
+ assert {:ok, completion} =
+ project
+ |> complete(source)
+ |> fetch_completion("@my_attribute")
+
+ assert completion.label == "@my_attribute"
+
+ assert apply_completion(completion) == ~q[
+ defmodule Attr do
+ @my_attribute :foo
+ @my_attribute
+ end
+ ]
+ end
+ end
+
+ describe "@spec completion" do
+ test "with no function following", %{project: project} do
+ source = ~q[
+ defmodule MyModule do
+ @spe|
+ end
+ ]
+
+ assert {:ok, completion} =
+ project
+ |> complete(source)
+ |> fetch_completion("@spec")
+
+ assert apply_completion(completion) == ~q[
+ defmodule MyModule do
+ @spec ${1:function}(${2:term()}) :: ${3:term()}
+ def ${1:function}(${4:args}) do
+ $0
+ end
+ end
+ ]
+ end
+
+ test "with a function with args after it", %{project: project} do
+ source = ~q[
+ defmodule MyModule do
+ @spe|
+ def my_function(arg1, arg2, arg3) do
+ :ok
+ end
+ end
+ ]
+
+ assert {:ok, [spec_my_function, spec]} =
+ project
+ |> complete(source)
+ |> fetch_completion(kind: :property)
+
+ assert spec_my_function.label == "@spec my_function"
+
+ assert apply_completion(spec_my_function) == ~q[
+ defmodule MyModule do
+ @spec my_function(${1:term()}, ${2:term()}, ${3:term()}) :: ${0:term()}
+ def my_function(arg1, arg2, arg3) do
+ :ok
+ end
+ end
+ ]
+
+ assert spec.label == "@spec"
+
+ assert apply_completion(spec) == ~q[
+ defmodule MyModule do
+ @spec ${1:function}(${2:term()}) :: ${3:term()}
+ def ${1:function}(${4:args}) do
+ $0
+ end
+ def my_function(arg1, arg2, arg3) do
+ :ok
+ end
+ end
+ ]
+ end
+
+ test "with a function without args after it", %{project: project} do
+ source = ~q[
+ defmodule MyModule do
+ @spe|
+ def my_function do
+ :ok
+ end
+ end
+ ]
+
+ assert {:ok, [spec_my_function, spec]} =
+ project
+ |> complete(source)
+ |> fetch_completion(kind: :property)
+
+ assert spec_my_function.label == "@spec my_function"
+
+ assert apply_completion(spec_my_function) == ~q[
+ defmodule MyModule do
+ @spec my_function() :: ${0:term()}
+ def my_function do
+ :ok
+ end
+ end
+ ]
+
+ assert spec.label == "@spec"
+
+ assert apply_completion(spec) == ~q[
+ defmodule MyModule do
+ @spec ${1:function}(${2:term()}) :: ${3:term()}
+ def ${1:function}(${4:args}) do
+ $0
+ end
+ def my_function do
+ :ok
+ end
+ end
+ ]
+ end
+
+ test "with a private function after it", %{project: project} do
+ source = ~q[
+ defmodule MyModule do
+ @spe|
+ defp my_function(arg1, arg2, arg3) do
+ :ok
+ end
+ end
+ ]
+
+ assert {:ok, [spec_my_function, _spec]} =
+ project
+ |> complete(source)
+ |> fetch_completion(kind: :property)
+
+ assert spec_my_function.label == "@spec my_function"
+
+ assert apply_completion(spec_my_function) == ~q[
+ defmodule MyModule do
+ @spec my_function(${1:term()}, ${2:term()}, ${3:term()}) :: ${0:term()}
+ defp my_function(arg1, arg2, arg3) do
+ :ok
+ end
+ end
+ ]
+ end
+ end
+end
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/module_or_behaviour_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/module_or_behaviour_test.exs
similarity index 98%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/module_or_behaviour_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/module_or_behaviour_test.exs
index 6ff91ad1..f9aa36ca 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/module_or_behaviour_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/module_or_behaviour_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleOrBehaviourTest do
- use Lexical.Test.Server.CompletionCase
+defmodule Expert.CodeIntelligence.Completion.Translations.ModuleOrBehaviourTest do
+ use Expert.Test.Expert.CompletionCase
describe "module completions" do
test "modules should emit a completion for stdlib modules", %{project: project} do
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/struct_field_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/struct_field_test.exs
similarity index 98%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/struct_field_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/struct_field_test.exs
index d9c2c9b5..c86e1379 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/struct_field_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/struct_field_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.StructFieldTest do
- use Lexical.Test.Server.CompletionCase
+defmodule Expert.CodeIntelligence.Completion.Translations.StructFieldTest do
+ use Expert.Test.Expert.CompletionCase
test "a struct's fields are completed", %{project: project} do
source = ~q[
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/struct_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/struct_test.exs
similarity index 98%
rename from apps/server/test/lexical/server/code_intelligence/completion/translations/struct_test.exs
rename to apps/expert/test/expert/code_intelligence/completion/translations/struct_test.exs
index 327b64de..12a1185b 100644
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/struct_test.exs
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/struct_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.StructTest do
- use Lexical.Test.Server.CompletionCase
+defmodule Expert.CodeIntelligence.Completion.Translations.StructTest do
+ use Expert.Test.Expert.CompletionCase
describe "structs" do
test "should complete after %", %{project: project} do
diff --git a/apps/expert/test/expert/code_intelligence/completion/translations/variable_test.exs b/apps/expert/test/expert/code_intelligence/completion/translations/variable_test.exs
new file mode 100644
index 00000000..60f573c8
--- /dev/null
+++ b/apps/expert/test/expert/code_intelligence/completion/translations/variable_test.exs
@@ -0,0 +1,45 @@
+defmodule Expert.CodeIntelligence.Completion.Translations.VariableTest do
+ use Expert.Test.Expert.CompletionCase
+
+ test "variables are completed", %{project: project} do
+ source = ~q[
+ def my_function do
+ stinky = :smelly
+ st|
+ end
+ ]
+
+ assert {:ok, completion} =
+ project
+ |> complete(source)
+ |> fetch_completion(kind: :variable)
+
+ assert completion.label == "stinky"
+ assert completion.detail == "stinky"
+
+ assert apply_completion(completion) == ~q[
+ def my_function do
+ stinky = :smelly
+ stinky
+ end
+ ]
+ end
+
+ test "all variables are returned", %{project: project} do
+ source = ~q[
+ def my_function do
+ var_1 = 3
+ var_2 = 5
+ va|
+ end
+ ]
+
+ assert {:ok, [c1, c2]} =
+ project
+ |> complete(source)
+ |> fetch_completion(kind: :variable)
+
+ assert c1.label == "var_1"
+ assert c2.label == "var_2"
+ end
+end
diff --git a/apps/expert/test/expert/code_intelligence/completion_test.exs b/apps/expert/test/expert/code_intelligence/completion_test.exs
new file mode 100644
index 00000000..29551d0d
--- /dev/null
+++ b/apps/expert/test/expert/code_intelligence/completion_test.exs
@@ -0,0 +1,282 @@
+defmodule Expert.CodeIntelligence.CompletionTest do
+ alias Engine.Completion.Candidate
+ alias Expert.CodeIntelligence.Completion.SortScope
+ alias Expert.Protocol.Types.Completion
+ alias Expert.Protocol.Types.Completion.Item, as: CompletionItem
+
+ use Expert.Test.Expert.CompletionCase
+ use Patch
+
+ describe "excluding modules from expert dependencies" do
+ test "expert modules are removed", %{project: project} do
+ assert [] = complete(project, "Expert.CodeIntelligence|")
+ end
+
+ test "Expert submodules are removed", %{project: project} do
+ assert [] = complete(project, "Engin|e")
+ assert [] = complete(project, "Forg|e")
+ end
+
+ test "Expert functions are removed", %{project: project} do
+ assert [] = complete(project, "Engine.|")
+ end
+
+ test "Dependency modules are removed", %{project: project} do
+ assert [] = complete(project, "ElixirSense|")
+ end
+
+ test "Dependency functions are removed", %{project: project} do
+ assert [] = complete(project, "Jason.encod|")
+ end
+
+ test "Dependency protocols are removed", %{project: project} do
+ assert [] = complete(project, "Jason.Encode|")
+ end
+
+ test "Dependency structs are removed", %{project: project} do
+ assert [] = complete(project, "Jason.Fragment|")
+ end
+
+ test "Dependency exceptions are removed", %{project: project} do
+ assert [] = complete(project, "Jason.DecodeErro|")
+ end
+ end
+
+ test "ensure completion works for project", %{project: project} do
+ refute [] == complete(project, "Project.|")
+ end
+
+ describe "single character atom completions" do
+ test "complete elixir modules", %{project: project} do
+ assert [_ | _] = completions = complete(project, "E|")
+
+ for completion <- completions do
+ assert completion.kind == :module
+ end
+ end
+
+ test "ignore erlang modules", %{project: project} do
+ assert [] == complete(project, ":e|")
+ end
+ end
+
+ describe "ignoring things" do
+ test "returns an incomplete completion list when the context is empty", %{project: project} do
+ assert %Completion.List{is_incomplete: true, items: []} =
+ complete(project, " ", as_list: false)
+ end
+
+ test "returns no completions in a comment at the beginning of a line", %{project: project} do
+ assert [] == complete(project, "# IO.in|")
+ end
+
+ test "returns no completions in a comment at the end of a line", %{project: project} do
+ assert [] == complete(project, "IO.inspe # IO.in|")
+ end
+
+ test "returns no completions in double quoted strings", %{project: project} do
+ assert [] = complete(project, ~S/"IO.in|"/)
+ end
+
+ test "returns no completions inside heredocs", %{project: project} do
+ assert [] = complete(project, ~S/
+ """
+ This is my heredoc
+ It does not IO.in|
+ """
+ /)
+ end
+
+ test "returns no completions inside ~s", %{project: project} do
+ assert [] = complete(project, ~S/~s[ IO.in|]/)
+ end
+
+ test "returns no completions inside ~S", %{project: project} do
+ assert [] = complete(project, ~S/ ~S[ IO.in|] /)
+ end
+
+ test "only modules that are behaviuors are completed in an @impl", %{project: project} do
+ assert [behaviour] = complete(project, "@impl U|")
+ assert behaviour.label == "Unary"
+ assert behaviour.kind == :module
+ end
+ end
+
+ describe "do/end" do
+ test "returns do/end when the last token is 'do'", %{project: project} do
+ assert [completion] = complete(project, "for a <- something do|")
+ assert completion.label == "do/end block"
+ end
+
+ test "returns do/end when the last token is 'd'", %{project: project} do
+ assert [completion] = complete(project, "for a <- something d|")
+ assert completion.label == "do/end block"
+ end
+ end
+
+ describe "sorting dunder function/macro completions" do
+ test "dunder functions are sorted last in their sort scope", %{project: project} do
+ {:ok, completion} =
+ project
+ |> complete("Enum.|")
+ |> fetch_completion("__info__")
+
+ %CompletionItem{
+ sort_text: sort_text
+ } = completion
+
+ assert sort_text =~ SortScope.remote(false, 9)
+ end
+
+ test "dunder macros are sorted last in their scope", %{project: project} do
+ {:ok, completion} =
+ project
+ |> complete("Project.__dunder_macro__|")
+ |> fetch_completion("__dunder_macro__")
+
+ %CompletionItem{
+ sort_text: sort_text
+ } = completion
+
+ assert sort_text =~ SortScope.remote(false, 9)
+ end
+
+ test "typespecs with no origin are completed", %{project: project} do
+ candidate = %Candidate.Typespec{
+ argument_names: [],
+ metadata: %{builtin: true},
+ arity: 0,
+ name: "any",
+ origin: nil
+ }
+
+ patch(Engine.Api, :complete, [candidate])
+
+ [completion] = complete(project, " @type a|")
+ assert completion.label == "any()"
+ end
+
+ test "typespecs with no full_name are completed", %{project: project} do
+ candidate = %Candidate.Struct{full_name: nil, metadata: %{}, name: "Struct"}
+ patch(Engine.Api, :complete, [candidate])
+
+ [completion] = complete(project, " %Stru|")
+ assert completion.label == "Struct"
+ end
+ end
+
+ def with_all_completion_candidates(_) do
+ name = "Foo"
+ full_name = "A.B.Foo"
+
+ all_completions = [
+ %Candidate.Behaviour{name: "#{name}-behaviour", full_name: full_name},
+ %Candidate.BitstringOption{name: "#{name}-bitstring", type: "integer"},
+ %Candidate.Callback{
+ name: "#{name}-callback",
+ origin: full_name,
+ argument_names: [],
+ metadata: %{},
+ arity: 0
+ },
+ %Candidate.Exception{name: "#{name}-exception", full_name: full_name},
+ %Candidate.Function{
+ name: "my_func",
+ origin: full_name,
+ argument_names: [],
+ metadata: %{},
+ arity: 0
+ },
+ %Candidate.Macro{
+ name: "my_macro",
+ origin: full_name,
+ argument_names: [],
+ metadata: %{},
+ arity: 0
+ },
+ %Candidate.MixTask{name: "#{name}-mix-task", full_name: full_name},
+ %Candidate.Module{name: "#{name}-module", full_name: full_name},
+ %Candidate.Module{name: "#{name}-submodule", full_name: "#{full_name}.Bar"},
+ %Candidate.ModuleAttribute{name: "#{name}-module-attribute"},
+ %Candidate.Protocol{name: "#{name}-protocol", full_name: full_name},
+ %Candidate.Struct{name: "#{name}-struct", full_name: full_name},
+ %Candidate.StructField{name: "#{name}-struct-field", origin: full_name},
+ %Candidate.Typespec{
+ name: "#{name}-typespec",
+ origin: full_name,
+ argument_names: ["value"],
+ arity: 1,
+ metadata: %{}
+ },
+ %Candidate.Variable{name: "#{name}-variable"}
+ ]
+
+ patch(Engine.Api, :complete, all_completions)
+ :ok
+ end
+
+ describe "context aware inclusions and exclusions" do
+ setup [:with_all_completion_candidates]
+
+ test "only modules and module-like completions are returned in an alias", %{project: project} do
+ completions = complete(project, "alias Foo.")
+
+ for completion <- complete(project, "alias Foo.") do
+ assert %_{kind: :module} = completion
+ end
+
+ assert {:ok, _} = fetch_completion(completions, label: "Foo-behaviour")
+ assert {:ok, _} = fetch_completion(completions, label: "Foo-module")
+ assert {:ok, _} = fetch_completion(completions, label: "Foo-protocol")
+ assert {:ok, _} = fetch_completion(completions, label: "Foo-struct")
+ end
+
+ test "only modules, typespecs and module attributes are returned in types", %{
+ project: project
+ } do
+ completions =
+ for completion <- complete(project, "@spec F"), into: MapSet.new() do
+ completion.label
+ end
+
+ assert "Foo-module" in completions
+ assert "Foo-module-attribute" in completions
+ assert "Foo-submodule" in completions
+ assert "Foo-typespec(value)" in completions
+ assert Enum.count(completions) == 4
+ end
+
+ test "modules are sorted before functions", %{project: project} do
+ code = ~q[
+ def in_function do
+ Foo.|
+ end
+ ]
+
+ completions =
+ project
+ |> complete(code)
+ |> Enum.sort_by(& &1.sort_text)
+
+ module_index = Enum.find_index(completions, &(&1.label == "Foo-module"))
+ behaviour_index = Enum.find_index(completions, &(&1.label == "Foo-behaviour"))
+ submodule_index = Enum.find_index(completions, &(&1.label == "Foo-submodule"))
+
+ function_index = Enum.find_index(completions, &(&1.label == "my_function()"))
+ macro_index = Enum.find_index(completions, &(&1.label == "my_macro()"))
+ callback_index = Enum.find_index(completions, &(&1.label == "Foo-callback()"))
+
+ assert submodule_index < function_index
+ assert submodule_index < macro_index
+ assert submodule_index < callback_index
+
+ assert module_index < function_index
+ assert module_index < macro_index
+ assert module_index < callback_index
+
+ assert behaviour_index < function_index
+ assert behaviour_index < macro_index
+ assert behaviour_index < callback_index
+ end
+ end
+end
diff --git a/apps/expert/test/expert/project/diagnostics/state_test.exs b/apps/expert/test/expert/project/diagnostics/state_test.exs
new file mode 100644
index 00000000..234066ae
--- /dev/null
+++ b/apps/expert/test/expert/project/diagnostics/state_test.exs
@@ -0,0 +1,167 @@
+defmodule Forge.Project.Diagnostics.StateTest do
+ alias Expert.Project.Diagnostics.State
+ alias Forge.Document
+ alias Forge.Document.Edit
+ alias Forge.Plugin.V1.Diagnostic
+ alias Forge.Project
+
+ import Engine.Test.Fixtures
+
+ use Engine.Test.CodeMod.Case
+
+ setup do
+ {:ok, _} = start_supervised(Forge.Document.Store)
+
+ project = project()
+ state = State.new(project)
+ {:ok, project: project(), state: state}
+ end
+
+ def existing_file_path do
+ Path.join([Project.root_path(project()), "lib", "project.ex"])
+ end
+
+ def document(contents, file_path \\ existing_file_path()) do
+ file_uri = Document.Path.to_uri(file_path)
+
+ with :ok <- Document.Store.open(file_uri, contents, 0),
+ {:ok, document} <- Document.Store.fetch(file_uri) do
+ document
+ end
+ end
+
+ def change_with(document, content) do
+ changes = [Edit.new(content)]
+
+ {:ok, document} =
+ Document.Store.get_and_update(
+ document.uri,
+ &Document.apply_content_changes(&1, 2, changes)
+ )
+
+ document
+ end
+
+ def diagnostic(opts \\ []) do
+ file_uri =
+ opts
+ |> Keyword.get(:file, existing_file_path())
+ |> Document.Path.ensure_uri()
+
+ position = Keyword.get(opts, :position, 1)
+ message = Keyword.get(opts, :message, "This file is broken")
+ severity = Keyword.get(opts, :severity, :error)
+ Diagnostic.Result.new(file_uri, position, message, severity, "Elixir")
+ end
+
+ describe "add/3" do
+ test "allows you to add a diagnostic to a new uri", %{state: state} do
+ diagnostic = diagnostic(message: "this code is bad!")
+
+ state = State.add(state, 1, diagnostic)
+
+ assert [%Diagnostic.Result{}] = State.get(state, diagnostic.uri)
+ end
+
+ test "allows you to add multiple diagnostics with the same build number", %{state: state} do
+ diag_1 = diagnostic(message: "hey!")
+ diag_2 = diagnostic(message: "there")
+
+ state =
+ state
+ |> State.add(1, diag_1)
+ |> State.add(1, diag_2)
+
+ assert [^diag_1, ^diag_2] = State.get(state, diag_1.uri)
+ end
+
+ test "diagnostics with older build numbers are overwritten", %{state: state} do
+ diag_1 = diagnostic(message: "one")
+ diag_2 = diagnostic(message: "two")
+ diag_3 = diagnostic(message: "three")
+
+ state =
+ state
+ |> State.add(1, diag_1)
+ |> State.add(1, diag_2)
+ |> State.add(2, diag_3)
+
+ assert [^diag_3] = State.get(state, diag_3.uri)
+ end
+
+ test "duplicate diagnostics are collapsed", %{state: state} do
+ diag_1 = diagnostic(message: "dupe")
+ diag_2 = diagnostic(message: "two")
+ diag_3 = diagnostic(message: "dupe")
+
+ state =
+ state
+ |> State.add(1, diag_1)
+ |> State.add(1, diag_2)
+ |> State.add(1, diag_3)
+
+ assert [^diag_1, ^diag_2] = State.get(state, diag_1.uri)
+ end
+ end
+
+ test "it allows you to add a global diagnostic", %{state: state} do
+ diagnostic = diagnostic(message: "This code is awful")
+
+ state = State.add(state, 1, diagnostic)
+
+ assert [%Diagnostic.Result{}] = State.get(state, diagnostic.uri)
+ end
+
+ describe "clear_all_flushed/1" do
+ test "it should not clear a dirty open file", %{state: state} do
+ document =
+ "hello"
+ |> document()
+ |> change_with("hello2")
+
+ state = State.add(state, 1, diagnostic(message: "The code is awful"))
+
+ old_diagnostics = State.get(state, document.uri)
+ state = State.clear_all_flushed(state)
+ assert ^old_diagnostics = State.get(state, document.uri)
+ end
+
+ test "it should not clear a script file even if it is clean", %{
+ state: state,
+ project: project
+ } do
+ script_file_path = Path.join([Project.root_path(project), "test", "*.exs"])
+ document = document("assert f() == 0", script_file_path)
+
+ state = State.add(state, 1, diagnostic(message: "undefined function f/0"))
+
+ old_diagnostics = State.get(state, document.uri)
+ state = State.clear_all_flushed(state)
+ assert ^old_diagnostics = State.get(state, document.uri)
+ end
+
+ test "it should clear a file's diagnostics if it is just open", %{state: state} do
+ document = document("hello")
+
+ state = State.add(state, 1, diagnostic(message: "The code is awful"))
+
+ state = State.clear_all_flushed(state)
+ diagnostics = State.get(state, document.uri)
+
+ assert diagnostics == []
+ end
+
+ test "it should clear a file's diagnostics if it is closed", %{state: state} do
+ document = document("hello")
+
+ state = State.add(state, 1, diagnostic(message: "The code is awful"))
+
+ :ok = Document.Store.close(document.uri)
+
+ state = State.clear_all_flushed(state)
+ diagnostics = State.get(state, document.uri)
+
+ assert diagnostics == []
+ end
+ end
+end
diff --git a/apps/server/test/lexical/server/project/diagnostics_test.exs b/apps/expert/test/expert/project/diagnostics_test.exs
similarity index 75%
rename from apps/server/test/lexical/server/project/diagnostics_test.exs
rename to apps/expert/test/expert/project/diagnostics_test.exs
index eaaa08b1..89082602 100644
--- a/apps/server/test/lexical/server/project/diagnostics_test.exs
+++ b/apps/expert/test/expert/project/diagnostics_test.exs
@@ -1,25 +1,23 @@
-defmodule Lexical.Server.Project.DiagnosticsTest do
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.Protocol.Notifications.PublishDiagnostics
- alias Lexical.RemoteControl
- alias Lexical.Server
- alias Lexical.Server.Transport
- alias Lexical.Test.DispatchFake
+defmodule Expert.Project.DiagnosticsTest do
+ alias Expert.Protocol.Notifications.PublishDiagnostics
+ alias Expert.Test.DispatchFake
+ alias Expert.Transport
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
use ExUnit.Case
use Patch
use DispatchFake
- import Lexical.RemoteControl.Api.Messages
- import Lexical.Test.Fixtures
+ import Engine.Api.Messages
+ import Engine.Test.Fixtures
setup do
project = project()
DispatchFake.start()
- start_supervised!(Lexical.Document.Store)
- start_supervised!({Server.Project.Diagnostics, project})
+ start_supervised!(Forge.Document.Store)
+ start_supervised!({Expert.Project.Diagnostics, project})
{:ok, project: project}
end
@@ -65,12 +63,12 @@ defmodule Lexical.Server.Project.DiagnosticsTest do
file_diagnostics_message =
file_diagnostics(diagnostics: [diagnostic(document.uri)], uri: document.uri)
- RemoteControl.Api.broadcast(project, file_diagnostics_message)
+ Engine.Api.broadcast(project, file_diagnostics_message)
assert_receive {:transport, %PublishDiagnostics{}}
Document.Store.get_and_update(document.uri, &Document.mark_clean/1)
- RemoteControl.Api.broadcast(project, project_diagnostics(diagnostics: []))
+ Engine.Api.broadcast(project, project_diagnostics(diagnostics: []))
assert_receive {:transport, %PublishDiagnostics{diagnostics: nil}}
end
@@ -83,11 +81,11 @@ defmodule Lexical.Server.Project.DiagnosticsTest do
file_diagnostics_message =
file_diagnostics(diagnostics: [diagnostic(document.uri)], uri: document.uri)
- RemoteControl.Api.broadcast(project, file_diagnostics_message)
+ Engine.Api.broadcast(project, file_diagnostics_message)
assert_receive {:transport, %PublishDiagnostics{}}, 500
Document.Store.close(document.uri)
- RemoteControl.Api.broadcast(project, project_diagnostics(diagnostics: []))
+ Engine.Api.broadcast(project, project_diagnostics(diagnostics: []))
assert_receive {:transport, %PublishDiagnostics{diagnostics: nil}}
end
@@ -103,7 +101,7 @@ defmodule Lexical.Server.Project.DiagnosticsTest do
file_diagnostics_message = file_diagnostics(diagnostics: [diagnostic], uri: document.uri)
- RemoteControl.Api.broadcast(project, file_diagnostics_message)
+ Engine.Api.broadcast(project, file_diagnostics_message)
assert_receive {:transport, %PublishDiagnostics{lsp: %{diagnostics: [diagnostic]}}}, 500
assert %Diagnostic.Result{} = diagnostic
diff --git a/apps/server/test/lexical/server/project/intelligence_test.exs b/apps/expert/test/expert/project/intelligence_test.exs
similarity index 96%
rename from apps/server/test/lexical/server/project/intelligence_test.exs
rename to apps/expert/test/expert/project/intelligence_test.exs
index 86e84112..17c661d2 100644
--- a/apps/server/test/lexical/server/project/intelligence_test.exs
+++ b/apps/expert/test/expert/project/intelligence_test.exs
@@ -1,9 +1,8 @@
-defmodule Lexical.Server.Project.IntelligenceTest do
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.Server.Project.Intelligence
- alias Lexical.Test.DispatchFake
- alias Lexical.Test.Fixtures
+defmodule Expert.Project.IntelligenceTest do
+ alias Engine.Api.Messages
+ alias Engine.Test.Fixtures
+ alias Expert.Project.Intelligence
+ alias Expert.Test.DispatchFake
use ExUnit.Case
use Patch
@@ -39,7 +38,7 @@ defmodule Lexical.Server.Project.IntelligenceTest do
struct: [name: nil]
)
]
- |> Enum.each(&RemoteControl.Api.broadcast(project, &1))
+ |> Enum.each(&Engine.Api.broadcast(project, &1))
Process.sleep(50)
:ok
diff --git a/apps/expert/test/expert/project/node_test.exs b/apps/expert/test/expert/project/node_test.exs
new file mode 100644
index 00000000..0a4db7d4
--- /dev/null
+++ b/apps/expert/test/expert/project/node_test.exs
@@ -0,0 +1,56 @@
+defmodule Expert.Project.NodeTest do
+ import Engine.Test.Fixtures
+ import Engine.Api.Messages
+ alias Expert.Project.Node, as: ProjectNode
+
+ use ExUnit.Case
+ use Forge.Test.EventualAssertions
+
+ setup do
+ project = project()
+
+ {:ok, _} = start_supervised({DynamicSupervisor, Expert.Project.Supervisor.options()})
+ {:ok, _} = start_supervised({Expert.Project.Supervisor, project})
+
+ :ok = Engine.Api.register_listener(project, self(), [project_compiled()])
+
+ {:ok, project: project}
+ end
+
+ test "the project should be compiled when the node starts" do
+ assert_receive project_compiled(), 750
+ end
+
+ test "remote control is started when the node starts", %{project: project} do
+ apps = Engine.call(project, Application, :started_applications)
+ app_names = Enum.map(apps, &elem(&1, 0))
+ assert :engine in app_names
+ end
+
+ test "the node is restarted when it goes down", %{project: project} do
+ node_name = ProjectNode.node_name(project)
+ old_pid = node_pid(project)
+
+ :ok = Engine.stop(project)
+ assert_eventually Node.ping(node_name) == :pong, 1000
+
+ new_pid = node_pid(project)
+ assert is_pid(new_pid)
+ assert new_pid != old_pid
+ end
+
+ test "the node restarts when the supervisor pid is killed", %{project: project} do
+ node_name = ProjectNode.node_name(project)
+ supervisor_pid = Engine.call(project, Process, :whereis, [Engine.Supervisor])
+
+ assert is_pid(supervisor_pid)
+ Process.exit(supervisor_pid, :kill)
+ assert_eventually Node.ping(node_name) == :pong, 750
+ end
+
+ defp node_pid(project) do
+ project
+ |> Engine.ProjectNode.name()
+ |> Process.whereis()
+ end
+end
diff --git a/apps/expert/test/expert/project/progress/state_test.exs b/apps/expert/test/expert/project/progress/state_test.exs
new file mode 100644
index 00000000..484a96a0
--- /dev/null
+++ b/apps/expert/test/expert/project/progress/state_test.exs
@@ -0,0 +1,72 @@
+defmodule Expert.Project.Progress.StateTest do
+ alias Expert.Project.Progress.State
+ alias Expert.Project.Progress.Value
+
+ import Engine.Api.Messages
+ import Engine.Test.Fixtures
+
+ use ExUnit.Case, async: true
+
+ setup do
+ project = project()
+ {:ok, project: project}
+ end
+
+ def progress(label, message \\ nil) do
+ project_progress(label: label, message: message)
+ end
+
+ test "it should be able to add a begin event and put the new token", %{project: project} do
+ label = "mix deps.get"
+ state = project |> State.new() |> State.begin(progress(label))
+
+ assert %Value{token: token, title: ^label, kind: :begin} = state.progress_by_label[label]
+ assert token != nil
+ end
+
+ test "it should be able to add a report event use the begin event token", %{project: project} do
+ label = "mix compile"
+ state = project |> State.new() |> State.begin(progress(label))
+
+ previous_token = state.progress_by_label[label].token
+
+ %{progress_by_label: progress_by_label} =
+ State.report(state, progress(label, "lib/my_module.ex"))
+
+ assert %Value{token: ^previous_token, message: "lib/my_module.ex", kind: :report} =
+ progress_by_label[label]
+ end
+
+ test "clear the token_by_label after received a complete event", %{project: project} do
+ state = project |> State.new() |> State.begin(progress("mix compile"))
+
+ %{progress_by_label: progress_by_label} =
+ State.complete(state, progress("mix compile", "in 2s"))
+
+ assert progress_by_label == %{}
+ end
+
+ test "set the progress value to nil when there is no begin event", %{
+ project: project
+ } do
+ state = project |> State.new() |> State.report(progress("mix compile"))
+ assert state.progress_by_label["mix compile"] == nil
+ end
+
+ test "set the progress value to nil when a complete event received before the report", %{
+ project: project
+ } do
+ label = "mix compile"
+
+ state =
+ project
+ |> State.new()
+ |> State.begin(progress(label))
+ |> State.complete(progress(label, "in 2s"))
+
+ %{progress_by_label: progress_by_label} =
+ State.report(state, progress(label, "lib/my_module.ex"))
+
+ assert progress_by_label[label] == nil
+ end
+end
diff --git a/apps/server/test/lexical/server/project/progress/support_test.exs b/apps/expert/test/expert/project/progress/support_test.exs
similarity index 82%
rename from apps/server/test/lexical/server/project/progress/support_test.exs
rename to apps/expert/test/expert/project/progress/support_test.exs
index 5a5d110b..37bc0e55 100644
--- a/apps/server/test/lexical/server/project/progress/support_test.exs
+++ b/apps/expert/test/expert/project/progress/support_test.exs
@@ -1,8 +1,8 @@
-defmodule Lexical.Server.Project.Progress.SupportTest do
- alias Lexical.Server.Project.Progress
+defmodule Expert.Project.Progress.SupportTest do
+ alias Expert.Project.Progress
- import Lexical.RemoteControl.Api.Messages
- import Lexical.Test.Fixtures
+ import Engine.Api.Messages
+ import Engine.Test.Fixtures
use ExUnit.Case
use Patch
diff --git a/apps/expert/test/expert/project/progress_test.exs b/apps/expert/test/expert/project/progress_test.exs
new file mode 100644
index 00000000..56549ef1
--- /dev/null
+++ b/apps/expert/test/expert/project/progress_test.exs
@@ -0,0 +1,156 @@
+defmodule Expert.Project.ProgressTest do
+ alias Expert.Configuration
+ alias Expert.Project
+ alias Expert.Protocol.Notifications
+ alias Expert.Protocol.Requests
+ alias Expert.Test.DispatchFake
+ alias Expert.Transport
+
+ import Engine.Test.Fixtures
+ import Engine.Api.Messages
+
+ use ExUnit.Case
+ use Patch
+ use DispatchFake
+ use Forge.Test.EventualAssertions
+
+ setup do
+ project = project()
+ pid = start_supervised!({Project.Progress, project})
+ DispatchFake.start()
+ Engine.Dispatch.register_listener(pid, project_progress())
+ Engine.Dispatch.register_listener(pid, percent_progress())
+
+ {:ok, project: project}
+ end
+
+ def percent_begin(project, label, max) do
+ message = percent_progress(stage: :begin, label: label, max: max)
+ Engine.Api.broadcast(project, message)
+ end
+
+ defp percent_report(project, label, delta, message \\ nil) do
+ message = percent_progress(stage: :report, label: label, message: message, delta: delta)
+ Engine.Api.broadcast(project, message)
+ end
+
+ defp percent_complete(project, label, message) do
+ message = percent_progress(stage: :complete, label: label, message: message)
+ Engine.Api.broadcast(project, message)
+ end
+
+ def progress(stage, label, message \\ "") do
+ project_progress(label: label, message: message, stage: stage)
+ end
+
+ def with_patched_transport(_) do
+ test = self()
+
+ patch(Transport, :write, fn message ->
+ send(test, {:transport, message})
+ end)
+
+ :ok
+ end
+
+ def with_work_done_progress_support(_) do
+ patch(Configuration, :client_supports?, fn :work_done_progress -> true end)
+ :ok
+ end
+
+ describe "report the progress message" do
+ setup [:with_patched_transport]
+
+ test "it should be able to send the report progress", %{project: project} do
+ patch(Configuration, :client_supports?, fn :work_done_progress -> true end)
+
+ begin_message = progress(:begin, "mix compile")
+ Engine.Api.broadcast(project, begin_message)
+
+ assert_receive {:transport, %Requests.CreateWorkDoneProgress{lsp: %{token: token}}}
+ assert_receive {:transport, %Notifications.Progress{}}
+
+ report_message = progress(:report, "mix compile", "lib/file.ex")
+ Engine.Api.broadcast(project, report_message)
+ assert_receive {:transport, %Notifications.Progress{lsp: %{token: ^token, value: value}}}
+
+ assert value.kind == "report"
+ assert value.message == "lib/file.ex"
+ assert value.percentage == nil
+ assert value.cancellable == nil
+ end
+
+ test "it should write nothing when the client does not support work done", %{project: project} do
+ patch(Configuration, :client_supports?, fn :work_done_progress -> false end)
+
+ begin_message = progress(:begin, "mix compile")
+ Engine.Api.broadcast(project, begin_message)
+
+ refute_receive {:transport, %Requests.CreateWorkDoneProgress{lsp: %{}}}
+ end
+ end
+
+ describe "reporting a percentage progress" do
+ setup [:with_patched_transport, :with_work_done_progress_support]
+
+ test "it should be able to increment the percentage", %{project: project} do
+ percent_begin(project, "indexing", 400)
+
+ assert_receive {:transport, %Requests.CreateWorkDoneProgress{lsp: %{token: token}}}
+ assert_receive {:transport, %Notifications.Progress{} = progress}
+
+ assert progress.lsp.value.kind == "begin"
+ assert progress.lsp.value.title == "indexing"
+ assert progress.lsp.value.percentage == 0
+
+ percent_report(project, "indexing", 100)
+
+ assert_receive {:transport, %Notifications.Progress{lsp: %{token: ^token, value: value}}}
+ assert value.kind == "report"
+ assert value.percentage == 25
+ assert value.message == nil
+
+ percent_report(project, "indexing", 260, "Almost done")
+
+ assert_receive {:transport, %Notifications.Progress{lsp: %{token: ^token, value: value}}}
+ assert value.percentage == 90
+ assert value.message == "Almost done"
+
+ percent_complete(project, "indexing", "Indexing Complete")
+
+ assert_receive {:transport, %Notifications.Progress{lsp: %{token: ^token, value: value}}}
+ assert value.kind == "end"
+ assert value.message == "Indexing Complete"
+ end
+
+ test "it caps the percentage at 100", %{project: project} do
+ percent_begin(project, "indexing", 100)
+ percent_report(project, "indexing", 1000)
+ assert_receive {:transport, %Notifications.Progress{lsp: %{value: %{kind: "begin"}}}}
+ assert_receive {:transport, %Notifications.Progress{lsp: %{value: value}}}
+ assert value.kind == "report"
+ assert value.percentage == 100
+ end
+
+ test "it only allows the percentage to grow", %{project: project} do
+ percent_begin(project, "indexing", 100)
+ assert_receive {:transport, %Notifications.Progress{lsp: %{value: %{kind: "begin"}}}}
+
+ percent_report(project, "indexing", 10)
+
+ assert_receive {:transport, %Notifications.Progress{lsp: %{value: value}}}
+ assert value.kind == "report"
+ assert value.percentage == 10
+
+ percent_report(project, "indexing", -10)
+ assert_receive {:transport, %Notifications.Progress{lsp: %{value: value}}}
+ assert value.kind == "report"
+ assert value.percentage == 10
+
+ percent_report(project, "indexing", 5)
+ assert_receive {:transport, %Notifications.Progress{lsp: %{value: value}}}
+ assert value.kind == "report"
+ assert value.percentage == 15
+ end
+ end
+end
diff --git a/apps/expert/test/expert/provider/handlers/code_lens_test.exs b/apps/expert/test/expert/provider/handlers/code_lens_test.exs
new file mode 100644
index 00000000..2ecfdfb1
--- /dev/null
+++ b/apps/expert/test/expert/provider/handlers/code_lens_test.exs
@@ -0,0 +1,96 @@
+defmodule Expert.Provider.Handlers.CodeLensTest do
+ alias Expert.Proto.Convert
+ alias Expert.Protocol.Requests.CodeLens
+ alias Expert.Protocol.Types
+ alias Expert.Provider.Handlers
+ alias Forge.Document
+ alias Forge.Project
+
+ import Expert.Test.Protocol.Fixtures.LspProtocol
+ import Engine.Api.Messages
+ import Engine.Test.Fixtures
+ import Forge.Test.RangeSupport
+
+ use ExUnit.Case, async: false
+ use Patch
+
+ setup_all do
+ start_supervised(Document.Store)
+ project = project(:umbrella)
+
+ start_supervised!({DynamicSupervisor, Expert.Project.Supervisor.options()})
+ start_supervised!({Expert.Project.Supervisor, project})
+
+ Engine.Api.register_listener(project, self(), [project_compiled()])
+ Engine.Api.schedule_compile(project, true)
+
+ assert_receive project_compiled(), 5000
+
+ {:ok, project: project}
+ end
+
+ defp with_indexing_enabled(_) do
+ patch(Engine.Api, :index_running?, false)
+ :ok
+ end
+
+ defp with_mix_exs(%{project: project}) do
+ path = Project.mix_exs_path(project)
+ %{uri: Document.Path.ensure_uri(path)}
+ end
+
+ def build_request(path) do
+ uri = Document.Path.ensure_uri(path)
+
+ params = [
+ text_document: [uri: uri]
+ ]
+
+ with {:ok, _} <- Document.Store.open_temporary(uri),
+ {:ok, req} <- build(CodeLens, params) do
+ Convert.to_native(req)
+ end
+ end
+
+ def handle(request, project) do
+ config = Expert.Configuration.new(project: project)
+ Handlers.CodeLens.handle(request, config)
+ end
+
+ describe "code lens for mix.exs" do
+ setup [:with_mix_exs, :with_indexing_enabled]
+
+ test "emits a code lens at the project definition", %{project: project, uri: referenced_uri} do
+ mix_exs_path = Document.Path.ensure_path(referenced_uri)
+ mix_exs = File.read!(mix_exs_path)
+
+ {:ok, request} = build_request(mix_exs_path)
+ {:reply, %{result: lenses}} = handle(request, project)
+
+ assert [%Types.CodeLens{} = code_lens] = lenses
+
+ assert extract(mix_exs, code_lens.range) =~ "def project"
+ assert code_lens.command == Handlers.Commands.reindex_command(project)
+ end
+
+ test "does not emit a code lens for a project file", %{project: project} do
+ {:ok, request} =
+ project
+ |> Project.project_path()
+ |> Path.join("apps/first/lib/umbrella/first.ex")
+ |> build_request()
+
+ assert {:reply, %{result: []}} = handle(request, project)
+ end
+
+ test "does not emite a code lens for an umbrella app's mix.exs", %{project: project} do
+ {:ok, request} =
+ project
+ |> Project.project_path()
+ |> Path.join("apps/first/mix.exs")
+ |> build_request()
+
+ assert {:reply, %{result: []}} = handle(request, project)
+ end
+ end
+end
diff --git a/apps/expert/test/expert/provider/handlers/find_references_test.exs b/apps/expert/test/expert/provider/handlers/find_references_test.exs
new file mode 100644
index 00000000..f9f1622d
--- /dev/null
+++ b/apps/expert/test/expert/provider/handlers/find_references_test.exs
@@ -0,0 +1,79 @@
+defmodule Expert.Provider.Handlers.FindReferencesTest do
+ alias Expert.Proto.Convert
+ alias Expert.Protocol.Requests.FindReferences
+ alias Expert.Protocol.Responses
+ alias Expert.Provider.Handlers
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.Document.Location
+
+ import Expert.Test.Protocol.Fixtures.LspProtocol
+ import Engine.Test.Fixtures
+
+ use ExUnit.Case, async: false
+ use Patch
+
+ setup_all do
+ start_supervised(Expert.Application.document_store_child_spec())
+ :ok
+ end
+
+ setup do
+ project = project(:navigations)
+ path = file_path(project, Path.join("lib", "my_definition.ex"))
+ uri = Document.Path.ensure_uri(path)
+ {:ok, project: project, uri: uri}
+ end
+
+ def build_request(path, line, char) do
+ uri = Document.Path.ensure_uri(path)
+
+ params = [
+ text_document: [uri: uri],
+ position: [line: line, character: char]
+ ]
+
+ with {:ok, _} <- Document.Store.open_temporary(uri),
+ {:ok, req} <- build(FindReferences, params) do
+ Convert.to_native(req)
+ end
+ end
+
+ def handle(request, project) do
+ config = Expert.Configuration.new(project: project)
+ Handlers.FindReferences.handle(request, config)
+ end
+
+ describe "find references" do
+ test "returns locations that the entity returns", %{project: project, uri: uri} do
+ patch(Engine.Api, :references, fn ^project, %Analysis{document: document}, _position, _ ->
+ locations = [
+ Location.new(
+ Document.Range.new(
+ Document.Position.new(document, 1, 5),
+ Document.Position.new(document, 1, 10)
+ ),
+ Document.Path.to_uri("/path/to/file.ex")
+ )
+ ]
+
+ locations
+ end)
+
+ {:ok, request} = build_request(uri, 5, 6)
+
+ assert {:reply, %Responses.FindReferences{} = response} = handle(request, project)
+ assert [%Location{} = location] = response.result
+ assert location.uri =~ "file.ex"
+ end
+
+ test "returns nothing if the entity can't resolve it", %{project: project, uri: uri} do
+ patch(Engine.Api, :references, nil)
+
+ {:ok, request} = build_request(uri, 1, 5)
+
+ assert {:reply, %Responses.FindReferences{} = response} = handle(request, project)
+ assert response.result == nil
+ end
+ end
+end
diff --git a/apps/expert/test/expert/provider/handlers/go_to_definition_test.exs b/apps/expert/test/expert/provider/handlers/go_to_definition_test.exs
new file mode 100644
index 00000000..84f891c6
--- /dev/null
+++ b/apps/expert/test/expert/provider/handlers/go_to_definition_test.exs
@@ -0,0 +1,90 @@
+defmodule Expert.Provider.Handlers.GoToDefinitionTest do
+ alias Expert.Proto.Convert
+ alias Expert.Protocol.Requests.GoToDefinition
+ alias Expert.Provider.Handlers
+ alias Forge.Document
+ alias Forge.Document.Location
+
+ import Expert.Test.Protocol.Fixtures.LspProtocol
+ import Engine.Api.Messages
+ import Engine.Test.Fixtures
+
+ use ExUnit.Case, async: false
+
+ setup_all do
+ project = project(:navigations)
+
+ start_supervised!(Expert.Application.document_store_child_spec())
+ start_supervised!({DynamicSupervisor, Expert.Project.Supervisor.options()})
+ start_supervised!({Expert.Project.Supervisor, project})
+
+ Engine.Api.register_listener(project, self(), [
+ project_compiled(),
+ project_index_ready()
+ ])
+
+ Engine.Api.schedule_compile(project, true)
+ assert_receive project_compiled(), 5000
+ assert_receive project_index_ready(), 5000
+
+ {:ok, project: project}
+ end
+
+ defp with_referenced_file(%{project: project}) do
+ path = file_path(project, Path.join("lib", "my_definition.ex"))
+ %{uri: Document.Path.ensure_uri(path)}
+ end
+
+ def build_request(path, line, char) do
+ uri = Document.Path.ensure_uri(path)
+
+ params = [
+ text_document: [uri: uri],
+ position: [line: line, character: char]
+ ]
+
+ with {:ok, _} <- Document.Store.open_temporary(uri),
+ {:ok, req} <- build(GoToDefinition, params) do
+ Convert.to_native(req)
+ end
+ end
+
+ def handle(request, project) do
+ config = Expert.Configuration.new(project: project)
+ Handlers.GoToDefinition.handle(request, config)
+ end
+
+ describe "go to definition" do
+ setup [:with_referenced_file]
+
+ test "finds user-defined functions", %{project: project, uri: referenced_uri} do
+ uses_file_path = file_path(project, Path.join("lib", "uses.ex"))
+ {:ok, request} = build_request(uses_file_path, 4, 17)
+
+ {:reply, %{result: %Location{} = location}} = handle(request, project)
+ assert Location.uri(location) == referenced_uri
+ end
+
+ test "finds user-defined modules", %{project: project, uri: referenced_uri} do
+ uses_file_path = file_path(project, Path.join("lib", "uses.ex"))
+ {:ok, request} = build_request(uses_file_path, 4, 4)
+
+ {:reply, %{result: %Location{} = location}} = handle(request, project)
+ assert Location.uri(location) == referenced_uri
+ end
+
+ test "does not find built-in functions", %{project: project} do
+ uses_file_path = file_path(project, Path.join("lib", "uses.ex"))
+ {:ok, request} = build_request(uses_file_path, 8, 7)
+
+ {:reply, %{result: nil}} = handle(request, project)
+ end
+
+ test "does not find built-in modules", %{project: project} do
+ uses_file_path = file_path(project, Path.join("lib", "uses.ex"))
+ {:ok, request} = build_request(uses_file_path, 8, 4)
+
+ {:reply, %{result: nil}} = handle(request, project)
+ end
+ end
+end
diff --git a/apps/server/test/lexical/server/provider/handlers/hover_test.exs b/apps/expert/test/expert/provider/handlers/hover_test.exs
similarity index 94%
rename from apps/server/test/lexical/server/provider/handlers/hover_test.exs
rename to apps/expert/test/expert/provider/handlers/hover_test.exs
index 661ec549..249d6320 100644
--- a/apps/server/test/lexical/server/provider/handlers/hover_test.exs
+++ b/apps/expert/test/expert/provider/handlers/hover_test.exs
@@ -1,19 +1,19 @@
-defmodule Lexical.Server.Provider.Handlers.HoverTest do
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Proto.Convert
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Types
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.Server
- alias Lexical.Server.Provider.Handlers
- alias Lexical.Test.Fixtures
- alias Lexical.Test.Protocol.Fixtures.LspProtocol
-
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
- import Lexical.Test.RangeSupport
+defmodule Expert.Provider.Handlers.HoverTest do
+ alias Engine.Api.Messages
+ alias Engine.Test.Fixtures
+
+ alias Expert.Proto.Convert
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Types
+ alias Expert.Provider.Handlers
+ alias Expert.Test.Protocol.Fixtures.LspProtocol
+
+ alias Forge.Document
+ alias Forge.Document.Position
+
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Forge.Test.RangeSupport
require Messages
@@ -22,11 +22,11 @@ defmodule Lexical.Server.Provider.Handlers.HoverTest do
setup_all do
project = Fixtures.project()
- start_supervised!(Server.Application.document_store_child_spec())
- start_supervised!({DynamicSupervisor, Server.Project.Supervisor.options()})
- start_supervised!({Server.Project.Supervisor, project})
+ start_supervised!(Expert.Application.document_store_child_spec())
+ start_supervised!({DynamicSupervisor, Expert.Project.Supervisor.options()})
+ start_supervised!({Expert.Project.Supervisor, project})
- :ok = RemoteControl.Api.register_listener(project, self(), [Messages.project_compiled()])
+ :ok = Engine.Api.register_listener(project, self(), [Messages.project_compiled()])
assert_receive Messages.project_compiled(), 5000
{:ok, project: project}
@@ -44,12 +44,12 @@ defmodule Lexical.Server.Provider.Handlers.HoverTest do
with_tmp_file(tmp_path, code, fn ->
{:ok, compile_path} =
- RemoteControl.Mix.in_project(project, fn _ ->
+ Engine.Mix.in_project(project, fn _ ->
Mix.Project.compile_path()
end)
{:ok, modules, _} =
- RemoteControl.call(project, Kernel.ParallelCompiler, :compile_to_path, [
+ Engine.call(project, Kernel.ParallelCompiler, :compile_to_path, [
[tmp_path],
compile_path
])
@@ -58,8 +58,8 @@ defmodule Lexical.Server.Provider.Handlers.HoverTest do
fun.()
after
for module <- modules do
- path = RemoteControl.call(project, :code, :which, [module])
- RemoteControl.call(project, :code, :delete, [module])
+ path = Engine.call(project, :code, :which, [module])
+ Engine.call(project, :code, :delete, [module])
File.rm!(path)
end
end
@@ -724,7 +724,7 @@ defmodule Lexical.Server.Provider.Handlers.HoverTest do
with {position, hovered} <- pop_cursor(hovered),
{:ok, document} <- document_with_content(project, hovered),
{:ok, request} <- hover_request(document.uri, position) do
- config = Server.Configuration.new(project: project)
+ config = Expert.Configuration.new(project: project)
Handlers.Hover.handle(request, config)
end
end
diff --git a/apps/server/test/lexical/server/task_queue_test.exs b/apps/expert/test/expert/task_queue_test.exs
similarity index 91%
rename from apps/server/test/lexical/server/task_queue_test.exs
rename to apps/expert/test/expert/task_queue_test.exs
index d034a119..0dfe94cd 100644
--- a/apps/server/test/lexical/server/task_queue_test.exs
+++ b/apps/expert/test/expert/task_queue_test.exs
@@ -1,15 +1,15 @@
-defmodule Lexical.Server.TaskQueueTest do
- alias Lexical.Protocol.Notifications
- alias Lexical.Protocol.Requests
- alias Lexical.Server.Configuration
- alias Lexical.Server.Provider.Handlers
- alias Lexical.Server.TaskQueue
- alias Lexical.Server.Transport
- alias Lexical.Test.Fixtures
+defmodule Expert.TaskQueueTest do
+ alias Engine.Test.Fixtures
+ alias Expert.Configuration
+ alias Expert.Protocol.Notifications
+ alias Expert.Protocol.Requests
+ alias Expert.Provider.Handlers
+ alias Expert.TaskQueue
+ alias Expert.Transport
use ExUnit.Case
use Patch
- use Lexical.Test.EventualAssertions
+ use Forge.Test.EventualAssertions
setup_all do
{:ok, config: Configuration.new(project: Fixtures.project())}
@@ -27,7 +27,7 @@ defmodule Lexical.Server.TaskQueueTest do
def request(config, func) do
id = System.unique_integer([:positive])
- patch(Lexical.Server, :handler_for, fn _ -> {:ok, Handlers.Completion} end)
+ patch(Expert, :handler_for, fn _ -> {:ok, Handlers.Completion} end)
patch(Handlers.Completion, :handle, fn request, %Configuration{} = ^config ->
func.(request, config)
diff --git a/apps/server/test/lexical/server/transport/std_io_test.exs b/apps/expert/test/expert/transport/std_io_test.exs
similarity index 93%
rename from apps/server/test/lexical/server/transport/std_io_test.exs
rename to apps/expert/test/expert/transport/std_io_test.exs
index 85b35c96..f3e9a5a5 100644
--- a/apps/server/test/lexical/server/transport/std_io_test.exs
+++ b/apps/expert/test/expert/transport/std_io_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.Server.Transport.StdIoTest do
- alias Lexical.Protocol.JsonRpc
- alias Lexical.Server.Transport.StdIO
+defmodule Expert.Transport.StdIoTest do
+ alias Expert.Protocol.JsonRpc
+ alias Expert.Transport.StdIO
use ExUnit.Case
diff --git a/apps/server/test/support/lexical/test/completion_case.ex b/apps/expert/test/support/test/completion_case.ex
similarity index 76%
rename from apps/server/test/support/lexical/test/completion_case.ex
rename to apps/expert/test/support/test/completion_case.ex
index 9c735b74..bd8c8570 100644
--- a/apps/server/test/support/lexical/test/completion_case.ex
+++ b/apps/expert/test/support/test/completion_case.ex
@@ -1,32 +1,31 @@
-defmodule Lexical.Test.Server.CompletionCase do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.Protocol.Types.Completion.Context, as: CompletionContext
- alias Lexical.Protocol.Types.Completion.Item, as: CompletionItem
- alias Lexical.Protocol.Types.Completion.List, as: CompletionList
- alias Lexical.RemoteControl
- alias Lexical.Server
- alias Lexical.Server.CodeIntelligence.Completion
- alias Lexical.Test.CodeSigil
+defmodule Expert.Test.Expert.CompletionCase do
+ alias Expert.CodeIntelligence.Completion
+ alias Expert.Protocol.Types.Completion.Context, as: CompletionContext
+ alias Expert.Protocol.Types.Completion.Item, as: CompletionItem
+ alias Expert.Protocol.Types.Completion.List, as: CompletionList
+
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Project
+ alias Forge.Test.CodeSigil
use ExUnit.CaseTemplate
- import Lexical.Test.CursorSupport
- import Lexical.Test.Fixtures
- import RemoteControl.Api.Messages
+ import Forge.Test.CursorSupport
+ import Engine.Test.Fixtures
+ import Engine.Api.Messages
setup_all do
project = project()
- start_supervised!({DynamicSupervisor, Server.Project.Supervisor.options()})
- start_supervised!({Server.Project.Supervisor, project})
+ start_supervised!({DynamicSupervisor, Expert.Project.Supervisor.options()})
+ start_supervised!({Expert.Project.Supervisor, project})
- RemoteControl.Api.register_listener(project, self(), [
+ Engine.Api.register_listener(project, self(), [
project_compiled(),
project_index_ready()
])
- RemoteControl.Api.schedule_compile(project, true)
+ Engine.Api.schedule_compile(project, true)
assert_receive project_compiled(), 5000
assert_receive project_index_ready(), 5000
{:ok, project: project}
diff --git a/apps/expert/test/support/test/dispatch_fake.ex b/apps/expert/test/support/test/dispatch_fake.ex
new file mode 100644
index 00000000..e70aeb76
--- /dev/null
+++ b/apps/expert/test/support/test/dispatch_fake.ex
@@ -0,0 +1,26 @@
+defmodule Expert.Test.DispatchFake do
+ alias Engine.Dispatch
+
+ defmacro __using__(_) do
+ quote do
+ require unquote(__MODULE__)
+ end
+ end
+
+ # This is a macro because patch requires that you're in a unit test, and have a setup block
+ # We need to defer the patch macros until we get inside a unit test context, and the macro
+ # does that for us.
+ defmacro start do
+ quote do
+ patch(Engine.Api, :register_listener, fn _project, listener_pid, message_types ->
+ Dispatch.register_listener(listener_pid, message_types)
+ end)
+
+ patch(Engine.Api, :broadcast, fn _project, message ->
+ Dispatch.broadcast(message)
+ end)
+
+ start_supervised!(Dispatch)
+ end
+ end
+end
diff --git a/apps/expert/test/support/transport/no_op.ex b/apps/expert/test/support/transport/no_op.ex
new file mode 100644
index 00000000..e9515833
--- /dev/null
+++ b/apps/expert/test/support/transport/no_op.ex
@@ -0,0 +1,5 @@
+defmodule Expert.Test.Transport.NoOp do
+ @behaviour Expert.Transport
+
+ def write(_message), do: :ok
+end
diff --git a/apps/server/test/test_helper.exs b/apps/expert/test/test_helper.exs
similarity index 100%
rename from apps/server/test/test_helper.exs
rename to apps/expert/test/test_helper.exs
diff --git a/apps/server/.credo.exs b/apps/expert_credo/.credo.exs
similarity index 100%
rename from apps/server/.credo.exs
rename to apps/expert_credo/.credo.exs
diff --git a/apps/remote_control/test/fixtures/umbrella/apps/second/.formatter.exs b/apps/expert_credo/.formatter.exs
similarity index 100%
rename from apps/remote_control/test/fixtures/umbrella/apps/second/.formatter.exs
rename to apps/expert_credo/.formatter.exs
diff --git a/apps/expert_credo/.gitignore b/apps/expert_credo/.gitignore
new file mode 100644
index 00000000..398ef6e2
--- /dev/null
+++ b/apps/expert_credo/.gitignore
@@ -0,0 +1,26 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where third-party dependencies like ExDoc output generated docs.
+/doc/
+
+# Ignore .fetch files in case you like to edit your project deps locally.
+/.fetch
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Ignore package tarball (built via "mix hex.build").
+expert_credo-*.tar
+
+# Temporary files, for example, from tests.
+/tmp/
diff --git a/apps/lexical_credo/LICENSE b/apps/expert_credo/LICENSE
similarity index 100%
rename from apps/lexical_credo/LICENSE
rename to apps/expert_credo/LICENSE
diff --git a/apps/expert_credo/README.md b/apps/expert_credo/README.md
new file mode 100644
index 00000000..47ca71ff
--- /dev/null
+++ b/apps/expert_credo/README.md
@@ -0,0 +1,36 @@
+# Expert Credo - An Expert plugin that enables credo checks
+
+This is a plugin to expert (our first) that enables [Credo](https://github.com/rrrene/credo) to
+run whenever you type. Now you can immediately see and address any issues that credo flags when you
+make them.
+
+It's essential to have this as a test dependency, as expert runs your code in test mode by default.
+
+## Installation
+
+```elixir
+def deps do
+ [
+ {:expert_credo, "~> 0.1.0", only: [:dev, :test]}
+ ]
+end
+```
+
+When starting expert, you should see the following in your `project.log` file in the project's `.expert`
+directory:
+
+```
+info: Loaded [:expert_credo]
+```
+
+Once you see that, you should start seeing Credo diagnostics in your editor.
+
+
+## Notes on the plugin
+
+The plugin runs Credo whenever you type, and most of your settings will be respected... for the most part.
+Because Credo is designed to work with files on the disk, and the code you're editing isn't the same as
+the code that's on the disk, we are sending the file's contents to Credo via standard output. However, when
+we do this, Credo doesn't have a provision to provide the file name and the filename is lost.
+This means that there's no way for Credo to know if the file is being ignored by your project, and you'll see
+errors in it as you type. However, the errors will disappear once you save the file.
diff --git a/apps/expert_credo/lib/expert_credo.ex b/apps/expert_credo/lib/expert_credo.ex
new file mode 100644
index 00000000..f62febe5
--- /dev/null
+++ b/apps/expert_credo/lib/expert_credo.ex
@@ -0,0 +1,107 @@
+defmodule ExpertCredo do
+ @moduledoc false
+
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
+ alias Forge.Project
+
+ use Diagnostic, name: :expert_credo
+ require Logger
+
+ @doc false
+ def init do
+ with {:ok, _} <- Application.ensure_all_started(:credo) do
+ :ok
+ end
+ end
+
+ @doc false
+ def document do
+ %Document{}
+ end
+
+ @doc false
+ def diagnose(%Document{} = doc) do
+ doc_contents = Document.to_string(doc)
+
+ execution_args = [
+ "--mute-exit-status",
+ "--read-from-stdin",
+ Document.Path.absolute_from_uri(doc.uri)
+ ]
+
+ execution = Credo.Execution.build(execution_args)
+
+ with_stdin(
+ doc_contents,
+ fn ->
+ Credo.CLI.Output.Shell.suppress_output(fn ->
+ Credo.Execution.run(execution)
+ end)
+ end
+ )
+
+ diagnostics =
+ execution
+ |> Credo.Execution.get_issues()
+ |> Enum.map(&to_diagnostic/1)
+
+ {:ok, diagnostics}
+ end
+
+ @doc false
+ def diagnose(%Project{}) do
+ results =
+ Credo.Execution.build()
+ |> Credo.Execution.run()
+ |> Credo.Execution.get_issues()
+ |> Enum.map(&to_diagnostic/1)
+
+ {:ok, results}
+ end
+
+ @doc false
+ def with_stdin(stdin_contents, function) when is_function(function, 0) do
+ {:ok, stdio} = StringIO.open(stdin_contents)
+ caller = self()
+
+ spawn(fn ->
+ Process.group_leader(self(), stdio)
+ result = function.()
+ send(caller, {:result, result})
+ end)
+
+ receive do
+ {:result, result} ->
+ {:ok, result}
+ end
+ end
+
+ defp to_diagnostic(%Credo.Issue{} = issue) do
+ file_path = Document.Path.ensure_uri(issue.filename)
+
+ Diagnostic.Result.new(
+ file_path,
+ location(issue),
+ issue.message,
+ priority_to_severity(issue),
+ "Credo"
+ )
+ end
+
+ defp priority_to_severity(%Credo.Issue{priority: priority}) do
+ case Credo.Priority.to_atom(priority) do
+ :higher -> :error
+ :high -> :warning
+ :normal -> :information
+ _ -> :hint
+ end
+ end
+
+ defp location(%Credo.Issue{} = issue) do
+ case {issue.line_no, issue.column} do
+ {line, nil} -> line
+ location -> location
+ end
+ end
+end
diff --git a/apps/expert_credo/mix.exs b/apps/expert_credo/mix.exs
new file mode 100644
index 00000000..8df35506
--- /dev/null
+++ b/apps/expert_credo/mix.exs
@@ -0,0 +1,47 @@
+defmodule ExpertCredo.MixProject do
+ use Mix.Project
+ Code.require_file("../../mix_dialyzer.exs")
+ @repo_url "https://github.com/elixir-lang/expert/"
+ @version "0.5.0"
+
+ def project do
+ [
+ app: :expert_credo,
+ version: @version,
+ elixir: "~> 1.15",
+ start_permanent: Mix.env() == :prod,
+ deps: deps(),
+ docs: docs(),
+ dialyzer: Mix.Dialyzer.config(add_apps: [:jason])
+ ]
+ end
+
+ # Run "mix help compile.app" to learn about applications.
+ def application do
+ [
+ extra_applications: [:logger],
+ env: [expert_plugin: true]
+ ]
+ end
+
+ # Run "mix help deps" to learn about dependencies.
+ defp deps do
+ [
+ {:forge, path: "../forge", env: Mix.env()},
+ {:credo, "> 0.0.0", only: [:dev, :test]},
+ Mix.Dialyzer.dependency(),
+ {:jason, "> 0.0.0", optional: true},
+ {:ex_doc, "~> 0.34", optional: true, only: [:dev, :hex]}
+ ]
+ end
+
+ defp docs do
+ [
+ extras: ["README.md": [title: "Overview"]],
+ main: "readme",
+ homepage_url: @repo_url,
+ source_ref: "v#{@version}",
+ source_url: @repo_url
+ ]
+ end
+end
diff --git a/apps/lexical_credo/mix.lock b/apps/expert_credo/mix.lock
similarity index 100%
rename from apps/lexical_credo/mix.lock
rename to apps/expert_credo/mix.lock
diff --git a/apps/expert_credo/test/expert_credo_test.exs b/apps/expert_credo/test/expert_credo_test.exs
new file mode 100644
index 00000000..0a94e7e2
--- /dev/null
+++ b/apps/expert_credo/test/expert_credo_test.exs
@@ -0,0 +1,36 @@
+defmodule ExpertCredoTest do
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic.Result
+
+ import ExpertCredo
+ use ExUnit.Case
+
+ setup_all do
+ Application.ensure_all_started(:credo)
+ :ok
+ end
+
+ def doc(contents) do
+ Document.new("file:///file.ex", contents, 1)
+ end
+
+ test "reports errors on documents" do
+ has_inspect =
+ """
+ defmodule Bad do
+ def test do
+ IO.inspect("hello")
+ end
+ end
+ """
+ |> doc()
+ |> diagnose()
+
+ assert {:ok, [%Result{} = result]} = has_inspect
+ assert result.position == {3, 5}
+ assert result.message == "There should be no calls to `IO.inspect/1`."
+ assert String.ends_with?(result.uri, "/file.ex")
+ assert result.severity == :warning
+ assert result.source == "Credo"
+ end
+end
diff --git a/apps/lexical_credo/test/test_helper.exs b/apps/expert_credo/test/test_helper.exs
similarity index 100%
rename from apps/lexical_credo/test/test_helper.exs
rename to apps/expert_credo/test/test_helper.exs
diff --git a/apps/common/.credo.exs b/apps/forge/.credo.exs
similarity index 100%
rename from apps/common/.credo.exs
rename to apps/forge/.credo.exs
diff --git a/apps/forge/.formatter.exs b/apps/forge/.formatter.exs
new file mode 100644
index 00000000..a2d1d585
--- /dev/null
+++ b/apps/forge/.formatter.exs
@@ -0,0 +1,27 @@
+# Used by "mix format"
+eventual_assertions = [
+ assert_eventually: 1,
+ assert_eventually: 2,
+ refute_eventually: 1,
+ refute_eventually: 2
+]
+
+detected_assertions = [
+ assert_detected: 1,
+ assert_detected: 2,
+ refute_detected: 1,
+ refute_detected: 2
+]
+
+assertions = eventual_assertions ++ detected_assertions
+
+[
+ inputs: [
+ "{mix,.formatter}.exs",
+ "{config,test}/**/*.{ex,exs}",
+ "lib/forge/**/*.{ex,ex}",
+ "lib/mix/**/*.{ex,exs}"
+ ],
+ locals_without_parens: assertions,
+ export: [locals_without_parens: assertions]
+]
diff --git a/apps/forge/.gitignore b/apps/forge/.gitignore
new file mode 100644
index 00000000..848aea9f
--- /dev/null
+++ b/apps/forge/.gitignore
@@ -0,0 +1,26 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where third-party dependencies like ExDoc output generated docs.
+/doc/
+
+# Ignore .fetch files in case you like to edit your project deps locally.
+/.fetch
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Ignore package tarball (built via "mix hex.build").
+forge-*.tar
+
+# Temporary files, for example, from tests.
+/tmp/
diff --git a/apps/common/README.md b/apps/forge/README.md
similarity index 100%
rename from apps/common/README.md
rename to apps/forge/README.md
diff --git a/apps/common/benchmarks/snowflake_bench.exs b/apps/forge/benchmarks/snowflake_bench.exs
similarity index 100%
rename from apps/common/benchmarks/snowflake_bench.exs
rename to apps/forge/benchmarks/snowflake_bench.exs
diff --git a/apps/common/config/config.exs b/apps/forge/config/config.exs
similarity index 100%
rename from apps/common/config/config.exs
rename to apps/forge/config/config.exs
diff --git a/apps/common/lib/elixir/features.ex b/apps/forge/lib/elixir/features.ex
similarity index 97%
rename from apps/common/lib/elixir/features.ex
rename to apps/forge/lib/elixir/features.ex
index e9e984b2..e9cfee0f 100644
--- a/apps/common/lib/elixir/features.ex
+++ b/apps/forge/lib/elixir/features.ex
@@ -1,5 +1,5 @@
defmodule Elixir.Features do
- alias Lexical.VM.Versions
+ alias Forge.VM.Versions
def with_diagnostics? do
function_exported?(Code, :with_diagnostics, 1)
diff --git a/apps/forge/lib/forge.ex b/apps/forge/lib/forge.ex
new file mode 100644
index 00000000..403999ee
--- /dev/null
+++ b/apps/forge/lib/forge.ex
@@ -0,0 +1,21 @@
+defmodule Forge do
+ @moduledoc """
+ Common data structures and utilities for the Expert Language Server.
+
+ If you're building a plugin, You're probably going to want to look at the documentation
+ for core data structures like
+
+ `Forge.Project` - The Expert project structure
+
+ `Forge.Document` - A text document, given to you by the language server
+
+ `Forge.Document.Position` - A position inside a document
+
+ `Forge.Document.Range` - A range of text inside a document
+ """
+ @typedoc "A string representation of a uri"
+ @type uri :: String.t()
+
+ @typedoc "A string representation of a path on the filesystem"
+ @type path :: String.t()
+end
diff --git a/apps/common/lib/lexical/ast.ex b/apps/forge/lib/forge/ast.ex
similarity index 97%
rename from apps/common/lib/lexical/ast.ex
rename to apps/forge/lib/forge/ast.ex
index cd1db8e5..bb02f61a 100644
--- a/apps/common/lib/lexical/ast.ex
+++ b/apps/forge/lib/forge/ast.ex
@@ -1,11 +1,11 @@
-defmodule Lexical.Ast do
+defmodule Forge.Ast do
@moduledoc """
- Utilities for analyzing Lexical documents as syntax trees.
+ Utilities for analyzing Expert documents as syntax trees.
## Analysis
The preferred way to use this module is by first passing a document to
- `analyze/1`, which returns a `%Lexical.Ast.Analysis{}` struct that
+ `analyze/1`, which returns a `%Forge.Ast.Analysis{}` struct that
will have already parsed and analyzed a significant portion of the
document, thus reducing the cost of successive operations.
@@ -17,7 +17,7 @@ defmodule Lexical.Ast do
## Differences from `Code`
- This module includes functions for parsing documents (`t:Lexical.Document/0`)
+ This module includes functions for parsing documents (`t:Forge.Document/0`)
and strings into AST's that can be used with the `Sourceror` API and
include some additional metadata.
@@ -39,9 +39,9 @@ defmodule Lexical.Ast do
iex> list_with_tuple_syntax |> Code.string_to_quoted()
{:ok, [foo: :bar]}
- In contrast, `Lexical.Ast.from/1` does:
+ In contrast, `Forge.Ast.from/1` does:
- iex> list_with_kw_syntax |> Lexical.Ast.from()
+ iex> list_with_kw_syntax |> Forge.Ast.from()
{:ok,
{:__block__, [closing: [line: 1, column: 11], line: 1, column: 1],
[
@@ -51,7 +51,7 @@ defmodule Lexical.Ast do
]
]}}
- iex> list_with_tuple_syntax |> Lexical.Ast.from()
+ iex> list_with_tuple_syntax |> Forge.Ast.from()
{:ok,
{:__block__, [closing: [line: 1, column: 14], line: 1, column: 1],
[
@@ -66,12 +66,12 @@ defmodule Lexical.Ast do
"""
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.Document.Edit
+ alias Forge.Document.Position
+ alias Forge.Document.Range
alias Future.Code, as: Code
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.Document.Edit
- alias Lexical.Document.Position
- alias Lexical.Document.Range
alias Sourceror.Zipper
require Logger
diff --git a/apps/common/lib/lexical/ast/analysis.ex b/apps/forge/lib/forge/ast/analysis.ex
similarity index 97%
rename from apps/common/lib/lexical/ast/analysis.ex
rename to apps/forge/lib/forge/ast/analysis.ex
index 9d87a043..e2ecd2a8 100644
--- a/apps/common/lib/lexical/ast/analysis.ex
+++ b/apps/forge/lib/forge/ast/analysis.ex
@@ -1,20 +1,20 @@
-defmodule Lexical.Ast.Analysis do
+defmodule Forge.Ast.Analysis do
@moduledoc """
A data structure representing an analyzed AST.
- See `Lexical.Ast.analyze/1`.
+ See `Forge.Ast.analyze/1`.
"""
- alias Lexical.Ast.Analysis.Alias
- alias Lexical.Ast.Analysis.Import
- alias Lexical.Ast.Analysis.Require
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Ast.Analysis.State
- alias Lexical.Ast.Analysis.Use
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Identifier
+ alias Forge.Ast.Analysis.Alias
+ alias Forge.Ast.Analysis.Import
+ alias Forge.Ast.Analysis.Require
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Ast.Analysis.State
+ alias Forge.Ast.Analysis.Use
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Identifier
alias Sourceror.Zipper
defstruct [:ast, :document, :parse_error, scopes: [], comments_by_line: %{}, valid?: true]
diff --git a/apps/forge/lib/forge/ast/analysis/alias.ex b/apps/forge/lib/forge/ast/analysis/alias.ex
new file mode 100644
index 00000000..ca46f60b
--- /dev/null
+++ b/apps/forge/lib/forge/ast/analysis/alias.ex
@@ -0,0 +1,52 @@
+defmodule Forge.Ast.Analysis.Alias do
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+
+ defstruct [:module, :as, :range, explicit?: true]
+
+ @type t :: %__MODULE__{
+ module: [atom],
+ as: module(),
+ range: Range.t() | nil
+ }
+
+ def explicit(%Document{} = document, ast, module, as) when is_list(module) do
+ range = range_for_ast(document, ast, module, as)
+ %__MODULE__{module: module, as: as, range: range}
+ end
+
+ def implicit(%Document{} = document, ast, module, as) when is_list(module) do
+ range = implicit_range(document, ast)
+ %__MODULE__{module: module, as: as, range: range, explicit?: false}
+ end
+
+ def to_module(%__MODULE__{} = alias) do
+ Module.concat(alias.module)
+ end
+
+ @implicit_aliases [:__MODULE__, :"@for", :"@protocol"]
+ defp range_for_ast(document, ast, _alias, as) when as in @implicit_aliases do
+ implicit_range(document, ast)
+ end
+
+ defp range_for_ast(document, ast, _alias, _as) do
+ # All other kinds of aliases defined with the `alias` special form
+ Ast.Range.get(ast, document)
+ end
+
+ defp implicit_range(%Document{} = document, ast) do
+ # There are kinds of aliases that are automatically generated by elixir
+ # such as __MODULE__, these don't really have any code that defines them,
+ with [line: line, column: _] <- Sourceror.get_start_position(ast),
+ {:ok, line_text} <- Document.fetch_text_at(document, line) do
+ line_length = String.length(line_text)
+ alias_start = Position.new(document, line, line_length)
+ Range.new(alias_start, alias_start)
+ else
+ _ ->
+ nil
+ end
+ end
+end
diff --git a/apps/forge/lib/forge/ast/analysis/import.ex b/apps/forge/lib/forge/ast/analysis/import.ex
new file mode 100644
index 00000000..d541cf2b
--- /dev/null
+++ b/apps/forge/lib/forge/ast/analysis/import.ex
@@ -0,0 +1,92 @@
+defmodule Forge.Ast.Analysis.Import do
+ alias Forge.Ast
+ alias Forge.Document
+ alias Forge.Document.Range
+
+ defstruct module: nil, selector: :all, range: nil, explicit?: true
+
+ @type function_name :: atom()
+ @type function_arity :: {function_name(), arity()}
+ @type selector ::
+ :functions
+ | :macros
+ | :sigils
+ | [only: [function_arity()]]
+ | [except: [function_arity()]]
+ @type t :: %{
+ module: module(),
+ selector: selector(),
+ line: non_neg_integer()
+ }
+ def new(%Document{} = document, ast, module) do
+ %__MODULE__{module: module, range: Ast.Range.get(ast, document)}
+ end
+
+ def new(%Document{} = document, ast, module, selector) do
+ %__MODULE__{
+ module: module,
+ selector: expand_selector(selector),
+ range: Ast.Range.get(ast, document)
+ }
+ end
+
+ def implicit(%Range{} = range, module) do
+ %__MODULE__{module: module, range: range, explicit?: false}
+ end
+
+ defp expand_selector(selectors) when is_list(selectors) do
+ selectors =
+ Enum.reduce(selectors, [], fn
+ {{:__block__, _, [type]}, {:__block__, _, [selector]}}, acc
+ when type in [:only, :except] ->
+ expanded =
+ case selector do
+ :functions ->
+ :functions
+
+ :macros ->
+ :macros
+
+ :sigils ->
+ :sigils
+
+ keyword when is_list(keyword) ->
+ keyword
+ |> Enum.reduce([], &expand_function_keywords/2)
+ |> Enum.reverse()
+
+ _ ->
+ # they're likely in the middle of typing in something, and have produced an
+ # invalid import
+ []
+ end
+
+ [{type, expanded} | acc]
+
+ _, acc ->
+ acc
+ end)
+
+ if selectors == [] do
+ :all
+ else
+ selectors
+ end
+ end
+
+ # If the selectors is not valid, like: `import SomeModule, o `, we default to :all
+ defp expand_selector(_) do
+ :all
+ end
+
+ defp expand_function_keywords(
+ {{:__block__, _, [function_name]}, {:__block__, _, [arity]}},
+ acc
+ )
+ when is_atom(function_name) and is_number(arity) do
+ [{function_name, arity} | acc]
+ end
+
+ defp expand_function_keywords(_ignored, acc),
+ do: acc
+end
diff --git a/apps/forge/lib/forge/ast/analysis/require.ex b/apps/forge/lib/forge/ast/analysis/require.ex
new file mode 100644
index 00000000..9d0240f0
--- /dev/null
+++ b/apps/forge/lib/forge/ast/analysis/require.ex
@@ -0,0 +1,10 @@
+defmodule Forge.Ast.Analysis.Require do
+ alias Forge.Ast
+ alias Forge.Document
+ defstruct [:module, :as, :range]
+
+ def new(%Document{} = document, ast, module, as \\ nil) when is_list(module) do
+ range = Ast.Range.get(ast, document)
+ %__MODULE__{module: module, as: as || module, range: range}
+ end
+end
diff --git a/apps/common/lib/lexical/ast/analysis/scope.ex b/apps/forge/lib/forge/ast/analysis/scope.ex
similarity index 93%
rename from apps/common/lib/lexical/ast/analysis/scope.ex
rename to apps/forge/lib/forge/ast/analysis/scope.ex
index 26f9ebd2..6304127c 100644
--- a/apps/common/lib/lexical/ast/analysis/scope.ex
+++ b/apps/forge/lib/forge/ast/analysis/scope.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Ast.Analysis.Scope do
- alias Lexical.Ast.Analysis.Alias
- alias Lexical.Document.Position
- alias Lexical.Document.Range
+defmodule Forge.Ast.Analysis.Scope do
+ alias Forge.Ast.Analysis.Alias
+ alias Forge.Document.Position
+ alias Forge.Document.Range
defstruct [
:id,
diff --git a/apps/forge/lib/forge/ast/analysis/state.ex b/apps/forge/lib/forge/ast/analysis/state.ex
new file mode 100644
index 00000000..5972d3ad
--- /dev/null
+++ b/apps/forge/lib/forge/ast/analysis/state.ex
@@ -0,0 +1,180 @@
+defmodule Forge.Ast.Analysis.State do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Alias
+ alias Forge.Ast.Analysis.Import
+ alias Forge.Ast.Analysis.Require
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Ast.Analysis.Use
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+
+ defstruct [:document, scopes: [], visited: %{}]
+
+ def new(%Document{} = document) do
+ state = %__MODULE__{document: document}
+
+ scope =
+ document
+ |> global_range()
+ |> Scope.global()
+
+ push_scope(state, scope)
+ end
+
+ def current_scope(%__MODULE__{scopes: [scope | _]}), do: scope
+
+ def current_module(%__MODULE__{} = state) do
+ current_scope(state).module
+ end
+
+ def push_scope(%__MODULE__{} = state, %Scope{} = scope) do
+ Map.update!(state, :scopes, &[scope | &1])
+ end
+
+ def push_scope(%__MODULE__{} = state, id, %Range{} = range, module) when is_list(module) do
+ scope =
+ state
+ |> current_scope()
+ |> Scope.new(id, range, module)
+
+ push_scope(state, scope)
+ end
+
+ def push_scope_for(%__MODULE__{} = state, quoted, %Range{} = range, module) do
+ module = module || current_module(state)
+
+ id = Analysis.scope_id(quoted)
+ push_scope(state, id, range, module)
+ end
+
+ def push_scope_for(%__MODULE__{} = state, quoted, module) do
+ range = get_range(quoted, state.document)
+ push_scope_for(state, quoted, range, module)
+ end
+
+ def maybe_push_scope_for(%__MODULE__{} = state, quoted) do
+ case get_range(quoted, state.document) do
+ %Range{} = range ->
+ push_scope_for(state, quoted, range, nil)
+
+ nil ->
+ state
+ end
+ end
+
+ def pop_scope(%__MODULE__{scopes: [scope | rest]} = state) do
+ %__MODULE__{state | scopes: rest, visited: Map.put(state.visited, scope.id, scope)}
+ end
+
+ def push_alias(%__MODULE__{} = state, %Alias{} = alias) do
+ update_current_scope(state, fn %Scope{} = scope ->
+ [prefix | rest] = alias.module
+
+ alias =
+ case Scope.fetch_alias_with_prefix(scope, prefix) do
+ {:ok, %Alias{} = existing_alias} ->
+ %Alias{alias | module: existing_alias.module ++ rest}
+
+ :error ->
+ alias
+ end
+
+ Map.update!(scope, :aliases, &[alias | &1])
+ end)
+ end
+
+ def push_import(%__MODULE__{} = state, %Import{} = import) do
+ update_current_scope(state, fn %Scope{} = scope ->
+ Map.update!(scope, :imports, &[import | &1])
+ end)
+ end
+
+ def push_require(%__MODULE__{} = state, %Require{} = require) do
+ update_current_scope(state, fn %Scope{} = scope ->
+ Map.update!(scope, :requires, &[require | &1])
+ end)
+ end
+
+ def push_use(%__MODULE__{} = state, %Use{} = use) do
+ update_current_scope(state, fn %Scope{} = scope ->
+ Map.update!(scope, :uses, &[use | &1])
+ end)
+ end
+
+ defp update_current_scope(%__MODULE__{} = state, fun) do
+ update_in(state, [Access.key(:scopes), Access.at!(0)], fn %Scope{} = scope ->
+ fun.(scope)
+ end)
+ end
+
+ # if there is no code after a stab operator, then the end position
+ # it gives us can be in the middle of the line, as it's derived from
+ # the start of some entity on the last line. So we increment the line
+ # by one, and that should be the end of the stab block
+ defp get_range({:->, _, _} = quoted, %Document{} = document) do
+ start_pos = get_start_position(quoted)
+
+ case Sourceror.get_end_position(quoted, line: -1, column: -1) do
+ [line: -1, column: -1] ->
+ nil
+
+ [line: line, column: 1] ->
+ Range.new(
+ Position.new(document, start_pos[:line], start_pos[:column]),
+ Position.new(document, line + 1, 1)
+ )
+
+ [line: line, column: _] ->
+ Range.new(
+ Position.new(document, start_pos[:line], start_pos[:column]),
+ Position.new(document, line + 1, 1)
+ )
+ end
+ end
+
+ defp get_range(quoted, %Document{} = document) do
+ start_pos = get_start_position(quoted)
+
+ case Sourceror.get_end_position(quoted, line: -1, column: -1) do
+ [line: -1, column: -1] ->
+ nil
+
+ [line: end_line, column: end_column] ->
+ Range.new(
+ Position.new(document, start_pos[:line], start_pos[:column]),
+ Position.new(document, end_line, end_column)
+ )
+ end
+ end
+
+ defp global_range(%Document{} = document) do
+ num_lines = Document.size(document)
+
+ Range.new(
+ Position.new(document, 1, 1),
+ Position.new(document, num_lines + 1, 1)
+ )
+ end
+
+ defp get_start_position({_, metadata, _} = ast) do
+ case Keyword.fetch(metadata, :do) do
+ {:ok, [line: line, column: column]} ->
+ # add 2 to position us after the do keyword
+ [line: line, column: column + 2]
+
+ _ ->
+ Sourceror.get_start_position(ast)
+ end
+ end
+
+ defp get_start_position({block_meta, _rest}) do
+ case Sourceror.get_start_position(block_meta) do
+ [line: line, column: column] ->
+ [line: line, column: column + 2]
+
+ other ->
+ other
+ end
+ end
+end
diff --git a/apps/forge/lib/forge/ast/analysis/use.ex b/apps/forge/lib/forge/ast/analysis/use.ex
new file mode 100644
index 00000000..7891e0f1
--- /dev/null
+++ b/apps/forge/lib/forge/ast/analysis/use.ex
@@ -0,0 +1,10 @@
+defmodule Forge.Ast.Analysis.Use do
+ alias Forge.Ast
+ alias Forge.Document
+ defstruct [:module, :range, :opts]
+
+ def new(%Document{} = document, ast, module, opts) do
+ range = Ast.Range.get(ast, document)
+ %__MODULE__{range: range, module: module, opts: opts}
+ end
+end
diff --git a/apps/common/lib/lexical/ast/detection.ex b/apps/forge/lib/forge/ast/detection.ex
similarity index 95%
rename from apps/common/lib/lexical/ast/detection.ex
rename to apps/forge/lib/forge/ast/detection.ex
index b248b928..9b64fee3 100644
--- a/apps/common/lib/lexical/ast/detection.ex
+++ b/apps/forge/lib/forge/ast/detection.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Ast.Detection do
+defmodule Forge.Ast.Detection do
@moduledoc """
A behavior for context detection
@@ -8,10 +8,10 @@ defmodule Lexical.Ast.Detection do
Note: a given context might be detected by more than one module.
"""
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Document.Position
- alias Lexical.Document.Range
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Document.Position
+ alias Forge.Document.Range
@doc """
Returns true if the given position is detected by the current module
diff --git a/apps/forge/lib/forge/ast/detection/alias.ex b/apps/forge/lib/forge/ast/detection/alias.ex
new file mode 100644
index 00000000..0ba863a0
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/alias.ex
@@ -0,0 +1,64 @@
+defmodule Forge.Ast.Detection.Alias do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Tokens
+ alias Forge.Document.Position
+
+ use Detection
+
+ @doc """
+ Recognizes an alias at the current position
+
+ Aliases are complicated, especially if we're trying to find out if we're in
+ them from the current cursor position moving backwards.
+ I'll try to describe the state machine below.
+ First off, if we're outside of a } on the current line, we cannot be in an alias, so that
+ halts with false.
+ Similarly an alias on the current line is also simple, we just backtrack until we see the alias identifier.
+ However, if we're on the current line, and see an EOL, we set that as our accumulator, then we get
+ to the previous line, we see if it ends in a comma. If not, we can't be in an alias. If it does, we keep
+ backtracking until we hit the alias keyword.
+ So basically, if we hit an EOL, and the previous token isn't an open curly or a comma, we stop, otherwise
+ we backtrack until we hit the alias keyword
+ """
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ result =
+ analysis.document
+ |> Tokens.prefix_stream(position)
+ |> Stream.with_index()
+ |> Enum.reduce_while(false, fn
+ {{:curly, :"{", _}, _index}, :eol ->
+ {:cont, false}
+
+ {{:comma, _, _}, _index}, :eol ->
+ {:cont, false}
+
+ {{:eol, _, _}, _index}, _acc ->
+ {:cont, :eol}
+
+ {{_, _, _}, _}, :eol ->
+ {:halt, false}
+
+ {{:curly, :"}", _}, _index}, _ ->
+ {:halt, false}
+
+ {{:identifier, ~c"alias", _}, 0}, _ ->
+ # there is nothing after the alias directive, so we're not
+ # inside the context *yet*
+
+ {:halt, false}
+
+ {{:identifier, ~c"alias", _}, _index}, _ ->
+ {:halt, true}
+
+ _, _ ->
+ {:cont, false}
+ end)
+
+ case result do
+ b when is_boolean(b) -> b
+ :eol -> false
+ end
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/bitstring.ex b/apps/forge/lib/forge/ast/detection/bitstring.ex
new file mode 100644
index 00000000..13f0d3b2
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/bitstring.ex
@@ -0,0 +1,26 @@
+defmodule Forge.Ast.Detection.Bitstring do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Tokens
+ alias Forge.Document
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ document = analysis.document
+ Document.fragment(document, Position.new(document, position.line, 1), position)
+
+ document
+ |> Tokens.prefix_stream(position)
+ |> Enum.reduce_while(
+ false,
+ fn
+ {:operator, :">>", _}, _ -> {:halt, false}
+ {:operator, :"<<", _}, _ -> {:halt, true}
+ _, _ -> {:cont, false}
+ end
+ )
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/comment.ex b/apps/forge/lib/forge/ast/detection/comment.ex
new file mode 100644
index 00000000..f3dd652c
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/comment.ex
@@ -0,0 +1,8 @@
+defmodule Forge.Ast.Detection.Comment do
+ alias Forge.Ast.Analysis
+ alias Forge.Document.Position
+
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ Analysis.commented?(analysis, position)
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/directive.ex b/apps/forge/lib/forge/ast/detection/directive.ex
new file mode 100644
index 00000000..e0243d8e
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/directive.ex
@@ -0,0 +1,21 @@
+defmodule Forge.Ast.Detection.Directive do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Tokens
+ alias Forge.Document.Position
+
+ def detected?(%Analysis{} = analysis, %Position{} = position, directive_type) do
+ analysis.document
+ |> Tokens.prefix_stream(position)
+ |> Enum.to_list()
+ |> Enum.reduce_while(false, fn
+ {:identifier, ^directive_type, _}, _ ->
+ {:halt, true}
+
+ {:eol, _, _}, _ ->
+ {:halt, false}
+
+ _, _ ->
+ {:cont, false}
+ end)
+ end
+end
diff --git a/apps/common/lib/lexical/ast/detection/function_capture.ex b/apps/forge/lib/forge/ast/detection/function_capture.ex
similarity index 78%
rename from apps/common/lib/lexical/ast/detection/function_capture.ex
rename to apps/forge/lib/forge/ast/detection/function_capture.ex
index 25ef5006..85d5d726 100644
--- a/apps/common/lib/lexical/ast/detection/function_capture.ex
+++ b/apps/forge/lib/forge/ast/detection/function_capture.ex
@@ -1,8 +1,8 @@
-defmodule Lexical.Ast.Detection.FunctionCapture do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Tokens
- alias Lexical.Document.Position
+defmodule Forge.Ast.Detection.FunctionCapture do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Tokens
+ alias Forge.Document.Position
use Detection
diff --git a/apps/forge/lib/forge/ast/detection/import.ex b/apps/forge/lib/forge/ast/detection/import.ex
new file mode 100644
index 00000000..9684e75f
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/import.ex
@@ -0,0 +1,13 @@
+defmodule Forge.Ast.Detection.Import do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Detection.Directive
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ Directive.detected?(analysis, position, ~c"import")
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/module_attribute.ex b/apps/forge/lib/forge/ast/detection/module_attribute.ex
new file mode 100644
index 00000000..38e128ce
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/module_attribute.ex
@@ -0,0 +1,16 @@
+defmodule Forge.Ast.Detection.ModuleAttribute do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ ancestor_is_attribute?(analysis, position)
+ end
+
+ def detected?(%Analysis{} = analysis, %Position{} = position, name) do
+ ancestor_is_attribute?(analysis, position, name)
+ end
+end
diff --git a/apps/common/lib/lexical/ast/detection/pipe.ex b/apps/forge/lib/forge/ast/detection/pipe.ex
similarity index 77%
rename from apps/common/lib/lexical/ast/detection/pipe.ex
rename to apps/forge/lib/forge/ast/detection/pipe.ex
index f24f674b..8ca61f93 100644
--- a/apps/common/lib/lexical/ast/detection/pipe.ex
+++ b/apps/forge/lib/forge/ast/detection/pipe.ex
@@ -1,8 +1,8 @@
-defmodule Lexical.Ast.Detection.Pipe do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Tokens
- alias Lexical.Document.Position
+defmodule Forge.Ast.Detection.Pipe do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Tokens
+ alias Forge.Document.Position
use Detection
diff --git a/apps/forge/lib/forge/ast/detection/require.ex b/apps/forge/lib/forge/ast/detection/require.ex
new file mode 100644
index 00000000..f989f1ea
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/require.ex
@@ -0,0 +1,13 @@
+defmodule Forge.Ast.Detection.Require do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Detection.Directive
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ Directive.detected?(analysis, position, ~c"require")
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/spec.ex b/apps/forge/lib/forge/ast/detection/spec.ex
new file mode 100644
index 00000000..7503a79a
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/spec.ex
@@ -0,0 +1,12 @@
+defmodule Forge.Ast.Detection.Spec do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ ancestor_is_spec?(analysis, position)
+ end
+end
diff --git a/apps/common/lib/lexical/ast/detection/string.ex b/apps/forge/lib/forge/ast/detection/string.ex
similarity index 91%
rename from apps/common/lib/lexical/ast/detection/string.ex
rename to apps/forge/lib/forge/ast/detection/string.ex
index abfb5133..adfeda6d 100644
--- a/apps/common/lib/lexical/ast/detection/string.ex
+++ b/apps/forge/lib/forge/ast/detection/string.ex
@@ -1,10 +1,10 @@
-defmodule Lexical.Ast.Detection.String do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Detection
- alias Lexical.Document.Position
- alias Lexical.Document.Position
- alias Lexical.Document.Range
+defmodule Forge.Ast.Detection.String do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Document.Position
+ alias Forge.Document.Position
+ alias Forge.Document.Range
use Detection
diff --git a/apps/forge/lib/forge/ast/detection/struct_field_key.ex b/apps/forge/lib/forge/ast/detection/struct_field_key.ex
new file mode 100644
index 00000000..4d3d2635
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/struct_field_key.ex
@@ -0,0 +1,21 @@
+defmodule Forge.Ast.Detection.StructFieldKey do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ cursor_path = Ast.cursor_path(analysis, position)
+
+ match?(
+ # in the key position, the cursor will always be followed by the
+ # map node because, in any other case, there will minimally be a
+ # 2-element key-value tuple containing the cursor
+ [{:__cursor__, _, _}, {:%{}, _, _}, {:%, _, _} | _],
+ cursor_path
+ )
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/struct_field_value.ex b/apps/forge/lib/forge/ast/detection/struct_field_value.ex
new file mode 100644
index 00000000..3584190e
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/struct_field_value.ex
@@ -0,0 +1,11 @@
+defmodule Forge.Ast.Detection.StructFieldValue do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection.StructFieldKey
+ alias Forge.Ast.Detection.StructFields
+ alias Forge.Document.Position
+
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ StructFields.detected?(analysis, position) and
+ not StructFieldKey.detected?(analysis, position)
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/struct_fields.ex b/apps/forge/lib/forge/ast/detection/struct_fields.ex
new file mode 100644
index 00000000..a1bfb8b8
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/struct_fields.ex
@@ -0,0 +1,15 @@
+defmodule Forge.Ast.Detection.StructFields do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ analysis.document
+ |> Ast.cursor_path(position)
+ |> Enum.any?(&match?({:%, _, _}, &1))
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/struct_reference.ex b/apps/forge/lib/forge/ast/detection/struct_reference.ex
new file mode 100644
index 00000000..2deec733
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/struct_reference.ex
@@ -0,0 +1,43 @@
+defmodule Forge.Ast.Detection.StructReference do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Tokens
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ case Ast.cursor_context(analysis, position) do
+ {:ok, {:struct, []}} ->
+ false
+
+ {:ok, {:struct, _}} ->
+ true
+
+ {:ok, {:local_or_var, [?_ | _rest] = possible_module_struct}} ->
+ # a reference to `%__MODULE`, often in a function head, as in
+ # def foo(%__)
+
+ starts_with_percent? =
+ analysis.document
+ |> Tokens.prefix_stream(position)
+ |> Enum.take(2)
+ |> Enum.any?(fn
+ {:percent, :%, _} -> true
+ _ -> false
+ end)
+
+ starts_with_percent? and possible_dunder_module(possible_module_struct) and
+ (ancestor_is_def?(analysis, position) or ancestor_is_type?(analysis, position))
+
+ _ ->
+ false
+ end
+ end
+
+ def possible_dunder_module(charlist) do
+ String.starts_with?("__MODULE__", to_string(charlist))
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/type.ex b/apps/forge/lib/forge/ast/detection/type.ex
new file mode 100644
index 00000000..43ee1cbf
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/type.ex
@@ -0,0 +1,12 @@
+defmodule Forge.Ast.Detection.Type do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ ancestor_is_type?(analysis, position)
+ end
+end
diff --git a/apps/forge/lib/forge/ast/detection/use.ex b/apps/forge/lib/forge/ast/detection/use.ex
new file mode 100644
index 00000000..0057b112
--- /dev/null
+++ b/apps/forge/lib/forge/ast/detection/use.ex
@@ -0,0 +1,13 @@
+defmodule Forge.Ast.Detection.Use do
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Detection.Directive
+ alias Forge.Document.Position
+
+ use Detection
+
+ @impl Detection
+ def detected?(%Analysis{} = analysis, %Position{} = position) do
+ Directive.detected?(analysis, position, ~c"use")
+ end
+end
diff --git a/apps/common/lib/lexical/ast/env.ex b/apps/forge/lib/forge/ast/env.ex
similarity index 96%
rename from apps/common/lib/lexical/ast/env.ex
rename to apps/forge/lib/forge/ast/env.ex
index bd36c956..b25dcc5e 100644
--- a/apps/common/lib/lexical/ast/env.ex
+++ b/apps/forge/lib/forge/ast/env.ex
@@ -1,16 +1,16 @@
-defmodule Lexical.Ast.Env do
+defmodule Forge.Ast.Env do
@moduledoc """
Representation of the environment at a given position in a document.
"""
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Ast.Detection
- alias Lexical.Ast.Tokens
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Project
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Analysis.Scope
+ alias Forge.Ast.Detection
+ alias Forge.Ast.Tokens
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Project
defstruct [
:project,
diff --git a/apps/forge/lib/forge/ast/module.ex b/apps/forge/lib/forge/ast/module.ex
new file mode 100644
index 00000000..0a4ad54d
--- /dev/null
+++ b/apps/forge/lib/forge/ast/module.ex
@@ -0,0 +1,99 @@
+defmodule Forge.Ast.Module do
+ @moduledoc """
+ Module utilities
+ """
+
+ @doc """
+ Formats a module name as a string.
+ """
+ @spec name(module() | Macro.t() | String.t()) :: String.t()
+ def name([{:__MODULE__, _, _} | rest]) do
+ [__MODULE__ | rest]
+ |> Module.concat()
+ |> name()
+ end
+
+ def name(module_name) when is_list(module_name) do
+ module_name
+ |> Module.concat()
+ |> name()
+ end
+
+ def name(module_name) when is_binary(module_name) do
+ module_name
+ end
+
+ def name(module_name) when is_atom(module_name) do
+ module_name
+ |> inspect()
+ |> name()
+ end
+
+ @doc """
+ local module name is the last part of a module name
+
+ ## Examples:
+ iex> local_name('Forge.Ast.Module')
+ "Module"
+ """
+ def local_name(entity) when is_list(entity) do
+ entity
+ |> to_string()
+ |> local_name()
+ end
+
+ def local_name(entity) when is_binary(entity) do
+ entity
+ |> String.split(".")
+ |> List.last()
+ end
+
+ @doc """
+ Splits a module into is parts, but handles erlang modules
+
+ Module.split will explode violently when called on an erlang module. This
+ implementation will tell you which kind of module it has split, and return the
+ pieces. You can also use the options to determine if the pieces are returned as
+ strings or atoms
+
+ Options:
+ `as` :atoms or :binaries. Default is :binary. Determines what type the elements
+ of the returned list are.
+
+ Returns:
+ A tuple where the first element is either `:elixir` or `:erlang`, which tells you
+ the kind of module that has been split. The second element is a list of the
+ module's components. Note: Erlang modules will only ever have a single component.
+ """
+ @type split_opt :: {:as, :binaries | :atoms}
+ @type split_opts :: [split_opt()]
+ @type split_return :: {:elixir | :erlang, [String.t()] | [atom()]}
+
+ @spec safe_split(module()) :: split_return()
+ @spec safe_split(module(), split_opts()) :: split_return()
+ def safe_split(module, opts \\ [])
+
+ def safe_split(module, opts) when is_atom(module) do
+ string_name = Atom.to_string(module)
+
+ {type, split_module} =
+ case String.split(string_name, ".") do
+ ["Elixir" | rest] ->
+ {:elixir, rest}
+
+ [_erlang_module] = module ->
+ {:erlang, module}
+ end
+
+ split_module =
+ case Keyword.get(opts, :as, :binaries) do
+ :binaries ->
+ split_module
+
+ :atoms ->
+ Enum.map(split_module, &String.to_atom/1)
+ end
+
+ {type, split_module}
+ end
+end
diff --git a/apps/forge/lib/forge/ast/range.ex b/apps/forge/lib/forge/ast/range.ex
new file mode 100644
index 00000000..e5190d4f
--- /dev/null
+++ b/apps/forge/lib/forge/ast/range.ex
@@ -0,0 +1,48 @@
+defmodule Forge.Ast.Range do
+ @moduledoc """
+ Utilities for extracting ranges from ast nodes
+ """
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+
+ @spec fetch(Macro.t(), Document.t()) :: {:ok, Range.t()} | :error
+ def fetch(ast, %Document{} = document) do
+ case Sourceror.get_range(ast) do
+ %{start: start_pos, end: end_pos} ->
+ [line: start_line, column: start_column] = start_pos
+ [line: end_line, column: end_column] = end_pos
+
+ range =
+ Range.new(
+ Position.new(document, start_line, start_column),
+ Position.new(document, end_line, end_column)
+ )
+
+ {:ok, range}
+
+ _ ->
+ :error
+ end
+ end
+
+ @spec fetch!(Macro.t(), Document.t()) :: Range.t()
+ def fetch!(ast, %Document{} = document) do
+ case fetch(ast, document) do
+ {:ok, range} ->
+ range
+
+ :error ->
+ raise ArgumentError,
+ message: "Could not get a range for #{inspect(ast)} in #{document.path}"
+ end
+ end
+
+ @spec get(Macro.t(), Document.t()) :: Range.t() | nil
+ def get(ast, %Document{} = document) do
+ case fetch(ast, document) do
+ {:ok, range} -> range
+ :error -> nil
+ end
+ end
+end
diff --git a/apps/common/lib/lexical/ast/tokens.ex b/apps/forge/lib/forge/ast/tokens.ex
similarity index 98%
rename from apps/common/lib/lexical/ast/tokens.ex
rename to apps/forge/lib/forge/ast/tokens.ex
index f8c3494c..2feb234c 100644
--- a/apps/common/lib/lexical/ast/tokens.ex
+++ b/apps/forge/lib/forge/ast/tokens.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Ast.Tokens do
- alias Lexical.Document
- alias Lexical.Document.Position
+defmodule Forge.Ast.Tokens do
+ alias Forge.Document
+ alias Forge.Document.Position
@doc """
Returns a stream of tokens starting at the given position and working backwards through
diff --git a/apps/common/lib/lexical/code_unit.ex b/apps/forge/lib/forge/code_unit.ex
similarity index 99%
rename from apps/common/lib/lexical/code_unit.ex
rename to apps/forge/lib/forge/code_unit.ex
index 63ef7ba5..ce2d67dc 100644
--- a/apps/common/lib/lexical/code_unit.ex
+++ b/apps/forge/lib/forge/code_unit.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.CodeUnit do
+defmodule Forge.CodeUnit do
@moduledoc """
Code unit and offset conversions.
diff --git a/apps/common/lib/lexical/convertible.ex b/apps/forge/lib/forge/convertible.ex
similarity index 88%
rename from apps/common/lib/lexical/convertible.ex
rename to apps/forge/lib/forge/convertible.ex
index affb74c0..3a58df3f 100644
--- a/apps/common/lib/lexical/convertible.ex
+++ b/apps/forge/lib/forge/convertible.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Convertible.Helpers do
+defmodule Forge.Convertible.Helpers do
@moduledoc false
- alias Lexical.Document
+ alias Forge.Document
def apply(%{} = map, func) do
result =
@@ -83,7 +83,7 @@ defmodule Lexical.Convertible.Helpers do
end
end
-defprotocol Lexical.Convertible do
+defprotocol Forge.Convertible do
@moduledoc """
A protocol that details conversions to and from Language Server idioms
@@ -94,14 +94,14 @@ defprotocol Lexical.Convertible do
UTF-16 code unit) offsets. If not handled centrally, this leads to a profusion of conversion code throughout the language
server codebase.
- That's where this protocol comes in. Using this protocol allows us to define native `Lexical.Document.Position` and
- `Lexical.Document.Range` structs and have them automatically convert into their Language Server counterparts, centralizing
+ That's where this protocol comes in. Using this protocol allows us to define native `Forge.Document.Position` and
+ `Forge.Document.Range` structs and have them automatically convert into their Language Server counterparts, centralizing
the conversion logic in a single pace.
Note: You do not need to do conversions manually, If you define a new type, it is sufficient to implement this
protocol for your new type
"""
- alias Lexical.Document
+ alias Forge.Document
@fallback_to_any true
@@ -133,9 +133,9 @@ defprotocol Lexical.Convertible do
def to_lsp(t)
end
-defimpl Lexical.Convertible, for: List do
- alias Lexical.Convertible
- alias Lexical.Convertible.Helpers
+defimpl Forge.Convertible, for: List do
+ alias Forge.Convertible
+ alias Forge.Convertible.Helpers
def to_native(l, context_document) do
case Helpers.apply(l, &Convertible.to_native/2, context_document) do
@@ -152,9 +152,9 @@ defimpl Lexical.Convertible, for: List do
end
end
-defimpl Lexical.Convertible, for: Map do
- alias Lexical.Convertible
- alias Lexical.Convertible.Helpers
+defimpl Forge.Convertible, for: Map do
+ alias Forge.Convertible
+ alias Forge.Convertible.Helpers
def to_native(map, context_document) do
case Helpers.apply(map, &Convertible.to_native/2, context_document) do
@@ -168,10 +168,10 @@ defimpl Lexical.Convertible, for: Map do
end
end
-defimpl Lexical.Convertible, for: Any do
- alias Lexical.Convertible
- alias Lexical.Document
- alias Lexical.Convertible.Helpers
+defimpl Forge.Convertible, for: Any do
+ alias Forge.Convertible
+ alias Forge.Convertible.Helpers
+ alias Forge.Document
def to_native(%_struct_module{} = struct, context_document) do
context_document = Document.Container.context_document(struct, context_document)
diff --git a/apps/common/lib/lexical/debug.ex b/apps/forge/lib/forge/debug.ex
similarity index 85%
rename from apps/common/lib/lexical/debug.ex
rename to apps/forge/lib/forge/debug.ex
index 82c6c2e0..0a0b5121 100644
--- a/apps/common/lib/lexical/debug.ex
+++ b/apps/forge/lib/forge/debug.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Logging do
+defmodule Forge.Logging do
require Logger
defmacro timed(label, do: block) do
@@ -17,7 +17,7 @@ defmodule Lexical.Logging do
elapsed_ms = elapsed_us / 1000
if elapsed_ms >= threshold_ms do
- Logger.info("#{label} took #{Lexical.Formats.time(elapsed_us)}")
+ Logger.info("#{label} took #{Forge.Formats.time(elapsed_us)}")
end
result
diff --git a/apps/forge/lib/forge/document.ex b/apps/forge/lib/forge/document.ex
new file mode 100644
index 00000000..099826c5
--- /dev/null
+++ b/apps/forge/lib/forge/document.ex
@@ -0,0 +1,401 @@
+defmodule Forge.Document do
+ @moduledoc """
+ A representation of a LSP text document
+
+ A document is the fundamental data structure of the Expert language server.
+ All language server documents are represented and backed by documents, which
+ provide functionality for fetching lines, applying changes, and tracking versions.
+ """
+ alias Forge.Convertible
+ alias Forge.Document.Edit
+ alias Forge.Document.Line
+ alias Forge.Document.Lines
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Math
+
+ import Forge.Document.Line
+
+ require Logger
+
+ alias __MODULE__.Path, as: DocumentPath
+
+ @derive {Inspect, only: [:path, :version, :dirty?, :lines]}
+
+ defstruct [:uri, :language_id, :path, :version, dirty?: false, lines: nil]
+
+ @type version :: non_neg_integer()
+ @type fragment_position :: Position.t() | Convertible.t()
+ @type t :: %__MODULE__{
+ uri: String.t(),
+ language_id: String.t(),
+ version: version(),
+ dirty?: boolean,
+ lines: Lines.t(),
+ path: String.t()
+ }
+
+ @type change_application_error :: {:error, {:invalid_range, map()}}
+
+ # public
+
+ @doc """
+ Creates a new document from a uri or path, the source code
+ as a binary and the vewrsion.
+ """
+ @spec new(Forge.path() | Forge.uri(), String.t(), version()) :: t
+ def new(maybe_uri, text, version, language_id \\ nil) do
+ uri = DocumentPath.ensure_uri(maybe_uri)
+ path = DocumentPath.from_uri(uri)
+
+ language_id =
+ if String.ends_with?(path, ".exs") do
+ "elixir-script"
+ else
+ language_id || language_id_from_path(path)
+ end
+
+ %__MODULE__{
+ uri: uri,
+ version: version,
+ lines: Lines.new(text),
+ path: path,
+ language_id: language_id
+ }
+ end
+
+ @doc """
+ Returns the number of lines in the document
+
+ This is a constant time operation.
+ """
+ @spec size(t) :: non_neg_integer()
+ def size(%__MODULE__{} = document) do
+ Lines.size(document.lines)
+ end
+
+ @doc """
+ Marks the document file as dirty
+
+ This function is mainly used internally by expert
+ """
+ @spec mark_dirty(t) :: t
+ def mark_dirty(%__MODULE__{} = document) do
+ %__MODULE__{document | dirty?: true}
+ end
+
+ @doc """
+ Marks the document file as clean
+
+ This function is mainly used internally by expert
+ """
+ @spec mark_clean(t) :: t
+ def mark_clean(%__MODULE__{} = document) do
+ %__MODULE__{document | dirty?: false}
+ end
+
+ @doc """
+ Get the text at the given line using `fetch` semantics
+
+ Returns `{:ok, text}` if the line exists, and `:error` if it doesn't. The line text is
+ returned without the line end character(s).
+
+ This is a constant time operation.
+ """
+ @spec fetch_text_at(t, version()) :: {:ok, String.t()} | :error
+ def fetch_text_at(%__MODULE__{} = document, line_number) do
+ case fetch_line_at(document, line_number) do
+ {:ok, line(text: text)} -> {:ok, text}
+ _ -> :error
+ end
+ end
+
+ @doc """
+ Get the `Forge.Document.Line` at the given index using `fetch` semantics.
+
+ This function is of limited utility, you probably want `fetch_text_at/2` instead.
+ """
+ @spec fetch_line_at(t, version()) :: {:ok, Line.t()} | :error
+ def fetch_line_at(%__MODULE__{} = document, line_number) do
+ case Lines.fetch_line(document.lines, line_number) do
+ {:ok, line} -> {:ok, line}
+ _ -> :error
+ end
+ end
+
+ @doc """
+ Returns a fragment defined by the from and to arguments
+
+ Builds a string that represents the text of the document from the two positions given.
+ The from position, defaults to `:beginning` meaning the start of the document.
+ Positions can be a `Document.Position.t` or anything that will convert to a position using
+ `Forge.Convertible.to_native/2`.
+ """
+ @spec fragment(t, fragment_position() | :beginning, fragment_position()) :: String.t()
+ @spec fragment(t, fragment_position()) :: String.t()
+ def fragment(%__MODULE__{} = document, from \\ :beginning, to) do
+ line_count = size(document)
+ from_pos = convert_fragment_position(document, from)
+ to_pos = convert_fragment_position(document, to)
+
+ from_line = Math.clamp(from_pos.line, document.lines.starting_index, line_count)
+ to_line = Math.clamp(to_pos.line, from_line, line_count + 1)
+
+ # source code positions are 1 based, but string slices are zero-based. Need an ad-hoc conversion
+ # here.
+ from_character = from_pos.character - 1
+ to_character = to_pos.character - 1
+
+ line_range = from_line..to_line
+
+ line_range
+ |> Enum.reduce([], fn line_number, acc ->
+ to_append =
+ case fetch_line_at(document, line_number) do
+ {:ok, line(text: text, ending: ending)} ->
+ line_text = text <> ending
+
+ cond do
+ line_number == from_line and line_number == to_line ->
+ slice_length = to_character - from_character
+ String.slice(line_text, from_character, slice_length)
+
+ line_number == from_line ->
+ slice_length = String.length(line_text) - from_character
+ String.slice(line_text, from_character, slice_length)
+
+ line_number == to_line ->
+ String.slice(line_text, 0, to_character)
+
+ true ->
+ line_text
+ end
+
+ :error ->
+ []
+ end
+
+ [acc, to_append]
+ end)
+ |> IO.iodata_to_binary()
+ end
+
+ @doc false
+ @spec apply_content_changes(t, version(), [Convertible.t() | nil]) ::
+ {:ok, t} | change_application_error()
+ def apply_content_changes(%__MODULE__{version: current_version}, new_version, _)
+ when new_version <= current_version do
+ {:error, :invalid_version}
+ end
+
+ def apply_content_changes(%__MODULE__{} = document, _, []) do
+ {:ok, document}
+ end
+
+ def apply_content_changes(%__MODULE__{} = document, version, changes) when is_list(changes) do
+ result =
+ Enum.reduce_while(changes, document, fn
+ nil, document ->
+ {:cont, document}
+
+ change, document ->
+ case apply_change(document, change) do
+ {:ok, new_document} ->
+ {:cont, new_document}
+
+ error ->
+ {:halt, error}
+ end
+ end)
+
+ case result do
+ %__MODULE__{} = document ->
+ document = mark_dirty(%__MODULE__{document | version: version})
+
+ {:ok, document}
+
+ error ->
+ error
+ end
+ end
+
+ @doc """
+ Converts a document to a string
+
+ This function converts the given document back into a string, with line endings
+ preserved.
+ """
+ def to_string(%__MODULE__{} = document) do
+ document
+ |> to_iodata()
+ |> IO.iodata_to_binary()
+ end
+
+ @spec language_id_from_path(Forge.path()) :: String.t()
+ defp language_id_from_path(path) do
+ case Path.extname(path) do
+ ".ex" ->
+ "elixir"
+
+ ".exs" ->
+ "elixir-script"
+
+ ".eex" ->
+ "eex"
+
+ ".heex" ->
+ "phoenix-heex"
+
+ extension ->
+ Logger.warning("can't infer lang ID for #{path}, ext: #{extension}.")
+
+ "unsupported (#{extension})"
+ end
+ end
+
+ # private
+
+ defp line_count(%__MODULE__{} = document) do
+ Lines.size(document.lines)
+ end
+
+ defp apply_change(
+ %__MODULE__{} = document,
+ %Range{start: %Position{} = start_pos, end: %Position{} = end_pos},
+ new_text
+ ) do
+ start_line = start_pos.line
+
+ new_lines_iodata =
+ cond do
+ start_line > line_count(document) ->
+ append_to_end(document, new_text)
+
+ start_line < 1 ->
+ prepend_to_beginning(document, new_text)
+
+ true ->
+ apply_valid_edits(document, new_text, start_pos, end_pos)
+ end
+
+ new_document =
+ new_lines_iodata
+ |> IO.iodata_to_binary()
+ |> Lines.new()
+
+ {:ok, %__MODULE__{document | lines: new_document}}
+ end
+
+ defp apply_change(%__MODULE__{} = document, %Edit{range: nil} = edit) do
+ {:ok, %__MODULE__{document | lines: Lines.new(edit.text)}}
+ end
+
+ defp apply_change(%__MODULE__{} = document, %Edit{range: %Range{}} = edit) do
+ if valid_edit?(edit) do
+ apply_change(document, edit.range, edit.text)
+ else
+ {:error, {:invalid_range, edit.range}}
+ end
+ end
+
+ defp apply_change(%__MODULE__{} = document, %{range: range, text: text}) do
+ with {:ok, native_range} <- Convertible.to_native(range, document) do
+ apply_change(document, Edit.new(text, native_range))
+ end
+ end
+
+ defp apply_change(%__MODULE__{} = document, convertable_edit) do
+ with {:ok, edit} <- Convertible.to_native(convertable_edit, document) do
+ apply_change(document, edit)
+ end
+ end
+
+ defp valid_edit?(%Edit{
+ range: %Range{start: %Position{} = start_pos, end: %Position{} = end_pos}
+ }) do
+ start_pos.line >= 0 and start_pos.character >= 0 and end_pos.line >= 0 and
+ end_pos.character >= 0
+ end
+
+ defp append_to_end(%__MODULE__{} = document, edit_text) do
+ [to_iodata(document), edit_text]
+ end
+
+ defp prepend_to_beginning(%__MODULE__{} = document, edit_text) do
+ [edit_text, to_iodata(document)]
+ end
+
+ defp apply_valid_edits(%__MODULE__{} = document, edit_text, start_pos, end_pos) do
+ Lines.reduce(document.lines, [], fn line() = line, acc ->
+ case edit_action(line, edit_text, start_pos, end_pos) do
+ :drop ->
+ acc
+
+ {:append, io_data} ->
+ [acc, io_data]
+ end
+ end)
+ end
+
+ defp edit_action(line() = line, edit_text, %Position{} = start_pos, %Position{} = end_pos) do
+ %Position{line: start_line, character: start_char} = start_pos
+ %Position{line: end_line, character: end_char} = end_pos
+
+ line(line_number: line_number, text: text, ending: ending) = line
+
+ cond do
+ line_number < start_line ->
+ {:append, [text, ending]}
+
+ line_number > end_line ->
+ {:append, [text, ending]}
+
+ line_number == start_line && line_number == end_line ->
+ prefix_text = utf8_prefix(line, start_char)
+ suffix_text = utf8_suffix(line, end_char)
+ {:append, [prefix_text, edit_text, suffix_text, ending]}
+
+ line_number == start_line ->
+ prefix_text = utf8_prefix(line, start_char)
+ {:append, [prefix_text, edit_text]}
+
+ line_number == end_line ->
+ suffix_text = utf8_suffix(line, end_char)
+ {:append, [suffix_text, ending]}
+
+ true ->
+ :drop
+ end
+ end
+
+ defp utf8_prefix(line(text: text), start_code_unit) do
+ length = max(0, start_code_unit - 1)
+ binary_part(text, 0, length)
+ end
+
+ defp utf8_suffix(line(text: text), start_code_unit) do
+ byte_count = byte_size(text)
+ start_index = min(start_code_unit - 1, byte_count)
+ length = byte_count - start_index
+
+ binary_part(text, start_index, length)
+ end
+
+ defp to_iodata(%__MODULE__{} = document) do
+ Lines.to_iodata(document.lines)
+ end
+
+ @spec convert_fragment_position(t, Position.t() | :beginning | Convertible.t()) :: Position.t()
+ defp convert_fragment_position(%__MODULE__{}, %Position{} = pos) do
+ pos
+ end
+
+ defp convert_fragment_position(%__MODULE__{} = document, :beginning) do
+ Position.new(document, 1, 1)
+ end
+
+ defp convert_fragment_position(%__MODULE__{} = document, convertible) do
+ {:ok, %Position{} = converted} = Convertible.to_native(convertible, document)
+ converted
+ end
+end
diff --git a/apps/common/lib/lexical/document/changes.ex b/apps/forge/lib/forge/document/changes.ex
similarity index 86%
rename from apps/common/lib/lexical/document/changes.ex
rename to apps/forge/lib/forge/document/changes.ex
index fc848452..2911dd80 100644
--- a/apps/common/lib/lexical/document/changes.ex
+++ b/apps/forge/lib/forge/document/changes.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Document.Changes do
+defmodule Forge.Document.Changes do
@moduledoc """
- A `Lexical.Document.Container` for text edits.
+ A `Forge.Document.Container` for text edits.
This struct is helpful if you need to express one or several text edits in an LSP response.
It will convert cleanly into either a single `TextEdit` or a list of `TextEdit`s depending on
@@ -10,9 +10,9 @@ defmodule Lexical.Document.Changes do
doesn't have to be looked up (and possibly read off the filesystem) by the language server.
"""
defstruct [:document, :edits]
- alias Lexical.Document
+ alias Forge.Document
- use Lexical.StructAccess
+ use Forge.StructAccess
@type edits :: Document.Edit.t() | [Document.Edit.t()]
@type t :: %__MODULE__{
diff --git a/apps/common/lib/lexical/document/container.ex b/apps/forge/lib/forge/document/container.ex
similarity index 88%
rename from apps/common/lib/lexical/document/container.ex
rename to apps/forge/lib/forge/document/container.ex
index 52629295..1a9298ad 100644
--- a/apps/common/lib/lexical/document/container.ex
+++ b/apps/forge/lib/forge/document/container.ex
@@ -1,4 +1,4 @@
-defprotocol Lexical.Document.Container do
+defprotocol Forge.Document.Container do
@moduledoc """
A protocol used to find relevant documents in structs
@@ -13,11 +13,11 @@ defprotocol Lexical.Document.Container do
would _not_ need an implementation:
```
- %MyStruct{document: %Lexical.Document{}}
+ %MyStruct{document: %Forge.Document{}}
%MyStruct{text_document: %{uri: "file:///path/to/document.ex"}}
```
"""
- alias Lexical.Document
+ alias Forge.Document
@fallback_to_any true
@type maybe_context_document :: Document.t() | nil
@@ -25,8 +25,8 @@ defprotocol Lexical.Document.Container do
def context_document(t, parent_context_document)
end
-defimpl Lexical.Document.Container, for: Any do
- alias Lexical.Document
+defimpl Forge.Document.Container, for: Any do
+ alias Forge.Document
def context_document(%{document: %Document{} = document}, _) do
document
diff --git a/apps/forge/lib/forge/document/edit.ex b/apps/forge/lib/forge/document/edit.ex
new file mode 100644
index 00000000..545947eb
--- /dev/null
+++ b/apps/forge/lib/forge/document/edit.ex
@@ -0,0 +1,35 @@
+defmodule Forge.Document.Edit do
+ @moduledoc """
+ A change to a document
+
+ A `Forge.Document.Edit` represents a single change to a document. It contains
+ the new text and a range where the edit applies.
+ """
+ alias Forge.Document.Range
+ alias Forge.StructAccess
+
+ defstruct [:text, :range]
+
+ @type t :: %__MODULE__{
+ text: String.t(),
+ range: Range.t() | nil
+ }
+
+ use StructAccess
+
+ @doc "Creates a new edit that replaces all text in the document"
+ @spec new(String.t(), Range.t() | nil) :: t
+ @spec new(String.t()) :: t
+ def new(text) when is_binary(text) do
+ %__MODULE__{text: text}
+ end
+
+ @doc "Creates a new edit that replaces text in the given range"
+ def new(text, %Range{} = range) do
+ %__MODULE__{text: text, range: range}
+ end
+
+ def new(text, nil) when is_binary(text) do
+ %__MODULE__{text: text}
+ end
+end
diff --git a/apps/common/lib/lexical/document/line.ex b/apps/forge/lib/forge/document/line.ex
similarity index 95%
rename from apps/common/lib/lexical/document/line.ex
rename to apps/forge/lib/forge/document/line.ex
index 5abb6a83..8d9ea409 100644
--- a/apps/common/lib/lexical/document/line.ex
+++ b/apps/forge/lib/forge/document/line.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Document.Line do
+defmodule Forge.Document.Line do
@moduledoc ~S"""
A record representing a line of text in a document
diff --git a/apps/common/lib/lexical/document/line_parser.ex b/apps/forge/lib/forge/document/line_parser.ex
similarity index 96%
rename from apps/common/lib/lexical/document/line_parser.ex
rename to apps/forge/lib/forge/document/line_parser.ex
index 2be20417..575d4711 100644
--- a/apps/common/lib/lexical/document/line_parser.ex
+++ b/apps/forge/lib/forge/document/line_parser.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Document.LineParser do
+defmodule Forge.Document.LineParser do
@moduledoc """
- A parser that parses a binary into `Lexical.Document.Line` records.
+ A parser that parses a binary into `Forge.Document.Line` records.
The approach taken by the parser is to first go through the binary to find out where
the lines break, what their endings are and if the line is ascii. As we go through the
@@ -12,7 +12,7 @@ defmodule Lexical.Document.LineParser do
each byte is greater than 0 and less than 128. UTF-16 files won't be marked as ascii, which
allows us to skip a lot of byte conversions later in the process.
"""
- import Lexical.Document.Line
+ import Forge.Document.Line
# it's important that "\r\n" comes before \r here, otherwise the generated pattern
# matches won't match.
diff --git a/apps/common/lib/lexical/document/lines.ex b/apps/forge/lib/forge/document/lines.ex
similarity index 92%
rename from apps/common/lib/lexical/document/lines.ex
rename to apps/forge/lib/forge/document/lines.ex
index 9d266a67..a7994af8 100644
--- a/apps/common/lib/lexical/document/lines.ex
+++ b/apps/forge/lib/forge/document/lines.ex
@@ -1,11 +1,11 @@
-defmodule Lexical.Document.Lines do
+defmodule Forge.Document.Lines do
@moduledoc """
A hyper-optimized, line-based backing store for text documents
"""
- alias Lexical.Document.Line
- alias Lexical.Document.LineParser
+ alias Forge.Document.Line
+ alias Forge.Document.LineParser
- use Lexical.StructAccess
+ use Forge.StructAccess
import Line
@default_starting_index 1
@@ -88,9 +88,9 @@ defmodule Lexical.Document.Lines do
end
end
-defimpl Inspect, for: Lexical.Document.Lines do
- alias Lexical.Document.Lines
- alias Lexical.Document.Line
+defimpl Inspect, for: Forge.Document.Lines do
+ alias Forge.Document.Lines
+ alias Forge.Document.Line
import Inspect.Algebra
import Line
@@ -130,8 +130,8 @@ defimpl Inspect, for: Lexical.Document.Lines do
end
end
-defimpl Enumerable, for: Lexical.Document.Lines do
- alias Lexical.Document.Lines
+defimpl Enumerable, for: Forge.Document.Lines do
+ alias Forge.Document.Lines
def count(%Lines{} = document) do
{:ok, Lines.size(document)}
diff --git a/apps/forge/lib/forge/document/location.ex b/apps/forge/lib/forge/document/location.ex
new file mode 100644
index 00000000..73d00d55
--- /dev/null
+++ b/apps/forge/lib/forge/document/location.ex
@@ -0,0 +1,41 @@
+defmodule Forge.Document.Location do
+ @moduledoc """
+ A location in a document
+
+ One of the fundamental LSP structures, this represents a subset of text in a document.
+ The location is bounded by the given range, and the document can be given as a `Forge.Document`
+ struct, or a uri referencing the document
+ """
+ alias Forge.Document
+ alias Forge.Document.Range
+
+ defstruct [:range, :document, :uri]
+
+ @type t :: %__MODULE__{
+ range: Range.t(),
+ document: Document.t() | nil,
+ uri: Forge.uri()
+ }
+ use Forge.StructAccess
+
+ @spec new(Range.t(), Document.t() | String.t()) :: t()
+ def new(%Range{} = range, %Document{} = document) do
+ %__MODULE__{range: range, document: document, uri: document.uri}
+ end
+
+ def new(%Range{} = range, uri) when is_binary(uri) do
+ %__MODULE__{range: range, uri: uri}
+ end
+
+ def uri(%__MODULE__{document: %Document{} = document}) do
+ document.uri
+ end
+
+ @doc """
+ Returns the location document's uri.
+ """
+ @spec uri(t) :: Forge.uri()
+ def uri(%__MODULE__{} = location) do
+ location.uri
+ end
+end
diff --git a/apps/common/lib/lexical/document/path.ex b/apps/forge/lib/forge/document/path.ex
similarity index 95%
rename from apps/common/lib/lexical/document/path.ex
rename to apps/forge/lib/forge/document/path.ex
index 0412bb26..f0ecc120 100644
--- a/apps/common/lib/lexical/document/path.ex
+++ b/apps/forge/lib/forge/document/path.ex
@@ -1,15 +1,15 @@
-defmodule Lexical.Document.Path do
+defmodule Forge.Document.Path do
@moduledoc """
A collection of functions dealing with converting filesystem paths to URIs and back
"""
@file_scheme "file"
- @type uri_or_path :: Lexical.uri() | Lexical.path()
+ @type uri_or_path :: Forge.uri() | Forge.path()
@doc """
Given a uri or a path, either return the uri unmodified or converts the path to a uri
"""
- @spec ensure_uri(uri_or_path()) :: Lexical.uri()
+ @spec ensure_uri(uri_or_path()) :: Forge.uri()
def ensure_uri("file://" <> _ = uri), do: uri
def ensure_uri("untitled:" <> _ = uri), do: uri
@@ -19,7 +19,7 @@ defmodule Lexical.Document.Path do
@doc """
Given a uri or a path, either return the path unmodified or converts the uri to a path
"""
- @spec ensure_path(uri_or_path()) :: Lexical.path()
+ @spec ensure_path(uri_or_path()) :: Forge.path()
def ensure_path("file://" <> _ = uri), do: from_uri(uri)
def ensure_path(path) when is_binary(path), do: path
diff --git a/apps/forge/lib/forge/document/position.ex b/apps/forge/lib/forge/document/position.ex
new file mode 100644
index 00000000..562a39c7
--- /dev/null
+++ b/apps/forge/lib/forge/document/position.ex
@@ -0,0 +1,117 @@
+defmodule Forge.Document.Position do
+ @moduledoc """
+ A position inside of a document
+
+ This struct represents a cursor position inside a document, using one-based line and character
+ numbers. It's important to note that the position starts before the character given, so positions
+ are inclusive of the given character.
+
+ Given the following line of text:
+ "Hello there, welcome to Forge"
+
+ the position: `%Forge.Document.Position{line: 1, character: 1}` starts before the "H" in "Hello"
+ """
+
+ alias Forge.Document
+ alias Forge.Document.Lines
+
+ defstruct [
+ :line,
+ :character,
+ valid?: false,
+ context_line: nil,
+ document_line_count: 0,
+ starting_index: 1
+ ]
+
+ @type line :: non_neg_integer()
+ @type character :: non_neg_integer()
+ @type line_container :: Document.t() | Lines.t()
+
+ @type t :: %__MODULE__{
+ character: character(),
+ context_line: Document.Line.t() | nil,
+ document_line_count: non_neg_integer(),
+ line: line(),
+ starting_index: non_neg_integer(),
+ valid?: boolean()
+ }
+
+ use Forge.StructAccess
+
+ @spec new(line_container(), line(), character()) :: t
+ def new(%Document{} = document, line, character)
+ when is_number(line) and is_number(character) do
+ new(document.lines, line, character)
+ end
+
+ def new(%Document.Lines{} = lines, line, character)
+ when is_number(line) and is_number(character) do
+ line_count = Document.Lines.size(lines)
+ starting_index = lines.starting_index
+
+ case Lines.fetch_line(lines, line) do
+ {:ok, context_line} ->
+ %__MODULE__{
+ character: character,
+ context_line: context_line,
+ document_line_count: line_count,
+ line: line,
+ starting_index: starting_index,
+ valid?: true
+ }
+
+ :error ->
+ %__MODULE__{
+ line: line,
+ character: character,
+ document_line_count: line_count,
+ starting_index: starting_index
+ }
+ end
+ end
+
+ @doc """
+ Compares two positions.
+
+ Returns `:gt`, `:lt`, or `:eq` depending on the location of the first
+ position relative to the second.
+ """
+ @spec compare(t | {line, character}, t | {line, character}) :: :lt | :eq | :gt
+ def compare(%__MODULE__{} = pos1, %__MODULE__{} = pos2) do
+ compare({pos1.line, pos1.character}, {pos2.line, pos2.character})
+ end
+
+ def compare(%__MODULE__{} = pos1, {_, _} = pos2) do
+ compare({pos1.line, pos1.character}, pos2)
+ end
+
+ def compare({_, _} = pos1, %__MODULE__{} = pos2) do
+ compare(pos1, {pos2.line, pos2.character})
+ end
+
+ def compare({l1, c1} = first, {l2, c2} = second)
+ when is_integer(l1) and is_integer(c1) and is_integer(l2) and is_integer(c2) do
+ cond do
+ first < second -> :lt
+ first > second -> :gt
+ true -> :eq
+ end
+ end
+end
+
+defimpl Inspect, for: Forge.Document.Position do
+ import Inspect.Algebra
+
+ def inspect(nil, _), do: "nil"
+
+ def inspect(pos, _) do
+ concat(["XpPos", to_string(pos)])
+ end
+end
+
+defimpl String.Chars, for: Forge.Document.Position do
+ def to_string(pos) do
+ "<<#{pos.line}, #{pos.character}>>"
+ end
+end
diff --git a/apps/forge/lib/forge/document/range.ex b/apps/forge/lib/forge/document/range.ex
new file mode 100644
index 00000000..8e4e9791
--- /dev/null
+++ b/apps/forge/lib/forge/document/range.ex
@@ -0,0 +1,65 @@
+defmodule Forge.Document.Range do
+ @moduledoc """
+ A range in a document
+
+ Note that ranges represent a cursor position, and so are inclusive of
+ lines, but exclusive of the end position.
+
+ Note: To select an entire line, construct a range that runs from the
+ first character on the line to the first character on the next line.
+
+ ```
+ whole_line =
+ Range.new(
+ Position.new(doc, 1, 1),
+ Position.new(doc, 2, 1)
+ )
+ ```
+ """
+ alias Forge.Document.Position
+
+ defstruct start: nil, end: nil
+
+ @type t :: %__MODULE__{
+ start: Position.t(),
+ end: Position.t()
+ }
+
+ use Forge.StructAccess
+
+ @doc """
+ Builds a new range.
+ """
+ def new(%Position{} = start_pos, %Position{} = end_pos) do
+ %__MODULE__{start: start_pos, end: end_pos}
+ end
+
+ @doc """
+ Returns whether the range contains the given position.
+ """
+ def contains?(%__MODULE__{} = range, %Position{} = position) do
+ %__MODULE__{start: start_pos, end: end_pos} = range
+
+ cond do
+ position.line == start_pos.line and position.line == end_pos.line ->
+ position.character >= start_pos.character and position.character <= end_pos.character
+
+ position.line == start_pos.line ->
+ position.character >= start_pos.character
+
+ position.line == end_pos.line ->
+ position.character < end_pos.character
+
+ true ->
+ position.line > start_pos.line and position.line < end_pos.line
+ end
+ end
+end
+
+defimpl Inspect, for: Forge.Document.Range do
+ import Inspect.Algebra
+
+ def inspect(range, _) do
+ concat(["XpRange[", to_string(range.start), "...", to_string(range.end), "]"])
+ end
+end
diff --git a/apps/forge/lib/forge/document/store.ex b/apps/forge/lib/forge/document/store.ex
new file mode 100644
index 00000000..70dfe144
--- /dev/null
+++ b/apps/forge/lib/forge/document/store.ex
@@ -0,0 +1,423 @@
+defmodule Forge.Document.Store do
+ @moduledoc """
+ Backing store for source file documents.
+ """
+
+ alias Forge.Document
+ alias Forge.ProcessCache
+
+ use GenServer
+
+ @type updater :: (Document.t() -> {:ok, Document.t()} | {:error, any()})
+
+ @type derivations :: [derivation]
+ @type derivation :: {derivation_key, derivation_fun}
+ @type derivation_key :: atom()
+ @type derivation_fun :: (Document.t() -> derived_value)
+ @type derived_value :: any()
+
+ @type start_opts :: [start_opt]
+ @type start_opt :: {:derive, derivations}
+
+ defmodule State do
+ @moduledoc false
+
+ alias Forge.Document
+ alias Forge.Document.Store
+
+ require Logger
+
+ import Record
+
+ defstruct open: %{}, temporary_open_refs: %{}, derivation_funs: %{}
+
+ @type t :: %__MODULE__{}
+
+ defrecord :open_doc, document: nil, derived: %{}
+
+ def new(opts \\ []) do
+ {derivation_funs, invalid} =
+ opts
+ |> Keyword.validate!(derive: [])
+ |> Keyword.fetch!(:derive)
+ |> Enum.split_with(fn
+ {atom, fun} when is_atom(atom) and is_function(fun, 1) -> true
+ _ -> false
+ end)
+
+ if invalid != [] do
+ raise ArgumentError, "invalid derive: #{inspect(invalid)}"
+ end
+
+ %__MODULE__{derivation_funs: Map.new(derivation_funs)}
+ end
+
+ @spec fetch(t, Forge.uri()) :: {:ok, Document.t(), t} | {:error, :not_open}
+ def fetch(%__MODULE__{} = store, uri) do
+ case store.open do
+ %{^uri => open_doc(document: document)} -> {:ok, document, store}
+ _ -> {:error, :not_open}
+ end
+ end
+
+ @spec fetch(t, Forge.uri(), Store.derivation_key()) ::
+ {:ok, Document.t(), Store.derived_value(), t} | {:error, :not_open}
+ def fetch(%__MODULE__{} = store, uri, key) do
+ case store.open do
+ %{^uri => open_doc(document: document, derived: %{^key => derivation})} ->
+ {:ok, document, derivation, store}
+
+ %{^uri => open_doc(document: document, derived: derived)} ->
+ derivation = derive(store, key, document)
+ derived = Map.put(derived, key, derivation)
+ store = put_open_doc(store, document, derived)
+ {:ok, document, derivation, store}
+
+ _ ->
+ {:error, :not_open}
+ end
+ end
+
+ @spec save(t, Forge.uri()) :: {:ok, t} | {:error, :not_open}
+ def save(%__MODULE__{} = store, uri) do
+ case store.open do
+ %{^uri => open_doc(document: document, derived: derived)} ->
+ document = Document.mark_clean(document)
+ store = put_open_doc(store, document, derived)
+ {:ok, store}
+
+ _ ->
+ {:error, :not_open}
+ end
+ end
+
+ @spec open(t, Forge.uri(), String.t(), pos_integer(), String.t()) ::
+ {:ok, t} | {:error, :already_open}
+ def open(%__MODULE__{temporary_open_refs: refs} = store, uri, text, version, language_id)
+ when is_map_key(refs, uri) do
+ {_, store} =
+ store
+ |> maybe_cancel_ref(uri)
+ |> pop_open_doc(uri)
+
+ open(store, uri, text, version, language_id)
+ end
+
+ def open(%__MODULE__{} = store, uri, text, version, language_id) do
+ case store.open do
+ %{^uri => _} ->
+ {:error, :already_open}
+
+ _ ->
+ document = Document.new(uri, text, version, language_id)
+ store = put_open_doc(store, document)
+ {:ok, store}
+ end
+ end
+
+ @spec open?(t, Forge.uri()) :: boolean
+ def open?(%__MODULE__{} = store, uri) do
+ Map.has_key?(store.open, uri)
+ end
+
+ @spec close(t, Forge.uri()) :: {:ok, t} | {:error, :not_open}
+ def close(%__MODULE__{} = store, uri) do
+ case pop_open_doc(store, uri) do
+ {nil, _} ->
+ {:error, :not_open}
+
+ {_, store} ->
+ {:ok, maybe_cancel_ref(store, uri)}
+ end
+ end
+
+ @spec get_and_update(t, Forge.uri(), Store.updater()) ::
+ {:ok, Document.t(), t} | {:error, any()}
+ def get_and_update(%__MODULE__{} = store, uri, updater_fn) do
+ with {:ok, open_doc(document: document)} <- Map.fetch(store.open, uri),
+ {:ok, document} <- updater_fn.(document) do
+ {:ok, document, put_open_doc(store, document)}
+ else
+ error ->
+ normalize_error(error)
+ end
+ end
+
+ @spec update(t, Forge.uri(), Store.updater()) :: {:ok, t} | {:error, any()}
+ def update(%__MODULE__{} = store, uri, updater_fn) do
+ with {:ok, _, store} <- get_and_update(store, uri, updater_fn) do
+ {:ok, store}
+ end
+ end
+
+ @spec open_temporarily(t, Forge.uri() | Path.t(), timeout()) ::
+ {:ok, Document.t(), t} | {:error, term()}
+ def open_temporarily(%__MODULE__{} = store, path_or_uri, timeout) do
+ uri = Document.Path.ensure_uri(path_or_uri)
+ path = Document.Path.ensure_path(path_or_uri)
+
+ with {:ok, contents} <- File.read(path) do
+ document = Document.new(uri, contents, 0)
+ ref = schedule_unload(uri, timeout)
+
+ new_store =
+ store
+ |> maybe_cancel_ref(uri)
+ |> put_ref(uri, ref)
+ |> put_open_doc(document)
+
+ {:ok, document, new_store}
+ end
+ end
+
+ @spec extend_timeout(t, Forge.uri(), timeout()) :: t
+ def extend_timeout(%__MODULE__{} = store, uri, timeout) do
+ case store.temporary_open_refs do
+ %{^uri => ref} ->
+ Process.cancel_timer(ref)
+ new_ref = schedule_unload(uri, timeout)
+ put_ref(store, uri, new_ref)
+
+ _ ->
+ store
+ end
+ end
+
+ @spec unload(t, Forge.uri()) :: t
+ def unload(%__MODULE__{} = store, uri) do
+ {_, store} = pop_open_doc(store, uri)
+ maybe_cancel_ref(store, uri)
+ end
+
+ defp put_open_doc(%__MODULE__{} = store, %Document{} = document, derived \\ %{}) do
+ put_in(store.open[document.uri], open_doc(document: document, derived: derived))
+ end
+
+ defp pop_open_doc(%__MODULE__{} = store, uri) do
+ case Map.pop(store.open, uri) do
+ {open_doc() = doc, open} -> {doc, %__MODULE__{store | open: open}}
+ {nil, _} -> {nil, store}
+ end
+ end
+
+ defp put_ref(%__MODULE__{} = store, uri, ref) do
+ put_in(store.temporary_open_refs[uri], ref)
+ end
+
+ defp maybe_cancel_ref(%__MODULE__{} = store, uri) do
+ case pop_in(store.temporary_open_refs[uri]) do
+ {ref, store} when is_reference(ref) ->
+ Process.cancel_timer(ref)
+ store
+
+ _ ->
+ store
+ end
+ end
+
+ defp schedule_unload(uri, timeout) do
+ Process.send_after(self(), {:unload, uri}, timeout)
+ end
+
+ defp normalize_error(:error), do: {:error, :not_open}
+ defp normalize_error(e), do: e
+
+ defp derive(%__MODULE__{} = store, key, document) do
+ case store.derivation_funs do
+ %{^key => fun} ->
+ fun.(document)
+
+ _ ->
+ known = Map.keys(store.derivation_funs)
+
+ raise ArgumentError,
+ "No derivation for #{inspect(key)}, expected one of #{inspect(known)}"
+ end
+ end
+ end
+
+ @spec fetch(Forge.uri()) :: {:ok, Document.t()} | {:error, :not_open}
+ def fetch(uri) do
+ GenServer.call(name(), {:fetch, uri})
+ end
+
+ @spec fetch(Forge.uri(), derivation_key) ::
+ {:ok, Document.t(), derived_value} | {:error, :not_open}
+ def fetch(uri, key) do
+ GenServer.call(name(), {:fetch, uri, key})
+ end
+
+ @spec save(Forge.uri()) :: :ok | {:error, :not_open}
+ def save(uri) do
+ GenServer.call(name(), {:save, uri})
+ end
+
+ @spec open?(Forge.uri()) :: boolean()
+ def open?(uri) do
+ GenServer.call(name(), {:open?, uri})
+ end
+
+ @spec open(Forge.uri(), String.t(), String.t(), pos_integer() | nil) ::
+ :ok | {:error, :already_open}
+ def open(uri, text, version, language_id \\ nil) do
+ GenServer.call(name(), {:open, uri, text, version, language_id})
+ end
+
+ @spec open_temporary(Forge.uri() | Path.t()) ::
+ {:ok, Document.t()} | {:error, term()}
+
+ @spec open_temporary(Forge.uri() | Path.t(), timeout()) ::
+ {:ok, Document.t()} | {:error, term()}
+ def open_temporary(uri, timeout \\ 5000) when is_binary(uri) do
+ ProcessCache.trans(uri, 50, fn ->
+ GenServer.call(name(), {:open_temporarily, uri, timeout})
+ end)
+ end
+
+ @spec close(Forge.uri()) :: :ok | {:error, :not_open}
+ def close(uri) do
+ GenServer.call(name(), {:close, uri})
+ end
+
+ @spec get_and_update(Forge.uri(), updater) :: {:ok, Document.t()} | {:error, any()}
+ def get_and_update(uri, update_fn) do
+ GenServer.call(name(), {:get_and_update, uri, update_fn})
+ end
+
+ @spec update(Forge.uri(), updater) :: :ok | {:error, any()}
+ def update(uri, update_fn) do
+ GenServer.call(name(), {:update, uri, update_fn})
+ end
+
+ @spec start_link(start_opts) :: GenServer.on_start()
+ def start_link(opts) do
+ GenServer.start_link(__MODULE__, opts, name: name())
+ end
+
+ @impl GenServer
+ def init(opts) do
+ {:ok, State.new(opts)}
+ end
+
+ @impl GenServer
+ def handle_call({:save, uri}, _from, %State{} = state) do
+ {reply, new_state} =
+ case State.save(state, uri) do
+ {:ok, _} = success -> success
+ error -> {error, state}
+ end
+
+ {:reply, reply, new_state}
+ end
+
+ def handle_call({:open, uri, text, version, language_id}, _from, %State{} = state) do
+ {reply, new_state} =
+ case State.open(state, uri, text, version, language_id) do
+ {:ok, _} = success -> success
+ error -> {error, state}
+ end
+
+ {:reply, reply, new_state}
+ end
+
+ def handle_call({:open?, uri}, _from, %State{} = state) do
+ reply = State.open?(state, uri)
+ {:reply, reply, state}
+ end
+
+ def handle_call({:open_temporarily, uri, timeout_ms}, _, %State{} = state) do
+ {reply, new_state} =
+ with {:error, :not_open} <- State.fetch(state, uri),
+ {:ok, document, new_state} <- State.open_temporarily(state, uri, timeout_ms) do
+ {{:ok, document}, new_state}
+ else
+ {:ok, document, new_state} ->
+ {{:ok, document}, State.extend_timeout(new_state, uri, timeout_ms)}
+
+ error ->
+ {error, state}
+ end
+
+ {:reply, reply, new_state}
+ end
+
+ def handle_call({:fetch, uri}, _from, %State{} = state) do
+ {reply, new_state} =
+ case State.fetch(state, uri) do
+ {:ok, value, new_state} -> {{:ok, value}, new_state}
+ error -> {error, state}
+ end
+
+ {:reply, reply, new_state}
+ end
+
+ def handle_call({:fetch, uri, key}, _from, %State{} = state) do
+ {reply, new_state} =
+ case State.fetch(state, uri, key) do
+ {:ok, value, derived_value, new_state} -> {{:ok, value, derived_value}, new_state}
+ error -> {error, state}
+ end
+
+ {:reply, reply, new_state}
+ end
+
+ def handle_call({:close, uri}, _from, %State{} = state) do
+ {reply, new_state} =
+ case State.close(state, uri) do
+ {:ok, new_state} -> {:ok, new_state}
+ error -> {error, state}
+ end
+
+ {:reply, reply, new_state}
+ end
+
+ def handle_call({:get_and_update, uri, update_fn}, _from, %State{} = state) do
+ {reply, new_state} =
+ case State.get_and_update(state, uri, update_fn) do
+ {:ok, updated_source, new_state} -> {{:ok, updated_source}, new_state}
+ error -> {error, state}
+ end
+
+ {:reply, reply, new_state}
+ end
+
+ def handle_call({:update, uri, updater_fn}, _, %State{} = state) do
+ {reply, new_state} =
+ case State.update(state, uri, updater_fn) do
+ {:ok, new_state} -> {:ok, new_state}
+ error -> {error, state}
+ end
+
+ {:reply, reply, new_state}
+ end
+
+ @impl GenServer
+ def handle_info({:unload, uri}, %State{} = state) do
+ {:noreply, State.unload(state, uri)}
+ end
+
+ def set_entropy(entropy) do
+ :persistent_term.put(entropy_key(), entropy)
+ entropy
+ end
+
+ def entropy do
+ case :persistent_term.get(entropy_key(), :undefined) do
+ :undefined ->
+ [:positive]
+ |> System.unique_integer()
+ |> set_entropy()
+
+ entropy ->
+ entropy
+ end
+ end
+
+ def name do
+ {:via, :global, {__MODULE__, entropy()}}
+ end
+
+ defp entropy_key do
+ {__MODULE__, :entropy}
+ end
+end
diff --git a/apps/common/lib/lexical/formats.ex b/apps/forge/lib/forge/formats.ex
similarity index 98%
rename from apps/common/lib/lexical/formats.ex
rename to apps/forge/lib/forge/formats.ex
index f4fc6c9b..9cfd6845 100644
--- a/apps/common/lib/lexical/formats.ex
+++ b/apps/forge/lib/forge/formats.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Formats do
+defmodule Forge.Formats do
@moduledoc """
A collection of formatting functions
"""
diff --git a/apps/forge/lib/forge/identifier.ex b/apps/forge/lib/forge/identifier.ex
new file mode 100644
index 00000000..79e1464c
--- /dev/null
+++ b/apps/forge/lib/forge/identifier.ex
@@ -0,0 +1,27 @@
+defmodule Forge.Identifier do
+ @doc """
+ Returns the next globally unique identifier.
+ Raises a MatchError if this cannot be computed.
+ """
+ def next_global! do
+ {:ok, next_id} = Snowflake.next_id()
+ next_id
+ end
+
+ def to_unix(id) do
+ Snowflake.Util.real_timestamp_of_id(id)
+ end
+
+ def to_datetime(id) do
+ id
+ |> to_unix()
+ |> DateTime.from_unix!(:millisecond)
+ end
+
+ def to_erl(id) do
+ %DateTime{year: year, month: month, day: day, hour: hour, minute: minute, second: second} =
+ to_datetime(id)
+
+ {{year, month, day}, {hour, minute, second}}
+ end
+end
diff --git a/apps/common/lib/lexical/math.ex b/apps/forge/lib/forge/math.ex
similarity index 95%
rename from apps/common/lib/lexical/math.ex
rename to apps/forge/lib/forge/math.ex
index 7393bbbd..524829c6 100644
--- a/apps/common/lib/lexical/math.ex
+++ b/apps/forge/lib/forge/math.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Math do
+defmodule Forge.Math do
@moduledoc """
Utilities related to mathematical operations
"""
diff --git a/apps/forge/lib/forge/plugin/v1/diagnostic.ex b/apps/forge/lib/forge/plugin/v1/diagnostic.ex
new file mode 100644
index 00000000..d5dfa707
--- /dev/null
+++ b/apps/forge/lib/forge/plugin/v1/diagnostic.ex
@@ -0,0 +1,93 @@
+defmodule Forge.Plugin.V1.Diagnostic do
+ @moduledoc """
+ A use-able diagnostic plugin module
+
+ A diagnostic result examines a `diagnosable` data structure and emits
+ a list of `Forge.Plugin.V1.Diagnostic.Result` structs.
+
+ Diagnostic plugins are called in two places. When a file is saved, they're called with the
+ `Forge.Project` struct and are expected to perform diagnostics across the whole project.
+
+ On receipt of a change to a `Forge.Document`, they're called with the changed document and
+ are expected to analyze that document and emit any diagnostics they find.
+
+ Both calls to `diagnose` operate on tight deadlines, though when `diagnose` is called with a `Forge.Project`,
+ the deadline is much longer than it is when `diagnose` is called with a `Forge.Document`. This is because
+ analyzing a project should take a lot longer than analyzing a single document. Currently, Expert sets
+ a deadline of around a second on project-level diagnostics and a tens-of-milliseconds deadline on document
+ plugins.
+
+
+ ## Errors and Timeouts
+ Plugins are very sensitive to errors and are disabled by expert if they cause too many. At present,
+ a disabled plugin can only be re-enabled by restarting expert, so ensure that your plugin doesn't crash,
+ and if you don't have diagnostics, return `{:ok, []}` rather than some error response.
+
+ ## Plugin lifecycle
+
+ When a plugin is started, Expert calls the `init/0` function, which can perform setup actions, like starting
+ the application associated with the plugin. Implementing this function is optional.
+ From then on, the plugin will be resident and run in tasks whenever files are changed or saved.
+
+ ## A simple do-nothing plugin
+
+ ```
+ defmodule DoesNothing do
+ use Forge.Plugin.V1.Diagnostic
+
+ def diagnose(%Forge.Document{} = doc) do
+ {:ok, []}
+ end
+
+ def diagnose(%Forge.Project{}) do
+ {:ok, []}
+ end
+ end
+ ```
+ Check out the README for a plugin that does something more.
+ """
+ alias Forge.Document
+ alias Forge.Plugin.V1.Diagnostic
+ alias Forge.Project
+
+ @type state :: any()
+ @type results :: [Diagnostic.Result.t()]
+
+ @type diagnosable :: Project.t() | Document.t()
+ @type diagnostics_reply :: {:ok, results} | {:error, any()}
+
+ @callback diagnose(diagnosable) :: diagnostics_reply()
+
+ defmacro __using__(opts) do
+ name = Keyword.get(opts, :name)
+
+ quote location: :keep do
+ require Logger
+ Module.register_attribute(__MODULE__, :expert_plugin, persist: true)
+ @expert_plugin true
+ @behaviour unquote(__MODULE__)
+
+ def __expert_plugin__ do
+ __MODULE__
+ end
+
+ def __plugin_type__ do
+ :diagnostic
+ end
+
+ def name do
+ unquote(name)
+ end
+
+ def init do
+ :ok
+ end
+
+ def diagnose(_) do
+ {:ok, []}
+ end
+
+ defoverridable init: 0, diagnose: 1
+ end
+ end
+end
diff --git a/apps/forge/lib/forge/plugin/v1/diagnostic/result.ex b/apps/forge/lib/forge/plugin/v1/diagnostic/result.ex
new file mode 100644
index 00000000..a0cd687c
--- /dev/null
+++ b/apps/forge/lib/forge/plugin/v1/diagnostic/result.ex
@@ -0,0 +1,105 @@
+defmodule Forge.Plugin.V1.Diagnostic.Result do
+ @moduledoc """
+ The result of a diagnostic run
+
+ A diagnostic plugin emits a list of `Result` structs that inform the user about issues
+ the plugin has found. The results contain the following keys:
+
+ `uri` - The URI of the document where the error occurs. `Forge.Document` structs contain a
+ `uri` field which can be used to fill out this field. If you have a filesystem path, the function
+ `Forge.Document.Path.to_uri/1` can be used to transform a path to a URI.
+
+ `message` - The diagnostic message displayed to the user
+
+ `details` - Further details about the message
+
+ `position` - Where the message occurred (see the `Positions` section for details)
+
+ `severity` - How important the issue is. Can be one of (from least severe to most severe)
+ `:hint`, `:information`, `:warning`, `:error`
+
+ `source` - The name of the plugin that produced this as a human-readable string.
+
+
+ ## Positions
+
+ Diagnostics need to inform the language client where the error occurs. Positions are the
+ mechanism they use to do so. Positions take several forms, which are:
+
+ `line number` - If the position is a one-based line number, the diagnostic will refer to
+ the entire flagged line
+
+ `{line_number, column}` - If the position is a two-tuple of a one-based line number and a one-based
+ column, then the diagnostic will begin on the line indicated, and start at the column indicated. The
+ diagnostic will run to the end of the line.
+
+ `{start_line, start_column, end_line, end_column}` - If the position is a four-tuple of of one-based
+ line and column numbers, the diagnostic will start on `start_line` at `start_column` and run until
+ `end_line` at `end_column`. This is the most detailed form of describing a position, and should be preferred
+ to the others, as it will produce the most accurate highlighting of the diagnostic.
+
+ `Document.Range.t` - Equivalent to the {start_line, start_column, end_line, end_column}, but saves a
+ conversion step
+
+ `Document.Position.t` - Equivalent to `{line_number, column}`, but saves a conversion step.
+ """
+ alias Forge.Document
+ defstruct [:details, :message, :position, :severity, :source, :uri]
+
+ @typedoc """
+ A path or a uri.
+ """
+ @type path_or_uri :: Forge.path() | Forge.uri()
+
+ @typedoc """
+ The severity of the diagnostic.
+ """
+ @type severity :: :hint | :information | :warning | :error
+
+ @typedoc false
+ @type mix_position ::
+ non_neg_integer()
+ | {pos_integer(), non_neg_integer()}
+ | {pos_integer(), non_neg_integer(), pos_integer(), non_neg_integer()}
+
+ @typedoc """
+ Where the error occurs in the document.
+ """
+ @type position :: mix_position() | Document.Range.t() | Document.Position.t()
+
+ @typedoc """
+ A result emitted by a diagnostic plugin.
+
+ These results are displayed in the editor to the user.
+ """
+ @type t :: %__MODULE__{
+ position: position,
+ message: iodata(),
+ severity: severity(),
+ source: String.t(),
+ uri: Forge.uri()
+ }
+
+ @doc """
+ Creates a new diagnostic result.
+ """
+ @spec new(path_or_uri, position, iodata(), severity(), String.t()) :: t
+ @spec new(path_or_uri, position, iodata(), severity(), String.t(), any()) :: t
+ def new(maybe_uri_or_path, position, message, severity, source, details \\ nil) do
+ uri =
+ if maybe_uri_or_path do
+ Document.Path.ensure_uri(maybe_uri_or_path)
+ end
+
+ message = IO.iodata_to_binary(message)
+
+ %__MODULE__{
+ uri: uri,
+ position: position,
+ message: message,
+ source: source,
+ severity: severity,
+ details: details
+ }
+ end
+end
diff --git a/apps/common/lib/lexical/process_cache.ex b/apps/forge/lib/forge/process_cache.ex
similarity index 98%
rename from apps/common/lib/lexical/process_cache.ex
rename to apps/forge/lib/forge/process_cache.ex
index 9ca7bd01..69d02890 100644
--- a/apps/common/lib/lexical/process_cache.ex
+++ b/apps/forge/lib/forge/process_cache.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.ProcessCache do
+defmodule Forge.ProcessCache do
@moduledoc """
A simple cache with a timeout that lives in the process dictionary
"""
diff --git a/apps/forge/lib/forge/project.ex b/apps/forge/lib/forge/project.ex
new file mode 100644
index 00000000..ad612ab5
--- /dev/null
+++ b/apps/forge/lib/forge/project.ex
@@ -0,0 +1,324 @@
+defmodule Forge.Project do
+ @moduledoc """
+ The representation of the current state of an elixir project.
+
+ This struct contains all the information required to build a project and interrogate its configuration,
+ as well as business logic for how to change its attributes.
+ """
+ alias Forge.Document
+
+ defstruct root_uri: nil,
+ mix_exs_uri: nil,
+ mix_project?: false,
+ mix_env: nil,
+ mix_target: nil,
+ env_variables: %{},
+ project_module: nil,
+ entropy: 1
+
+ @type message :: String.t()
+ @type restart_notification :: {:restart, Logger.level(), String.t()}
+ @type t :: %__MODULE__{
+ root_uri: Forge.uri() | nil,
+ mix_exs_uri: Forge.uri() | nil,
+ entropy: non_neg_integer()
+ # mix_env: atom(),
+ # mix_target: atom(),
+ # env_variables: %{String.t() => String.t()}
+ }
+ @type error_with_message :: {:error, message}
+
+ @workspace_directory_name ".expert"
+
+ # Public
+ @spec new(Forge.uri()) :: t
+ def new(root_uri) do
+ entropy = :rand.uniform(65_536)
+
+ %__MODULE__{entropy: entropy}
+ |> maybe_set_root_uri(root_uri)
+ |> maybe_set_mix_exs_uri()
+ end
+
+ @spec set_project_module(t(), module() | nil) :: t()
+ def set_project_module(%__MODULE__{} = project, nil) do
+ project
+ end
+
+ def set_project_module(%__MODULE__{} = project, module) when is_atom(module) do
+ %__MODULE__{project | project_module: module}
+ end
+
+ @doc """
+ Retrieves the name of the project
+ """
+ @spec name(t) :: String.t()
+
+ def name(%__MODULE__{} = project) do
+ sanitized =
+ project
+ |> folder_name()
+ |> String.replace(~r/[^a-zA-Z0-9_]/, "_")
+
+ # This might be a litte verbose, but this code is hot.
+ case sanitized do
+ <> when c in ?a..?z ->
+ sanitized
+
+ <> when c in ?A..?Z ->
+ String.downcase("#{[c]}") <> rest
+
+ other ->
+ "p_#{other}"
+ end
+ end
+
+ @doc """
+ The project node's name
+ """
+ def node_name(%__MODULE__{} = project) do
+ :"project-#{name(project)}-#{entropy(project)}@127.0.0.1"
+ end
+
+ def entropy(%__MODULE__{} = project) do
+ project.entropy
+ end
+
+ def config(%__MODULE__{} = project) do
+ config_key = {__MODULE__, name(project), :config}
+
+ case :persistent_term.get(config_key, :not_found) do
+ :not_found ->
+ config = project.project_module.project()
+ :persistent_term.put(config_key, config)
+ config
+
+ config ->
+ config
+ end
+ end
+
+ @doc """
+ Returns the the name definied in the `project/0` of mix.exs file
+ """
+ def display_name(%__MODULE__{} = project) do
+ case config(project) do
+ [] ->
+ folder_name(project)
+
+ config ->
+ Keyword.get(config, :name, folder_name(project))
+ end
+ end
+
+ @doc """
+ Retrieves the name of the project as an atom
+ """
+ @spec atom_name(t) :: atom
+ def atom_name(%__MODULE__{project_module: nil} = project) do
+ project
+ |> name()
+ |> String.to_atom()
+ end
+
+ def atom_name(%__MODULE__{} = project) do
+ project.project_module
+ end
+
+ @doc """
+ Returns the full path of the project's root directory
+ """
+ @spec root_path(t) :: Path.t() | nil
+ def root_path(%__MODULE__{root_uri: nil}) do
+ nil
+ end
+
+ def root_path(%__MODULE__{} = project) do
+ Document.Path.from_uri(project.root_uri)
+ end
+
+ @spec project_path(t) :: Path.t() | nil
+ def project_path(%__MODULE__{root_uri: nil}) do
+ nil
+ end
+
+ def project_path(%__MODULE__{} = project) do
+ Document.Path.from_uri(project.root_uri)
+ end
+
+ @doc """
+ Returns the full path to the project's mix.exs file
+ """
+ @spec mix_exs_path(t) :: Path.t() | nil
+ def mix_exs_path(%__MODULE__{mix_exs_uri: nil}) do
+ nil
+ end
+
+ def mix_exs_path(%__MODULE__{mix_exs_uri: mix_exs_uri}) do
+ Document.Path.from_uri(mix_exs_uri)
+ end
+
+ @spec change_environment_variables(t, map() | nil) ::
+ {:ok, t} | error_with_message() | restart_notification()
+ def change_environment_variables(%__MODULE__{} = project, environment_variables) do
+ set_env_vars(project, environment_variables)
+ end
+
+ @doc """
+ Returns the full path to the project's expert workspace directory
+
+ Expert maintains a workspace directory in project it konws about, and places various
+ artifacts there. This function returns the full path to that directory
+ """
+ @spec workspace_path(t) :: String.t()
+ def workspace_path(%__MODULE__{} = project) do
+ project
+ |> root_path()
+ |> Path.join(@workspace_directory_name)
+ end
+
+ @doc """
+ Returns the full path to a file in expert's workspace directory
+ """
+ @spec workspace_path(t, String.t() | [String.t()]) :: String.t()
+ def workspace_path(%__MODULE__{} = project, relative_path) when is_binary(relative_path) do
+ workspace_path(project, [relative_path])
+ end
+
+ def workspace_path(%__MODULE__{} = project, relative_path) when is_list(relative_path) do
+ Path.join([workspace_path(project) | relative_path])
+ end
+
+ @doc """
+ Returns the full path to the directory where expert puts build artifacts
+ """
+ def build_path(%__MODULE__{} = project) do
+ project
+ |> workspace_path()
+ |> Path.join("build")
+ end
+
+ @doc """
+ Creates and initializes expert's workspace directory if it doesn't already exist
+ """
+ def ensure_workspace(%__MODULE__{} = project) do
+ with :ok <- ensure_workspace_directory(project) do
+ ensure_git_ignore(project)
+ end
+ end
+
+ defp ensure_workspace_directory(project) do
+ workspace_path = workspace_path(project)
+
+ cond do
+ File.exists?(workspace_path) and File.dir?(workspace_path) ->
+ :ok
+
+ File.exists?(workspace_path) ->
+ :ok = File.rm(workspace_path)
+ :ok = File.mkdir_p(workspace_path)
+
+ true ->
+ :ok = File.mkdir(workspace_path)
+ end
+ end
+
+ defp ensure_git_ignore(project) do
+ contents = """
+ *
+ """
+
+ path = workspace_path(project, ".gitignore")
+
+ if File.exists?(path) do
+ :ok
+ else
+ File.write(path, contents)
+ end
+ end
+
+ defp maybe_set_root_uri(%__MODULE__{} = project, nil),
+ do: %__MODULE__{project | root_uri: nil}
+
+ defp maybe_set_root_uri(%__MODULE__{} = project, "file://" <> _ = root_uri) do
+ root_path =
+ root_uri
+ |> Document.Path.absolute_from_uri()
+ |> Path.expand()
+
+ if File.exists?(root_path) do
+ expanded_uri = Document.Path.to_uri(root_path)
+ %__MODULE__{project | root_uri: expanded_uri}
+ else
+ project
+ end
+ end
+
+ defp maybe_set_mix_exs_uri(%__MODULE__{} = project) do
+ possible_mix_exs_path =
+ project
+ |> root_path()
+ |> find_mix_exs_path()
+
+ if mix_exs_exists?(possible_mix_exs_path) do
+ %__MODULE__{
+ project
+ | mix_exs_uri: Document.Path.to_uri(possible_mix_exs_path),
+ mix_project?: true
+ }
+ else
+ project
+ end
+ end
+
+ # Project Path
+
+ # Environment variables
+
+ def set_env_vars(%__MODULE__{} = old_project, %{} = env_vars) do
+ case {old_project.env_variables, env_vars} do
+ {nil, vars} when map_size(vars) == 0 ->
+ {:ok, %__MODULE__{old_project | env_variables: vars}}
+
+ {nil, new_vars} ->
+ System.put_env(new_vars)
+ {:ok, %__MODULE__{old_project | env_variables: new_vars}}
+
+ {same, same} ->
+ {:ok, old_project}
+
+ _ ->
+ {:restart, :warning, "Environment variables have changed. Expert needs to restart"}
+ end
+ end
+
+ def set_env_vars(%__MODULE__{} = old_project, _) do
+ {:ok, old_project}
+ end
+
+ defp find_mix_exs_path(nil) do
+ System.get_env("MIX_EXS")
+ end
+
+ defp find_mix_exs_path(project_directory) do
+ case System.get_env("MIX_EXS") do
+ nil ->
+ Path.join(project_directory, "mix.exs")
+
+ mix_exs ->
+ mix_exs
+ end
+ end
+
+ defp mix_exs_exists?(nil), do: false
+
+ defp mix_exs_exists?(mix_exs_path) do
+ File.exists?(mix_exs_path)
+ end
+
+ defp folder_name(project) do
+ project
+ |> root_path()
+ |> Path.basename()
+ end
+end
diff --git a/apps/common/lib/lexical/struct_access.ex b/apps/forge/lib/forge/struct_access.ex
similarity index 96%
rename from apps/common/lib/lexical/struct_access.ex
rename to apps/forge/lib/forge/struct_access.ex
index b68490e6..afb8919e 100644
--- a/apps/common/lib/lexical/struct_access.ex
+++ b/apps/forge/lib/forge/struct_access.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.StructAccess do
+defmodule Forge.StructAccess do
@moduledoc """
Allows structs to easily adopt the `Access` behaviour.
"""
diff --git a/apps/forge/lib/forge/text.ex b/apps/forge/lib/forge/text.ex
new file mode 100644
index 00000000..ce115dec
--- /dev/null
+++ b/apps/forge/lib/forge/text.ex
@@ -0,0 +1,8 @@
+defmodule Forge.Text do
+ def count_leading_spaces(str), do: count_leading_spaces(str, 0)
+
+ def count_leading_spaces(<>, count) when c in [?\s, ?\t],
+ do: count_leading_spaces(rest, count + 1)
+
+ def count_leading_spaces(_, count), do: count
+end
diff --git a/apps/common/lib/lexical/vm/versions.ex b/apps/forge/lib/forge/vm/versions.ex
similarity index 99%
rename from apps/common/lib/lexical/vm/versions.ex
rename to apps/forge/lib/forge/vm/versions.ex
index 3f21751f..4bf9a48b 100644
--- a/apps/common/lib/lexical/vm/versions.ex
+++ b/apps/forge/lib/forge/vm/versions.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.VM.Versions do
+defmodule Forge.VM.Versions do
@moduledoc """
Reads and writes version tags for elixir and erlang
@@ -72,7 +72,7 @@ defmodule Lexical.VM.Versions do
@doc """
Tells whether or not the current version of VM is supported by
- Lexical's compiled artifacts.
+ Expert's compiled artifacts.
"""
@spec compatible?() :: boolean
@spec compatible?(Path.t()) :: boolean
diff --git a/apps/common/lib/future/code.ex b/apps/forge/lib/future/code.ex
similarity index 99%
rename from apps/common/lib/future/code.ex
rename to apps/forge/lib/future/code.ex
index c14ec8a6..80fbc204 100644
--- a/apps/common/lib/future/code.ex
+++ b/apps/forge/lib/future/code.ex
@@ -103,18 +103,18 @@ defmodule Future.Code do
following events are available to tracers:
* `:start` - (since v1.11.0) invoked whenever the compiler starts to trace
- a new lexical context. A lexical context is started when compiling a new
+ a new expert context. A expert context is started when compiling a new
file or when defining a module within a function. Note evaluated code
- does not start a new lexical context (because they don't track unused
+ does not start a new expert context (because they don't track unused
aliases, imports, etc) but defining a module inside evaluated code will.
Note this event may be emitted in parallel, where multiple files/modules
- invoke `:start` and run at the same time. The value of the `lexical_tracker`
+ invoke `:start` and run at the same time. The value of the `expert_tracker`
of the macro environment, albeit opaque, can be used to uniquely identify
the environment.
* `:stop` - (since v1.11.0) invoked whenever the compiler stops tracing a
- new lexical context, such as a new file.
+ new expert context, such as a new file.
* `{:import, meta, module, opts}` - traced whenever `module` is imported.
`meta` is the import AST metadata and `opts` are the import options.
diff --git a/apps/common/lib/future/code/fragment.ex b/apps/forge/lib/future/code/fragment.ex
similarity index 100%
rename from apps/common/lib/future/code/fragment.ex
rename to apps/forge/lib/future/code/fragment.ex
diff --git a/apps/common/lib/future/code/indentifier.ex b/apps/forge/lib/future/code/indentifier.ex
similarity index 100%
rename from apps/common/lib/future/code/indentifier.ex
rename to apps/forge/lib/future/code/indentifier.ex
diff --git a/apps/common/lib/future/code/typespec.ex b/apps/forge/lib/future/code/typespec.ex
similarity index 100%
rename from apps/common/lib/future/code/typespec.ex
rename to apps/forge/lib/future/code/typespec.ex
diff --git a/apps/common/lib/future/macro.ex b/apps/forge/lib/future/macro.ex
similarity index 100%
rename from apps/common/lib/future/macro.ex
rename to apps/forge/lib/future/macro.ex
diff --git a/apps/common/lib/future/mix/tasks/format.ex b/apps/forge/lib/future/mix/tasks/format.ex
similarity index 100%
rename from apps/common/lib/future/mix/tasks/format.ex
rename to apps/forge/lib/future/mix/tasks/format.ex
diff --git a/apps/common/lib/mix/tasks/hooks.ex b/apps/forge/lib/mix/tasks/hooks.ex
similarity index 100%
rename from apps/common/lib/mix/tasks/hooks.ex
rename to apps/forge/lib/mix/tasks/hooks.ex
diff --git a/apps/forge/mix.exs b/apps/forge/mix.exs
new file mode 100644
index 00000000..b2c63c05
--- /dev/null
+++ b/apps/forge/mix.exs
@@ -0,0 +1,43 @@
+defmodule Forge.MixProject do
+ use Mix.Project
+ Code.require_file("../../mix_includes.exs")
+
+ def project do
+ [
+ app: :forge,
+ version: "0.7.2",
+ elixir: "~> 1.15",
+ elixirc_paths: elixirc_paths(Mix.env()),
+ start_permanent: Mix.env() == :prod,
+ deps: deps(),
+ compilers: [:yecc] ++ Mix.compilers(),
+ dialyzer: Mix.Dialyzer.config()
+ ]
+ end
+
+ def application do
+ [
+ extra_applications: [:logger]
+ ]
+ end
+
+ defp elixirc_paths(:test) do
+ ["lib", "test/support"]
+ end
+
+ defp elixirc_paths(_) do
+ ["lib"]
+ end
+
+ defp deps do
+ [
+ {:benchee, "~> 1.3", only: :test},
+ Mix.Credo.dependency(),
+ Mix.Dialyzer.dependency(),
+ {:snowflake, "~> 1.0"},
+ {:sourceror, "~> 1.9"},
+ {:stream_data, "~> 1.1", only: [:test], runtime: false},
+ {:patch, "~> 0.15", only: [:test], optional: true, runtime: false}
+ ]
+ end
+end
diff --git a/apps/common/mix.lock b/apps/forge/mix.lock
similarity index 100%
rename from apps/common/mix.lock
rename to apps/forge/mix.lock
diff --git a/apps/common/src/future_elixir.erl b/apps/forge/src/future_elixir.erl
similarity index 96%
rename from apps/common/src/future_elixir.erl
rename to apps/forge/src/future_elixir.erl
index 4580963f..7d5671ac 100644
--- a/apps/common/src/future_elixir.erl
+++ b/apps/forge/src/future_elixir.erl
@@ -209,7 +209,7 @@ start_cli() ->
%% EVAL HOOKS
-env_for_eval(#{lexical_tracker := Pid} = Env) ->
+env_for_eval(#{expert_tracker := Pid} = Env) ->
NewEnv = Env#{
context := nil,
context_modules := [],
@@ -227,7 +227,7 @@ env_for_eval(#{lexical_tracker := Pid} = Env) ->
<<"an __ENV__ with outdated compilation information was given to eval, "
"call Macro.Env.prune_compile_info/1 to prune it">>
),
- NewEnv#{lexical_tracker := nil, tracers := []}
+ NewEnv#{expert_tracker := nil, tracers := []}
end;
false ->
NewEnv#{tracers := []}
@@ -293,17 +293,17 @@ env_for_eval(Opts) when is_list(Opts) ->
?key(Env, macros)
end,
- %% If there is a dead PID or lexical tracker is nil,
+ %% If there is a dead PID or expert tracker is nil,
%% we assume the tracers also cannot be (re)used.
- {LexicalTracker, Tracers} = case lists:keyfind(lexical_tracker, 1, Opts) of
- {lexical_tracker, Pid} when is_pid(Pid) ->
- 'Elixir.IO':warn(<<":lexical_tracker option in eval is deprecated">>),
+ {ExpertTracker, Tracers} = case lists:keyfind(expert_tracker, 1, Opts) of
+ {expert_tracker, Pid} when is_pid(Pid) ->
+ 'Elixir.IO':warn(<<":expert_tracker option in eval is deprecated">>),
case is_process_alive(Pid) of
true -> {Pid, TempTracers};
false -> {nil, []}
end;
- {lexical_tracker, nil} ->
- 'Elixir.IO':warn(<<":lexical_tracker option in eval is deprecated">>),
+ {expert_tracker, nil} ->
+ 'Elixir.IO':warn(<<":expert_tracker option in eval is deprecated">>),
{nil, []};
false ->
{nil, TempTracers}
@@ -311,7 +311,7 @@ env_for_eval(Opts) when is_list(Opts) ->
Env#{
file := File, module := Module, function := FA, tracers := Tracers,
- macros := Macros, functions := Functions, lexical_tracker := LexicalTracker,
+ macros := Macros, functions := Functions, expert_tracker := ExpertTracker,
requires := Requires, aliases := Aliases, line := Line
}.
diff --git a/apps/common/src/future_elixir.hrl b/apps/forge/src/future_elixir.hrl
similarity index 100%
rename from apps/common/src/future_elixir.hrl
rename to apps/forge/src/future_elixir.hrl
diff --git a/apps/common/src/future_elixir_errors.erl b/apps/forge/src/future_elixir_errors.erl
similarity index 99%
rename from apps/common/src/future_elixir_errors.erl
rename to apps/forge/src/future_elixir_errors.erl
index af0aadc7..72d5297e 100644
--- a/apps/common/src/future_elixir_errors.erl
+++ b/apps/forge/src/future_elixir_errors.erl
@@ -291,9 +291,9 @@ print_error(Meta, Env, Module, Desc) ->
%% Compilation error.
-spec compile_error(#{file := binary(), _ => _}) -> no_return().
-%% We check for the lexical tracker because pry() inside a module
+%% We check for the expert tracker because pry() inside a module
%% will have the environment but not a tracker.
-compile_error(#{module := Module, file := File, lexical_tracker := LT}) when Module /= nil, LT /= nil ->
+compile_error(#{module := Module, file := File, expert_tracker := LT}) when Module /= nil, LT /= nil ->
Inspected = elixir_aliases:inspect(Module),
Message = io_lib:format("cannot compile module ~ts (errors have been logged)", [Inspected]),
compile_error([], File, Message);
diff --git a/apps/common/src/future_elixir_interpolation.erl b/apps/forge/src/future_elixir_interpolation.erl
similarity index 100%
rename from apps/common/src/future_elixir_interpolation.erl
rename to apps/forge/src/future_elixir_interpolation.erl
diff --git a/apps/common/src/future_elixir_parser.yrl b/apps/forge/src/future_elixir_parser.yrl
similarity index 100%
rename from apps/common/src/future_elixir_parser.yrl
rename to apps/forge/src/future_elixir_parser.yrl
diff --git a/apps/common/src/future_elixir_tokenizer.erl b/apps/forge/src/future_elixir_tokenizer.erl
similarity index 100%
rename from apps/common/src/future_elixir_tokenizer.erl
rename to apps/forge/src/future_elixir_tokenizer.erl
diff --git a/apps/common/src/future_elixir_tokenizer.hrl b/apps/forge/src/future_elixir_tokenizer.hrl
similarity index 100%
rename from apps/common/src/future_elixir_tokenizer.hrl
rename to apps/forge/src/future_elixir_tokenizer.hrl
diff --git a/apps/forge/test/forge/ast/detection/alias_test.exs b/apps/forge/test/forge/ast/detection/alias_test.exs
new file mode 100644
index 00000000..3dba4142
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/alias_test.exs
@@ -0,0 +1,7 @@
+defmodule Forge.Ast.Detection.AliasTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.Alias,
+ assertions: [[:alias, :*]]
+end
diff --git a/apps/forge/test/forge/ast/detection/bitstring_test.exs b/apps/forge/test/forge/ast/detection/bitstring_test.exs
new file mode 100644
index 00000000..0f2ab329
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/bitstring_test.exs
@@ -0,0 +1,6 @@
+defmodule Forge.Ast.Detection.BitstringTest do
+ use Forge.Test.DetectionCase,
+ for: Forge.Ast.Detection.Bitstring,
+ assertions: [[:bitstring, :*]],
+ variations: [:match, :function_arguments, :function_body]
+end
diff --git a/apps/forge/test/forge/ast/detection/comment_test.exs b/apps/forge/test/forge/ast/detection/comment_test.exs
new file mode 100644
index 00000000..b87b3fe2
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/comment_test.exs
@@ -0,0 +1,7 @@
+defmodule Forge.Ast.Detection.CommentTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.Comment,
+ assertions: [[:comment, :*]]
+end
diff --git a/apps/common/test/lexical/ast/detection/function_capture_test.exs b/apps/forge/test/forge/ast/detection/function_capture_test.exs
similarity index 88%
rename from apps/common/test/lexical/ast/detection/function_capture_test.exs
rename to apps/forge/test/forge/ast/detection/function_capture_test.exs
index 5031c936..e8acedfd 100644
--- a/apps/common/test/lexical/ast/detection/function_capture_test.exs
+++ b/apps/forge/test/forge/ast/detection/function_capture_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.Ast.Detection.FunctionCaptureTest do
- alias Lexical.Ast.Detection
+defmodule Forge.Ast.Detection.FunctionCaptureTest do
+ alias Forge.Ast.Detection
- use Lexical.Test.DetectionCase,
+ use Forge.Test.DetectionCase,
for: Detection.FunctionCapture,
assertions: [[:function_capture, :*]],
variations: [:match, :function_body]
diff --git a/apps/forge/test/forge/ast/detection/import_test.exs b/apps/forge/test/forge/ast/detection/import_test.exs
new file mode 100644
index 00000000..c74702b8
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/import_test.exs
@@ -0,0 +1,16 @@
+defmodule Forge.Ast.Detection.ImportTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.Import,
+ assertions: [[:import, :*]]
+
+ test "works on multi line" do
+ assert_detected ~q(
+ import« Some.Module, only: »[
+ foo: 3,
+ bar: 6
+ ]
+ )
+ end
+end
diff --git a/apps/forge/test/forge/ast/detection/module_attribute_test.exs b/apps/forge/test/forge/ast/detection/module_attribute_test.exs
new file mode 100644
index 00000000..fc501fc5
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/module_attribute_test.exs
@@ -0,0 +1,17 @@
+defmodule Forge.Ast.Detection.ModuleAttributeTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.ModuleAttribute,
+ assertions: [
+ [:module_attribute, :*],
+ [:callbacks, :*]
+ ],
+ skip: [
+ [:doc, :*],
+ [:module_doc, :*],
+ [:spec, :*],
+ [:type, :*]
+ ],
+ variations: [:module]
+end
diff --git a/apps/forge/test/forge/ast/detection/pipe_test.exs b/apps/forge/test/forge/ast/detection/pipe_test.exs
new file mode 100644
index 00000000..6902d245
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/pipe_test.exs
@@ -0,0 +1,13 @@
+defmodule Forge.Ast.Detection.PipeTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.Pipe,
+ assertions: [[:pipe, :*]],
+ variations: [:function_arguments],
+ skip: [[:module_attribute, :multi_line_pipe]]
+
+ test "is false if there is no pipe in the string" do
+ refute_detected ~q[Enum.foo]
+ end
+end
diff --git a/apps/forge/test/forge/ast/detection/require_test.exs b/apps/forge/test/forge/ast/detection/require_test.exs
new file mode 100644
index 00000000..12a58388
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/require_test.exs
@@ -0,0 +1,7 @@
+defmodule Forge.Ast.Detection.RequireTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.Require,
+ assertions: [[:require, :*]]
+end
diff --git a/apps/forge/test/forge/ast/detection/spec_test.exs b/apps/forge/test/forge/ast/detection/spec_test.exs
new file mode 100644
index 00000000..c281d1bf
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/spec_test.exs
@@ -0,0 +1,7 @@
+defmodule Forge.Ast.Detection.SpecTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.Spec,
+ assertions: [[:spec, :*]]
+end
diff --git a/apps/common/test/lexical/ast/detection/string_test.exs b/apps/forge/test/forge/ast/detection/string_test.exs
similarity index 83%
rename from apps/common/test/lexical/ast/detection/string_test.exs
rename to apps/forge/test/forge/ast/detection/string_test.exs
index 4ffedd33..12dba947 100644
--- a/apps/common/test/lexical/ast/detection/string_test.exs
+++ b/apps/forge/test/forge/ast/detection/string_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.Ast.Detection.StringTest do
- alias Lexical.Ast.Detection
+defmodule Forge.Ast.Detection.StringTest do
+ alias Forge.Ast.Detection
- use Lexical.Test.DetectionCase,
+ use Forge.Test.DetectionCase,
for: Detection.String,
assertions: [[:strings, :*]],
# we skip other tests that have strings in them
diff --git a/apps/forge/test/forge/ast/detection/struct_field_key_test.exs b/apps/forge/test/forge/ast/detection/struct_field_key_test.exs
new file mode 100644
index 00000000..fd19d676
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/struct_field_key_test.exs
@@ -0,0 +1,17 @@
+defmodule Forge.Ast.Detection.StructFieldKeyTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.StructFieldKey,
+ assertions: [[:struct_field_key, :*]],
+ skip: [
+ [:struct_fields, :*],
+ [:struct_reference, :*],
+ [:struct_field_value, :*]
+ ],
+ variations: [:module]
+
+ test "is detected if a key is partially typed" do
+ assert_detected ~q[%User{«fo»}]
+ end
+end
diff --git a/apps/common/test/lexical/ast/detection/struct_field_value_test.exs b/apps/forge/test/forge/ast/detection/struct_field_value_test.exs
similarity index 80%
rename from apps/common/test/lexical/ast/detection/struct_field_value_test.exs
rename to apps/forge/test/forge/ast/detection/struct_field_value_test.exs
index bfff2d6e..07b24221 100644
--- a/apps/common/test/lexical/ast/detection/struct_field_value_test.exs
+++ b/apps/forge/test/forge/ast/detection/struct_field_value_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.Ast.Detection.StructFieldValueTest do
- alias Lexical.Ast.Detection
+defmodule Forge.Ast.Detection.StructFieldValueTest do
+ alias Forge.Ast.Detection
- use Lexical.Test.DetectionCase,
+ use Forge.Test.DetectionCase,
for: Detection.StructFieldValue,
assertions: [[:struct_field_value, :*]],
skip: [
diff --git a/apps/common/test/lexical/ast/detection/struct_fields_test.exs b/apps/forge/test/forge/ast/detection/struct_fields_test.exs
similarity index 86%
rename from apps/common/test/lexical/ast/detection/struct_fields_test.exs
rename to apps/forge/test/forge/ast/detection/struct_fields_test.exs
index a3aa2f6b..477144cc 100644
--- a/apps/common/test/lexical/ast/detection/struct_fields_test.exs
+++ b/apps/forge/test/forge/ast/detection/struct_fields_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.Ast.Detection.StructFieldsTest do
- alias Lexical.Ast.Detection
+defmodule Forge.Ast.Detection.StructFieldsTest do
+ alias Forge.Ast.Detection
- use Lexical.Test.DetectionCase,
+ use Forge.Test.DetectionCase,
for: Detection.StructFields,
assertions: [[:struct_fields, :*]],
variations: [:match, :function_body, :function_arguments, :module],
diff --git a/apps/forge/test/forge/ast/detection/struct_reference_test.exs b/apps/forge/test/forge/ast/detection/struct_reference_test.exs
new file mode 100644
index 00000000..90232d47
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/struct_reference_test.exs
@@ -0,0 +1,45 @@
+defmodule Forge.Ast.Detection.StructReferenceTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.StructReference,
+ assertions: [[:struct_reference, :*]],
+ skip: [[:struct_fields, :*], [:struct_field_value, :*], [:struct_field_key, :*]],
+ variations: [:match, :function_arguments]
+
+ test "is detected if a module reference starts in function arguments" do
+ assert_detected ~q[def my_function(%_«»)]
+ end
+
+ test "is detected if a module reference start in a t type spec" do
+ assert_detected ~q[@type t :: %_«»]
+ end
+
+ test "is detected if the reference is for %__MOD in a function definition " do
+ assert_detected ~q[def my_fn(%_«_MOD»]
+ end
+
+ test "is detected if the reference is on the right side of a match" do
+ assert_detected ~q[foo = %U«se»]
+ end
+
+ test "is detected if the reference is on the left side of a match" do
+ assert_detected ~q[ %U«se» = foo]
+ end
+
+ test "is detected if the reference is for %__} " do
+ assert_detected ~q[%__]
+ end
+
+ test "is not detected if the reference is for %__MOC in a function definition" do
+ refute_detected ~q[def my_fn(%__MOC)]
+ end
+
+ test "is detected if module reference starts with %" do
+ assert_detected ~q[def something(my_thing, %S«truct»{})]
+ end
+
+ test "is not detected if a module reference lacks a %" do
+ refute_detected ~q[def my_function(__)]
+ end
+end
diff --git a/apps/forge/test/forge/ast/detection/type_test.exs b/apps/forge/test/forge/ast/detection/type_test.exs
new file mode 100644
index 00000000..c8403bf8
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/type_test.exs
@@ -0,0 +1,18 @@
+defmodule Forge.Ast.Detection.TypeTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.Type,
+ assertions: [[:type, :*]]
+
+ test "is not detected if you're in a variable named type" do
+ refute_detected ~q[type = 3]
+ end
+
+ test "is not detected right after the type ends" do
+ refute_detected ~q[
+ @type« my_type :: atom»
+
+ ]
+ end
+end
diff --git a/apps/forge/test/forge/ast/detection/use_test.exs b/apps/forge/test/forge/ast/detection/use_test.exs
new file mode 100644
index 00000000..b7685841
--- /dev/null
+++ b/apps/forge/test/forge/ast/detection/use_test.exs
@@ -0,0 +1,7 @@
+defmodule Forge.Ast.Detection.UseTest do
+ alias Forge.Ast.Detection
+
+ use Forge.Test.DetectionCase,
+ for: Detection.Use,
+ assertions: [[:use, :*]]
+end
diff --git a/apps/common/test/lexical/ast/env_test.exs b/apps/forge/test/forge/ast/env_test.exs
similarity index 97%
rename from apps/common/test/lexical/ast/env_test.exs
rename to apps/forge/test/forge/ast/env_test.exs
index 5698a607..071cab7b 100644
--- a/apps/common/test/lexical/ast/env_test.exs
+++ b/apps/forge/test/forge/ast/env_test.exs
@@ -1,11 +1,11 @@
-defmodule Lexical.Ast.EnvTest do
+defmodule Forge.Ast.EnvTest do
use ExUnit.Case, async: true
- alias Lexical.Ast
- alias Lexical.Project
+ alias Forge.Ast
+ alias Forge.Project
- import Lexical.Ast.Env
- import Lexical.Test.CursorSupport
+ import Forge.Ast.Env
+ import Forge.Test.CursorSupport
def new_env(text, opts \\ []) do
opts = Keyword.merge([as: :document], opts)
diff --git a/apps/forge/test/forge/ast/module_test.exs b/apps/forge/test/forge/ast/module_test.exs
new file mode 100644
index 00000000..492eb388
--- /dev/null
+++ b/apps/forge/test/forge/ast/module_test.exs
@@ -0,0 +1,26 @@
+defmodule Forge.Ast.ModuleTest do
+ import Forge.Ast.Module
+ use ExUnit.Case, async: true
+
+ describe "safe_split/2" do
+ test "splits elixir modules into binaries by default" do
+ assert {:elixir, ~w(Forge Document Store)} == safe_split(Forge.Document.Store)
+ end
+
+ test "splits elixir modules into binaries" do
+ assert {:elixir, ~w(Forge Document Store)} ==
+ safe_split(Forge.Document.Store, as: :binaries)
+ end
+
+ test "splits elixir modules into atoms" do
+ assert {:elixir, ~w(Forge Document Store)a} ==
+ safe_split(Forge.Document.Store, as: :atoms)
+ end
+
+ test "splits erlang modules" do
+ assert {:erlang, ["ets"]} = safe_split(:ets)
+ assert {:erlang, ["ets"]} = safe_split(:ets, as: :binaries)
+ assert {:erlang, [:ets]} = safe_split(:ets, as: :atoms)
+ end
+ end
+end
diff --git a/apps/common/test/lexical/ast/tokens_test.exs b/apps/forge/test/forge/ast/tokens_test.exs
similarity index 94%
rename from apps/common/test/lexical/ast/tokens_test.exs
rename to apps/forge/test/forge/ast/tokens_test.exs
index 8b656ec1..05f21713 100644
--- a/apps/common/test/lexical/ast/tokens_test.exs
+++ b/apps/forge/test/forge/ast/tokens_test.exs
@@ -1,8 +1,8 @@
-defmodule Lexical.Ast.TokensTest do
- alias Lexical.Ast.Tokens
+defmodule Forge.Ast.TokensTest do
+ alias Forge.Ast.Tokens
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
use ExUnit.Case, async: true
diff --git a/apps/common/test/lexical/ast_test.exs b/apps/forge/test/forge/ast_test.exs
similarity index 96%
rename from apps/common/test/lexical/ast_test.exs
rename to apps/forge/test/forge/ast_test.exs
index 76fa7c85..16796b65 100644
--- a/apps/common/test/lexical/ast_test.exs
+++ b/apps/forge/test/forge/ast_test.exs
@@ -1,14 +1,14 @@
-defmodule Lexical.AstTest do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.Document.Position
+defmodule Forge.AstTest do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Document
+ alias Forge.Document.Position
alias Sourceror.Zipper
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
- import Lexical.Test.PositionSupport
- import Lexical.Test.RangeSupport
+ import Forge.Test.CodeSigil
+ import Forge.Test.CursorSupport
+ import Forge.Test.PositionSupport
+ import Forge.Test.RangeSupport
use ExUnit.Case, async: true
diff --git a/apps/common/test/lexical/code_unit_test.exs b/apps/forge/test/forge/code_unit_test.exs
similarity index 98%
rename from apps/common/test/lexical/code_unit_test.exs
rename to apps/forge/test/forge/code_unit_test.exs
index 25166f86..d278b3dc 100644
--- a/apps/common/test/lexical/code_unit_test.exs
+++ b/apps/forge/test/forge/code_unit_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.CodeUnitTest do
- alias Lexical.CodeUnit
+defmodule Forge.CodeUnitTest do
+ alias Forge.CodeUnit
use ExUnit.Case
diff --git a/apps/common/test/lexical/document/lines_test.exs b/apps/forge/test/forge/document/lines_test.exs
similarity index 94%
rename from apps/common/test/lexical/document/lines_test.exs
rename to apps/forge/test/forge/document/lines_test.exs
index 03b5ebc6..4b1f213a 100644
--- a/apps/common/test/lexical/document/lines_test.exs
+++ b/apps/forge/test/forge/document/lines_test.exs
@@ -1,6 +1,6 @@
-defmodule Lexical.Document.LinesTest do
- alias Lexical.Document.Line
- alias Lexical.Document.Lines
+defmodule Forge.Document.LinesTest do
+ alias Forge.Document.Line
+ alias Forge.Document.Lines
use ExUnit.Case, async: true
use ExUnitProperties
diff --git a/apps/common/test/lexical/document/path_test.exs b/apps/forge/test/forge/document/path_test.exs
similarity index 98%
rename from apps/common/test/lexical/document/path_test.exs
rename to apps/forge/test/forge/document/path_test.exs
index 4668986d..90197fc2 100644
--- a/apps/common/test/lexical/document/path_test.exs
+++ b/apps/forge/test/forge/document/path_test.exs
@@ -2,13 +2,13 @@ defmodule ElixirLS.LanguageServer.SourceFile.PathTest do
use ExUnit.Case
use Patch
- import Lexical.Document.Path
+ import Forge.Document.Path
defp patch_os(os_type, fun) do
test = self()
spawn(fn ->
- patch(Lexical.Document.Path, :os_type, os_type)
+ patch(Forge.Document.Path, :os_type, os_type)
try do
rv = fun.()
diff --git a/apps/common/test/lexical/document/position_test.exs b/apps/forge/test/forge/document/position_test.exs
similarity index 90%
rename from apps/common/test/lexical/document/position_test.exs
rename to apps/forge/test/forge/document/position_test.exs
index 41561aff..85a1e344 100644
--- a/apps/common/test/lexical/document/position_test.exs
+++ b/apps/forge/test/forge/document/position_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.Document.PositionTest do
- alias Lexical.Document.Line
- alias Lexical.Document.Lines
- alias Lexical.Document.Position
+defmodule Forge.Document.PositionTest do
+ alias Forge.Document.Line
+ alias Forge.Document.Lines
+ alias Forge.Document.Position
import Line
diff --git a/apps/common/test/lexical/document/range_test.exs b/apps/forge/test/forge/document/range_test.exs
similarity index 91%
rename from apps/common/test/lexical/document/range_test.exs
rename to apps/forge/test/forge/document/range_test.exs
index 1d4c8860..e2e16264 100644
--- a/apps/common/test/lexical/document/range_test.exs
+++ b/apps/forge/test/forge/document/range_test.exs
@@ -1,9 +1,9 @@
-defmodule Lexical.Document.RangeTest do
- alias Lexical.Document.Lines
- alias Lexical.Document.Position
- alias Lexical.Document.Range
+defmodule Forge.Document.RangeTest do
+ alias Forge.Document.Lines
+ alias Forge.Document.Position
+ alias Forge.Document.Range
- import Lexical.Document.Line
+ import Forge.Document.Line
use ExUnit.Case, async: true
diff --git a/apps/forge/test/forge/document/store_test.exs b/apps/forge/test/forge/document/store_test.exs
new file mode 100644
index 00000000..f3f6b623
--- /dev/null
+++ b/apps/forge/test/forge/document/store_test.exs
@@ -0,0 +1,265 @@
+defmodule Forge.Document.StoreTest do
+ alias Forge.Document
+ alias Forge.Document.Edit
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+
+ use ExUnit.Case
+
+ def with_store(%{} = context) do
+ store_opts = Map.get(context, :store, [])
+ {:ok, _} = start_supervised({Document.Store, store_opts})
+ :ok
+ end
+
+ def with_an_open_document(_) do
+ :ok = Document.Store.open(uri(), "hello", 1)
+ end
+
+ def uri do
+ "file:///file.ex"
+ end
+
+ defp build_position(_, nil) do
+ nil
+ end
+
+ defp build_position(%Document{} = document, opts) do
+ line = Keyword.get(opts, :line)
+ character = Keyword.get(opts, :character)
+ Position.new(document, line, character)
+ end
+
+ defp build_range(_, nil) do
+ nil
+ end
+
+ defp build_range(%Document{} = document, opts) do
+ start_pos = build_position(document, Keyword.get(opts, :start))
+
+ end_pos = build_position(document, Keyword.get(opts, :end))
+
+ Range.new(start_pos, end_pos)
+ end
+
+ defp build_change(opts) do
+ text = Keyword.get(opts, :text, "")
+ document = Document.new("file:///file.ex", text, 1)
+
+ range = build_range(document, Keyword.get(opts, :range))
+ Edit.new(text, range)
+ end
+
+ describe "startup" do
+ test "succeeds without options" do
+ assert {:ok, _} = start_supervised(Document.Store)
+ end
+
+ test "succeeds with empty :derive" do
+ assert {:ok, _} = start_supervised({Document.Store, [derive: []]})
+ end
+
+ test "succeeds with valid :derive" do
+ valid_fun = fn _ -> :ok end
+ assert {:ok, _} = start_supervised({Document.Store, [derive: [valid: valid_fun]]})
+ end
+
+ test "fails with invalid :derive" do
+ invalid_fun = fn _, _ -> :ok end
+ assert {:error, _} = start_supervised({Document.Store, [derive: [invalid: invalid_fun]]})
+ end
+
+ test "fails with invalid options" do
+ assert {:error, _} = start_supervised({Document.Store, [invalid: []]})
+ end
+ end
+
+ describe "a clean store" do
+ setup [:with_store]
+
+ test "a document can be opened" do
+ :ok = Document.Store.open(uri(), "hello", 1)
+ assert {:ok, file} = Document.Store.fetch(uri())
+ assert Document.to_string(file) == "hello"
+ assert file.version == 1
+ end
+
+ test "rejects changes to a file that isn't open" do
+ event = build_change(text: "dog", range: nil)
+
+ assert {:error, :not_open} =
+ Document.Store.get_and_update(
+ "file:///another.ex",
+ &Document.apply_content_changes(&1, 3, [event])
+ )
+ end
+ end
+
+ describe "a document that is already open" do
+ setup [:with_store, :with_an_open_document]
+
+ test "can be fetched" do
+ assert {:ok, doc} = Document.Store.fetch(uri())
+ assert doc.uri == uri()
+ assert Document.to_string(doc) == "hello"
+ end
+
+ test "can be closed" do
+ assert :ok = Document.Store.close(uri())
+ assert {:error, :not_open} = Document.Store.fetch(uri())
+ end
+
+ test "can be saved" do
+ assert :ok = Document.Store.save(uri())
+ assert {:ok, %{dirty?: false}} = Document.Store.fetch(uri())
+ end
+
+ test "can have its content changed" do
+ event =
+ build_change(
+ text: "dog",
+ range: [
+ start: [line: 1, character: 1],
+ end: [line: 1, character: 4]
+ ]
+ )
+
+ assert {:ok, doc} =
+ Document.Store.get_and_update(uri(), fn document ->
+ Document.apply_content_changes(document, 2, [
+ event
+ ])
+ end)
+
+ assert Document.to_string(doc) == "doglo"
+ assert {:ok, file} = Document.Store.fetch(uri())
+ assert Document.to_string(file) == "doglo"
+ end
+
+ test "rejects a change if the version is less than the current version" do
+ event = build_change(text: "dog", range: nil)
+
+ assert {:error, :invalid_version} =
+ Document.Store.get_and_update(
+ uri(),
+ &Document.apply_content_changes(&1, -1, [event])
+ )
+ end
+
+ test "a change cannot be applied once a file is closed" do
+ event = build_change(text: "dog", range: nil)
+ assert :ok = Document.Store.close(uri())
+
+ assert {:error, :not_open} =
+ Document.Store.get_and_update(
+ uri(),
+ &Document.apply_content_changes(&1, 3, [event])
+ )
+ end
+ end
+
+ def with_a_temp_document(_) do
+ contents = """
+ defmodule FakeLines do
+ end
+ """
+
+ :ok = File.write("/tmp/file.ex", contents)
+
+ on_exit(fn ->
+ File.rm!("/tmp/file.ex")
+ end)
+
+ {:ok, contents: contents, uri: "file:///tmp/file.ex"}
+ end
+
+ describe "a temp document" do
+ setup [:with_store, :with_a_temp_document]
+
+ test "can be opened", ctx do
+ assert {:ok, doc} = Document.Store.open_temporary(ctx.uri, 100)
+ assert Document.to_string(doc) == ctx.contents
+ end
+
+ test "closes after a timeout", ctx do
+ assert {:ok, _} = Document.Store.open_temporary(ctx.uri, 100)
+ Process.sleep(101)
+ refute Document.Store.open?(ctx.uri)
+ assert Document.Store.fetch(ctx.uri) == {:error, :not_open}
+ end
+
+ test "the extension is extended on subsequent access", ctx do
+ assert {:ok, _doc} = Document.Store.open_temporary(ctx.uri, 100)
+ Process.sleep(75)
+ assert {:ok, _} = Document.Store.open_temporary(ctx.uri, 100)
+ Process.sleep(75)
+ assert Document.Store.open?(ctx.uri)
+ Process.sleep(50)
+ refute Document.Store.open?(ctx.uri)
+ end
+
+ test "opens permanently when a call to open is made", ctx do
+ assert {:ok, _doc} = Document.Store.open_temporary(ctx.uri, 100)
+ assert :ok = Document.Store.open(ctx.uri, ctx.contents, 1)
+ Process.sleep(120)
+ assert Document.Store.open?(ctx.uri)
+ end
+ end
+
+ describe "derived values" do
+ setup context do
+ me = self()
+
+ length_fun = fn doc ->
+ send(me, :length_called)
+
+ doc
+ |> Document.to_string()
+ |> String.length()
+ end
+
+ :ok = with_store(%{store: [derive: [length: length_fun]]})
+ :ok = with_an_open_document(context)
+ end
+
+ test "can be fetched with the document by key" do
+ assert {:ok, doc, 5} = Document.Store.fetch(uri(), :length)
+ assert Document.to_string(doc) == "hello"
+ end
+
+ test "update when the document changes" do
+ assert :ok =
+ Document.Store.update(uri(), fn document ->
+ Document.apply_content_changes(document, 2, [
+ build_change(text: "dog")
+ ])
+ end)
+
+ assert {:ok, doc, 3} = Document.Store.fetch(uri(), :length)
+ assert Document.to_string(doc) == "dog"
+ end
+
+ test "are lazily computed when first fetched" do
+ assert {:ok, %Document{}, 5} = Document.Store.fetch(uri(), :length)
+ assert_received :length_called
+ end
+
+ test "are only computed again when the document changes" do
+ assert {:ok, %Document{}, 5} = Document.Store.fetch(uri(), :length)
+ assert_received :length_called
+
+ assert {:ok, %Document{}, 5} = Document.Store.fetch(uri(), :length)
+ refute_received :length_called
+
+ assert :ok =
+ Document.Store.update(uri(), fn document ->
+ Document.apply_content_changes(document, 2, [
+ build_change(text: "dog")
+ ])
+ end)
+
+ assert {:ok, %Document{}, 3} = Document.Store.fetch(uri(), :length)
+ assert_received :length_called
+ end
+ end
+end
diff --git a/apps/common/test/lexical/formats_test.exs b/apps/forge/test/forge/formats_test.exs
similarity index 90%
rename from apps/common/test/lexical/formats_test.exs
rename to apps/forge/test/forge/formats_test.exs
index 194ef5df..d7607850 100644
--- a/apps/common/test/lexical/formats_test.exs
+++ b/apps/forge/test/forge/formats_test.exs
@@ -1,15 +1,15 @@
-defmodule Lexical.FormatsTest do
- alias Lexical.Formats
+defmodule Forge.FormatsTest do
+ alias Forge.Formats
use ExUnit.Case
describe "stringifying modules" do
test "it correctly handles a top-level module" do
- assert "Lexical" == Formats.module(Lexical)
+ assert "Forge" == Formats.module(Forge)
end
test "it correctly handles a nested module" do
- assert "Lexical.Text" == Formats.module(Lexical.Text)
+ assert "Forge.Text" == Formats.module(Forge.Text)
end
test "it correctly handles an erlang module name" do
diff --git a/apps/common/test/lexical/line_parser_test.exs b/apps/forge/test/forge/line_parser_test.exs
similarity index 96%
rename from apps/common/test/lexical/line_parser_test.exs
rename to apps/forge/test/forge/line_parser_test.exs
index eaef8149..e9d9f6e8 100644
--- a/apps/common/test/lexical/line_parser_test.exs
+++ b/apps/forge/test/forge/line_parser_test.exs
@@ -1,7 +1,7 @@
-defmodule Lexical.Document.LineParserTest do
- alias Lexical.Document.LineParser
+defmodule Forge.Document.LineParserTest do
+ alias Forge.Document.LineParser
- import Lexical.Document.Line
+ import Forge.Document.Line
use ExUnit.Case
use ExUnitProperties
diff --git a/apps/common/test/lexical/math_test.exs b/apps/forge/test/forge/math_test.exs
similarity index 94%
rename from apps/common/test/lexical/math_test.exs
rename to apps/forge/test/forge/math_test.exs
index a87b21e1..8adea42c 100644
--- a/apps/common/test/lexical/math_test.exs
+++ b/apps/forge/test/forge/math_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.MathTest do
- alias Lexical.Math
+defmodule Forge.MathTest do
+ alias Forge.Math
use ExUnit.Case, async: true
use ExUnitProperties
diff --git a/apps/common/test/lexical/process_cache_test.exs b/apps/forge/test/forge/process_cache_test.exs
similarity index 97%
rename from apps/common/test/lexical/process_cache_test.exs
rename to apps/forge/test/forge/process_cache_test.exs
index fe4484cb..2996bd43 100644
--- a/apps/common/test/lexical/process_cache_test.exs
+++ b/apps/forge/test/forge/process_cache_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.ProcessCacheTest do
- alias Lexical.ProcessCache
+defmodule Forge.ProcessCacheTest do
+ alias Forge.ProcessCache
import ProcessCache
diff --git a/apps/common/test/lexical/project_test.exs b/apps/forge/test/forge/project_test.exs
similarity index 89%
rename from apps/common/test/lexical/project_test.exs
rename to apps/forge/test/forge/project_test.exs
index 5616f37f..74f3df8e 100644
--- a/apps/common/test/lexical/project_test.exs
+++ b/apps/forge/test/forge/project_test.exs
@@ -1,12 +1,12 @@
-defmodule Lexical.ProjectTest do
- alias Lexical.Project
+defmodule Forge.ProjectTest do
+ alias Forge.Project
use ExUnit.Case, async: false
use ExUnitProperties
use Patch
def project do
- root = Lexical.Document.Path.to_uri(__DIR__)
+ root = Forge.Document.Path.to_uri(__DIR__)
Project.new(root)
end
diff --git a/apps/common/test/lexical/text_test.exs b/apps/forge/test/forge/text_test.exs
similarity index 89%
rename from apps/common/test/lexical/text_test.exs
rename to apps/forge/test/forge/text_test.exs
index 860f41b0..9c5ac5c3 100644
--- a/apps/common/test/lexical/text_test.exs
+++ b/apps/forge/test/forge/text_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.TextTest do
- alias Lexical.Text
+defmodule Forge.TextTest do
+ alias Forge.Text
use ExUnit.Case, async: true
use ExUnitProperties
diff --git a/apps/common/test/lexical/vm/versions_test.exs b/apps/forge/test/forge/vm/versions_test.exs
similarity index 97%
rename from apps/common/test/lexical/vm/versions_test.exs
rename to apps/forge/test/forge/vm/versions_test.exs
index 2166eb31..29bf0ae8 100644
--- a/apps/common/test/lexical/vm/versions_test.exs
+++ b/apps/forge/test/forge/vm/versions_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.VM.VersionTest do
- alias Lexical.VM.Versions
+defmodule Forge.VM.VersionTest do
+ alias Forge.VM.Versions
use ExUnit.Case
use Patch
import Versions
diff --git a/apps/common/test/support/lexical/test/code_sigil.ex b/apps/forge/test/support/test/code_sigil.ex
similarity index 96%
rename from apps/common/test/support/lexical/test/code_sigil.ex
rename to apps/forge/test/support/test/code_sigil.ex
index 577928bc..30946f51 100644
--- a/apps/common/test/support/lexical/test/code_sigil.ex
+++ b/apps/forge/test/support/test/code_sigil.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Test.CodeSigil do
+defmodule Forge.Test.CodeSigil do
def sigil_q(text, opts \\ []) do
{first, rest} =
case String.split(text, "\n") do
diff --git a/apps/common/test/support/lexical/test/cursor_support.ex b/apps/forge/test/support/test/cursor_support.ex
similarity index 94%
rename from apps/common/test/support/lexical/test/cursor_support.ex
rename to apps/forge/test/support/test/cursor_support.ex
index 7936d843..ce9f5a4e 100644
--- a/apps/common/test/support/lexical/test/cursor_support.ex
+++ b/apps/forge/test/support/test/cursor_support.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.Test.CursorSupport do
+defmodule Forge.Test.CursorSupport do
@moduledoc """
Utilities for extracting cursor position in code fragments and documents.
"""
- alias Lexical.Document
- alias Lexical.Document.Line
- alias Lexical.Document.Position
- alias Lexical.Test.PositionSupport
+ alias Forge.Document
+ alias Forge.Document.Line
+ alias Forge.Document.Position
+ alias Forge.Test.PositionSupport
import Line
@@ -26,7 +26,7 @@ defmodule Lexical.Test.CursorSupport do
`#{inspect(@default_cursor)}`.
* `:as` - one of `:text` (default) or `:document`. If `:document`,
- wraps the text in a `Lexical.Document` using the URI `"file:///file.ex"`.
+ wraps the text in a `Forge.Document` using the URI `"file:///file.ex"`.
* `:document` - the document path or URI. Setting this option implies
`as: :document`.
diff --git a/apps/common/test/support/lexical/test/detection_case.ex b/apps/forge/test/support/test/detection_case.ex
similarity index 95%
rename from apps/common/test/support/lexical/test/detection_case.ex
rename to apps/forge/test/support/test/detection_case.ex
index a825b598..e8551a7c 100644
--- a/apps/common/test/support/lexical/test/detection_case.ex
+++ b/apps/forge/test/support/test/detection_case.ex
@@ -1,14 +1,14 @@
-defmodule Lexical.Test.DetectionCase do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Tokens
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Test.DetectionCase.Suite
- alias Lexical.Test.Variations
-
- import Lexical.Test.RangeSupport
+defmodule Forge.Test.DetectionCase do
+ alias Forge.Ast
+ alias Forge.Ast.Analysis
+ alias Forge.Ast.Tokens
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Test.DetectionCase.Suite
+ alias Forge.Test.Variations
+
+ import Forge.Test.RangeSupport
import ExUnit.Assertions
use ExUnit.CaseTemplate
@@ -38,9 +38,9 @@ defmodule Lexical.Test.DetectionCase do
quote location: :keep do
@context unquote(context)
- alias Lexical.Test.DetectionCase
+ alias Forge.Test.DetectionCase
- import Lexical.Test.CodeSigil
+ import Forge.Test.CodeSigil
import unquote(__MODULE__),
only: [
diff --git a/apps/common/test/support/lexical/test/detection_case/suite.ex b/apps/forge/test/support/test/detection_case/suite.ex
similarity index 99%
rename from apps/common/test/support/lexical/test/detection_case/suite.ex
rename to apps/forge/test/support/test/detection_case/suite.ex
index 269de3b6..7e531999 100644
--- a/apps/common/test/support/lexical/test/detection_case/suite.ex
+++ b/apps/forge/test/support/test/detection_case/suite.ex
@@ -1,8 +1,8 @@
-defmodule Lexical.Test.DetectionCase.Suite do
+defmodule Forge.Test.DetectionCase.Suite do
@moduledoc """
Defines a test suite for the detection case tests.
"""
- import Lexical.Test.CodeSigil
+ import Forge.Test.CodeSigil
@doc """
Returns a list of tuples where:
diff --git a/apps/common/test/support/lexical/test/diagnostic_support.ex b/apps/forge/test/support/test/diagnostic_support.ex
similarity index 84%
rename from apps/common/test/support/lexical/test/diagnostic_support.ex
rename to apps/forge/test/support/test/diagnostic_support.ex
index 8ce5a7ea..2e37310e 100644
--- a/apps/common/test/support/lexical/test/diagnostic_support.ex
+++ b/apps/forge/test/support/test/diagnostic_support.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Test.DiagnosticSupport do
+defmodule Forge.Test.DiagnosticSupport do
def execute_if(feature_condition) do
matched? =
Enum.all?(feature_condition, fn {feature_fn, value} ->
diff --git a/apps/common/test/support/lexical/test/document_support.ex b/apps/forge/test/support/test/document_support.ex
similarity index 80%
rename from apps/common/test/support/lexical/test/document_support.ex
rename to apps/forge/test/support/test/document_support.ex
index 9c233ce3..2ff235d4 100644
--- a/apps/common/test/support/lexical/test/document_support.ex
+++ b/apps/forge/test/support/test/document_support.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.Test.DocumentSupport do
- alias Lexical.Document
+defmodule Forge.Test.DocumentSupport do
+ alias Forge.Document
use ExUnit.CaseTemplate
setup do
@@ -9,7 +9,7 @@ defmodule Lexical.Test.DocumentSupport do
using do
quote do
- alias Lexical.Document
+ alias Forge.Document
def open_file(file_uri \\ "file:///file.ex", contents) do
with :ok <- Document.Store.open(file_uri, contents, 0),
diff --git a/apps/common/test/support/lexical/test/eventual_assertions.ex b/apps/forge/test/support/test/eventual_assertions.ex
similarity index 97%
rename from apps/common/test/support/lexical/test/eventual_assertions.ex
rename to apps/forge/test/support/test/eventual_assertions.ex
index 7bd37a8e..f3ea17ea 100644
--- a/apps/common/test/support/lexical/test/eventual_assertions.ex
+++ b/apps/forge/test/support/test/eventual_assertions.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Test.EventualAssertions do
+defmodule Forge.Test.EventualAssertions do
@moduledoc """
Assertion macros for an eventually consistent world
Sometimes, despite our best efforts, we want to assert that some condition holds, but it doesn't
diff --git a/apps/common/test/support/lexical/test/position_support.ex b/apps/forge/test/support/test/position_support.ex
similarity index 87%
rename from apps/common/test/support/lexical/test/position_support.ex
rename to apps/forge/test/support/test/position_support.ex
index 71beb19e..3b4cbb56 100644
--- a/apps/common/test/support/lexical/test/position_support.ex
+++ b/apps/forge/test/support/test/position_support.ex
@@ -1,9 +1,9 @@
-defmodule Lexical.Test.PositionSupport do
- alias Lexical.Document.Edit
- alias Lexical.Document.Line
- alias Lexical.Document.Lines
- alias Lexical.Document.Position
- alias Lexical.Document.Range
+defmodule Forge.Test.PositionSupport do
+ alias Forge.Document.Edit
+ alias Forge.Document.Line
+ alias Forge.Document.Lines
+ alias Forge.Document.Position
+ alias Forge.Document.Range
import Line
diff --git a/apps/common/test/support/lexical/test/quiet.ex b/apps/forge/test/support/test/quiet.ex
similarity index 88%
rename from apps/common/test/support/lexical/test/quiet.ex
rename to apps/forge/test/support/test/quiet.ex
index a6778570..91e1d519 100644
--- a/apps/common/test/support/lexical/test/quiet.ex
+++ b/apps/forge/test/support/test/quiet.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Test.Quiet do
+defmodule Forge.Test.Quiet do
import ExUnit.CaptureIO
def quiet(io_device \\ :stdio, fun) do
diff --git a/apps/common/test/support/lexical/test/range_support.ex b/apps/forge/test/support/test/range_support.ex
similarity index 94%
rename from apps/common/test/support/lexical/test/range_support.ex
rename to apps/forge/test/support/test/range_support.ex
index 20ab9845..f82cf162 100644
--- a/apps/common/test/support/lexical/test/range_support.ex
+++ b/apps/forge/test/support/test/range_support.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.Test.RangeSupport do
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Math
- alias Lexical.Test.CursorSupport
- alias Lexical.Text
-
- import Lexical.Document.Line, only: [line: 1]
+defmodule Forge.Test.RangeSupport do
+ alias Forge.Document
+ alias Forge.Document.Position
+ alias Forge.Document.Range
+ alias Forge.Math
+ alias Forge.Test.CursorSupport
+ alias Forge.Text
+
+ import Forge.Document.Line, only: [line: 1]
@range_start_marker "«"
@range_end_marker "»"
diff --git a/apps/common/test/support/lexical/test/variations.ex b/apps/forge/test/support/test/variations.ex
similarity index 95%
rename from apps/common/test/support/lexical/test/variations.ex
rename to apps/forge/test/support/test/variations.ex
index 72854881..febe54d3 100644
--- a/apps/common/test/support/lexical/test/variations.ex
+++ b/apps/forge/test/support/test/variations.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Test.Variations do
+defmodule Forge.Test.Variations do
def wrap_with(:nothing, text) do
text
end
diff --git a/apps/common/test/test_helper.exs b/apps/forge/test/test_helper.exs
similarity index 100%
rename from apps/common/test/test_helper.exs
rename to apps/forge/test/test_helper.exs
diff --git a/apps/lexical_credo/.gitignore b/apps/lexical_credo/.gitignore
deleted file mode 100644
index 6943771a..00000000
--- a/apps/lexical_credo/.gitignore
+++ /dev/null
@@ -1,26 +0,0 @@
-# The directory Mix will write compiled artifacts to.
-/_build/
-
-# If you run "mix test --cover", coverage assets end up here.
-/cover/
-
-# The directory Mix downloads your dependencies sources to.
-/deps/
-
-# Where third-party dependencies like ExDoc output generated docs.
-/doc/
-
-# Ignore .fetch files in case you like to edit your project deps locally.
-/.fetch
-
-# If the VM crashes, it generates a dump, let's ignore it too.
-erl_crash.dump
-
-# Also ignore archive artifacts (built via "mix archive.build").
-*.ez
-
-# Ignore package tarball (built via "mix hex.build").
-lexical_credo-*.tar
-
-# Temporary files, for example, from tests.
-/tmp/
diff --git a/apps/lexical_credo/README.md b/apps/lexical_credo/README.md
deleted file mode 100644
index eabc9df9..00000000
--- a/apps/lexical_credo/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Lexical Credo - A Lexical plugin that enables credo checks
-
-This is a plugin to lexical (our first) that enables [Credo](https://github.com/rrrene/credo) to
-run whenever you type. Now you can immediately see and address any issues that credo flags when you
-make them.
-
-It's essential to have this as a test dependency, as lexical runs your code in test mode by default.
-
-## Installation
-
-```elixir
-def deps do
- [
- {:lexical_credo, "~> 0.1.0", only: [:dev, :test]}
- ]
-end
-```
-
-When starting lexical, you should see the following in your `project.log` file in the project's `.lexical`
-directory:
-
-```
-info: Loaded [:lexical_credo]
-```
-
-Once you see that, you should start seeing Credo diagnostics in your editor.
-
-
-## Notes on the plugin
-
-The plugin runs Credo whenever you type, and most of your settings will be respected... for the most part.
-Because Credo is designed to work with files on the disk, and the code you're editing isn't the same as
-the code that's on the disk, we are sending the file's contents to Credo via standard output. However, when
-we do this, Credo doesn't have a provision to provide the file name and the filename is lost.
-This means that there's no way for Credo to know if the file is being ignored by your project, and you'll see
-errors in it as you type. However, the errors will disappear once you save the file.
diff --git a/apps/lexical_credo/lib/lexical_credo.ex b/apps/lexical_credo/lib/lexical_credo.ex
deleted file mode 100644
index c042b0b2..00000000
--- a/apps/lexical_credo/lib/lexical_credo.ex
+++ /dev/null
@@ -1,107 +0,0 @@
-defmodule LexicalCredo do
- @moduledoc false
-
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.Project
-
- use Diagnostic, name: :lexical_credo
- require Logger
-
- @doc false
- def init do
- with {:ok, _} <- Application.ensure_all_started(:credo) do
- :ok
- end
- end
-
- @doc false
- def document do
- %Document{}
- end
-
- @doc false
- def diagnose(%Document{} = doc) do
- doc_contents = Document.to_string(doc)
-
- execution_args = [
- "--mute-exit-status",
- "--read-from-stdin",
- Document.Path.absolute_from_uri(doc.uri)
- ]
-
- execution = Credo.Execution.build(execution_args)
-
- with_stdin(
- doc_contents,
- fn ->
- Credo.CLI.Output.Shell.suppress_output(fn ->
- Credo.Execution.run(execution)
- end)
- end
- )
-
- diagnostics =
- execution
- |> Credo.Execution.get_issues()
- |> Enum.map(&to_diagnostic/1)
-
- {:ok, diagnostics}
- end
-
- @doc false
- def diagnose(%Project{}) do
- results =
- Credo.Execution.build()
- |> Credo.Execution.run()
- |> Credo.Execution.get_issues()
- |> Enum.map(&to_diagnostic/1)
-
- {:ok, results}
- end
-
- @doc false
- def with_stdin(stdin_contents, function) when is_function(function, 0) do
- {:ok, stdio} = StringIO.open(stdin_contents)
- caller = self()
-
- spawn(fn ->
- Process.group_leader(self(), stdio)
- result = function.()
- send(caller, {:result, result})
- end)
-
- receive do
- {:result, result} ->
- {:ok, result}
- end
- end
-
- defp to_diagnostic(%Credo.Issue{} = issue) do
- file_path = Document.Path.ensure_uri(issue.filename)
-
- Diagnostic.Result.new(
- file_path,
- location(issue),
- issue.message,
- priority_to_severity(issue),
- "Credo"
- )
- end
-
- defp priority_to_severity(%Credo.Issue{priority: priority}) do
- case Credo.Priority.to_atom(priority) do
- :higher -> :error
- :high -> :warning
- :normal -> :information
- _ -> :hint
- end
- end
-
- defp location(%Credo.Issue{} = issue) do
- case {issue.line_no, issue.column} do
- {line, nil} -> line
- location -> location
- end
- end
-end
diff --git a/apps/lexical_credo/mix.exs b/apps/lexical_credo/mix.exs
deleted file mode 100644
index 28ac08ff..00000000
--- a/apps/lexical_credo/mix.exs
+++ /dev/null
@@ -1,47 +0,0 @@
-defmodule LexicalCredo.MixProject do
- use Mix.Project
- Code.require_file("../../mix_dialyzer.exs")
- @repo_url "https://github.com/lexical-lsp/lexical/"
- @version "0.5.0"
-
- def project do
- [
- app: :lexical_credo,
- version: @version,
- elixir: "~> 1.15",
- start_permanent: Mix.env() == :prod,
- deps: deps(),
- docs: docs(),
- dialyzer: Mix.Dialyzer.config(add_apps: [:jason])
- ]
- end
-
- # Run "mix help compile.app" to learn about applications.
- def application do
- [
- extra_applications: [:logger],
- env: [lexical_plugin: true]
- ]
- end
-
- # Run "mix help deps" to learn about dependencies.
- defp deps do
- [
- {:common, path: "../common", env: Mix.env()},
- {:credo, "> 0.0.0", only: [:dev, :test]},
- Mix.Dialyzer.dependency(),
- {:jason, "> 0.0.0", optional: true},
- {:ex_doc, "~> 0.34", optional: true, only: [:dev, :hex]}
- ]
- end
-
- defp docs do
- [
- extras: ["README.md": [title: "Overview"]],
- main: "readme",
- homepage_url: @repo_url,
- source_ref: "v#{@version}",
- source_url: @repo_url
- ]
- end
-end
diff --git a/apps/lexical_credo/test/lexical_credo_test.exs b/apps/lexical_credo/test/lexical_credo_test.exs
deleted file mode 100644
index 3e497451..00000000
--- a/apps/lexical_credo/test/lexical_credo_test.exs
+++ /dev/null
@@ -1,36 +0,0 @@
-defmodule LexicalCredoTest do
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic.Result
-
- import LexicalCredo
- use ExUnit.Case
-
- setup_all do
- Application.ensure_all_started(:credo)
- :ok
- end
-
- def doc(contents) do
- Document.new("file:///file.ex", contents, 1)
- end
-
- test "reports errors on documents" do
- has_inspect =
- """
- defmodule Bad do
- def test do
- IO.inspect("hello")
- end
- end
- """
- |> doc()
- |> diagnose()
-
- assert {:ok, [%Result{} = result]} = has_inspect
- assert result.position == {3, 5}
- assert result.message == "There should be no calls to `IO.inspect/1`."
- assert String.ends_with?(result.uri, "/file.ex")
- assert result.severity == :warning
- assert result.source == "Credo"
- end
-end
diff --git a/apps/proto/lib/lexical/proto.ex b/apps/proto/lib/expert/proto.ex
similarity index 87%
rename from apps/proto/lib/lexical/proto.ex
rename to apps/proto/lib/expert/proto.ex
index 5cd0aba6..b45dda7b 100644
--- a/apps/proto/lib/lexical/proto.ex
+++ b/apps/proto/lib/expert/proto.ex
@@ -1,12 +1,12 @@
-defmodule Lexical.Proto do
- alias Lexical.Proto.Decoders
+defmodule Expert.Proto do
+ alias Expert.Proto.Decoders
defmacro __using__([]) do
quote location: :keep do
- alias Lexical.Proto
- alias Lexical.Proto.LspTypes
+ alias Expert.Proto
+ alias Expert.Proto.LspTypes
- import Lexical.Proto.TypeFunctions
+ import Expert.Proto.TypeFunctions
import Proto.Alias, only: [defalias: 1]
import Proto.Enum, only: [defenum: 1]
import Proto.Notification, only: [defnotification: 1, defnotification: 2]
diff --git a/apps/proto/lib/expert/proto/alias.ex b/apps/proto/lib/expert/proto/alias.ex
new file mode 100644
index 00000000..0d844da1
--- /dev/null
+++ b/apps/proto/lib/expert/proto/alias.ex
@@ -0,0 +1,38 @@
+defmodule Expert.Proto.Alias do
+ alias Expert.Proto.CompileMetadata
+ alias Expert.Proto.Field
+ alias Expert.Proto.Macros.Typespec
+
+ defmacro defalias(alias_definition) do
+ caller_module = __CALLER__.module
+ CompileMetadata.add_type_alias_module(caller_module)
+
+ quote location: :keep do
+ @type t :: unquote(Typespec.typespec(alias_definition, __CALLER__))
+
+ def parse(lsp_map) do
+ Field.extract(unquote(alias_definition), :alias, lsp_map)
+ end
+
+ def definition do
+ unquote(alias_definition)
+ end
+
+ def __meta__(:type) do
+ :type_alias
+ end
+
+ def __meta__(:param_names) do
+ []
+ end
+
+ def __meta__(:definition) do
+ unquote(alias_definition)
+ end
+
+ def __meta__(:raw_definition) do
+ unquote(Macro.escape(alias_definition))
+ end
+ end
+ end
+end
diff --git a/apps/proto/lib/lexical/proto/compile_metadata.ex b/apps/proto/lib/expert/proto/compile_metadata.ex
similarity index 97%
rename from apps/proto/lib/lexical/proto/compile_metadata.ex
rename to apps/proto/lib/expert/proto/compile_metadata.ex
index 34e93ec1..60270a02 100644
--- a/apps/proto/lib/lexical/proto/compile_metadata.ex
+++ b/apps/proto/lib/expert/proto/compile_metadata.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Proto.CompileMetadata do
+defmodule Expert.Proto.CompileMetadata do
@moduledoc """
Compile-time storage of protocol metadata
"""
diff --git a/apps/proto/lib/lexical/proto/convert.ex b/apps/proto/lib/expert/proto/convert.ex
similarity index 90%
rename from apps/proto/lib/lexical/proto/convert.ex
rename to apps/proto/lib/expert/proto/convert.ex
index 73c4cfb3..8c44d1fb 100644
--- a/apps/proto/lib/lexical/proto/convert.ex
+++ b/apps/proto/lib/expert/proto/convert.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Proto.Convert do
- alias Lexical.Convertible
- alias Lexical.Document
+defmodule Expert.Proto.Convert do
+ alias Forge.Convertible
+ alias Forge.Document
def to_lsp(%_{result: result} = response) do
case Convertible.to_lsp(result) do
diff --git a/apps/proto/lib/lexical/proto/decoders.ex b/apps/proto/lib/expert/proto/decoders.ex
similarity index 97%
rename from apps/proto/lib/lexical/proto/decoders.ex
rename to apps/proto/lib/expert/proto/decoders.ex
index 91c3eddf..2c2fe4b1 100644
--- a/apps/proto/lib/lexical/proto/decoders.ex
+++ b/apps/proto/lib/expert/proto/decoders.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Proto.Decoders do
- alias Lexical.Proto.CompileMetadata
- alias Lexical.Proto.Typespecs
+defmodule Expert.Proto.Decoders do
+ alias Expert.Proto.CompileMetadata
+ alias Expert.Proto.Typespecs
defmacro for_notifications(_) do
notification_modules = CompileMetadata.notification_modules()
diff --git a/apps/proto/lib/lexical/proto/enum.ex b/apps/proto/lib/expert/proto/enum.ex
similarity index 95%
rename from apps/proto/lib/lexical/proto/enum.ex
rename to apps/proto/lib/expert/proto/enum.ex
index 85df34fd..9db5e43a 100644
--- a/apps/proto/lib/lexical/proto/enum.ex
+++ b/apps/proto/lib/expert/proto/enum.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.Proto.Enum do
- alias Lexical.Proto.Macros.Typespec
+defmodule Expert.Proto.Enum do
+ alias Expert.Proto.Macros.Typespec
defmacro defenum(opts) do
names =
diff --git a/apps/proto/lib/lexical/proto/field.ex b/apps/proto/lib/expert/proto/field.ex
similarity index 99%
rename from apps/proto/lib/lexical/proto/field.ex
rename to apps/proto/lib/expert/proto/field.ex
index 043b7248..e90b7cba 100644
--- a/apps/proto/lib/lexical/proto/field.ex
+++ b/apps/proto/lib/expert/proto/field.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.Proto.Field do
- alias Lexical.Proto.Text
+defmodule Expert.Proto.Field do
+ alias Expert.Proto.Text
def extract(:any, _, value) do
{:ok, value}
diff --git a/apps/proto/lib/lexical/proto/lsp_types.ex b/apps/proto/lib/expert/proto/lsp_types.ex
similarity index 94%
rename from apps/proto/lib/lexical/proto/lsp_types.ex
rename to apps/proto/lib/expert/proto/lsp_types.ex
index 0d89e9c1..db3180b6 100644
--- a/apps/proto/lib/lexical/proto/lsp_types.ex
+++ b/apps/proto/lib/expert/proto/lsp_types.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.Proto.LspTypes do
- alias Lexical.Proto
+defmodule Expert.Proto.LspTypes do
+ alias Expert.Proto
use Proto
defmodule ErrorCodes do
diff --git a/apps/proto/lib/lexical/proto/macros/access.ex b/apps/proto/lib/expert/proto/macros/access.ex
similarity index 95%
rename from apps/proto/lib/lexical/proto/macros/access.ex
rename to apps/proto/lib/expert/proto/macros/access.ex
index feabee15..c5c8b351 100644
--- a/apps/proto/lib/lexical/proto/macros/access.ex
+++ b/apps/proto/lib/expert/proto/macros/access.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Proto.Macros.Access do
+defmodule Expert.Proto.Macros.Access do
def build do
quote location: :keep do
def fetch(proto, key) when is_map_key(proto, key) do
diff --git a/apps/proto/lib/lexical/proto/macros/inspect.ex b/apps/proto/lib/expert/proto/macros/inspect.ex
similarity index 95%
rename from apps/proto/lib/lexical/proto/macros/inspect.ex
rename to apps/proto/lib/expert/proto/macros/inspect.ex
index a8a3f72e..a82cc5a7 100644
--- a/apps/proto/lib/lexical/proto/macros/inspect.ex
+++ b/apps/proto/lib/expert/proto/macros/inspect.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Proto.Macros.Inspect do
+defmodule Expert.Proto.Macros.Inspect do
def build(dest_module) do
trimmed_name = trim_module_name(dest_module)
diff --git a/apps/proto/lib/lexical/proto/macros/json.ex b/apps/proto/lib/expert/proto/macros/json.ex
similarity index 95%
rename from apps/proto/lib/lexical/proto/macros/json.ex
rename to apps/proto/lib/expert/proto/macros/json.ex
index 240166fe..6e0193ff 100644
--- a/apps/proto/lib/lexical/proto/macros/json.ex
+++ b/apps/proto/lib/expert/proto/macros/json.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.Proto.Macros.Json do
- alias Lexical.Proto.Field
+defmodule Expert.Proto.Macros.Json do
+ alias Expert.Proto.Field
def build(dest_module) do
quote location: :keep do
diff --git a/apps/proto/lib/lexical/proto/macros/match.ex b/apps/proto/lib/expert/proto/macros/match.ex
similarity index 91%
rename from apps/proto/lib/lexical/proto/macros/match.ex
rename to apps/proto/lib/expert/proto/macros/match.ex
index 62252cd0..38ad34d8 100644
--- a/apps/proto/lib/lexical/proto/macros/match.ex
+++ b/apps/proto/lib/expert/proto/macros/match.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Proto.Macros.Match do
+defmodule Expert.Proto.Macros.Match do
def build(field_types, dest_module) do
macro_name =
dest_module
diff --git a/apps/proto/lib/lexical/proto/macros/message.ex b/apps/proto/lib/expert/proto/macros/message.ex
similarity index 92%
rename from apps/proto/lib/lexical/proto/macros/message.ex
rename to apps/proto/lib/expert/proto/macros/message.ex
index 06fdadf2..bed22ce1 100644
--- a/apps/proto/lib/lexical/proto/macros/message.ex
+++ b/apps/proto/lib/expert/proto/macros/message.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Proto.Macros.Message do
- alias Lexical.Document
+defmodule Expert.Proto.Macros.Message do
+ alias Forge.Document
- alias Lexical.Proto.Macros.{
+ alias Expert.Proto.Macros.{
Access,
Meta,
Parse,
diff --git a/apps/proto/lib/lexical/proto/macros/meta.ex b/apps/proto/lib/expert/proto/macros/meta.ex
similarity index 96%
rename from apps/proto/lib/lexical/proto/macros/meta.ex
rename to apps/proto/lib/expert/proto/macros/meta.ex
index 47c4945c..8460ee8c 100644
--- a/apps/proto/lib/lexical/proto/macros/meta.ex
+++ b/apps/proto/lib/expert/proto/macros/meta.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Proto.Macros.Meta do
+defmodule Expert.Proto.Macros.Meta do
def build(opts) do
field_types =
for {field_name, field_type} <- opts do
diff --git a/apps/proto/lib/expert/proto/macros/parse.ex b/apps/proto/lib/expert/proto/macros/parse.ex
new file mode 100644
index 00000000..aa26f8c7
--- /dev/null
+++ b/apps/proto/lib/expert/proto/macros/parse.ex
@@ -0,0 +1,140 @@
+defmodule Expert.Proto.Macros.Parse do
+ alias Expert.Proto.Field
+ alias Expert.Proto.Text
+
+ def build(opts) do
+ {optional_opts, required_opts} =
+ Enum.split_with(opts, fn
+ {_key, {:optional, _, _}} -> true
+ {:.., _} -> true
+ _ -> false
+ end)
+
+ {splat_opt, optional_opts} = Keyword.pop(optional_opts, :..)
+
+ required_keys = Keyword.keys(required_opts)
+
+ map_parameter_var =
+ if Enum.empty?(optional_opts) && is_nil(splat_opt) do
+ Macro.var(:_, nil)
+ else
+ Macro.var(:json_rpc_message, nil)
+ end
+
+ struct_keys = Keyword.keys(opts)
+
+ map_vars = Map.new(struct_keys, fn k -> {k, Macro.var(k, nil)} end)
+
+ map_keys = Enum.map(required_keys, &Text.camelize/1)
+
+ map_pairs =
+ map_vars
+ |> Map.take(required_keys)
+ |> Enum.map(fn {k, v} -> {Text.camelize(k), v} end)
+
+ map_extractors = map_extractor(map_pairs)
+
+ required_extractors =
+ for {field_name, field_type} <- required_opts do
+ quote location: :keep do
+ {unquote(field_name),
+ Field.extract(
+ unquote(field_type),
+ unquote(field_name),
+ unquote(Map.get(map_vars, field_name))
+ )}
+ end
+ end
+
+ optional_extractors =
+ for {field_name, field_type} <- optional_opts do
+ quote location: :keep do
+ {unquote(field_name),
+ Field.extract(
+ unquote(field_type),
+ unquote(field_name),
+ Map.get(unquote(map_parameter_var), unquote(Text.camelize(field_name)))
+ )}
+ end
+ end
+
+ splat_extractors =
+ if splat_opt do
+ known_keys = opts |> Keyword.keys() |> Enum.map(&Text.camelize/1)
+
+ quoted_extractor =
+ quote location: :keep do
+ {(fn ->
+ {:map, _, field_name} = unquote(splat_opt)
+ field_name
+ end).(),
+ Field.extract(
+ unquote(splat_opt),
+ :..,
+ unquote(map_parameter_var)
+ |> Enum.reject(fn {k, _} -> k in unquote(known_keys) end)
+ |> Map.new()
+ )}
+ end
+
+ [quoted_extractor]
+ else
+ []
+ end
+
+ all_extractors = required_extractors ++ optional_extractors ++ splat_extractors
+ error_parse = maybe_build_error_parse(required_extractors, map_keys)
+
+ quote location: :keep do
+ def parse(unquote(map_extractors) = unquote(map_parameter_var)) do
+ result =
+ unquote(all_extractors)
+ |> Enum.reduce_while([], fn
+ {field, {:ok, result}}, acc ->
+ {:cont, [{field, result} | acc]}
+
+ {field, {:error, _} = err}, acc ->
+ {:halt, err}
+ end)
+
+ case result do
+ {:error, _} = err -> err
+ keyword when is_list(keyword) -> {:ok, struct(__MODULE__, keyword)}
+ end
+ end
+
+ unquote(error_parse)
+
+ def parse(not_map) do
+ {:error, {:invalid_map, not_map}}
+ end
+ end
+ end
+
+ defp maybe_build_error_parse([], _) do
+ end
+
+ defp maybe_build_error_parse(_, map_keys) do
+ # this is only built if there are required fields
+ quote do
+ def parse(%{} = unmatched) do
+ missing_keys =
+ Enum.reduce(unquote(map_keys), [], fn key, acc ->
+ if Map.has_key?(unmatched, key) do
+ acc
+ else
+ [key | acc]
+ end
+ end)
+
+ {:error, {:missing_keys, missing_keys, __MODULE__}}
+ end
+ end
+ end
+
+ defp map_extractor(map_pairs) do
+ quote location: :keep do
+ %{unquote_splicing(map_pairs)}
+ end
+ end
+end
diff --git a/apps/proto/lib/expert/proto/macros/struct.ex b/apps/proto/lib/expert/proto/macros/struct.ex
new file mode 100644
index 00000000..fba57123
--- /dev/null
+++ b/apps/proto/lib/expert/proto/macros/struct.ex
@@ -0,0 +1,55 @@
+defmodule Expert.Proto.Macros.Struct do
+ alias Expert.Proto.Macros.Typespec
+
+ def build(opts, env) do
+ keys = Keyword.keys(opts)
+ required_keys = required_keys(opts)
+
+ keys =
+ if :.. in keys do
+ {splat_def, rest} = Keyword.pop(opts, :..)
+
+ quote location: :keep do
+ [
+ (fn ->
+ {_, _, field_name} = unquote(splat_def)
+ field_name
+ end).()
+ | unquote(rest)
+ ]
+ end
+ else
+ keys
+ end
+
+ quote location: :keep do
+ @enforce_keys unquote(required_keys)
+ defstruct unquote(keys)
+ @type option :: unquote(Typespec.keyword_constructor_options(opts, env))
+ @type options :: [option]
+
+ @spec new() :: t()
+ @spec new(options()) :: t()
+ def new(opts \\ []) do
+ struct!(__MODULE__, opts)
+ end
+
+ defoverridable new: 0, new: 1
+ end
+ end
+
+ defp required_keys(opts) do
+ opts
+ |> Enum.filter(fn
+ # ignore the splat, it's always optional
+ {:.., _} -> false
+ # an optional signifier tuple
+ {_, {:optional, _}} -> false
+ # ast for an optional signifier tuple
+ {_, {:optional, _, _}} -> false
+ # everything else is required
+ _ -> true
+ end)
+ |> Keyword.keys()
+ end
+end
diff --git a/apps/proto/lib/expert/proto/macros/typespec.ex b/apps/proto/lib/expert/proto/macros/typespec.ex
new file mode 100644
index 00000000..6aa552a1
--- /dev/null
+++ b/apps/proto/lib/expert/proto/macros/typespec.ex
@@ -0,0 +1,202 @@
+defmodule Expert.Proto.Macros.Typespec do
+ def typespec(opts \\ [], env \\ nil)
+
+ def typespec([], _) do
+ quote do
+ %__MODULE__{}
+ end
+ end
+
+ def typespec(opts, env) when is_list(opts) do
+ typespecs =
+ for {name, type} <- opts,
+ name != :.. do
+ {name, do_typespec(type, env)}
+ end
+
+ quote do
+ %__MODULE__{unquote_splicing(typespecs)}
+ end
+ end
+
+ def typespec(typespec, env) do
+ do_typespec(typespec, env)
+ end
+
+ def choice(options, env) do
+ do_typespec({:one_of, [], [options]}, env)
+ end
+
+ def keyword_constructor_options(opts, env) do
+ for {name, type} <- opts,
+ name != :.. do
+ {name, do_typespec(type, env)}
+ end
+ |> or_types()
+ end
+
+ defp do_typespec([], _env) do
+ # This is what's presented to typespec when a response has no results, as in the Shutdown response
+ nil
+ end
+
+ defp do_typespec(nil, _env) do
+ quote(do: nil)
+ end
+
+ defp do_typespec({:boolean, _, _}, _env) do
+ quote(do: boolean())
+ end
+
+ defp do_typespec({:string, _, _}, _env) do
+ quote(do: String.t())
+ end
+
+ defp do_typespec({:integer, _, _}, _env) do
+ quote(do: integer())
+ end
+
+ defp do_typespec({:float, _, _}, _env) do
+ quote(do: float())
+ end
+
+ defp do_typespec({:__MODULE__, _, nil}, env) do
+ env.module
+ end
+
+ defp do_typespec({:optional, _, [optional_type]}, env) do
+ quote do
+ unquote(do_typespec(optional_type, env)) | nil
+ end
+ end
+
+ defp do_typespec({:__aliases__, _, raw_alias} = aliased_module, env) do
+ expanded_alias = Macro.expand(aliased_module, env)
+
+ case List.last(raw_alias) do
+ :Position ->
+ other_alias =
+ case expanded_alias do
+ Forge.Document.Range ->
+ Expert.Protocol.Types.Range
+
+ _ ->
+ Forge.Document.Range
+ end
+
+ quote do
+ unquote(expanded_alias).t() | unquote(other_alias).t()
+ end
+
+ :Range ->
+ other_alias =
+ case expanded_alias do
+ Forge.Document.Range ->
+ Expert.Protocol.Types.Range
+
+ _ ->
+ Forge.Document.Range
+ end
+
+ quote do
+ unquote(expanded_alias).t() | unquote(other_alias).t()
+ end
+
+ _ ->
+ quote do
+ unquote(expanded_alias).t()
+ end
+ end
+ end
+
+ defp do_typespec({:literal, _, value}, _env) when is_atom(value) do
+ value
+ end
+
+ defp do_typespec({:literal, _, [value]}, _env) do
+ literal_type(value)
+ end
+
+ defp do_typespec({:type_alias, _, [alias_dest]}, env) do
+ do_typespec(alias_dest, env)
+ end
+
+ defp do_typespec({:one_of, _, [type_list]}, env) do
+ type_list
+ |> Enum.map(&do_typespec(&1, env))
+ |> or_types()
+ end
+
+ defp do_typespec({:list_of, _, items}, env) do
+ refined =
+ items
+ |> Enum.map(&do_typespec(&1, env))
+ |> or_types()
+
+ quote do
+ [unquote(refined)]
+ end
+ end
+
+ defp do_typespec({:tuple_of, _, [items]}, env) do
+ refined = Enum.map(items, &do_typespec(&1, env))
+
+ quote do
+ {unquote_splicing(refined)}
+ end
+ end
+
+ defp do_typespec({:map_of, _, items}, env) do
+ value_types =
+ items
+ |> Enum.map(&do_typespec(&1, env))
+ |> or_types()
+
+ quote do
+ %{String.t() => unquote(value_types)}
+ end
+ end
+
+ defp do_typespec({:any, _, _}, _env) do
+ quote do
+ any()
+ end
+ end
+
+ defp or_types(list_of_types) do
+ Enum.reduce(list_of_types, nil, fn
+ type, nil ->
+ type
+
+ type, acc ->
+ quote do
+ unquote(type) | unquote(acc)
+ end
+ end)
+ end
+
+ defp literal_type(thing) do
+ case thing do
+ string when is_binary(string) ->
+ quote(do: String.t())
+
+ integer when is_integer(integer) ->
+ quote(do: integer())
+
+ float when is_binary(float) ->
+ quote(do: float())
+
+ boolean when is_boolean(boolean) ->
+ quote(do: boolean())
+
+ atom when is_atom(atom) ->
+ atom
+
+ [] ->
+ quote(do: [])
+
+ [elem | _] ->
+ quote(do: [unquote(literal_type(elem))])
+ end
+ end
+end
diff --git a/apps/proto/lib/lexical/proto/notification.ex b/apps/proto/lib/expert/proto/notification.ex
similarity index 95%
rename from apps/proto/lib/lexical/proto/notification.ex
rename to apps/proto/lib/expert/proto/notification.ex
index 1cfefc59..54cd10fa 100644
--- a/apps/proto/lib/lexical/proto/notification.ex
+++ b/apps/proto/lib/expert/proto/notification.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Proto.Notification do
- alias Lexical.Proto.CompileMetadata
- alias Lexical.Proto.Macros.Message
+defmodule Expert.Proto.Notification do
+ alias Expert.Proto.CompileMetadata
+ alias Expert.Proto.Macros.Message
defmacro defnotification(method) do
do_defnotification(method, [], __CALLER__)
diff --git a/apps/proto/lib/lexical/proto/request.ex b/apps/proto/lib/expert/proto/request.ex
similarity index 94%
rename from apps/proto/lib/lexical/proto/request.ex
rename to apps/proto/lib/expert/proto/request.ex
index 72163551..be98645c 100644
--- a/apps/proto/lib/lexical/proto/request.ex
+++ b/apps/proto/lib/expert/proto/request.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Proto.Request do
- alias Lexical.Proto.CompileMetadata
- alias Lexical.Proto.Macros.Message
- alias Lexical.Proto.TypeFunctions
+defmodule Expert.Proto.Request do
+ alias Expert.Proto.CompileMetadata
+ alias Expert.Proto.Macros.Message
+ alias Expert.Proto.TypeFunctions
import TypeFunctions, only: [optional: 1, literal: 1]
@@ -76,8 +76,8 @@ defmodule Lexical.Proto.Request do
end
end
- alias Lexical.Proto.Convert
- alias Lexical.Protocol.Types
+ alias Expert.Proto.Convert
+ alias Expert.Protocol.Types
unquote(
Message.build({:request, :elixir}, method, elixir_types, param_names, caller,
diff --git a/apps/proto/lib/lexical/proto/response.ex b/apps/proto/lib/expert/proto/response.ex
similarity index 90%
rename from apps/proto/lib/lexical/proto/response.ex
rename to apps/proto/lib/expert/proto/response.ex
index 84b26e75..b8c70999 100644
--- a/apps/proto/lib/lexical/proto/response.ex
+++ b/apps/proto/lib/expert/proto/response.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Proto.Response do
- alias Lexical.Proto.CompileMetadata
+defmodule Expert.Proto.Response do
+ alias Expert.Proto.CompileMetadata
- alias Lexical.Proto.Macros.{
+ alias Expert.Proto.Macros.{
Access,
Meta,
Parse,
@@ -14,12 +14,12 @@ defmodule Lexical.Proto.Response do
jsonrpc_types = [
id: quote(do: optional(one_of([integer(), string()]))),
- error: quote(do: optional(Lexical.Proto.LspTypes.ResponseError)),
+ error: quote(do: optional(Expert.Proto.LspTypes.ResponseError)),
result: quote(do: optional(unquote(response_type)))
]
quote location: :keep do
- alias Lexical.Proto.LspTypes
+ alias Expert.Proto.LspTypes
unquote(Access.build())
unquote(Struct.build(jsonrpc_types, __CALLER__))
@type t :: unquote(Typespec.typespec())
diff --git a/apps/proto/lib/expert/proto/text.ex b/apps/proto/lib/expert/proto/text.ex
new file mode 100644
index 00000000..c6c8195e
--- /dev/null
+++ b/apps/proto/lib/expert/proto/text.ex
@@ -0,0 +1,10 @@
+defmodule Expert.Proto.Text do
+ def camelize(atom) when is_atom(atom) do
+ atom |> Atom.to_string() |> camelize()
+ end
+
+ def camelize(string) do
+ <> = Macro.camelize(string)
+ String.downcase(first) <> rest
+ end
+end
diff --git a/apps/proto/lib/expert/proto/type.ex b/apps/proto/lib/expert/proto/type.ex
new file mode 100644
index 00000000..38cf6ad4
--- /dev/null
+++ b/apps/proto/lib/expert/proto/type.ex
@@ -0,0 +1,40 @@
+defmodule Expert.Proto.Type do
+ alias Expert.Proto.CompileMetadata
+
+ alias Expert.Proto.Macros.{
+ Access,
+ Inspect,
+ Json,
+ Match,
+ Meta,
+ Parse,
+ Struct,
+ Typespec
+ }
+
+ defmacro deftype(types) do
+ caller_module = __CALLER__.module
+ CompileMetadata.add_type_module(caller_module)
+
+ quote location: :keep do
+ unquote(Json.build(caller_module))
+ unquote(Inspect.build(caller_module))
+ unquote(Access.build())
+ unquote(Struct.build(types, __CALLER__))
+
+ @type t :: unquote(Typespec.typespec(types, __CALLER__))
+
+ unquote(Parse.build(types))
+ unquote(Match.build(types, caller_module))
+ unquote(Meta.build(types))
+
+ def __meta__(:type) do
+ :type
+ end
+
+ def __meta__(:param_names) do
+ unquote(Keyword.keys(types))
+ end
+ end
+ end
+end
diff --git a/apps/proto/lib/lexical/proto/type_functions.ex b/apps/proto/lib/expert/proto/type_functions.ex
similarity index 94%
rename from apps/proto/lib/lexical/proto/type_functions.ex
rename to apps/proto/lib/expert/proto/type_functions.ex
index 0cd0e3b7..86137dc0 100644
--- a/apps/proto/lib/lexical/proto/type_functions.ex
+++ b/apps/proto/lib/expert/proto/type_functions.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Proto.TypeFunctions do
+defmodule Expert.Proto.TypeFunctions do
def integer do
:integer
end
diff --git a/apps/proto/lib/lexical/proto/typespecs.ex b/apps/proto/lib/expert/proto/typespecs.ex
similarity index 93%
rename from apps/proto/lib/lexical/proto/typespecs.ex
rename to apps/proto/lib/expert/proto/typespecs.ex
index 41112300..552a911c 100644
--- a/apps/proto/lib/lexical/proto/typespecs.ex
+++ b/apps/proto/lib/expert/proto/typespecs.ex
@@ -1,5 +1,5 @@
-defmodule Lexical.Proto.Typespecs do
- alias Lexical.Proto.CompileMetadata
+defmodule Expert.Proto.Typespecs do
+ alias Expert.Proto.CompileMetadata
defmacro __using__(opts) do
group_name = Keyword.fetch!(opts, :for)
diff --git a/apps/proto/lib/lexical/proto/alias.ex b/apps/proto/lib/lexical/proto/alias.ex
deleted file mode 100644
index 854f9ada..00000000
--- a/apps/proto/lib/lexical/proto/alias.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-defmodule Lexical.Proto.Alias do
- alias Lexical.Proto.CompileMetadata
- alias Lexical.Proto.Field
- alias Lexical.Proto.Macros.Typespec
-
- defmacro defalias(alias_definition) do
- caller_module = __CALLER__.module
- CompileMetadata.add_type_alias_module(caller_module)
-
- quote location: :keep do
- @type t :: unquote(Typespec.typespec(alias_definition, __CALLER__))
-
- def parse(lsp_map) do
- Field.extract(unquote(alias_definition), :alias, lsp_map)
- end
-
- def definition do
- unquote(alias_definition)
- end
-
- def __meta__(:type) do
- :type_alias
- end
-
- def __meta__(:param_names) do
- []
- end
-
- def __meta__(:definition) do
- unquote(alias_definition)
- end
-
- def __meta__(:raw_definition) do
- unquote(Macro.escape(alias_definition))
- end
- end
- end
-end
diff --git a/apps/proto/lib/lexical/proto/macros/parse.ex b/apps/proto/lib/lexical/proto/macros/parse.ex
deleted file mode 100644
index 67d530b7..00000000
--- a/apps/proto/lib/lexical/proto/macros/parse.ex
+++ /dev/null
@@ -1,140 +0,0 @@
-defmodule Lexical.Proto.Macros.Parse do
- alias Lexical.Proto.Field
- alias Lexical.Proto.Text
-
- def build(opts) do
- {optional_opts, required_opts} =
- Enum.split_with(opts, fn
- {_key, {:optional, _, _}} -> true
- {:.., _} -> true
- _ -> false
- end)
-
- {splat_opt, optional_opts} = Keyword.pop(optional_opts, :..)
-
- required_keys = Keyword.keys(required_opts)
-
- map_parameter_var =
- if Enum.empty?(optional_opts) && is_nil(splat_opt) do
- Macro.var(:_, nil)
- else
- Macro.var(:json_rpc_message, nil)
- end
-
- struct_keys = Keyword.keys(opts)
-
- map_vars = Map.new(struct_keys, fn k -> {k, Macro.var(k, nil)} end)
-
- map_keys = Enum.map(required_keys, &Text.camelize/1)
-
- map_pairs =
- map_vars
- |> Map.take(required_keys)
- |> Enum.map(fn {k, v} -> {Text.camelize(k), v} end)
-
- map_extractors = map_extractor(map_pairs)
-
- required_extractors =
- for {field_name, field_type} <- required_opts do
- quote location: :keep do
- {unquote(field_name),
- Field.extract(
- unquote(field_type),
- unquote(field_name),
- unquote(Map.get(map_vars, field_name))
- )}
- end
- end
-
- optional_extractors =
- for {field_name, field_type} <- optional_opts do
- quote location: :keep do
- {unquote(field_name),
- Field.extract(
- unquote(field_type),
- unquote(field_name),
- Map.get(unquote(map_parameter_var), unquote(Text.camelize(field_name)))
- )}
- end
- end
-
- splat_extractors =
- if splat_opt do
- known_keys = opts |> Keyword.keys() |> Enum.map(&Text.camelize/1)
-
- quoted_extractor =
- quote location: :keep do
- {(fn ->
- {:map, _, field_name} = unquote(splat_opt)
- field_name
- end).(),
- Field.extract(
- unquote(splat_opt),
- :..,
- unquote(map_parameter_var)
- |> Enum.reject(fn {k, _} -> k in unquote(known_keys) end)
- |> Map.new()
- )}
- end
-
- [quoted_extractor]
- else
- []
- end
-
- all_extractors = required_extractors ++ optional_extractors ++ splat_extractors
- error_parse = maybe_build_error_parse(required_extractors, map_keys)
-
- quote location: :keep do
- def parse(unquote(map_extractors) = unquote(map_parameter_var)) do
- result =
- unquote(all_extractors)
- |> Enum.reduce_while([], fn
- {field, {:ok, result}}, acc ->
- {:cont, [{field, result} | acc]}
-
- {field, {:error, _} = err}, acc ->
- {:halt, err}
- end)
-
- case result do
- {:error, _} = err -> err
- keyword when is_list(keyword) -> {:ok, struct(__MODULE__, keyword)}
- end
- end
-
- unquote(error_parse)
-
- def parse(not_map) do
- {:error, {:invalid_map, not_map}}
- end
- end
- end
-
- defp maybe_build_error_parse([], _) do
- end
-
- defp maybe_build_error_parse(_, map_keys) do
- # this is only built if there are required fields
- quote do
- def parse(%{} = unmatched) do
- missing_keys =
- Enum.reduce(unquote(map_keys), [], fn key, acc ->
- if Map.has_key?(unmatched, key) do
- acc
- else
- [key | acc]
- end
- end)
-
- {:error, {:missing_keys, missing_keys, __MODULE__}}
- end
- end
- end
-
- defp map_extractor(map_pairs) do
- quote location: :keep do
- %{unquote_splicing(map_pairs)}
- end
- end
-end
diff --git a/apps/proto/lib/lexical/proto/macros/struct.ex b/apps/proto/lib/lexical/proto/macros/struct.ex
deleted file mode 100644
index af10cda7..00000000
--- a/apps/proto/lib/lexical/proto/macros/struct.ex
+++ /dev/null
@@ -1,55 +0,0 @@
-defmodule Lexical.Proto.Macros.Struct do
- alias Lexical.Proto.Macros.Typespec
-
- def build(opts, env) do
- keys = Keyword.keys(opts)
- required_keys = required_keys(opts)
-
- keys =
- if :.. in keys do
- {splat_def, rest} = Keyword.pop(opts, :..)
-
- quote location: :keep do
- [
- (fn ->
- {_, _, field_name} = unquote(splat_def)
- field_name
- end).()
- | unquote(rest)
- ]
- end
- else
- keys
- end
-
- quote location: :keep do
- @enforce_keys unquote(required_keys)
- defstruct unquote(keys)
- @type option :: unquote(Typespec.keyword_constructor_options(opts, env))
- @type options :: [option]
-
- @spec new() :: t()
- @spec new(options()) :: t()
- def new(opts \\ []) do
- struct!(__MODULE__, opts)
- end
-
- defoverridable new: 0, new: 1
- end
- end
-
- defp required_keys(opts) do
- opts
- |> Enum.filter(fn
- # ignore the splat, it's always optional
- {:.., _} -> false
- # an optional signifier tuple
- {_, {:optional, _}} -> false
- # ast for an optional signifier tuple
- {_, {:optional, _, _}} -> false
- # everything else is required
- _ -> true
- end)
- |> Keyword.keys()
- end
-end
diff --git a/apps/proto/lib/lexical/proto/macros/typespec.ex b/apps/proto/lib/lexical/proto/macros/typespec.ex
deleted file mode 100644
index 80ce0b4e..00000000
--- a/apps/proto/lib/lexical/proto/macros/typespec.ex
+++ /dev/null
@@ -1,202 +0,0 @@
-defmodule Lexical.Proto.Macros.Typespec do
- def typespec(opts \\ [], env \\ nil)
-
- def typespec([], _) do
- quote do
- %__MODULE__{}
- end
- end
-
- def typespec(opts, env) when is_list(opts) do
- typespecs =
- for {name, type} <- opts,
- name != :.. do
- {name, do_typespec(type, env)}
- end
-
- quote do
- %__MODULE__{unquote_splicing(typespecs)}
- end
- end
-
- def typespec(typespec, env) do
- do_typespec(typespec, env)
- end
-
- def choice(options, env) do
- do_typespec({:one_of, [], [options]}, env)
- end
-
- def keyword_constructor_options(opts, env) do
- for {name, type} <- opts,
- name != :.. do
- {name, do_typespec(type, env)}
- end
- |> or_types()
- end
-
- defp do_typespec([], _env) do
- # This is what's presented to typespec when a response has no results, as in the Shutdown response
- nil
- end
-
- defp do_typespec(nil, _env) do
- quote(do: nil)
- end
-
- defp do_typespec({:boolean, _, _}, _env) do
- quote(do: boolean())
- end
-
- defp do_typespec({:string, _, _}, _env) do
- quote(do: String.t())
- end
-
- defp do_typespec({:integer, _, _}, _env) do
- quote(do: integer())
- end
-
- defp do_typespec({:float, _, _}, _env) do
- quote(do: float())
- end
-
- defp do_typespec({:__MODULE__, _, nil}, env) do
- env.module
- end
-
- defp do_typespec({:optional, _, [optional_type]}, env) do
- quote do
- unquote(do_typespec(optional_type, env)) | nil
- end
- end
-
- defp do_typespec({:__aliases__, _, raw_alias} = aliased_module, env) do
- expanded_alias = Macro.expand(aliased_module, env)
-
- case List.last(raw_alias) do
- :Position ->
- other_alias =
- case expanded_alias do
- Lexical.Document.Range ->
- Lexical.Protocol.Types.Range
-
- _ ->
- Lexical.Document.Range
- end
-
- quote do
- unquote(expanded_alias).t() | unquote(other_alias).t()
- end
-
- :Range ->
- other_alias =
- case expanded_alias do
- Lexical.Document.Range ->
- Lexical.Protocol.Types.Range
-
- _ ->
- Lexical.Document.Range
- end
-
- quote do
- unquote(expanded_alias).t() | unquote(other_alias).t()
- end
-
- _ ->
- quote do
- unquote(expanded_alias).t()
- end
- end
- end
-
- defp do_typespec({:literal, _, value}, _env) when is_atom(value) do
- value
- end
-
- defp do_typespec({:literal, _, [value]}, _env) do
- literal_type(value)
- end
-
- defp do_typespec({:type_alias, _, [alias_dest]}, env) do
- do_typespec(alias_dest, env)
- end
-
- defp do_typespec({:one_of, _, [type_list]}, env) do
- type_list
- |> Enum.map(&do_typespec(&1, env))
- |> or_types()
- end
-
- defp do_typespec({:list_of, _, items}, env) do
- refined =
- items
- |> Enum.map(&do_typespec(&1, env))
- |> or_types()
-
- quote do
- [unquote(refined)]
- end
- end
-
- defp do_typespec({:tuple_of, _, [items]}, env) do
- refined = Enum.map(items, &do_typespec(&1, env))
-
- quote do
- {unquote_splicing(refined)}
- end
- end
-
- defp do_typespec({:map_of, _, items}, env) do
- value_types =
- items
- |> Enum.map(&do_typespec(&1, env))
- |> or_types()
-
- quote do
- %{String.t() => unquote(value_types)}
- end
- end
-
- defp do_typespec({:any, _, _}, _env) do
- quote do
- any()
- end
- end
-
- defp or_types(list_of_types) do
- Enum.reduce(list_of_types, nil, fn
- type, nil ->
- type
-
- type, acc ->
- quote do
- unquote(type) | unquote(acc)
- end
- end)
- end
-
- defp literal_type(thing) do
- case thing do
- string when is_binary(string) ->
- quote(do: String.t())
-
- integer when is_integer(integer) ->
- quote(do: integer())
-
- float when is_binary(float) ->
- quote(do: float())
-
- boolean when is_boolean(boolean) ->
- quote(do: boolean())
-
- atom when is_atom(atom) ->
- atom
-
- [] ->
- quote(do: [])
-
- [elem | _] ->
- quote(do: [unquote(literal_type(elem))])
- end
- end
-end
diff --git a/apps/proto/lib/lexical/proto/text.ex b/apps/proto/lib/lexical/proto/text.ex
deleted file mode 100644
index 0c51d2bb..00000000
--- a/apps/proto/lib/lexical/proto/text.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-defmodule Lexical.Proto.Text do
- def camelize(atom) when is_atom(atom) do
- atom |> Atom.to_string() |> camelize()
- end
-
- def camelize(string) do
- <> = Macro.camelize(string)
- String.downcase(first) <> rest
- end
-end
diff --git a/apps/proto/lib/lexical/proto/type.ex b/apps/proto/lib/lexical/proto/type.ex
deleted file mode 100644
index 385cad84..00000000
--- a/apps/proto/lib/lexical/proto/type.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-defmodule Lexical.Proto.Type do
- alias Lexical.Proto.CompileMetadata
-
- alias Lexical.Proto.Macros.{
- Access,
- Inspect,
- Json,
- Match,
- Meta,
- Parse,
- Struct,
- Typespec
- }
-
- defmacro deftype(types) do
- caller_module = __CALLER__.module
- CompileMetadata.add_type_module(caller_module)
-
- quote location: :keep do
- unquote(Json.build(caller_module))
- unquote(Inspect.build(caller_module))
- unquote(Access.build())
- unquote(Struct.build(types, __CALLER__))
-
- @type t :: unquote(Typespec.typespec(types, __CALLER__))
-
- unquote(Parse.build(types))
- unquote(Match.build(types, caller_module))
- unquote(Meta.build(types))
-
- def __meta__(:type) do
- :type
- end
-
- def __meta__(:param_names) do
- unquote(Keyword.keys(types))
- end
- end
- end
-end
diff --git a/apps/proto/lib/mix/tasks/lsp/data_model/type.ex b/apps/proto/lib/mix/tasks/lsp/data_model/type.ex
index a6f6e359..adcf45cd 100644
--- a/apps/proto/lib/mix/tasks/lsp/data_model/type.ex
+++ b/apps/proto/lib/mix/tasks/lsp/data_model/type.ex
@@ -38,7 +38,7 @@ defmodule Mix.Tasks.Lsp.DataModel.Type do
"null" -> quote(do: nil)
"DocumentUri" -> quote(do: String.t())
"decimal" -> quote(do: float())
- "URI" -> quote(do: Lexical.uri())
+ "URI" -> quote(do: Expert.uri())
end
end
diff --git a/apps/proto/lib/mix/tasks/lsp/mappings.ex b/apps/proto/lib/mix/tasks/lsp/mappings.ex
index 8e4fe768..94cc60ed 100644
--- a/apps/proto/lib/mix/tasks/lsp/mappings.ex
+++ b/apps/proto/lib/mix/tasks/lsp/mappings.ex
@@ -17,8 +17,8 @@ defmodule Mix.Tasks.Lsp.Mappings do
end
end
- @types_module Lexical.Protocol.Types
- @proto_module Lexical.Proto
+ @types_module Expert.Protocol.Types
+ @proto_module Expert.Proto
defstruct [:mappings, :imported_lsp_names, :types_module, :proto_module]
diff --git a/apps/proto/lib/mix/tasks/lsp/mappings/generate.ex b/apps/proto/lib/mix/tasks/lsp/mappings/generate.ex
index ab98d78c..f7cfd4fa 100644
--- a/apps/proto/lib/mix/tasks/lsp/mappings/generate.ex
+++ b/apps/proto/lib/mix/tasks/lsp/mappings/generate.ex
@@ -16,9 +16,9 @@ defmodule Mix.Tasks.Lsp.Mappings.Generate do
## Command line options
* `--types-module` - Controls the module in which the generated structures are placed.
- (defaults to `Lexical.Protocol.Types`)
+ (defaults to `Expert.Protocol.Types`)
* `--proto-module` - Controls the module in which the generated structures are placed.
- (defaults to `Lexical.Proto`)
+ (defaults to `Expert.Proto`)
* `--only` - Only generate the LSP types in the comma separated list
* `--roots` - A comma separated list of types to import. The types given will be interrogated
and all their references will also be imported. This is useful when importing complex structures,
diff --git a/apps/proto/mix.exs b/apps/proto/mix.exs
index fda25348..03b5d844 100644
--- a/apps/proto/mix.exs
+++ b/apps/proto/mix.exs
@@ -23,7 +23,7 @@ defmodule Proto.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- {:common, path: "../common", env: Mix.env()},
+ {:forge, path: "../forge", env: Mix.env()},
Mix.Credo.dependency(),
Mix.Dialyzer.dependency(),
{:jason, "~> 1.4", optional: true}
diff --git a/apps/protocol/README.md b/apps/protocol/README.md
index c6725d7c..dc900400 100644
--- a/apps/protocol/README.md
+++ b/apps/protocol/README.md
@@ -1,3 +1,3 @@
-# Lexical.Protocol
+# Expert.Protocol
Language Server Protocol data structures and conversion utilities.
diff --git a/apps/protocol/lib/lexical/protocol/conversions.ex b/apps/protocol/lib/expert/protocol/conversions.ex
similarity index 93%
rename from apps/protocol/lib/lexical/protocol/conversions.ex
rename to apps/protocol/lib/expert/protocol/conversions.ex
index 26dc384c..7b0b03de 100644
--- a/apps/protocol/lib/lexical/protocol/conversions.ex
+++ b/apps/protocol/lib/expert/protocol/conversions.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Protocol.Conversions do
+defmodule Expert.Protocol.Conversions do
@moduledoc """
Functions to convert between language server representations and elixir-native representations.
@@ -7,15 +7,15 @@ defmodule Lexical.Protocol.Conversions do
the line contains non-ascii characters. If it's a pure ascii line, then the positions
are the same in both utf-8 and utf-16, since they reference characters and not bytes.
"""
- alias Lexical.CodeUnit
- alias Lexical.Document
- alias Lexical.Document.Line
- alias Lexical.Document.Lines
- alias Lexical.Document.Position, as: ElixirPosition
- alias Lexical.Document.Range, as: ElixirRange
- alias Lexical.Math
- alias Lexical.Protocol.Types.Position, as: LSPosition
- alias Lexical.Protocol.Types.Range, as: LSRange
+ alias Expert.Protocol.Types.Position, as: LSPosition
+ alias Expert.Protocol.Types.Range, as: LSRange
+ alias Forge.CodeUnit
+ alias Forge.Document
+ alias Forge.Document.Line
+ alias Forge.Document.Lines
+ alias Forge.Document.Position, as: ElixirPosition
+ alias Forge.Document.Range, as: ElixirRange
+ alias Forge.Math
import Line
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.document.changes.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.document.changes.ex
new file mode 100644
index 00000000..fbef3f52
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.document.changes.ex
@@ -0,0 +1,11 @@
+defimpl Forge.Convertible, for: Forge.Document.Changes do
+ alias Forge.Document
+
+ def to_lsp(%Document.Changes{} = changes) do
+ Forge.Convertible.to_lsp(changes.edits)
+ end
+
+ def to_native(%Document.Changes{} = changes, _) do
+ {:ok, changes}
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.document.edit.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.document.edit.ex
new file mode 100644
index 00000000..53922035
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.document.edit.ex
@@ -0,0 +1,19 @@
+defimpl Forge.Convertible, for: Forge.Document.Edit do
+ alias Expert.Protocol.Conversions
+ alias Expert.Protocol.Types
+ alias Forge.Document
+
+ def to_lsp(%Document.Edit{range: nil} = edit) do
+ {:ok, Types.TextEdit.new(new_text: edit.text, range: nil)}
+ end
+
+ def to_lsp(%Document.Edit{} = edit) do
+ with {:ok, range} <- Conversions.to_lsp(edit.range) do
+ {:ok, Types.TextEdit.new(new_text: edit.text, range: range)}
+ end
+ end
+
+ def to_native(%Document.Edit{} = edit, _context_document) do
+ {:ok, edit}
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.document.location.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.document.location.ex
new file mode 100644
index 00000000..8fc353d9
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.document.location.ex
@@ -0,0 +1,16 @@
+defimpl Forge.Convertible, for: Forge.Document.Location do
+ alias Expert.Protocol.Conversions
+ alias Expert.Protocol.Types
+ alias Forge.Document
+
+ def to_lsp(%Document.Location{} = location) do
+ with {:ok, range} <- Conversions.to_lsp(location.range) do
+ uri = Document.Location.uri(location)
+ {:ok, Types.Location.new(uri: uri, range: range)}
+ end
+ end
+
+ def to_native(%Document.Location{} = location, _context_document) do
+ {:ok, location}
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.document.position.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.document.position.ex
new file mode 100644
index 00000000..b2a251af
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.document.position.ex
@@ -0,0 +1,12 @@
+defimpl Forge.Convertible, for: Forge.Document.Position do
+ alias Expert.Protocol.Conversions
+ alias Forge.Document
+
+ def to_lsp(%Document.Position{} = position) do
+ Conversions.to_lsp(position)
+ end
+
+ def to_native(%Document.Position{} = position, _context_document) do
+ {:ok, position}
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.document.range.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.document.range.ex
new file mode 100644
index 00000000..f653a855
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.document.range.ex
@@ -0,0 +1,12 @@
+defimpl Forge.Convertible, for: Forge.Document.Range do
+ alias Expert.Protocol.Conversions
+ alias Forge.Document
+
+ def to_lsp(%Document.Range{} = range) do
+ Conversions.to_lsp(range)
+ end
+
+ def to_native(%Document.Range{} = range, _context_document) do
+ {:ok, range}
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.location.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.location.ex
new file mode 100644
index 00000000..8f5e8da0
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.location.ex
@@ -0,0 +1,20 @@
+defimpl Forge.Convertible, for: Expert.Protocol.Types.Location do
+ alias Expert.Protocol.Conversions
+ alias Expert.Protocol.Types
+ alias Forge.Document.Container
+ alias Forge.Document
+
+ def to_lsp(%Types.Location{} = location) do
+ with {:ok, range} <- Conversions.to_lsp(location.range) do
+ {:ok, %Types.Location{location | range: range}}
+ end
+ end
+
+ def to_native(%Types.Location{} = location, context_document) do
+ context_document = Container.context_document(location, context_document)
+
+ with {:ok, range} <- Conversions.to_elixir(location.range, context_document) do
+ {:ok, Document.Location.new(range, context_document.uri)}
+ end
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.position.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.position.ex
new file mode 100644
index 00000000..9c2c7c06
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.position.ex
@@ -0,0 +1,12 @@
+defimpl Forge.Convertible, for: Expert.Protocol.Types.Position do
+ alias Expert.Protocol.Conversions
+ alias Expert.Protocol.Types
+
+ def to_lsp(%Types.Position{} = position) do
+ Conversions.to_lsp(position)
+ end
+
+ def to_native(%Types.Position{} = position, context_document) do
+ Conversions.to_elixir(position, context_document)
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.range.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.range.ex
new file mode 100644
index 00000000..11b1198d
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.range.ex
@@ -0,0 +1,23 @@
+defimpl Forge.Convertible, for: Expert.Protocol.Types.Range do
+ alias Expert.Protocol.Conversions
+ alias Expert.Protocol.Types
+
+ def to_lsp(%Types.Range{} = range) do
+ Conversions.to_lsp(range)
+ end
+
+ def to_native(
+ %Types.Range{
+ start: %Types.Position{line: start_line, character: start_character},
+ end: %Types.Position{line: end_line, character: end_character}
+ } = range,
+ _context_document
+ )
+ when start_line < 0 or start_character < 0 or end_line < 0 or end_character < 0 do
+ {:error, {:invalid_range, range}}
+ end
+
+ def to_native(%Types.Range{} = range, context_document) do
+ Conversions.to_elixir(range, context_document)
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.text_document.content_change_event1.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.text_document.content_change_event1.ex
new file mode 100644
index 00000000..7623f95f
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.text_document.content_change_event1.ex
@@ -0,0 +1,13 @@
+alias Forge.Convertible
+alias Forge.Document.Edit
+alias Expert.Protocol.Types.TextDocument.ContentChangeEvent
+
+defimpl Convertible, for: ContentChangeEvent.TextDocumentContentChangeEvent1 do
+ def to_lsp(event) do
+ {:ok, event}
+ end
+
+ def to_native(%ContentChangeEvent.TextDocumentContentChangeEvent1{} = event, _context_document) do
+ {:ok, Edit.new(event.text)}
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.text_edit.ex b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.text_edit.ex
new file mode 100644
index 00000000..989e6094
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/convertibles/expert.protocol.types.text_edit.ex
@@ -0,0 +1,22 @@
+defimpl Forge.Convertible, for: Expert.Protocol.Types.TextEdit do
+ alias Expert.Protocol.Conversions
+ alias Expert.Protocol.Types
+ alias Forge.Document
+
+ def to_lsp(%Types.TextEdit{} = text_edit) do
+ with {:ok, range} <- Conversions.to_lsp(text_edit.range) do
+ {:ok, %Types.TextEdit{text_edit | range: range}}
+ end
+ end
+
+ def to_native(%Types.TextEdit{range: nil} = text_edit, _context_document) do
+ {:ok, Document.Edit.new(text_edit.new_text)}
+ end
+
+ def to_native(%Types.TextEdit{} = text_edit, context_document) do
+ with {:ok, %Document.Range{} = range} <-
+ Conversions.to_elixir(text_edit.range, context_document) do
+ {:ok, Document.Edit.new(text_edit.new_text, range)}
+ end
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/document_containers/expert.document.changes.ex b/apps/protocol/lib/expert/protocol/document_containers/expert.document.changes.ex
new file mode 100644
index 00000000..1aa1fc99
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/document_containers/expert.document.changes.ex
@@ -0,0 +1,7 @@
+defimpl Forge.Document.Container, for: Forge.Document.Changes do
+ alias Forge.Document
+
+ def context_document(%Document.Changes{} = edits, prior_context_document) do
+ edits.document || prior_context_document
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/document_containers/expert.document.location.ex b/apps/protocol/lib/expert/protocol/document_containers/expert.document.location.ex
new file mode 100644
index 00000000..96c8edea
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/document_containers/expert.document.location.ex
@@ -0,0 +1,18 @@
+defimpl Forge.Document.Container, for: Forge.Document.Location do
+ alias Forge.Document
+ alias Forge.Document.Location
+
+ def context_document(%Location{document: %Document{} = context_document}, _) do
+ context_document
+ end
+
+ def context_document(%Location{uri: uri}, context_document) when is_binary(uri) do
+ case Document.Store.fetch(uri) do
+ {:ok, %Document{} = document} ->
+ document
+
+ _ ->
+ context_document
+ end
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/document_containers/expert.notifications.publish_diagnostics.ex b/apps/protocol/lib/expert/protocol/document_containers/expert.notifications.publish_diagnostics.ex
new file mode 100644
index 00000000..0344f066
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/document_containers/expert.notifications.publish_diagnostics.ex
@@ -0,0 +1,21 @@
+defimpl Forge.Document.Container, for: Expert.Protocol.Notifications.PublishDiagnostics do
+ alias Expert.Protocol.Notifications.PublishDiagnostics
+ alias Forge.Document
+ require Logger
+
+ def context_document(%PublishDiagnostics{uri: uri} = publish, context_document)
+ when is_binary(uri) do
+ case Document.Store.open_temporary(publish.uri) do
+ {:ok, source_doc} ->
+ source_doc
+
+ error ->
+ Logger.error("Failed to open #{uri} temporarily because #{inspect(error)}")
+ context_document
+ end
+ end
+
+ def context_document(_, context_doc) do
+ context_doc
+ end
+end
diff --git a/apps/protocol/lib/expert/protocol/document_containers/expert.protocol.types.location.ex b/apps/protocol/lib/expert/protocol/document_containers/expert.protocol.types.location.ex
new file mode 100644
index 00000000..518f06c8
--- /dev/null
+++ b/apps/protocol/lib/expert/protocol/document_containers/expert.protocol.types.location.ex
@@ -0,0 +1,11 @@
+defimpl Forge.Document.Container, for: Expert.Protocol.Types.Location do
+ alias Expert.Protocol.Types
+ alias Forge.Document
+
+ def context_document(%Types.Location{} = location, parent_document) do
+ case Document.Store.fetch(location.uri) do
+ {:ok, doc} -> doc
+ _ -> parent_document
+ end
+ end
+end
diff --git a/apps/protocol/lib/lexical/protocol/id.ex b/apps/protocol/lib/expert/protocol/id.ex
similarity index 75%
rename from apps/protocol/lib/lexical/protocol/id.ex
rename to apps/protocol/lib/expert/protocol/id.ex
index 4e3a28d3..220f0fe0 100644
--- a/apps/protocol/lib/lexical/protocol/id.ex
+++ b/apps/protocol/lib/expert/protocol/id.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Protocol.Id do
+defmodule Expert.Protocol.Id do
def next do
[:monotonic, :positive]
|> System.unique_integer()
diff --git a/apps/protocol/lib/lexical/protocol/json_rpc.ex b/apps/protocol/lib/expert/protocol/json_rpc.ex
similarity index 92%
rename from apps/protocol/lib/lexical/protocol/json_rpc.ex
rename to apps/protocol/lib/expert/protocol/json_rpc.ex
index 970bf220..5f0139ae 100644
--- a/apps/protocol/lib/lexical/protocol/json_rpc.ex
+++ b/apps/protocol/lib/expert/protocol/json_rpc.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Protocol.JsonRpc do
- alias Lexical.Protocol.Notifications
- alias Lexical.Protocol.Requests
+defmodule Expert.Protocol.JsonRpc do
+ alias Expert.Protocol.Notifications
+ alias Expert.Protocol.Requests
require Logger
diff --git a/apps/protocol/lib/lexical/protocol/notifications.ex b/apps/protocol/lib/expert/protocol/notifications.ex
similarity index 95%
rename from apps/protocol/lib/lexical/protocol/notifications.ex
rename to apps/protocol/lib/expert/protocol/notifications.ex
index d6b98de4..5a034be3 100644
--- a/apps/protocol/lib/lexical/protocol/notifications.ex
+++ b/apps/protocol/lib/expert/protocol/notifications.ex
@@ -1,6 +1,6 @@
-defmodule Lexical.Protocol.Notifications do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
+defmodule Expert.Protocol.Notifications do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
defmodule Initialized do
use Proto
diff --git a/apps/protocol/lib/lexical/protocol/requests.ex b/apps/protocol/lib/expert/protocol/requests.ex
similarity index 94%
rename from apps/protocol/lib/lexical/protocol/requests.ex
rename to apps/protocol/lib/expert/protocol/requests.ex
index 29ac9f72..a761832f 100644
--- a/apps/protocol/lib/lexical/protocol/requests.ex
+++ b/apps/protocol/lib/expert/protocol/requests.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Protocol.Requests do
- alias Lexical.Proto
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types
+defmodule Expert.Protocol.Requests do
+ alias Expert.Proto
+ alias Expert.Protocol.Responses
+ alias Expert.Protocol.Types
# Client -> Server request
defmodule Initialize do
diff --git a/apps/protocol/lib/lexical/protocol/responses.ex b/apps/protocol/lib/expert/protocol/responses.ex
similarity index 92%
rename from apps/protocol/lib/lexical/protocol/responses.ex
rename to apps/protocol/lib/expert/protocol/responses.ex
index 847805dd..9f57284f 100644
--- a/apps/protocol/lib/lexical/protocol/responses.ex
+++ b/apps/protocol/lib/expert/protocol/responses.ex
@@ -1,7 +1,7 @@
-defmodule Lexical.Protocol.Responses do
- alias Lexical.Proto
- alias Lexical.Proto.Typespecs
- alias Lexical.Protocol.Types
+defmodule Expert.Protocol.Responses do
+ alias Expert.Proto
+ alias Expert.Proto.Typespecs
+ alias Expert.Protocol.Types
defmodule Empty do
use Proto
diff --git a/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/client_capabilities.ex
new file mode 100644
index 00000000..5e9dc4e0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CallHierarchy.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/options.ex b/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/options.ex
new file mode 100644
index 00000000..9dd4a126
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CallHierarchy.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/registration/options.ex
new file mode 100644
index 00000000..9faa191c
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/call_hierarchy/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CallHierarchy.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/cancel/params.ex b/apps/protocol/lib/generated/expert/protocol/types/cancel/params.ex
new file mode 100644
index 00000000..a9587430
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/cancel/params.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Cancel.Params do
+ alias Expert.Proto
+ use Proto
+ deftype id: one_of([integer(), string()])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/change_annotation.ex b/apps/protocol/lib/generated/expert/protocol/types/change_annotation.ex
new file mode 100644
index 00000000..196d6b5f
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/change_annotation.ex
@@ -0,0 +1,9 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ChangeAnnotation do
+ alias Expert.Proto
+ use Proto
+
+ deftype description: optional(string()),
+ label: string(),
+ needs_confirmation: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/change_annotation/identifier.ex b/apps/protocol/lib/generated/expert/protocol/types/change_annotation/identifier.ex
new file mode 100644
index 00000000..31f55063
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/change_annotation/identifier.ex
@@ -0,0 +1,4 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ChangeAnnotation.Identifier do
+ @type t :: String.t()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/client_capabilities.ex
new file mode 100644
index 00000000..a9738a63
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/client_capabilities.ex
@@ -0,0 +1,13 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype experimental: optional(any()),
+ general: optional(Types.General.ClientCapabilities),
+ notebook_document: optional(Types.Notebook.Document.ClientCapabilities),
+ text_document: optional(Types.TextDocument.ClientCapabilities),
+ window: optional(Types.Window.ClientCapabilities),
+ workspace: optional(Types.Workspace.ClientCapabilities)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_action.ex b/apps/protocol/lib/generated/expert/protocol/types/code_action.ex
new file mode 100644
index 00000000..3ad16db4
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_action.ex
@@ -0,0 +1,21 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeAction do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule Disabled do
+ use Proto
+ deftype reason: string()
+ end
+
+ use Proto
+
+ deftype command: optional(Types.Command),
+ data: optional(any()),
+ diagnostics: optional(list_of(Types.Diagnostic)),
+ disabled: optional(Expert.Protocol.Types.CodeAction.Disabled),
+ edit: optional(Types.Workspace.Edit),
+ is_preferred: optional(boolean()),
+ kind: optional(Types.CodeAction.Kind),
+ title: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_action/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/code_action/client_capabilities.ex
new file mode 100644
index 00000000..83989aae
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_action/client_capabilities.ex
@@ -0,0 +1,32 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeAction.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule CodeActionKind do
+ use Proto
+ deftype value_set: list_of(Types.CodeAction.Kind)
+ end
+
+ defmodule CodeActionLiteralSupport do
+ use Proto
+ deftype code_action_kind: Expert.Protocol.Types.CodeAction.ClientCapabilities.CodeActionKind
+ end
+
+ defmodule ResolveSupport do
+ use Proto
+ deftype properties: list_of(string())
+ end
+
+ use Proto
+
+ deftype code_action_literal_support:
+ optional(Expert.Protocol.Types.CodeAction.ClientCapabilities.CodeActionLiteralSupport),
+ data_support: optional(boolean()),
+ disabled_support: optional(boolean()),
+ dynamic_registration: optional(boolean()),
+ honors_change_annotations: optional(boolean()),
+ is_preferred_support: optional(boolean()),
+ resolve_support:
+ optional(Expert.Protocol.Types.CodeAction.ClientCapabilities.ResolveSupport)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_action/context.ex b/apps/protocol/lib/generated/expert/protocol/types/code_action/context.ex
new file mode 100644
index 00000000..749652c5
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_action/context.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeAction.Context do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype diagnostics: list_of(Types.Diagnostic),
+ only: optional(list_of(Types.CodeAction.Kind)),
+ trigger_kind: optional(Types.CodeAction.Trigger.Kind)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_action/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/code_action/kind.ex
new file mode 100644
index 00000000..cb271df6
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_action/kind.ex
@@ -0,0 +1,15 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeAction.Kind do
+ alias Expert.Proto
+ use Proto
+
+ defenum empty: "",
+ quick_fix: "quickfix",
+ refactor: "refactor",
+ refactor_extract: "refactor.extract",
+ refactor_inline: "refactor.inline",
+ refactor_rewrite: "refactor.rewrite",
+ source: "source",
+ source_organize_imports: "source.organizeImports",
+ source_fix_all: "source.fixAll"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_action/options.ex b/apps/protocol/lib/generated/expert/protocol/types/code_action/options.ex
new file mode 100644
index 00000000..a6291e17
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_action/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeAction.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype code_action_kinds: optional(list_of(Types.CodeAction.Kind)),
+ resolve_provider: optional(boolean()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_action/params.ex b/apps/protocol/lib/generated/expert/protocol/types/code_action/params.ex
new file mode 100644
index 00000000..12606d1a
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_action/params.ex
@@ -0,0 +1,12 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeAction.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype context: Types.CodeAction.Context,
+ partial_result_token: optional(Types.Progress.Token),
+ range: Types.Range,
+ text_document: Types.TextDocument.Identifier,
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_action/trigger/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/code_action/trigger/kind.ex
new file mode 100644
index 00000000..1964bc3c
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_action/trigger/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeAction.Trigger.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum invoked: 1, automatic: 2
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_description.ex b/apps/protocol/lib/generated/expert/protocol/types/code_description.ex
new file mode 100644
index 00000000..d2bc8cb3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_description.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeDescription do
+ alias Expert.Proto
+ use Proto
+ deftype href: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_lens.ex b/apps/protocol/lib/generated/expert/protocol/types/code_lens.ex
new file mode 100644
index 00000000..1c2e8d95
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_lens.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeLens do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype command: optional(Types.Command), data: optional(any()), range: Types.Range
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_lens/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/code_lens/client_capabilities.ex
new file mode 100644
index 00000000..0ef685c8
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_lens/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeLens.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_lens/options.ex b/apps/protocol/lib/generated/expert/protocol/types/code_lens/options.ex
new file mode 100644
index 00000000..271a98eb
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_lens/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeLens.Options do
+ alias Expert.Proto
+ use Proto
+ deftype resolve_provider: optional(boolean()), work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_lens/params.ex b/apps/protocol/lib/generated/expert/protocol/types/code_lens/params.ex
new file mode 100644
index 00000000..2e5df794
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_lens/params.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeLens.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype partial_result_token: optional(Types.Progress.Token),
+ text_document: Types.TextDocument.Identifier,
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/code_lens/workspace/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/code_lens/workspace/client_capabilities.ex
new file mode 100644
index 00000000..10610323
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/code_lens/workspace/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CodeLens.Workspace.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype refresh_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/command.ex b/apps/protocol/lib/generated/expert/protocol/types/command.ex
new file mode 100644
index 00000000..4d6704d3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/command.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Command do
+ alias Expert.Proto
+ use Proto
+ deftype arguments: optional(list_of(any())), command: string(), title: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/client_capabilities.ex
new file mode 100644
index 00000000..e998fd22
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/client_capabilities.ex
@@ -0,0 +1,59 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule CompletionItem do
+ use Proto
+
+ deftype commit_characters_support: optional(boolean()),
+ deprecated_support: optional(boolean()),
+ documentation_format: optional(list_of(Types.Markup.Kind)),
+ insert_replace_support: optional(boolean()),
+ insert_text_mode_support:
+ optional(Expert.Protocol.Types.Completion.ClientCapabilities.InsertTextModeSupport),
+ label_details_support: optional(boolean()),
+ preselect_support: optional(boolean()),
+ resolve_support:
+ optional(Expert.Protocol.Types.Completion.ClientCapabilities.ResolveSupport),
+ snippet_support: optional(boolean()),
+ tag_support: optional(Expert.Protocol.Types.Completion.ClientCapabilities.TagSupport)
+ end
+
+ defmodule CompletionItemKind do
+ use Proto
+ deftype value_set: optional(list_of(Types.Completion.Item.Kind))
+ end
+
+ defmodule CompletionList do
+ use Proto
+ deftype item_defaults: optional(list_of(string()))
+ end
+
+ defmodule InsertTextModeSupport do
+ use Proto
+ deftype value_set: list_of(Types.InsertTextMode)
+ end
+
+ defmodule ResolveSupport do
+ use Proto
+ deftype properties: list_of(string())
+ end
+
+ defmodule TagSupport do
+ use Proto
+ deftype value_set: list_of(Types.Completion.Item.Tag)
+ end
+
+ use Proto
+
+ deftype completion_item:
+ optional(Expert.Protocol.Types.Completion.ClientCapabilities.CompletionItem),
+ completion_item_kind:
+ optional(Expert.Protocol.Types.Completion.ClientCapabilities.CompletionItemKind),
+ completion_list:
+ optional(Expert.Protocol.Types.Completion.ClientCapabilities.CompletionList),
+ context_support: optional(boolean()),
+ dynamic_registration: optional(boolean()),
+ insert_text_mode: optional(Types.InsertTextMode)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/context.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/context.ex
new file mode 100644
index 00000000..f3be6d2e
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/context.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.Context do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype trigger_character: optional(string()), trigger_kind: Types.Completion.Trigger.Kind
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/item.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/item.ex
new file mode 100644
index 00000000..784544f0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/item.ex
@@ -0,0 +1,26 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.Item do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype additional_text_edits: optional(list_of(Types.TextEdit)),
+ command: optional(Types.Command),
+ commit_characters: optional(list_of(string())),
+ data: optional(any()),
+ deprecated: optional(boolean()),
+ detail: optional(string()),
+ documentation: optional(one_of([string(), Types.Markup.Content])),
+ filter_text: optional(string()),
+ insert_text: optional(string()),
+ insert_text_format: optional(Types.InsertTextFormat),
+ insert_text_mode: optional(Types.InsertTextMode),
+ kind: optional(Types.Completion.Item.Kind),
+ label: string(),
+ label_details: optional(Types.Completion.Item.LabelDetails),
+ preselect: optional(boolean()),
+ sort_text: optional(string()),
+ tags: optional(list_of(Types.Completion.Item.Tag)),
+ text_edit: optional(one_of([Types.TextEdit, Types.InsertReplaceEdit])),
+ text_edit_text: optional(string())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/item/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/item/kind.ex
new file mode 100644
index 00000000..9d69b4d1
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/item/kind.ex
@@ -0,0 +1,31 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.Item.Kind do
+ alias Expert.Proto
+ use Proto
+
+ defenum text: 1,
+ method: 2,
+ function: 3,
+ constructor: 4,
+ field: 5,
+ variable: 6,
+ class: 7,
+ interface: 8,
+ module: 9,
+ property: 10,
+ unit: 11,
+ value: 12,
+ enum: 13,
+ keyword: 14,
+ snippet: 15,
+ color: 16,
+ file: 17,
+ reference: 18,
+ folder: 19,
+ enum_member: 20,
+ constant: 21,
+ struct: 22,
+ event: 23,
+ operator: 24,
+ type_parameter: 25
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/item/label_details.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/item/label_details.ex
new file mode 100644
index 00000000..4bd581ad
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/item/label_details.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.Item.LabelDetails do
+ alias Expert.Proto
+ use Proto
+ deftype description: optional(string()), detail: optional(string())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/item/tag.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/item/tag.ex
new file mode 100644
index 00000000..2d8c8243
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/item/tag.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.Item.Tag do
+ alias Expert.Proto
+ use Proto
+ defenum deprecated: 1
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/list.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/list.ex
new file mode 100644
index 00000000..fe625d1a
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/list.ex
@@ -0,0 +1,27 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.List do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule EditRange do
+ use Proto
+ deftype insert: Types.Range, replace: Types.Range
+ end
+
+ defmodule ItemDefaults do
+ use Proto
+
+ deftype commit_characters: optional(list_of(string())),
+ data: optional(any()),
+ edit_range:
+ optional(one_of([Types.Range, Expert.Protocol.Types.Completion.List.EditRange])),
+ insert_text_format: optional(Types.InsertTextFormat),
+ insert_text_mode: optional(Types.InsertTextMode)
+ end
+
+ use Proto
+
+ deftype is_incomplete: boolean(),
+ item_defaults: optional(Expert.Protocol.Types.Completion.List.ItemDefaults),
+ items: list_of(Types.Completion.Item)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/options.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/options.ex
new file mode 100644
index 00000000..47ec9d8d
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/options.ex
@@ -0,0 +1,17 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.Options do
+ alias Expert.Proto
+
+ defmodule CompletionItem do
+ use Proto
+ deftype label_details_support: optional(boolean())
+ end
+
+ use Proto
+
+ deftype all_commit_characters: optional(list_of(string())),
+ completion_item: optional(Expert.Protocol.Types.Completion.Options.CompletionItem),
+ resolve_provider: optional(boolean()),
+ trigger_characters: optional(list_of(string())),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/params.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/params.ex
new file mode 100644
index 00000000..48ac20a2
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/params.ex
@@ -0,0 +1,12 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype context: optional(Types.Completion.Context),
+ partial_result_token: optional(Types.Progress.Token),
+ position: Types.Position,
+ text_document: Types.TextDocument.Identifier,
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/completion/trigger/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/completion/trigger/kind.ex
new file mode 100644
index 00000000..13abba0a
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/completion/trigger/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Completion.Trigger.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum invoked: 1, trigger_character: 2, trigger_for_incomplete_completions: 3
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/create_file.ex b/apps/protocol/lib/generated/expert/protocol/types/create_file.ex
new file mode 100644
index 00000000..29fbb49d
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/create_file.ex
@@ -0,0 +1,11 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CreateFile do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype annotation_id: optional(Types.ChangeAnnotation.Identifier),
+ kind: literal("create"),
+ options: optional(Types.CreateFile.Options),
+ uri: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/create_file/options.ex b/apps/protocol/lib/generated/expert/protocol/types/create_file/options.ex
new file mode 100644
index 00000000..0eb7491b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/create_file/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.CreateFile.Options do
+ alias Expert.Proto
+ use Proto
+ deftype ignore_if_exists: optional(boolean()), overwrite: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/declaration/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/declaration/client_capabilities.ex
new file mode 100644
index 00000000..ba32281a
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/declaration/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Declaration.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean()), link_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/declaration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/declaration/options.ex
new file mode 100644
index 00000000..ecf4321f
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/declaration/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Declaration.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/declaration/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/declaration/registration/options.ex
new file mode 100644
index 00000000..c3aec425
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/declaration/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Declaration.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/definition/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/definition/client_capabilities.ex
new file mode 100644
index 00000000..5b5fa62c
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/definition/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Definition.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean()), link_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/definition/options.ex b/apps/protocol/lib/generated/expert/protocol/types/definition/options.ex
new file mode 100644
index 00000000..ad27401e
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/definition/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Definition.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/definition/params.ex b/apps/protocol/lib/generated/expert/protocol/types/definition/params.ex
new file mode 100644
index 00000000..e8b4312e
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/definition/params.ex
@@ -0,0 +1,11 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Definition.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype partial_result_token: optional(Types.Progress.Token),
+ position: Types.Position,
+ text_document: Types.TextDocument.Identifier,
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/delete_file.ex b/apps/protocol/lib/generated/expert/protocol/types/delete_file.ex
new file mode 100644
index 00000000..8ada2989
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/delete_file.ex
@@ -0,0 +1,11 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DeleteFile do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype annotation_id: optional(Types.ChangeAnnotation.Identifier),
+ kind: literal("delete"),
+ options: optional(Types.DeleteFile.Options),
+ uri: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/delete_file/options.ex b/apps/protocol/lib/generated/expert/protocol/types/delete_file/options.ex
new file mode 100644
index 00000000..2a2c74a1
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/delete_file/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DeleteFile.Options do
+ alias Expert.Proto
+ use Proto
+ deftype ignore_if_not_exists: optional(boolean()), recursive: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/diagnostic.ex b/apps/protocol/lib/generated/expert/protocol/types/diagnostic.ex
new file mode 100644
index 00000000..cb457815
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/diagnostic.ex
@@ -0,0 +1,16 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Diagnostic do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype code: optional(one_of([integer(), string()])),
+ code_description: optional(Types.CodeDescription),
+ data: optional(any()),
+ message: string(),
+ range: Types.Range,
+ related_information: optional(list_of(Types.Diagnostic.RelatedInformation)),
+ severity: optional(Types.Diagnostic.Severity),
+ source: optional(string()),
+ tags: optional(list_of(Types.Diagnostic.Tag))
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/diagnostic/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/client_capabilities.ex
new file mode 100644
index 00000000..b703dbff
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Diagnostic.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean()), related_document_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/diagnostic/options.ex b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/options.ex
new file mode 100644
index 00000000..a0be73f3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Diagnostic.Options do
+ alias Expert.Proto
+ use Proto
+
+ deftype identifier: optional(string()),
+ inter_file_dependencies: boolean(),
+ work_done_progress: optional(boolean()),
+ workspace_diagnostics: boolean()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/diagnostic/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/registration/options.ex
new file mode 100644
index 00000000..f0493f99
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/registration/options.ex
@@ -0,0 +1,13 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Diagnostic.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ identifier: optional(string()),
+ inter_file_dependencies: boolean(),
+ work_done_progress: optional(boolean()),
+ workspace_diagnostics: boolean()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/diagnostic/related_information.ex b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/related_information.ex
new file mode 100644
index 00000000..a300f8fa
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/related_information.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Diagnostic.RelatedInformation do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype location: Types.Location, message: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/diagnostic/severity.ex b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/severity.ex
new file mode 100644
index 00000000..9b86ac6e
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/severity.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Diagnostic.Severity do
+ alias Expert.Proto
+ use Proto
+ defenum error: 1, warning: 2, information: 3, hint: 4
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/diagnostic/tag.ex b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/tag.ex
new file mode 100644
index 00000000..0f4759f1
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/tag.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Diagnostic.Tag do
+ alias Expert.Proto
+ use Proto
+ defenum unnecessary: 1, deprecated: 2
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/diagnostic/workspace/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/workspace/client_capabilities.ex
new file mode 100644
index 00000000..bded8e95
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/diagnostic/workspace/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Diagnostic.Workspace.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype refresh_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_change_configuration/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/did_change_configuration/client_capabilities.ex
new file mode 100644
index 00000000..03e77bb6
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_change_configuration/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidChangeConfiguration.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_change_configuration/params.ex b/apps/protocol/lib/generated/expert/protocol/types/did_change_configuration/params.ex
new file mode 100644
index 00000000..6bd449bc
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_change_configuration/params.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidChangeConfiguration.Params do
+ alias Expert.Proto
+ use Proto
+ deftype settings: any()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_change_text_document/params.ex b/apps/protocol/lib/generated/expert/protocol/types/did_change_text_document/params.ex
new file mode 100644
index 00000000..e984c159
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_change_text_document/params.ex
@@ -0,0 +1,9 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidChangeTextDocument.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype content_changes: list_of(Types.TextDocument.ContentChangeEvent),
+ text_document: Types.TextDocument.Versioned.Identifier
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/client_capabilities.ex
new file mode 100644
index 00000000..22a1e16c
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidChangeWatchedFiles.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean()), relative_pattern_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/params.ex b/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/params.ex
new file mode 100644
index 00000000..6e5365f9
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidChangeWatchedFiles.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype changes: list_of(Types.FileEvent)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/registration/options.ex
new file mode 100644
index 00000000..9fe45520
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_change_watched_files/registration/options.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidChangeWatchedFiles.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype watchers: list_of(Types.FileSystemWatcher)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_close_text_document/params.ex b/apps/protocol/lib/generated/expert/protocol/types/did_close_text_document/params.ex
new file mode 100644
index 00000000..647322a8
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_close_text_document/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidCloseTextDocument.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype text_document: Types.TextDocument.Identifier
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_open_text_document/params.ex b/apps/protocol/lib/generated/expert/protocol/types/did_open_text_document/params.ex
new file mode 100644
index 00000000..df7c0121
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_open_text_document/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidOpenTextDocument.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype text_document: Types.TextDocument.Item
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/did_save_text_document/params.ex b/apps/protocol/lib/generated/expert/protocol/types/did_save_text_document/params.ex
new file mode 100644
index 00000000..dafa99dd
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/did_save_text_document/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.DidSaveTextDocument.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype text: optional(string()), text_document: Types.TextDocument.Identifier
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/color/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/document/color/client_capabilities.ex
new file mode 100644
index 00000000..ca0bb219
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/color/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Color.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/color/options.ex b/apps/protocol/lib/generated/expert/protocol/types/document/color/options.ex
new file mode 100644
index 00000000..b410f3d0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/color/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Color.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/color/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/document/color/registration/options.ex
new file mode 100644
index 00000000..8ccd60fe
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/color/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Color.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/filter.ex b/apps/protocol/lib/generated/expert/protocol/types/document/filter.ex
new file mode 100644
index 00000000..3c7494ac
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/filter.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Filter do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ defalias one_of([Types.TextDocument.Filter, Types.Notebook.Cell.TextDocument.Filter])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/formatting/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/document/formatting/client_capabilities.ex
new file mode 100644
index 00000000..6b383fa5
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/formatting/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Formatting.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/formatting/options.ex b/apps/protocol/lib/generated/expert/protocol/types/document/formatting/options.ex
new file mode 100644
index 00000000..7094c366
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/formatting/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Formatting.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/formatting/params.ex b/apps/protocol/lib/generated/expert/protocol/types/document/formatting/params.ex
new file mode 100644
index 00000000..ff925ebd
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/formatting/params.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Formatting.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype options: Types.Formatting.Options,
+ text_document: Types.TextDocument.Identifier,
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/highlight/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/document/highlight/client_capabilities.ex
new file mode 100644
index 00000000..2b446f4f
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/highlight/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Highlight.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/highlight/options.ex b/apps/protocol/lib/generated/expert/protocol/types/document/highlight/options.ex
new file mode 100644
index 00000000..74724d64
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/highlight/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Highlight.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/link/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/document/link/client_capabilities.ex
new file mode 100644
index 00000000..2d4962b0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/link/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Link.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean()), tooltip_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/link/options.ex b/apps/protocol/lib/generated/expert/protocol/types/document/link/options.ex
new file mode 100644
index 00000000..7b274caa
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/link/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Link.Options do
+ alias Expert.Proto
+ use Proto
+ deftype resolve_provider: optional(boolean()), work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/on_type_formatting/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/document/on_type_formatting/client_capabilities.ex
new file mode 100644
index 00000000..2a47a9a3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/on_type_formatting/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.OnTypeFormatting.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/on_type_formatting/options.ex b/apps/protocol/lib/generated/expert/protocol/types/document/on_type_formatting/options.ex
new file mode 100644
index 00000000..9b5fdda0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/on_type_formatting/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.OnTypeFormatting.Options do
+ alias Expert.Proto
+ use Proto
+ deftype first_trigger_character: string(), more_trigger_character: optional(list_of(string()))
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/range_formatting/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/document/range_formatting/client_capabilities.ex
new file mode 100644
index 00000000..8d578145
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/range_formatting/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.RangeFormatting.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/range_formatting/options.ex b/apps/protocol/lib/generated/expert/protocol/types/document/range_formatting/options.ex
new file mode 100644
index 00000000..a8676f6b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/range_formatting/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.RangeFormatting.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/selector.ex b/apps/protocol/lib/generated/expert/protocol/types/document/selector.ex
new file mode 100644
index 00000000..b43bff12
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/selector.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Selector do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ defalias list_of(Types.Document.Filter)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/symbol.ex b/apps/protocol/lib/generated/expert/protocol/types/document/symbol.ex
new file mode 100644
index 00000000..39f1b660
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/symbol.ex
@@ -0,0 +1,15 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Symbol do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype children: optional(list_of(Types.Document.Symbol)),
+ deprecated: optional(boolean()),
+ detail: optional(string()),
+ kind: Types.Symbol.Kind,
+ name: string(),
+ range: Types.Range,
+ selection_range: Types.Range,
+ tags: optional(list_of(Types.Symbol.Tag))
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/symbol/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/document/symbol/client_capabilities.ex
new file mode 100644
index 00000000..a4cd6ca8
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/symbol/client_capabilities.ex
@@ -0,0 +1,25 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Symbol.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule SymbolKind do
+ use Proto
+ deftype value_set: optional(list_of(Types.Symbol.Kind))
+ end
+
+ defmodule TagSupport do
+ use Proto
+ deftype value_set: list_of(Types.Symbol.Tag)
+ end
+
+ use Proto
+
+ deftype dynamic_registration: optional(boolean()),
+ hierarchical_document_symbol_support: optional(boolean()),
+ label_support: optional(boolean()),
+ symbol_kind:
+ optional(Expert.Protocol.Types.Document.Symbol.ClientCapabilities.SymbolKind),
+ tag_support:
+ optional(Expert.Protocol.Types.Document.Symbol.ClientCapabilities.TagSupport)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/symbol/options.ex b/apps/protocol/lib/generated/expert/protocol/types/document/symbol/options.ex
new file mode 100644
index 00000000..a2b8c54e
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/symbol/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Symbol.Options do
+ alias Expert.Proto
+ use Proto
+ deftype label: optional(string()), work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/document/symbol/params.ex b/apps/protocol/lib/generated/expert/protocol/types/document/symbol/params.ex
new file mode 100644
index 00000000..52bedbc2
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/document/symbol/params.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Document.Symbol.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype partial_result_token: optional(Types.Progress.Token),
+ text_document: Types.TextDocument.Identifier,
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/error_codes.ex b/apps/protocol/lib/generated/expert/protocol/types/error_codes.ex
similarity index 82%
rename from apps/protocol/lib/generated/lexical/protocol/types/error_codes.ex
rename to apps/protocol/lib/generated/expert/protocol/types/error_codes.ex
index 3bd0f455..1eee55ce 100644
--- a/apps/protocol/lib/generated/lexical/protocol/types/error_codes.ex
+++ b/apps/protocol/lib/generated/expert/protocol/types/error_codes.ex
@@ -1,6 +1,6 @@
# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ErrorCodes do
- alias Lexical.Proto
+defmodule Expert.Protocol.Types.ErrorCodes do
+ alias Expert.Proto
use Proto
defenum parse_error: -32_700,
diff --git a/apps/protocol/lib/generated/expert/protocol/types/execute_command/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/execute_command/client_capabilities.ex
new file mode 100644
index 00000000..92734a10
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/execute_command/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ExecuteCommand.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/execute_command/options.ex b/apps/protocol/lib/generated/expert/protocol/types/execute_command/options.ex
new file mode 100644
index 00000000..e7d87f42
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/execute_command/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ExecuteCommand.Options do
+ alias Expert.Proto
+ use Proto
+ deftype commands: list_of(string()), work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/execute_command/params.ex b/apps/protocol/lib/generated/expert/protocol/types/execute_command/params.ex
new file mode 100644
index 00000000..a10f7483
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/execute_command/params.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ExecuteCommand.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype arguments: optional(list_of(any())),
+ command: string(),
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/execute_command/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/execute_command/registration/options.ex
new file mode 100644
index 00000000..6670a8ec
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/execute_command/registration/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ExecuteCommand.Registration.Options do
+ alias Expert.Proto
+ use Proto
+ deftype commands: list_of(string()), work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/failure_handling/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/failure_handling/kind.ex
new file mode 100644
index 00000000..2f493d2b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/failure_handling/kind.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FailureHandling.Kind do
+ alias Expert.Proto
+ use Proto
+
+ defenum abort: "abort",
+ transactional: "transactional",
+ text_only_transactional: "textOnlyTransactional",
+ undo: "undo"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_change_type.ex b/apps/protocol/lib/generated/expert/protocol/types/file_change_type.ex
new file mode 100644
index 00000000..5c0005e6
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_change_type.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileChangeType do
+ alias Expert.Proto
+ use Proto
+ defenum created: 1, changed: 2, deleted: 3
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_event.ex b/apps/protocol/lib/generated/expert/protocol/types/file_event.ex
new file mode 100644
index 00000000..0548ffa5
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_event.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileEvent do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype type: Types.FileChangeType, uri: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_operation/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/file_operation/client_capabilities.ex
new file mode 100644
index 00000000..5929a9f0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_operation/client_capabilities.ex
@@ -0,0 +1,13 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileOperation.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+
+ deftype did_create: optional(boolean()),
+ did_delete: optional(boolean()),
+ did_rename: optional(boolean()),
+ dynamic_registration: optional(boolean()),
+ will_create: optional(boolean()),
+ will_delete: optional(boolean()),
+ will_rename: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_operation/filter.ex b/apps/protocol/lib/generated/expert/protocol/types/file_operation/filter.ex
new file mode 100644
index 00000000..c30fd576
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_operation/filter.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileOperation.Filter do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype pattern: Types.FileOperation.Pattern, scheme: optional(string())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_operation/options.ex b/apps/protocol/lib/generated/expert/protocol/types/file_operation/options.ex
new file mode 100644
index 00000000..eda1ac4b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_operation/options.ex
@@ -0,0 +1,13 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileOperation.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype did_create: optional(Types.FileOperation.Registration.Options),
+ did_delete: optional(Types.FileOperation.Registration.Options),
+ did_rename: optional(Types.FileOperation.Registration.Options),
+ will_create: optional(Types.FileOperation.Registration.Options),
+ will_delete: optional(Types.FileOperation.Registration.Options),
+ will_rename: optional(Types.FileOperation.Registration.Options)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern.ex b/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern.ex
new file mode 100644
index 00000000..bba8f71f
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileOperation.Pattern do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype glob: string(),
+ matches: optional(Types.FileOperation.Pattern.Kind),
+ options: optional(Types.FileOperation.Pattern.Options)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern/kind.ex
new file mode 100644
index 00000000..93930230
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileOperation.Pattern.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum file: "file", folder: "folder"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern/options.ex b/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern/options.ex
new file mode 100644
index 00000000..7b1ab11e
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_operation/pattern/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileOperation.Pattern.Options do
+ alias Expert.Proto
+ use Proto
+ deftype ignore_case: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_operation/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/file_operation/registration/options.ex
new file mode 100644
index 00000000..dba1ca71
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_operation/registration/options.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileOperation.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype filters: list_of(Types.FileOperation.Filter)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/file_system_watcher.ex b/apps/protocol/lib/generated/expert/protocol/types/file_system_watcher.ex
new file mode 100644
index 00000000..412f5a96
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/file_system_watcher.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FileSystemWatcher do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype glob_pattern: Types.GlobPattern, kind: optional(Types.Watch.Kind)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/folding_range/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/folding_range/client_capabilities.ex
new file mode 100644
index 00000000..36acb428
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/folding_range/client_capabilities.ex
@@ -0,0 +1,25 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FoldingRange.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule FoldingRange do
+ use Proto
+ deftype collapsed_text: optional(boolean())
+ end
+
+ defmodule FoldingRangeKind do
+ use Proto
+ deftype value_set: optional(list_of(Types.FoldingRange.Kind))
+ end
+
+ use Proto
+
+ deftype dynamic_registration: optional(boolean()),
+ folding_range:
+ optional(Expert.Protocol.Types.FoldingRange.ClientCapabilities.FoldingRange),
+ folding_range_kind:
+ optional(Expert.Protocol.Types.FoldingRange.ClientCapabilities.FoldingRangeKind),
+ line_folding_only: optional(boolean()),
+ range_limit: optional(integer())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/folding_range/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/folding_range/kind.ex
new file mode 100644
index 00000000..6b3eff01
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/folding_range/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FoldingRange.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum comment: "comment", imports: "imports", region: "region"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/folding_range/options.ex b/apps/protocol/lib/generated/expert/protocol/types/folding_range/options.ex
new file mode 100644
index 00000000..d83dc3e6
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/folding_range/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FoldingRange.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/folding_range/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/folding_range/registration/options.ex
new file mode 100644
index 00000000..24c459d0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/folding_range/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.FoldingRange.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/formatting/options.ex b/apps/protocol/lib/generated/expert/protocol/types/formatting/options.ex
new file mode 100644
index 00000000..a468e85b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/formatting/options.ex
@@ -0,0 +1,11 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Formatting.Options do
+ alias Expert.Proto
+ use Proto
+
+ deftype insert_final_newline: optional(boolean()),
+ insert_spaces: boolean(),
+ tab_size: integer(),
+ trim_final_newlines: optional(boolean()),
+ trim_trailing_whitespace: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/general/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/general/client_capabilities.ex
new file mode 100644
index 00000000..34ffc02f
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/general/client_capabilities.ex
@@ -0,0 +1,18 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.General.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule StaleRequestSupport do
+ use Proto
+ deftype cancel: boolean(), retry_on_content_modified: list_of(string())
+ end
+
+ use Proto
+
+ deftype markdown: optional(Types.Markdown.ClientCapabilities),
+ position_encodings: optional(list_of(Types.Position.Encoding.Kind)),
+ regular_expressions: optional(Types.RegularExpressions.ClientCapabilities),
+ stale_request_support:
+ optional(Expert.Protocol.Types.General.ClientCapabilities.StaleRequestSupport)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/glob_pattern.ex b/apps/protocol/lib/generated/expert/protocol/types/glob_pattern.ex
new file mode 100644
index 00000000..8c8050e2
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/glob_pattern.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.GlobPattern do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ defalias one_of([Types.Pattern, Types.RelativePattern])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/hover.ex b/apps/protocol/lib/generated/expert/protocol/types/hover.ex
new file mode 100644
index 00000000..29dcb3ea
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/hover.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Hover do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype contents:
+ one_of([Types.Markup.Content, Types.MarkedString, list_of(Types.MarkedString)]),
+ range: optional(Types.Range)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/hover/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/hover/client_capabilities.ex
new file mode 100644
index 00000000..4c4e3542
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/hover/client_capabilities.ex
@@ -0,0 +1,9 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Hover.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype content_format: optional(list_of(Types.Markup.Kind)),
+ dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/hover/options.ex b/apps/protocol/lib/generated/expert/protocol/types/hover/options.ex
new file mode 100644
index 00000000..f9716a92
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/hover/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Hover.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/hover/params.ex b/apps/protocol/lib/generated/expert/protocol/types/hover/params.ex
new file mode 100644
index 00000000..33b21c43
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/hover/params.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Hover.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype position: Types.Position,
+ text_document: Types.TextDocument.Identifier,
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/implementation/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/implementation/client_capabilities.ex
new file mode 100644
index 00000000..f6dbe9b5
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/implementation/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Implementation.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean()), link_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/implementation/options.ex b/apps/protocol/lib/generated/expert/protocol/types/implementation/options.ex
new file mode 100644
index 00000000..49a91073
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/implementation/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Implementation.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/implementation/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/implementation/registration/options.ex
new file mode 100644
index 00000000..1348b0bc
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/implementation/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Implementation.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/initialize/params.ex b/apps/protocol/lib/generated/expert/protocol/types/initialize/params.ex
new file mode 100644
index 00000000..a82a7ef6
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/initialize/params.ex
@@ -0,0 +1,26 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Initialize.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule ClientInfo1 do
+ use Proto
+ deftype name: string(), version: optional(string())
+ end
+
+ use Proto
+
+ deftype capabilities: Types.ClientCapabilities,
+ client_info: optional(Expert.Protocol.Types.Initialize.Params.ClientInfo1),
+ initialization_options: optional(any()),
+ locale: optional(string()),
+ process_id: one_of([integer(), nil]),
+ root_path: optional(one_of([string(), nil])),
+ root_uri: one_of([string(), nil]),
+ trace:
+ optional(
+ one_of([literal("off"), literal("messages"), literal("compact"), literal("verbose")])
+ ),
+ work_done_token: optional(Types.Progress.Token),
+ workspace_folders: optional(one_of([list_of(Types.Workspace.Folder), nil]))
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/initialize/result.ex b/apps/protocol/lib/generated/expert/protocol/types/initialize/result.ex
new file mode 100644
index 00000000..f67eafb3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/initialize/result.ex
@@ -0,0 +1,15 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Initialize.Result do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule ServerInfo do
+ use Proto
+ deftype name: string(), version: optional(string())
+ end
+
+ use Proto
+
+ deftype capabilities: Types.ServerCapabilities,
+ server_info: optional(Expert.Protocol.Types.Initialize.Result.ServerInfo)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/client_capabilities.ex
new file mode 100644
index 00000000..e452bf11
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/client_capabilities.ex
@@ -0,0 +1,15 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InlayHint.ClientCapabilities do
+ alias Expert.Proto
+
+ defmodule ResolveSupport do
+ use Proto
+ deftype properties: list_of(string())
+ end
+
+ use Proto
+
+ deftype dynamic_registration: optional(boolean()),
+ resolve_support:
+ optional(Expert.Protocol.Types.InlayHint.ClientCapabilities.ResolveSupport)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/options.ex b/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/options.ex
new file mode 100644
index 00000000..dcca9d72
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InlayHint.Options do
+ alias Expert.Proto
+ use Proto
+ deftype resolve_provider: optional(boolean()), work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/registration/options.ex
new file mode 100644
index 00000000..066c448a
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/inlay_hint/registration/options.ex
@@ -0,0 +1,11 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InlayHint.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ resolve_provider: optional(boolean()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/inlay_hint_workspace/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/inlay_hint_workspace/client_capabilities.ex
new file mode 100644
index 00000000..eea32e90
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/inlay_hint_workspace/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InlayHintWorkspace.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype refresh_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/inline_value/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/inline_value/client_capabilities.ex
new file mode 100644
index 00000000..cfe897af
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/inline_value/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InlineValue.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/inline_value/options.ex b/apps/protocol/lib/generated/expert/protocol/types/inline_value/options.ex
new file mode 100644
index 00000000..b7318185
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/inline_value/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InlineValue.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/inline_value/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/inline_value/registration/options.ex
new file mode 100644
index 00000000..17b80cc7
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/inline_value/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InlineValue.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/inline_value/workspace/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/inline_value/workspace/client_capabilities.ex
new file mode 100644
index 00000000..ef3a01d8
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/inline_value/workspace/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InlineValue.Workspace.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype refresh_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/insert_replace_edit.ex b/apps/protocol/lib/generated/expert/protocol/types/insert_replace_edit.ex
new file mode 100644
index 00000000..2d1bf556
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/insert_replace_edit.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InsertReplaceEdit do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype insert: Types.Range, new_text: string(), replace: Types.Range
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/insert_text_format.ex b/apps/protocol/lib/generated/expert/protocol/types/insert_text_format.ex
new file mode 100644
index 00000000..3657d2b9
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/insert_text_format.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InsertTextFormat do
+ alias Expert.Proto
+ use Proto
+ defenum plain_text: 1, snippet: 2
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/insert_text_mode.ex b/apps/protocol/lib/generated/expert/protocol/types/insert_text_mode.ex
new file mode 100644
index 00000000..ee48f1ab
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/insert_text_mode.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.InsertTextMode do
+ alias Expert.Proto
+ use Proto
+ defenum as_is: 1, adjust_indentation: 2
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/client_capabilities.ex
new file mode 100644
index 00000000..3941e6b3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.LinkedEditingRange.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/options.ex b/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/options.ex
new file mode 100644
index 00000000..dd2a2f28
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.LinkedEditingRange.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/registration/options.ex
new file mode 100644
index 00000000..060b7f97
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/linked_editing_range/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.LinkedEditingRange.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/location.ex b/apps/protocol/lib/generated/expert/protocol/types/location.ex
new file mode 100644
index 00000000..cd6e0796
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/location.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Location do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype range: Types.Range, uri: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/location/link.ex b/apps/protocol/lib/generated/expert/protocol/types/location/link.ex
new file mode 100644
index 00000000..cb607fae
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/location/link.ex
@@ -0,0 +1,11 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Location.Link do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype origin_selection_range: optional(Types.Range),
+ target_range: Types.Range,
+ target_selection_range: Types.Range,
+ target_uri: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/log_message/params.ex b/apps/protocol/lib/generated/expert/protocol/types/log_message/params.ex
new file mode 100644
index 00000000..c22cae29
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/log_message/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.LogMessage.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype message: string(), type: Types.Message.Type
+end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/lsp_error_codes.ex b/apps/protocol/lib/generated/expert/protocol/types/lsp_error_codes.ex
similarity index 75%
rename from apps/protocol/lib/generated/lexical/protocol/types/lsp_error_codes.ex
rename to apps/protocol/lib/generated/expert/protocol/types/lsp_error_codes.ex
index 1b66598c..afdb0a95 100644
--- a/apps/protocol/lib/generated/lexical/protocol/types/lsp_error_codes.ex
+++ b/apps/protocol/lib/generated/expert/protocol/types/lsp_error_codes.ex
@@ -1,6 +1,6 @@
# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.LSPErrorCodes do
- alias Lexical.Proto
+defmodule Expert.Protocol.Types.LSPErrorCodes do
+ alias Expert.Proto
use Proto
defenum request_failed: -32_803,
diff --git a/apps/protocol/lib/generated/expert/protocol/types/lsp_object.ex b/apps/protocol/lib/generated/expert/protocol/types/lsp_object.ex
new file mode 100644
index 00000000..f0914b00
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/lsp_object.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.LSPObject do
+ alias Expert.Proto
+ use Proto
+ deftype []
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/markdown/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/markdown/client_capabilities.ex
new file mode 100644
index 00000000..208f2088
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/markdown/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Markdown.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype allowed_tags: optional(list_of(string())), parser: string(), version: optional(string())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/marked_string.ex b/apps/protocol/lib/generated/expert/protocol/types/marked_string.ex
new file mode 100644
index 00000000..45e41fbf
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/marked_string.ex
@@ -0,0 +1,12 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.MarkedString do
+ alias Expert.Proto
+
+ defmodule MarkedString do
+ use Proto
+ deftype language: string(), value: string()
+ end
+
+ use Proto
+ defalias one_of([string(), Expert.Protocol.Types.MarkedString.MarkedString])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/markup/content.ex b/apps/protocol/lib/generated/expert/protocol/types/markup/content.ex
new file mode 100644
index 00000000..7d79d30e
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/markup/content.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Markup.Content do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype kind: Types.Markup.Kind, value: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/markup/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/markup/kind.ex
new file mode 100644
index 00000000..47326be9
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/markup/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Markup.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum plain_text: "plaintext", markdown: "markdown"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/message/action_item.ex b/apps/protocol/lib/generated/expert/protocol/types/message/action_item.ex
new file mode 100644
index 00000000..9ce129f5
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/message/action_item.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Message.ActionItem do
+ alias Expert.Proto
+ use Proto
+ deftype title: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/message/type.ex b/apps/protocol/lib/generated/expert/protocol/types/message/type.ex
new file mode 100644
index 00000000..868a9af4
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/message/type.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Message.Type do
+ alias Expert.Proto
+ use Proto
+ defenum error: 1, warning: 2, info: 3, log: 4
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/moniker/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/moniker/client_capabilities.ex
new file mode 100644
index 00000000..4cc0332c
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/moniker/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Moniker.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/moniker/options.ex b/apps/protocol/lib/generated/expert/protocol/types/moniker/options.ex
new file mode 100644
index 00000000..be3f1d92
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/moniker/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Moniker.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/moniker/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/moniker/registration/options.ex
new file mode 100644
index 00000000..228f012d
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/moniker/registration/options.ex
@@ -0,0 +1,9 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Moniker.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/notebook/cell/text_document/filter.ex b/apps/protocol/lib/generated/expert/protocol/types/notebook/cell/text_document/filter.ex
new file mode 100644
index 00000000..d2286f41
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/notebook/cell/text_document/filter.ex
@@ -0,0 +1,9 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Notebook.Cell.TextDocument.Filter do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype language: optional(string()),
+ notebook: one_of([string(), Types.Notebook.Document.Filter])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/notebook/document/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/client_capabilities.ex
new file mode 100644
index 00000000..d74f9cd5
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/client_capabilities.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Notebook.Document.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype synchronization: Types.Notebook.Document.Sync.ClientCapabilities
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/notebook/document/filter.ex b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/filter.ex
new file mode 100644
index 00000000..81858b53
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/filter.ex
@@ -0,0 +1,27 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Notebook.Document.Filter do
+ alias Expert.Proto
+
+ defmodule NotebookDocumentFilter do
+ use Proto
+ deftype notebook_type: string(), pattern: optional(string()), scheme: optional(string())
+ end
+
+ defmodule NotebookDocumentFilter1 do
+ use Proto
+ deftype notebook_type: optional(string()), pattern: optional(string()), scheme: string()
+ end
+
+ defmodule NotebookDocumentFilter2 do
+ use Proto
+ deftype notebook_type: optional(string()), pattern: string(), scheme: optional(string())
+ end
+
+ use Proto
+
+ defalias one_of([
+ Expert.Protocol.Types.Notebook.Document.Filter.NotebookDocumentFilter,
+ Expert.Protocol.Types.Notebook.Document.Filter.NotebookDocumentFilter1,
+ Expert.Protocol.Types.Notebook.Document.Filter.NotebookDocumentFilter2
+ ])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/client_capabilities.ex
new file mode 100644
index 00000000..965d9bd2
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/client_capabilities.ex
@@ -0,0 +1,8 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Notebook.Document.Sync.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+
+ deftype dynamic_registration: optional(boolean()),
+ execution_summary_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/options.ex b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/options.ex
new file mode 100644
index 00000000..21d1d7ea
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/options.ex
@@ -0,0 +1,40 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Notebook.Document.Sync.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule Cells do
+ use Proto
+ deftype language: string()
+ end
+
+ defmodule Cells1 do
+ use Proto
+ deftype language: string()
+ end
+
+ defmodule NotebookSelector do
+ use Proto
+
+ deftype cells: optional(list_of(Expert.Protocol.Types.Notebook.Document.Sync.Options.Cells)),
+ notebook: one_of([string(), Types.Notebook.Document.Filter])
+ end
+
+ defmodule NotebookSelector1 do
+ use Proto
+
+ deftype cells: list_of(Expert.Protocol.Types.Notebook.Document.Sync.Options.Cells1),
+ notebook: optional(one_of([string(), Types.Notebook.Document.Filter]))
+ end
+
+ use Proto
+
+ deftype notebook_selector:
+ list_of(
+ one_of([
+ Expert.Protocol.Types.Notebook.Document.Sync.Options.NotebookSelector,
+ Expert.Protocol.Types.Notebook.Document.Sync.Options.NotebookSelector1
+ ])
+ ),
+ save: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/registration/options.ex
new file mode 100644
index 00000000..4d46661d
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/notebook/document/sync/registration/options.ex
@@ -0,0 +1,45 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Notebook.Document.Sync.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule Cells2 do
+ use Proto
+ deftype language: string()
+ end
+
+ defmodule Cells3 do
+ use Proto
+ deftype language: string()
+ end
+
+ defmodule NotebookSelector2 do
+ use Proto
+
+ deftype cells:
+ optional(
+ list_of(Expert.Protocol.Types.Notebook.Document.Sync.Registration.Options.Cells2)
+ ),
+ notebook: one_of([string(), Types.Notebook.Document.Filter])
+ end
+
+ defmodule NotebookSelector3 do
+ use Proto
+
+ deftype cells:
+ list_of(Expert.Protocol.Types.Notebook.Document.Sync.Registration.Options.Cells3),
+ notebook: optional(one_of([string(), Types.Notebook.Document.Filter]))
+ end
+
+ use Proto
+
+ deftype id: optional(string()),
+ notebook_selector:
+ list_of(
+ one_of([
+ Expert.Protocol.Types.Notebook.Document.Sync.Registration.Options.NotebookSelector2,
+ Expert.Protocol.Types.Notebook.Document.Sync.Registration.Options.NotebookSelector3
+ ])
+ ),
+ save: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/pattern.ex b/apps/protocol/lib/generated/expert/protocol/types/pattern.ex
new file mode 100644
index 00000000..bb59bc0b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/pattern.ex
@@ -0,0 +1,4 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Pattern do
+ @type t :: String.t()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/position.ex b/apps/protocol/lib/generated/expert/protocol/types/position.ex
new file mode 100644
index 00000000..fb00273f
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/position.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Position do
+ alias Expert.Proto
+ use Proto
+ deftype character: integer(), line: integer()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/position/encoding/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/position/encoding/kind.ex
new file mode 100644
index 00000000..6fb803a8
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/position/encoding/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Position.Encoding.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum utf8: "utf-8", utf16: "utf-16", utf32: "utf-32"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/prepare_support_default_behavior.ex b/apps/protocol/lib/generated/expert/protocol/types/prepare_support_default_behavior.ex
new file mode 100644
index 00000000..2a6a53c9
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/prepare_support_default_behavior.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.PrepareSupportDefaultBehavior do
+ alias Expert.Proto
+ use Proto
+ defenum identifier: 1
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/progress/params.ex b/apps/protocol/lib/generated/expert/protocol/types/progress/params.ex
new file mode 100644
index 00000000..88be4f27
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/progress/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Progress.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype token: Types.Progress.Token, value: any()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/progress/token.ex b/apps/protocol/lib/generated/expert/protocol/types/progress/token.ex
new file mode 100644
index 00000000..43686a81
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/progress/token.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Progress.Token do
+ alias Expert.Proto
+ use Proto
+ defalias one_of([integer(), string()])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/publish_diagnostics/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/publish_diagnostics/client_capabilities.ex
new file mode 100644
index 00000000..2ed3a693
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/publish_diagnostics/client_capabilities.ex
@@ -0,0 +1,19 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.PublishDiagnostics.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule TagSupport do
+ use Proto
+ deftype value_set: list_of(Types.Diagnostic.Tag)
+ end
+
+ use Proto
+
+ deftype code_description_support: optional(boolean()),
+ data_support: optional(boolean()),
+ related_information: optional(boolean()),
+ tag_support:
+ optional(Expert.Protocol.Types.PublishDiagnostics.ClientCapabilities.TagSupport),
+ version_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/publish_diagnostics/params.ex b/apps/protocol/lib/generated/expert/protocol/types/publish_diagnostics/params.ex
new file mode 100644
index 00000000..9e26370b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/publish_diagnostics/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.PublishDiagnostics.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype diagnostics: list_of(Types.Diagnostic), uri: string(), version: optional(integer())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/range.ex b/apps/protocol/lib/generated/expert/protocol/types/range.ex
new file mode 100644
index 00000000..b0bc1bfc
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/range.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Range do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype end: Types.Position, start: Types.Position
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/reference/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/reference/client_capabilities.ex
new file mode 100644
index 00000000..e8ba6270
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/reference/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Reference.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/reference/context.ex b/apps/protocol/lib/generated/expert/protocol/types/reference/context.ex
new file mode 100644
index 00000000..66518ff8
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/reference/context.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Reference.Context do
+ alias Expert.Proto
+ use Proto
+ deftype include_declaration: boolean()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/reference/options.ex b/apps/protocol/lib/generated/expert/protocol/types/reference/options.ex
new file mode 100644
index 00000000..1bc42d64
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/reference/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Reference.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/reference/params.ex b/apps/protocol/lib/generated/expert/protocol/types/reference/params.ex
new file mode 100644
index 00000000..b61cfe47
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/reference/params.ex
@@ -0,0 +1,12 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Reference.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype context: Types.Reference.Context,
+ partial_result_token: optional(Types.Progress.Token),
+ position: Types.Position,
+ text_document: Types.TextDocument.Identifier,
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/registration.ex b/apps/protocol/lib/generated/expert/protocol/types/registration.ex
new file mode 100644
index 00000000..c5e24e0f
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/registration.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Registration do
+ alias Expert.Proto
+ use Proto
+ deftype id: string(), method: string(), register_options: optional(any())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/registration/params.ex b/apps/protocol/lib/generated/expert/protocol/types/registration/params.ex
new file mode 100644
index 00000000..23fc0dd8
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/registration/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Registration.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype registrations: list_of(Types.Registration)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/regular_expressions/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/regular_expressions/client_capabilities.ex
new file mode 100644
index 00000000..44cc951b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/regular_expressions/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.RegularExpressions.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype engine: string(), version: optional(string())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/relative_pattern.ex b/apps/protocol/lib/generated/expert/protocol/types/relative_pattern.ex
new file mode 100644
index 00000000..64107d03
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/relative_pattern.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.RelativePattern do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype base_uri: one_of([Types.Workspace.Folder, string()]), pattern: Types.Pattern
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/rename/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/rename/client_capabilities.ex
new file mode 100644
index 00000000..6e73460b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/rename/client_capabilities.ex
@@ -0,0 +1,11 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Rename.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype dynamic_registration: optional(boolean()),
+ honors_change_annotations: optional(boolean()),
+ prepare_support: optional(boolean()),
+ prepare_support_default_behavior: optional(Types.PrepareSupportDefaultBehavior)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/rename/options.ex b/apps/protocol/lib/generated/expert/protocol/types/rename/options.ex
new file mode 100644
index 00000000..06c609e0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/rename/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Rename.Options do
+ alias Expert.Proto
+ use Proto
+ deftype prepare_provider: optional(boolean()), work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/rename_file.ex b/apps/protocol/lib/generated/expert/protocol/types/rename_file.ex
new file mode 100644
index 00000000..0b6f4323
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/rename_file.ex
@@ -0,0 +1,12 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.RenameFile do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype annotation_id: optional(Types.ChangeAnnotation.Identifier),
+ kind: literal("rename"),
+ new_uri: string(),
+ old_uri: string(),
+ options: optional(Types.RenameFile.Options)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/rename_file/options.ex b/apps/protocol/lib/generated/expert/protocol/types/rename_file/options.ex
new file mode 100644
index 00000000..9c819384
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/rename_file/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.RenameFile.Options do
+ alias Expert.Proto
+ use Proto
+ deftype ignore_if_exists: optional(boolean()), overwrite: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/resource_operation/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/resource_operation/kind.ex
new file mode 100644
index 00000000..a2a4e2bd
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/resource_operation/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ResourceOperation.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum create: "create", rename: "rename", delete: "delete"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/save/options.ex b/apps/protocol/lib/generated/expert/protocol/types/save/options.ex
new file mode 100644
index 00000000..83991424
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/save/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Save.Options do
+ alias Expert.Proto
+ use Proto
+ deftype include_text: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/selection_range/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/selection_range/client_capabilities.ex
new file mode 100644
index 00000000..1afa51d3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/selection_range/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SelectionRange.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/selection_range/options.ex b/apps/protocol/lib/generated/expert/protocol/types/selection_range/options.ex
new file mode 100644
index 00000000..e1a39156
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/selection_range/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SelectionRange.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/selection_range/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/selection_range/registration/options.ex
new file mode 100644
index 00000000..dce3bd31
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/selection_range/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SelectionRange.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/client_capabilities.ex
new file mode 100644
index 00000000..e63204f4
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/client_capabilities.ex
@@ -0,0 +1,40 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SemanticTokens.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule Full do
+ use Proto
+ deftype delta: optional(boolean())
+ end
+
+ defmodule Range do
+ use Proto
+ deftype []
+ end
+
+ defmodule Requests do
+ use Proto
+
+ deftype full:
+ optional(
+ one_of([boolean(), Expert.Protocol.Types.SemanticTokens.ClientCapabilities.Full])
+ ),
+ range:
+ optional(
+ one_of([boolean(), Expert.Protocol.Types.SemanticTokens.ClientCapabilities.Range])
+ )
+ end
+
+ use Proto
+
+ deftype augments_syntax_tokens: optional(boolean()),
+ dynamic_registration: optional(boolean()),
+ formats: list_of(Types.TokenFormat),
+ multiline_token_support: optional(boolean()),
+ overlapping_token_support: optional(boolean()),
+ requests: Expert.Protocol.Types.SemanticTokens.ClientCapabilities.Requests,
+ server_cancel_support: optional(boolean()),
+ token_modifiers: list_of(string()),
+ token_types: list_of(string())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/legend.ex b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/legend.ex
new file mode 100644
index 00000000..c04a3620
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/legend.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SemanticTokens.Legend do
+ alias Expert.Proto
+ use Proto
+ deftype token_modifiers: list_of(string()), token_types: list_of(string())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/options.ex b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/options.ex
new file mode 100644
index 00000000..1cff6522
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/options.ex
@@ -0,0 +1,23 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SemanticTokens.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule Full do
+ use Proto
+ deftype delta: optional(boolean())
+ end
+
+ defmodule Range do
+ use Proto
+ deftype []
+ end
+
+ use Proto
+
+ deftype full: optional(one_of([boolean(), Expert.Protocol.Types.SemanticTokens.Options.Full])),
+ legend: Types.SemanticTokens.Legend,
+ range:
+ optional(one_of([boolean(), Expert.Protocol.Types.SemanticTokens.Options.Range])),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/registration/options.ex
new file mode 100644
index 00000000..ebd55c48
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/registration/options.ex
@@ -0,0 +1,33 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SemanticTokens.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule Full1 do
+ use Proto
+ deftype delta: optional(boolean())
+ end
+
+ defmodule Range1 do
+ use Proto
+ deftype []
+ end
+
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ full:
+ optional(
+ one_of([boolean(), Expert.Protocol.Types.SemanticTokens.Registration.Options.Full1])
+ ),
+ id: optional(string()),
+ legend: Types.SemanticTokens.Legend,
+ range:
+ optional(
+ one_of([
+ boolean(),
+ Expert.Protocol.Types.SemanticTokens.Registration.Options.Range1
+ ])
+ ),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/workspace/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/workspace/client_capabilities.ex
new file mode 100644
index 00000000..3c03c673
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/semantic_tokens/workspace/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SemanticTokens.Workspace.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype refresh_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/server_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/server_capabilities.ex
similarity index 96%
rename from apps/protocol/lib/generated/lexical/protocol/types/server_capabilities.ex
rename to apps/protocol/lib/generated/expert/protocol/types/server_capabilities.ex
index e223cf25..53bf2867 100644
--- a/apps/protocol/lib/generated/lexical/protocol/types/server_capabilities.ex
+++ b/apps/protocol/lib/generated/expert/protocol/types/server_capabilities.ex
@@ -1,7 +1,7 @@
# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ServerCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
+defmodule Expert.Protocol.Types.ServerCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
defmodule Workspace do
use Proto
@@ -135,6 +135,6 @@ defmodule Lexical.Protocol.Types.ServerCapabilities do
Types.TypeHierarchy.Registration.Options
])
),
- workspace: optional(Lexical.Protocol.Types.ServerCapabilities.Workspace),
+ workspace: optional(Expert.Protocol.Types.ServerCapabilities.Workspace),
workspace_symbol_provider: optional(one_of([boolean(), Types.Workspace.Symbol.Options]))
end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/show_document/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/show_document/client_capabilities.ex
new file mode 100644
index 00000000..1a7918f4
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/show_document/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ShowDocument.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype support: boolean()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/show_message/params.ex b/apps/protocol/lib/generated/expert/protocol/types/show_message/params.ex
new file mode 100644
index 00000000..8d2d9c9a
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/show_message/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ShowMessage.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype message: string(), type: Types.Message.Type
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/show_message_request/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/show_message_request/client_capabilities.ex
new file mode 100644
index 00000000..63ae2281
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/show_message_request/client_capabilities.ex
@@ -0,0 +1,16 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ShowMessageRequest.ClientCapabilities do
+ alias Expert.Proto
+
+ defmodule MessageActionItem do
+ use Proto
+ deftype additional_properties_support: optional(boolean())
+ end
+
+ use Proto
+
+ deftype message_action_item:
+ optional(
+ Expert.Protocol.Types.ShowMessageRequest.ClientCapabilities.MessageActionItem
+ )
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/show_message_request/params.ex b/apps/protocol/lib/generated/expert/protocol/types/show_message_request/params.ex
new file mode 100644
index 00000000..06bb25ba
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/show_message_request/params.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.ShowMessageRequest.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype actions: optional(list_of(Types.Message.ActionItem)),
+ message: string(),
+ type: Types.Message.Type
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/signature_help/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/signature_help/client_capabilities.ex
new file mode 100644
index 00000000..250c1549
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/signature_help/client_capabilities.ex
@@ -0,0 +1,28 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SignatureHelp.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule ParameterInformation do
+ use Proto
+ deftype label_offset_support: optional(boolean())
+ end
+
+ defmodule SignatureInformation do
+ use Proto
+
+ deftype active_parameter_support: optional(boolean()),
+ documentation_format: optional(list_of(Types.Markup.Kind)),
+ parameter_information:
+ optional(
+ Expert.Protocol.Types.SignatureHelp.ClientCapabilities.ParameterInformation
+ )
+ end
+
+ use Proto
+
+ deftype context_support: optional(boolean()),
+ dynamic_registration: optional(boolean()),
+ signature_information:
+ optional(Expert.Protocol.Types.SignatureHelp.ClientCapabilities.SignatureInformation)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/signature_help/options.ex b/apps/protocol/lib/generated/expert/protocol/types/signature_help/options.ex
new file mode 100644
index 00000000..99828247
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/signature_help/options.ex
@@ -0,0 +1,9 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.SignatureHelp.Options do
+ alias Expert.Proto
+ use Proto
+
+ deftype retrigger_characters: optional(list_of(string())),
+ trigger_characters: optional(list_of(string())),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/symbol/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/symbol/kind.ex
new file mode 100644
index 00000000..990f89fb
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/symbol/kind.ex
@@ -0,0 +1,32 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Symbol.Kind do
+ alias Expert.Proto
+ use Proto
+
+ defenum file: 1,
+ module: 2,
+ namespace: 3,
+ package: 4,
+ class: 5,
+ method: 6,
+ property: 7,
+ field: 8,
+ constructor: 9,
+ enum: 10,
+ interface: 11,
+ function: 12,
+ variable: 13,
+ constant: 14,
+ string: 15,
+ number: 16,
+ boolean: 17,
+ array: 18,
+ object: 19,
+ key: 20,
+ null: 21,
+ enum_member: 22,
+ struct: 23,
+ event: 24,
+ operator: 25,
+ type_parameter: 26
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/symbol/tag.ex b/apps/protocol/lib/generated/expert/protocol/types/symbol/tag.ex
new file mode 100644
index 00000000..a59090f9
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/symbol/tag.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Symbol.Tag do
+ alias Expert.Proto
+ use Proto
+ defenum deprecated: 1
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/client_capabilities.ex
new file mode 100644
index 00000000..62aedfa6
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/client_capabilities.ex
@@ -0,0 +1,37 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype call_hierarchy: optional(Types.CallHierarchy.ClientCapabilities),
+ code_action: optional(Types.CodeAction.ClientCapabilities),
+ code_lens: optional(Types.CodeLens.ClientCapabilities),
+ color_provider: optional(Types.Document.Color.ClientCapabilities),
+ completion: optional(Types.Completion.ClientCapabilities),
+ declaration: optional(Types.Declaration.ClientCapabilities),
+ definition: optional(Types.Definition.ClientCapabilities),
+ diagnostic: optional(Types.Diagnostic.ClientCapabilities),
+ document_highlight: optional(Types.Document.Highlight.ClientCapabilities),
+ document_link: optional(Types.Document.Link.ClientCapabilities),
+ document_symbol: optional(Types.Document.Symbol.ClientCapabilities),
+ folding_range: optional(Types.FoldingRange.ClientCapabilities),
+ formatting: optional(Types.Document.Formatting.ClientCapabilities),
+ hover: optional(Types.Hover.ClientCapabilities),
+ implementation: optional(Types.Implementation.ClientCapabilities),
+ inlay_hint: optional(Types.InlayHint.ClientCapabilities),
+ inline_value: optional(Types.InlineValue.ClientCapabilities),
+ linked_editing_range: optional(Types.LinkedEditingRange.ClientCapabilities),
+ moniker: optional(Types.Moniker.ClientCapabilities),
+ on_type_formatting: optional(Types.Document.OnTypeFormatting.ClientCapabilities),
+ publish_diagnostics: optional(Types.PublishDiagnostics.ClientCapabilities),
+ range_formatting: optional(Types.Document.RangeFormatting.ClientCapabilities),
+ references: optional(Types.Reference.ClientCapabilities),
+ rename: optional(Types.Rename.ClientCapabilities),
+ selection_range: optional(Types.SelectionRange.ClientCapabilities),
+ semantic_tokens: optional(Types.SemanticTokens.ClientCapabilities),
+ signature_help: optional(Types.SignatureHelp.ClientCapabilities),
+ synchronization: optional(Types.TextDocument.Sync.ClientCapabilities),
+ type_definition: optional(Types.TypeDefinition.ClientCapabilities),
+ type_hierarchy: optional(Types.TypeHierarchy.ClientCapabilities)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/content_change_event.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/content_change_event.ex
new file mode 100644
index 00000000..e8117245
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/content_change_event.ex
@@ -0,0 +1,22 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.ContentChangeEvent do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule TextDocumentContentChangeEvent do
+ use Proto
+ deftype range: Types.Range, range_length: optional(integer()), text: string()
+ end
+
+ defmodule TextDocumentContentChangeEvent1 do
+ use Proto
+ deftype text: string()
+ end
+
+ use Proto
+
+ defalias one_of([
+ Expert.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent,
+ Expert.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent1
+ ])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/edit.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/edit.ex
new file mode 100644
index 00000000..af2284fd
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/edit.ex
@@ -0,0 +1,9 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.Edit do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype edits: list_of(one_of([Types.TextEdit, Types.TextEdit.Annotated])),
+ text_document: Types.TextDocument.OptionalVersioned.Identifier
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/filter.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/filter.ex
new file mode 100644
index 00000000..88ef77c3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/filter.ex
@@ -0,0 +1,27 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.Filter do
+ alias Expert.Proto
+
+ defmodule TextDocumentFilter do
+ use Proto
+ deftype language: string(), pattern: optional(string()), scheme: optional(string())
+ end
+
+ defmodule TextDocumentFilter1 do
+ use Proto
+ deftype language: optional(string()), pattern: optional(string()), scheme: string()
+ end
+
+ defmodule TextDocumentFilter2 do
+ use Proto
+ deftype language: optional(string()), pattern: string(), scheme: optional(string())
+ end
+
+ use Proto
+
+ defalias one_of([
+ Expert.Protocol.Types.TextDocument.Filter.TextDocumentFilter,
+ Expert.Protocol.Types.TextDocument.Filter.TextDocumentFilter1,
+ Expert.Protocol.Types.TextDocument.Filter.TextDocumentFilter2
+ ])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/identifier.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/identifier.ex
new file mode 100644
index 00000000..9f20d51d
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/identifier.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.Identifier do
+ alias Expert.Proto
+ use Proto
+ deftype uri: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/item.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/item.ex
new file mode 100644
index 00000000..96f59367
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/item.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.Item do
+ alias Expert.Proto
+ use Proto
+ deftype language_id: string(), text: string(), uri: string(), version: integer()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/optional_versioned/identifier.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/optional_versioned/identifier.ex
new file mode 100644
index 00000000..043e12b0
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/optional_versioned/identifier.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.OptionalVersioned.Identifier do
+ alias Expert.Proto
+ use Proto
+ deftype uri: string(), version: one_of([integer(), nil])
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/client_capabilities.ex
new file mode 100644
index 00000000..950e13cb
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/client_capabilities.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.Sync.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+
+ deftype did_save: optional(boolean()),
+ dynamic_registration: optional(boolean()),
+ will_save: optional(boolean()),
+ will_save_wait_until: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/kind.ex
new file mode 100644
index 00000000..1c891445
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.Sync.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum none: 0, full: 1, incremental: 2
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/options.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/options.ex
new file mode 100644
index 00000000..9332d3f1
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/sync/options.ex
@@ -0,0 +1,12 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.Sync.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype change: optional(Types.TextDocument.Sync.Kind),
+ open_close: optional(boolean()),
+ save: optional(one_of([boolean(), Types.Save.Options])),
+ will_save: optional(boolean()),
+ will_save_wait_until: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_document/versioned/identifier.ex b/apps/protocol/lib/generated/expert/protocol/types/text_document/versioned/identifier.ex
new file mode 100644
index 00000000..108c252d
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_document/versioned/identifier.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextDocument.Versioned.Identifier do
+ alias Expert.Proto
+ use Proto
+ deftype uri: string(), version: integer()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_edit.ex b/apps/protocol/lib/generated/expert/protocol/types/text_edit.ex
new file mode 100644
index 00000000..4d2bf890
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_edit.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextEdit do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype new_text: string(), range: Types.Range
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/text_edit/annotated.ex b/apps/protocol/lib/generated/expert/protocol/types/text_edit/annotated.ex
new file mode 100644
index 00000000..fa8c4ddc
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/text_edit/annotated.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TextEdit.Annotated do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype annotation_id: Types.ChangeAnnotation.Identifier, new_text: string(), range: Types.Range
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/token_format.ex b/apps/protocol/lib/generated/expert/protocol/types/token_format.ex
new file mode 100644
index 00000000..1277598d
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/token_format.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TokenFormat do
+ alias Expert.Proto
+ use Proto
+ defenum relative: "relative"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/trace_values.ex b/apps/protocol/lib/generated/expert/protocol/types/trace_values.ex
new file mode 100644
index 00000000..04846420
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/trace_values.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TraceValues do
+ alias Expert.Proto
+ use Proto
+ defenum off: "off", messages: "messages", verbose: "verbose"
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/type_definition/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/type_definition/client_capabilities.ex
new file mode 100644
index 00000000..f89a97ef
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/type_definition/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TypeDefinition.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean()), link_support: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/type_definition/options.ex b/apps/protocol/lib/generated/expert/protocol/types/type_definition/options.ex
new file mode 100644
index 00000000..535ac3b9
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/type_definition/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TypeDefinition.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/type_definition/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/type_definition/registration/options.ex
new file mode 100644
index 00000000..e3839eb1
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/type_definition/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TypeDefinition.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/client_capabilities.ex
new file mode 100644
index 00000000..a0bb193a
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/client_capabilities.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TypeHierarchy.ClientCapabilities do
+ alias Expert.Proto
+ use Proto
+ deftype dynamic_registration: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/options.ex b/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/options.ex
new file mode 100644
index 00000000..b3abf94e
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TypeHierarchy.Options do
+ alias Expert.Proto
+ use Proto
+ deftype work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/registration/options.ex b/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/registration/options.ex
new file mode 100644
index 00000000..22bf931b
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/type_hierarchy/registration/options.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.TypeHierarchy.Registration.Options do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype document_selector: one_of([Types.Document.Selector, nil]),
+ id: optional(string()),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/watch/kind.ex b/apps/protocol/lib/generated/expert/protocol/types/watch/kind.ex
new file mode 100644
index 00000000..68681f57
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/watch/kind.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Watch.Kind do
+ alias Expert.Proto
+ use Proto
+ defenum create: 1, change: 2, delete: 4
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/window/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/window/client_capabilities.ex
new file mode 100644
index 00000000..6e8c9cc2
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/window/client_capabilities.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Window.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype show_document: optional(Types.ShowDocument.ClientCapabilities),
+ show_message: optional(Types.ShowMessageRequest.ClientCapabilities),
+ work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/begin.ex b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/begin.ex
similarity index 75%
rename from apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/begin.ex
rename to apps/protocol/lib/generated/expert/protocol/types/work_done/progress/begin.ex
index fef28518..d9f534e8 100644
--- a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/begin.ex
+++ b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/begin.ex
@@ -1,6 +1,6 @@
# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.WorkDone.Progress.Begin do
- alias Lexical.Proto
+defmodule Expert.Protocol.Types.WorkDone.Progress.Begin do
+ alias Expert.Proto
use Proto
deftype cancellable: optional(boolean()),
diff --git a/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/create/params.ex b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/create/params.ex
new file mode 100644
index 00000000..921a2f19
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/create/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.WorkDone.Progress.Create.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype token: Types.Progress.Token
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/end.ex b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/end.ex
new file mode 100644
index 00000000..a71e5c42
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/end.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.WorkDone.Progress.End do
+ alias Expert.Proto
+ use Proto
+ deftype kind: literal("end"), message: optional(string())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/params.ex b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/params.ex
new file mode 100644
index 00000000..371376af
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/params.ex
@@ -0,0 +1,7 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.WorkDone.Progress.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+ deftype work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/report.ex b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/report.ex
new file mode 100644
index 00000000..91774af3
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/work_done/progress/report.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.WorkDone.Progress.Report do
+ alias Expert.Proto
+ use Proto
+
+ deftype cancellable: optional(boolean()),
+ kind: literal("report"),
+ message: optional(string()),
+ percentage: optional(integer())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/client_capabilities.ex
new file mode 100644
index 00000000..b8c608a7
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/client_capabilities.ex
@@ -0,0 +1,21 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype apply_edit: optional(boolean()),
+ code_lens: optional(Types.CodeLens.Workspace.ClientCapabilities),
+ configuration: optional(boolean()),
+ diagnostics: optional(Types.Diagnostic.Workspace.ClientCapabilities),
+ did_change_configuration: optional(Types.DidChangeConfiguration.ClientCapabilities),
+ did_change_watched_files: optional(Types.DidChangeWatchedFiles.ClientCapabilities),
+ execute_command: optional(Types.ExecuteCommand.ClientCapabilities),
+ file_operations: optional(Types.FileOperation.ClientCapabilities),
+ inlay_hint: optional(Types.InlayHintWorkspace.ClientCapabilities),
+ inline_value: optional(Types.InlineValue.Workspace.ClientCapabilities),
+ semantic_tokens: optional(Types.SemanticTokens.Workspace.ClientCapabilities),
+ symbol: optional(Types.Workspace.Symbol.ClientCapabilities),
+ workspace_edit: optional(Types.Workspace.Edit.ClientCapabilities),
+ workspace_folders: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/edit.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/edit.ex
new file mode 100644
index 00000000..76b931ad
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/edit.ex
@@ -0,0 +1,20 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.Edit do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype change_annotations: optional(map_of(Types.ChangeAnnotation)),
+ changes: optional(map_of(list_of(Types.TextEdit))),
+ document_changes:
+ optional(
+ list_of(
+ one_of([
+ Types.TextDocument.Edit,
+ Types.CreateFile,
+ Types.RenameFile,
+ Types.DeleteFile
+ ])
+ )
+ )
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/edit/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/edit/client_capabilities.ex
new file mode 100644
index 00000000..e25434af
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/edit/client_capabilities.ex
@@ -0,0 +1,21 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.Edit.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule ChangeAnnotationSupport do
+ use Proto
+ deftype groups_on_label: optional(boolean())
+ end
+
+ use Proto
+
+ deftype change_annotation_support:
+ optional(
+ Expert.Protocol.Types.Workspace.Edit.ClientCapabilities.ChangeAnnotationSupport
+ ),
+ document_changes: optional(boolean()),
+ failure_handling: optional(Types.FailureHandling.Kind),
+ normalizes_line_endings: optional(boolean()),
+ resource_operations: optional(list_of(Types.ResourceOperation.Kind))
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/folder.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/folder.ex
new file mode 100644
index 00000000..e4656297
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/folder.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.Folder do
+ alias Expert.Proto
+ use Proto
+ deftype name: string(), uri: string()
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/folders_server_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/folders_server_capabilities.ex
new file mode 100644
index 00000000..00a22729
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/folders_server_capabilities.ex
@@ -0,0 +1,8 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.FoldersServerCapabilities do
+ alias Expert.Proto
+ use Proto
+
+ deftype change_notifications: optional(one_of([string(), boolean()])),
+ supported: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol.ex
new file mode 100644
index 00000000..93a6679c
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol.ex
@@ -0,0 +1,19 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.Symbol do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule Location do
+ use Proto
+ deftype uri: string()
+ end
+
+ use Proto
+
+ deftype container_name: optional(string()),
+ data: optional(any()),
+ kind: Types.Symbol.Kind,
+ location: one_of([Types.Location, Expert.Protocol.Types.Workspace.Symbol.Location]),
+ name: string(),
+ tags: optional(list_of(Types.Symbol.Tag))
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/client_capabilities.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/client_capabilities.ex
new file mode 100644
index 00000000..ce84fc33
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/client_capabilities.ex
@@ -0,0 +1,30 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.Symbol.ClientCapabilities do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+
+ defmodule ResolveSupport do
+ use Proto
+ deftype properties: list_of(string())
+ end
+
+ defmodule SymbolKind do
+ use Proto
+ deftype value_set: optional(list_of(Types.Symbol.Kind))
+ end
+
+ defmodule TagSupport do
+ use Proto
+ deftype value_set: list_of(Types.Symbol.Tag)
+ end
+
+ use Proto
+
+ deftype dynamic_registration: optional(boolean()),
+ resolve_support:
+ optional(Expert.Protocol.Types.Workspace.Symbol.ClientCapabilities.ResolveSupport),
+ symbol_kind:
+ optional(Expert.Protocol.Types.Workspace.Symbol.ClientCapabilities.SymbolKind),
+ tag_support:
+ optional(Expert.Protocol.Types.Workspace.Symbol.ClientCapabilities.TagSupport)
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/options.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/options.ex
new file mode 100644
index 00000000..856f76ff
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/options.ex
@@ -0,0 +1,6 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.Symbol.Options do
+ alias Expert.Proto
+ use Proto
+ deftype resolve_provider: optional(boolean()), work_done_progress: optional(boolean())
+end
diff --git a/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/params.ex b/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/params.ex
new file mode 100644
index 00000000..00fa822f
--- /dev/null
+++ b/apps/protocol/lib/generated/expert/protocol/types/workspace/symbol/params.ex
@@ -0,0 +1,10 @@
+# This file's contents are auto-generated. Do not edit.
+defmodule Expert.Protocol.Types.Workspace.Symbol.Params do
+ alias Expert.Proto
+ alias Expert.Protocol.Types
+ use Proto
+
+ deftype partial_result_token: optional(Types.Progress.Token),
+ query: string(),
+ work_done_token: optional(Types.Progress.Token)
+end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/client_capabilities.ex
deleted file mode 100644
index bffddcc4..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CallHierarchy.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/options.ex
deleted file mode 100644
index bb701c4b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CallHierarchy.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/registration/options.ex
deleted file mode 100644
index 0397f62e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/call_hierarchy/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CallHierarchy.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/cancel/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/cancel/params.ex
deleted file mode 100644
index 7057bf34..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/cancel/params.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Cancel.Params do
- alias Lexical.Proto
- use Proto
- deftype id: one_of([integer(), string()])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/change_annotation.ex b/apps/protocol/lib/generated/lexical/protocol/types/change_annotation.ex
deleted file mode 100644
index f24bbcb4..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/change_annotation.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ChangeAnnotation do
- alias Lexical.Proto
- use Proto
-
- deftype description: optional(string()),
- label: string(),
- needs_confirmation: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/change_annotation/identifier.ex b/apps/protocol/lib/generated/lexical/protocol/types/change_annotation/identifier.ex
deleted file mode 100644
index e97f4ef3..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/change_annotation/identifier.ex
+++ /dev/null
@@ -1,4 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ChangeAnnotation.Identifier do
- @type t :: String.t()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/client_capabilities.ex
deleted file mode 100644
index 44eab9e0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/client_capabilities.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype experimental: optional(any()),
- general: optional(Types.General.ClientCapabilities),
- notebook_document: optional(Types.Notebook.Document.ClientCapabilities),
- text_document: optional(Types.TextDocument.ClientCapabilities),
- window: optional(Types.Window.ClientCapabilities),
- workspace: optional(Types.Workspace.ClientCapabilities)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_action.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_action.ex
deleted file mode 100644
index 1326b6d4..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_action.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeAction do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule Disabled do
- use Proto
- deftype reason: string()
- end
-
- use Proto
-
- deftype command: optional(Types.Command),
- data: optional(any()),
- diagnostics: optional(list_of(Types.Diagnostic)),
- disabled: optional(Lexical.Protocol.Types.CodeAction.Disabled),
- edit: optional(Types.Workspace.Edit),
- is_preferred: optional(boolean()),
- kind: optional(Types.CodeAction.Kind),
- title: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_action/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_action/client_capabilities.ex
deleted file mode 100644
index d5369cb8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_action/client_capabilities.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeAction.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule CodeActionKind do
- use Proto
- deftype value_set: list_of(Types.CodeAction.Kind)
- end
-
- defmodule CodeActionLiteralSupport do
- use Proto
- deftype code_action_kind: Lexical.Protocol.Types.CodeAction.ClientCapabilities.CodeActionKind
- end
-
- defmodule ResolveSupport do
- use Proto
- deftype properties: list_of(string())
- end
-
- use Proto
-
- deftype code_action_literal_support:
- optional(
- Lexical.Protocol.Types.CodeAction.ClientCapabilities.CodeActionLiteralSupport
- ),
- data_support: optional(boolean()),
- disabled_support: optional(boolean()),
- dynamic_registration: optional(boolean()),
- honors_change_annotations: optional(boolean()),
- is_preferred_support: optional(boolean()),
- resolve_support:
- optional(Lexical.Protocol.Types.CodeAction.ClientCapabilities.ResolveSupport)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_action/context.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_action/context.ex
deleted file mode 100644
index a6f2861d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_action/context.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeAction.Context do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype diagnostics: list_of(Types.Diagnostic),
- only: optional(list_of(Types.CodeAction.Kind)),
- trigger_kind: optional(Types.CodeAction.Trigger.Kind)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_action/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_action/kind.ex
deleted file mode 100644
index 610a501b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_action/kind.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeAction.Kind do
- alias Lexical.Proto
- use Proto
-
- defenum empty: "",
- quick_fix: "quickfix",
- refactor: "refactor",
- refactor_extract: "refactor.extract",
- refactor_inline: "refactor.inline",
- refactor_rewrite: "refactor.rewrite",
- source: "source",
- source_organize_imports: "source.organizeImports",
- source_fix_all: "source.fixAll"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_action/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_action/options.ex
deleted file mode 100644
index eb7098e8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_action/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeAction.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype code_action_kinds: optional(list_of(Types.CodeAction.Kind)),
- resolve_provider: optional(boolean()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_action/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_action/params.ex
deleted file mode 100644
index ee680a50..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_action/params.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeAction.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype context: Types.CodeAction.Context,
- partial_result_token: optional(Types.Progress.Token),
- range: Types.Range,
- text_document: Types.TextDocument.Identifier,
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_action/trigger/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_action/trigger/kind.ex
deleted file mode 100644
index 58fda509..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_action/trigger/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeAction.Trigger.Kind do
- alias Lexical.Proto
- use Proto
- defenum invoked: 1, automatic: 2
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_description.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_description.ex
deleted file mode 100644
index 78924f15..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_description.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeDescription do
- alias Lexical.Proto
- use Proto
- deftype href: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_lens.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_lens.ex
deleted file mode 100644
index fb2cff13..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_lens.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeLens do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype command: optional(Types.Command), data: optional(any()), range: Types.Range
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_lens/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_lens/client_capabilities.ex
deleted file mode 100644
index b3f5c19c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_lens/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeLens.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_lens/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_lens/options.ex
deleted file mode 100644
index 648ceac8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_lens/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeLens.Options do
- alias Lexical.Proto
- use Proto
- deftype resolve_provider: optional(boolean()), work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_lens/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_lens/params.ex
deleted file mode 100644
index d2349151..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_lens/params.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeLens.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype partial_result_token: optional(Types.Progress.Token),
- text_document: Types.TextDocument.Identifier,
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/code_lens/workspace/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/code_lens/workspace/client_capabilities.ex
deleted file mode 100644
index f6075c43..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/code_lens/workspace/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CodeLens.Workspace.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype refresh_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/command.ex b/apps/protocol/lib/generated/lexical/protocol/types/command.ex
deleted file mode 100644
index 2afbdf4b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/command.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Command do
- alias Lexical.Proto
- use Proto
- deftype arguments: optional(list_of(any())), command: string(), title: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/client_capabilities.ex
deleted file mode 100644
index f880a8fe..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/client_capabilities.ex
+++ /dev/null
@@ -1,59 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule CompletionItem do
- use Proto
-
- deftype commit_characters_support: optional(boolean()),
- deprecated_support: optional(boolean()),
- documentation_format: optional(list_of(Types.Markup.Kind)),
- insert_replace_support: optional(boolean()),
- insert_text_mode_support:
- optional(Lexical.Protocol.Types.Completion.ClientCapabilities.InsertTextModeSupport),
- label_details_support: optional(boolean()),
- preselect_support: optional(boolean()),
- resolve_support:
- optional(Lexical.Protocol.Types.Completion.ClientCapabilities.ResolveSupport),
- snippet_support: optional(boolean()),
- tag_support: optional(Lexical.Protocol.Types.Completion.ClientCapabilities.TagSupport)
- end
-
- defmodule CompletionItemKind do
- use Proto
- deftype value_set: optional(list_of(Types.Completion.Item.Kind))
- end
-
- defmodule CompletionList do
- use Proto
- deftype item_defaults: optional(list_of(string()))
- end
-
- defmodule InsertTextModeSupport do
- use Proto
- deftype value_set: list_of(Types.InsertTextMode)
- end
-
- defmodule ResolveSupport do
- use Proto
- deftype properties: list_of(string())
- end
-
- defmodule TagSupport do
- use Proto
- deftype value_set: list_of(Types.Completion.Item.Tag)
- end
-
- use Proto
-
- deftype completion_item:
- optional(Lexical.Protocol.Types.Completion.ClientCapabilities.CompletionItem),
- completion_item_kind:
- optional(Lexical.Protocol.Types.Completion.ClientCapabilities.CompletionItemKind),
- completion_list:
- optional(Lexical.Protocol.Types.Completion.ClientCapabilities.CompletionList),
- context_support: optional(boolean()),
- dynamic_registration: optional(boolean()),
- insert_text_mode: optional(Types.InsertTextMode)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/context.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/context.ex
deleted file mode 100644
index 0642dc8e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/context.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.Context do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype trigger_character: optional(string()), trigger_kind: Types.Completion.Trigger.Kind
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/item.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/item.ex
deleted file mode 100644
index c0b17a34..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/item.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.Item do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype additional_text_edits: optional(list_of(Types.TextEdit)),
- command: optional(Types.Command),
- commit_characters: optional(list_of(string())),
- data: optional(any()),
- deprecated: optional(boolean()),
- detail: optional(string()),
- documentation: optional(one_of([string(), Types.Markup.Content])),
- filter_text: optional(string()),
- insert_text: optional(string()),
- insert_text_format: optional(Types.InsertTextFormat),
- insert_text_mode: optional(Types.InsertTextMode),
- kind: optional(Types.Completion.Item.Kind),
- label: string(),
- label_details: optional(Types.Completion.Item.LabelDetails),
- preselect: optional(boolean()),
- sort_text: optional(string()),
- tags: optional(list_of(Types.Completion.Item.Tag)),
- text_edit: optional(one_of([Types.TextEdit, Types.InsertReplaceEdit])),
- text_edit_text: optional(string())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/item/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/item/kind.ex
deleted file mode 100644
index e90f21f0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/item/kind.ex
+++ /dev/null
@@ -1,31 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.Item.Kind do
- alias Lexical.Proto
- use Proto
-
- defenum text: 1,
- method: 2,
- function: 3,
- constructor: 4,
- field: 5,
- variable: 6,
- class: 7,
- interface: 8,
- module: 9,
- property: 10,
- unit: 11,
- value: 12,
- enum: 13,
- keyword: 14,
- snippet: 15,
- color: 16,
- file: 17,
- reference: 18,
- folder: 19,
- enum_member: 20,
- constant: 21,
- struct: 22,
- event: 23,
- operator: 24,
- type_parameter: 25
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/item/label_details.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/item/label_details.ex
deleted file mode 100644
index 64e3213a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/item/label_details.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.Item.LabelDetails do
- alias Lexical.Proto
- use Proto
- deftype description: optional(string()), detail: optional(string())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/item/tag.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/item/tag.ex
deleted file mode 100644
index 19c5d4e4..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/item/tag.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.Item.Tag do
- alias Lexical.Proto
- use Proto
- defenum deprecated: 1
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/list.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/list.ex
deleted file mode 100644
index 7c5076b5..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/list.ex
+++ /dev/null
@@ -1,27 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.List do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule EditRange do
- use Proto
- deftype insert: Types.Range, replace: Types.Range
- end
-
- defmodule ItemDefaults do
- use Proto
-
- deftype commit_characters: optional(list_of(string())),
- data: optional(any()),
- edit_range:
- optional(one_of([Types.Range, Lexical.Protocol.Types.Completion.List.EditRange])),
- insert_text_format: optional(Types.InsertTextFormat),
- insert_text_mode: optional(Types.InsertTextMode)
- end
-
- use Proto
-
- deftype is_incomplete: boolean(),
- item_defaults: optional(Lexical.Protocol.Types.Completion.List.ItemDefaults),
- items: list_of(Types.Completion.Item)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/options.ex
deleted file mode 100644
index 34a51530..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/options.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.Options do
- alias Lexical.Proto
-
- defmodule CompletionItem do
- use Proto
- deftype label_details_support: optional(boolean())
- end
-
- use Proto
-
- deftype all_commit_characters: optional(list_of(string())),
- completion_item: optional(Lexical.Protocol.Types.Completion.Options.CompletionItem),
- resolve_provider: optional(boolean()),
- trigger_characters: optional(list_of(string())),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/params.ex
deleted file mode 100644
index 67d8877b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/params.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype context: optional(Types.Completion.Context),
- partial_result_token: optional(Types.Progress.Token),
- position: Types.Position,
- text_document: Types.TextDocument.Identifier,
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/completion/trigger/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/completion/trigger/kind.ex
deleted file mode 100644
index ccf9698a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/completion/trigger/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Completion.Trigger.Kind do
- alias Lexical.Proto
- use Proto
- defenum invoked: 1, trigger_character: 2, trigger_for_incomplete_completions: 3
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/create_file.ex b/apps/protocol/lib/generated/lexical/protocol/types/create_file.ex
deleted file mode 100644
index 3e356d5f..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/create_file.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CreateFile do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype annotation_id: optional(Types.ChangeAnnotation.Identifier),
- kind: literal("create"),
- options: optional(Types.CreateFile.Options),
- uri: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/create_file/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/create_file/options.ex
deleted file mode 100644
index 301f5d25..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/create_file/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.CreateFile.Options do
- alias Lexical.Proto
- use Proto
- deftype ignore_if_exists: optional(boolean()), overwrite: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/declaration/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/declaration/client_capabilities.ex
deleted file mode 100644
index 022aa914..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/declaration/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Declaration.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean()), link_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/declaration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/declaration/options.ex
deleted file mode 100644
index e343d91f..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/declaration/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Declaration.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/declaration/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/declaration/registration/options.ex
deleted file mode 100644
index c37a6c69..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/declaration/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Declaration.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/definition/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/definition/client_capabilities.ex
deleted file mode 100644
index 080aeb82..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/definition/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Definition.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean()), link_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/definition/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/definition/options.ex
deleted file mode 100644
index 193fe15e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/definition/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Definition.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/definition/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/definition/params.ex
deleted file mode 100644
index 5266d66a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/definition/params.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Definition.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype partial_result_token: optional(Types.Progress.Token),
- position: Types.Position,
- text_document: Types.TextDocument.Identifier,
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/delete_file.ex b/apps/protocol/lib/generated/lexical/protocol/types/delete_file.ex
deleted file mode 100644
index fd0829f8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/delete_file.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DeleteFile do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype annotation_id: optional(Types.ChangeAnnotation.Identifier),
- kind: literal("delete"),
- options: optional(Types.DeleteFile.Options),
- uri: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/delete_file/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/delete_file/options.ex
deleted file mode 100644
index 0ce58919..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/delete_file/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DeleteFile.Options do
- alias Lexical.Proto
- use Proto
- deftype ignore_if_not_exists: optional(boolean()), recursive: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic.ex b/apps/protocol/lib/generated/lexical/protocol/types/diagnostic.ex
deleted file mode 100644
index 14843d83..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Diagnostic do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype code: optional(one_of([integer(), string()])),
- code_description: optional(Types.CodeDescription),
- data: optional(any()),
- message: string(),
- range: Types.Range,
- related_information: optional(list_of(Types.Diagnostic.RelatedInformation)),
- severity: optional(Types.Diagnostic.Severity),
- source: optional(string()),
- tags: optional(list_of(Types.Diagnostic.Tag))
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/client_capabilities.ex
deleted file mode 100644
index 87908c6c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Diagnostic.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean()), related_document_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/options.ex
deleted file mode 100644
index 9f3880c1..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Diagnostic.Options do
- alias Lexical.Proto
- use Proto
-
- deftype identifier: optional(string()),
- inter_file_dependencies: boolean(),
- work_done_progress: optional(boolean()),
- workspace_diagnostics: boolean()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/registration/options.ex
deleted file mode 100644
index 2e0d5a9f..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/registration/options.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Diagnostic.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- identifier: optional(string()),
- inter_file_dependencies: boolean(),
- work_done_progress: optional(boolean()),
- workspace_diagnostics: boolean()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/related_information.ex b/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/related_information.ex
deleted file mode 100644
index 2758b0c9..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/related_information.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Diagnostic.RelatedInformation do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype location: Types.Location, message: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/severity.ex b/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/severity.ex
deleted file mode 100644
index b9634efe..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/severity.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Diagnostic.Severity do
- alias Lexical.Proto
- use Proto
- defenum error: 1, warning: 2, information: 3, hint: 4
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/tag.ex b/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/tag.ex
deleted file mode 100644
index 368427a7..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/tag.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Diagnostic.Tag do
- alias Lexical.Proto
- use Proto
- defenum unnecessary: 1, deprecated: 2
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/workspace/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/workspace/client_capabilities.ex
deleted file mode 100644
index fa7e9706..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/diagnostic/workspace/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Diagnostic.Workspace.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype refresh_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_change_configuration/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_change_configuration/client_capabilities.ex
deleted file mode 100644
index 43c23f20..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_change_configuration/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidChangeConfiguration.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_change_configuration/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_change_configuration/params.ex
deleted file mode 100644
index d31d23bb..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_change_configuration/params.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidChangeConfiguration.Params do
- alias Lexical.Proto
- use Proto
- deftype settings: any()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_change_text_document/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_change_text_document/params.ex
deleted file mode 100644
index 1c4fe6c2..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_change_text_document/params.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidChangeTextDocument.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype content_changes: list_of(Types.TextDocument.ContentChangeEvent),
- text_document: Types.TextDocument.Versioned.Identifier
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/client_capabilities.ex
deleted file mode 100644
index b828b871..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidChangeWatchedFiles.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean()), relative_pattern_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/params.ex
deleted file mode 100644
index 50e7b589..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidChangeWatchedFiles.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype changes: list_of(Types.FileEvent)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/registration/options.ex
deleted file mode 100644
index 2332248c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_change_watched_files/registration/options.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidChangeWatchedFiles.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype watchers: list_of(Types.FileSystemWatcher)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_close_text_document/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_close_text_document/params.ex
deleted file mode 100644
index f5f4d69e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_close_text_document/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidCloseTextDocument.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype text_document: Types.TextDocument.Identifier
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_open_text_document/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_open_text_document/params.ex
deleted file mode 100644
index 6e17ff7b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_open_text_document/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidOpenTextDocument.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype text_document: Types.TextDocument.Item
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/did_save_text_document/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/did_save_text_document/params.ex
deleted file mode 100644
index 2c06117b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/did_save_text_document/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.DidSaveTextDocument.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype text: optional(string()), text_document: Types.TextDocument.Identifier
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/color/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/color/client_capabilities.ex
deleted file mode 100644
index 938d6280..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/color/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Color.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/color/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/color/options.ex
deleted file mode 100644
index 764381b1..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/color/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Color.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/color/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/color/registration/options.ex
deleted file mode 100644
index 31a4ab27..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/color/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Color.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/filter.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/filter.ex
deleted file mode 100644
index 08f917eb..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/filter.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Filter do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- defalias one_of([Types.TextDocument.Filter, Types.Notebook.Cell.TextDocument.Filter])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/client_capabilities.ex
deleted file mode 100644
index 4b1e1b53..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Formatting.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/options.ex
deleted file mode 100644
index 03b48498..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Formatting.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/params.ex
deleted file mode 100644
index 2e567ddb..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/formatting/params.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Formatting.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype options: Types.Formatting.Options,
- text_document: Types.TextDocument.Identifier,
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/highlight/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/highlight/client_capabilities.ex
deleted file mode 100644
index a1560429..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/highlight/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Highlight.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/highlight/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/highlight/options.ex
deleted file mode 100644
index cf54837e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/highlight/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Highlight.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/link/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/link/client_capabilities.ex
deleted file mode 100644
index d9456eec..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/link/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Link.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean()), tooltip_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/link/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/link/options.ex
deleted file mode 100644
index 00616132..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/link/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Link.Options do
- alias Lexical.Proto
- use Proto
- deftype resolve_provider: optional(boolean()), work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/on_type_formatting/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/on_type_formatting/client_capabilities.ex
deleted file mode 100644
index b9b8b04d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/on_type_formatting/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.OnTypeFormatting.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/on_type_formatting/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/on_type_formatting/options.ex
deleted file mode 100644
index cfe4b0a5..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/on_type_formatting/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.OnTypeFormatting.Options do
- alias Lexical.Proto
- use Proto
- deftype first_trigger_character: string(), more_trigger_character: optional(list_of(string()))
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/range_formatting/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/range_formatting/client_capabilities.ex
deleted file mode 100644
index 9aa7466f..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/range_formatting/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.RangeFormatting.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/range_formatting/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/range_formatting/options.ex
deleted file mode 100644
index 7c5fa04e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/range_formatting/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.RangeFormatting.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/selector.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/selector.ex
deleted file mode 100644
index 91dc0f9b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/selector.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Selector do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- defalias list_of(Types.Document.Filter)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/symbol.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/symbol.ex
deleted file mode 100644
index ac39f53d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/symbol.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Symbol do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype children: optional(list_of(Types.Document.Symbol)),
- deprecated: optional(boolean()),
- detail: optional(string()),
- kind: Types.Symbol.Kind,
- name: string(),
- range: Types.Range,
- selection_range: Types.Range,
- tags: optional(list_of(Types.Symbol.Tag))
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/client_capabilities.ex
deleted file mode 100644
index 3467a239..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/client_capabilities.ex
+++ /dev/null
@@ -1,25 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Symbol.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule SymbolKind do
- use Proto
- deftype value_set: optional(list_of(Types.Symbol.Kind))
- end
-
- defmodule TagSupport do
- use Proto
- deftype value_set: list_of(Types.Symbol.Tag)
- end
-
- use Proto
-
- deftype dynamic_registration: optional(boolean()),
- hierarchical_document_symbol_support: optional(boolean()),
- label_support: optional(boolean()),
- symbol_kind:
- optional(Lexical.Protocol.Types.Document.Symbol.ClientCapabilities.SymbolKind),
- tag_support:
- optional(Lexical.Protocol.Types.Document.Symbol.ClientCapabilities.TagSupport)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/options.ex
deleted file mode 100644
index cf3d9601..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Symbol.Options do
- alias Lexical.Proto
- use Proto
- deftype label: optional(string()), work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/params.ex
deleted file mode 100644
index 35f5d65b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/document/symbol/params.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Document.Symbol.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype partial_result_token: optional(Types.Progress.Token),
- text_document: Types.TextDocument.Identifier,
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/execute_command/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/execute_command/client_capabilities.ex
deleted file mode 100644
index e0ce9c1b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/execute_command/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ExecuteCommand.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/execute_command/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/execute_command/options.ex
deleted file mode 100644
index bb14b161..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/execute_command/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ExecuteCommand.Options do
- alias Lexical.Proto
- use Proto
- deftype commands: list_of(string()), work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/execute_command/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/execute_command/params.ex
deleted file mode 100644
index b378f92d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/execute_command/params.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ExecuteCommand.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype arguments: optional(list_of(any())),
- command: string(),
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/execute_command/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/execute_command/registration/options.ex
deleted file mode 100644
index c9df46a0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/execute_command/registration/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ExecuteCommand.Registration.Options do
- alias Lexical.Proto
- use Proto
- deftype commands: list_of(string()), work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/failure_handling/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/failure_handling/kind.ex
deleted file mode 100644
index 7c920f96..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/failure_handling/kind.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FailureHandling.Kind do
- alias Lexical.Proto
- use Proto
-
- defenum abort: "abort",
- transactional: "transactional",
- text_only_transactional: "textOnlyTransactional",
- undo: "undo"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_change_type.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_change_type.ex
deleted file mode 100644
index e9e85e7c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_change_type.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileChangeType do
- alias Lexical.Proto
- use Proto
- defenum created: 1, changed: 2, deleted: 3
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_event.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_event.ex
deleted file mode 100644
index 5c916eea..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_event.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileEvent do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype type: Types.FileChangeType, uri: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_operation/client_capabilities.ex
deleted file mode 100644
index 3844bd49..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/client_capabilities.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileOperation.ClientCapabilities do
- alias Lexical.Proto
- use Proto
-
- deftype did_create: optional(boolean()),
- did_delete: optional(boolean()),
- did_rename: optional(boolean()),
- dynamic_registration: optional(boolean()),
- will_create: optional(boolean()),
- will_delete: optional(boolean()),
- will_rename: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/filter.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_operation/filter.ex
deleted file mode 100644
index b3e6c4e1..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/filter.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileOperation.Filter do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype pattern: Types.FileOperation.Pattern, scheme: optional(string())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_operation/options.ex
deleted file mode 100644
index f732ac2e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/options.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileOperation.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype did_create: optional(Types.FileOperation.Registration.Options),
- did_delete: optional(Types.FileOperation.Registration.Options),
- did_rename: optional(Types.FileOperation.Registration.Options),
- will_create: optional(Types.FileOperation.Registration.Options),
- will_delete: optional(Types.FileOperation.Registration.Options),
- will_rename: optional(Types.FileOperation.Registration.Options)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern.ex
deleted file mode 100644
index 3e64300f..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileOperation.Pattern do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype glob: string(),
- matches: optional(Types.FileOperation.Pattern.Kind),
- options: optional(Types.FileOperation.Pattern.Options)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern/kind.ex
deleted file mode 100644
index 93702aad..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileOperation.Pattern.Kind do
- alias Lexical.Proto
- use Proto
- defenum file: "file", folder: "folder"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern/options.ex
deleted file mode 100644
index 5751dcdc..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/pattern/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileOperation.Pattern.Options do
- alias Lexical.Proto
- use Proto
- deftype ignore_case: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_operation/registration/options.ex
deleted file mode 100644
index eab4d328..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_operation/registration/options.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileOperation.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype filters: list_of(Types.FileOperation.Filter)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/file_system_watcher.ex b/apps/protocol/lib/generated/lexical/protocol/types/file_system_watcher.ex
deleted file mode 100644
index 844e8228..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/file_system_watcher.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FileSystemWatcher do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype glob_pattern: Types.GlobPattern, kind: optional(Types.Watch.Kind)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/folding_range/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/folding_range/client_capabilities.ex
deleted file mode 100644
index e5b30bbf..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/folding_range/client_capabilities.ex
+++ /dev/null
@@ -1,25 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FoldingRange.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule FoldingRange do
- use Proto
- deftype collapsed_text: optional(boolean())
- end
-
- defmodule FoldingRangeKind do
- use Proto
- deftype value_set: optional(list_of(Types.FoldingRange.Kind))
- end
-
- use Proto
-
- deftype dynamic_registration: optional(boolean()),
- folding_range:
- optional(Lexical.Protocol.Types.FoldingRange.ClientCapabilities.FoldingRange),
- folding_range_kind:
- optional(Lexical.Protocol.Types.FoldingRange.ClientCapabilities.FoldingRangeKind),
- line_folding_only: optional(boolean()),
- range_limit: optional(integer())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/folding_range/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/folding_range/kind.ex
deleted file mode 100644
index 430001b0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/folding_range/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FoldingRange.Kind do
- alias Lexical.Proto
- use Proto
- defenum comment: "comment", imports: "imports", region: "region"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/folding_range/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/folding_range/options.ex
deleted file mode 100644
index bfafcf39..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/folding_range/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FoldingRange.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/folding_range/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/folding_range/registration/options.ex
deleted file mode 100644
index c45bfad9..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/folding_range/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.FoldingRange.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/formatting/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/formatting/options.ex
deleted file mode 100644
index a9ca331c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/formatting/options.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Formatting.Options do
- alias Lexical.Proto
- use Proto
-
- deftype insert_final_newline: optional(boolean()),
- insert_spaces: boolean(),
- tab_size: integer(),
- trim_final_newlines: optional(boolean()),
- trim_trailing_whitespace: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/general/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/general/client_capabilities.ex
deleted file mode 100644
index 1378c997..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/general/client_capabilities.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.General.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule StaleRequestSupport do
- use Proto
- deftype cancel: boolean(), retry_on_content_modified: list_of(string())
- end
-
- use Proto
-
- deftype markdown: optional(Types.Markdown.ClientCapabilities),
- position_encodings: optional(list_of(Types.Position.Encoding.Kind)),
- regular_expressions: optional(Types.RegularExpressions.ClientCapabilities),
- stale_request_support:
- optional(Lexical.Protocol.Types.General.ClientCapabilities.StaleRequestSupport)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/glob_pattern.ex b/apps/protocol/lib/generated/lexical/protocol/types/glob_pattern.ex
deleted file mode 100644
index d4699916..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/glob_pattern.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.GlobPattern do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- defalias one_of([Types.Pattern, Types.RelativePattern])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/hover.ex b/apps/protocol/lib/generated/lexical/protocol/types/hover.ex
deleted file mode 100644
index 36b00f0d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/hover.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Hover do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype contents:
- one_of([Types.Markup.Content, Types.MarkedString, list_of(Types.MarkedString)]),
- range: optional(Types.Range)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/hover/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/hover/client_capabilities.ex
deleted file mode 100644
index cef4b84c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/hover/client_capabilities.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Hover.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype content_format: optional(list_of(Types.Markup.Kind)),
- dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/hover/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/hover/options.ex
deleted file mode 100644
index 8836ae5a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/hover/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Hover.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/hover/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/hover/params.ex
deleted file mode 100644
index 2b7827a3..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/hover/params.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Hover.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype position: Types.Position,
- text_document: Types.TextDocument.Identifier,
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/implementation/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/implementation/client_capabilities.ex
deleted file mode 100644
index 04edf91c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/implementation/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Implementation.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean()), link_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/implementation/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/implementation/options.ex
deleted file mode 100644
index e691b1a3..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/implementation/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Implementation.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/implementation/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/implementation/registration/options.ex
deleted file mode 100644
index 676bd977..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/implementation/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Implementation.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/initialize/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/initialize/params.ex
deleted file mode 100644
index ac1d66e8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/initialize/params.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Initialize.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule ClientInfo1 do
- use Proto
- deftype name: string(), version: optional(string())
- end
-
- use Proto
-
- deftype capabilities: Types.ClientCapabilities,
- client_info: optional(Lexical.Protocol.Types.Initialize.Params.ClientInfo1),
- initialization_options: optional(any()),
- locale: optional(string()),
- process_id: one_of([integer(), nil]),
- root_path: optional(one_of([string(), nil])),
- root_uri: one_of([string(), nil]),
- trace:
- optional(
- one_of([literal("off"), literal("messages"), literal("compact"), literal("verbose")])
- ),
- work_done_token: optional(Types.Progress.Token),
- workspace_folders: optional(one_of([list_of(Types.Workspace.Folder), nil]))
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/initialize/result.ex b/apps/protocol/lib/generated/lexical/protocol/types/initialize/result.ex
deleted file mode 100644
index fcad0bf8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/initialize/result.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Initialize.Result do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule ServerInfo do
- use Proto
- deftype name: string(), version: optional(string())
- end
-
- use Proto
-
- deftype capabilities: Types.ServerCapabilities,
- server_info: optional(Lexical.Protocol.Types.Initialize.Result.ServerInfo)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/client_capabilities.ex
deleted file mode 100644
index 9eb2384d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/client_capabilities.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InlayHint.ClientCapabilities do
- alias Lexical.Proto
-
- defmodule ResolveSupport do
- use Proto
- deftype properties: list_of(string())
- end
-
- use Proto
-
- deftype dynamic_registration: optional(boolean()),
- resolve_support:
- optional(Lexical.Protocol.Types.InlayHint.ClientCapabilities.ResolveSupport)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/options.ex
deleted file mode 100644
index d7fb8cfc..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InlayHint.Options do
- alias Lexical.Proto
- use Proto
- deftype resolve_provider: optional(boolean()), work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/registration/options.ex
deleted file mode 100644
index 075d1640..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint/registration/options.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InlayHint.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- resolve_provider: optional(boolean()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint_workspace/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint_workspace/client_capabilities.ex
deleted file mode 100644
index 52dab8b9..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/inlay_hint_workspace/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InlayHintWorkspace.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype refresh_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/inline_value/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/inline_value/client_capabilities.ex
deleted file mode 100644
index 59be7e80..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/inline_value/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InlineValue.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/inline_value/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/inline_value/options.ex
deleted file mode 100644
index f15b7486..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/inline_value/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InlineValue.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/inline_value/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/inline_value/registration/options.ex
deleted file mode 100644
index ae95d3a1..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/inline_value/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InlineValue.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/inline_value/workspace/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/inline_value/workspace/client_capabilities.ex
deleted file mode 100644
index 0af9094d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/inline_value/workspace/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InlineValue.Workspace.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype refresh_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/insert_replace_edit.ex b/apps/protocol/lib/generated/lexical/protocol/types/insert_replace_edit.ex
deleted file mode 100644
index 609db544..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/insert_replace_edit.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InsertReplaceEdit do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype insert: Types.Range, new_text: string(), replace: Types.Range
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/insert_text_format.ex b/apps/protocol/lib/generated/lexical/protocol/types/insert_text_format.ex
deleted file mode 100644
index 361be738..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/insert_text_format.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InsertTextFormat do
- alias Lexical.Proto
- use Proto
- defenum plain_text: 1, snippet: 2
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/insert_text_mode.ex b/apps/protocol/lib/generated/lexical/protocol/types/insert_text_mode.ex
deleted file mode 100644
index 67e15e06..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/insert_text_mode.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.InsertTextMode do
- alias Lexical.Proto
- use Proto
- defenum as_is: 1, adjust_indentation: 2
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/client_capabilities.ex
deleted file mode 100644
index e5075c78..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.LinkedEditingRange.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/options.ex
deleted file mode 100644
index e70a7cf5..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.LinkedEditingRange.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/registration/options.ex
deleted file mode 100644
index 2b0e0d62..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/linked_editing_range/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.LinkedEditingRange.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/location.ex b/apps/protocol/lib/generated/lexical/protocol/types/location.ex
deleted file mode 100644
index 52df5251..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/location.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Location do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype range: Types.Range, uri: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/location/link.ex b/apps/protocol/lib/generated/lexical/protocol/types/location/link.ex
deleted file mode 100644
index 5472c56a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/location/link.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Location.Link do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype origin_selection_range: optional(Types.Range),
- target_range: Types.Range,
- target_selection_range: Types.Range,
- target_uri: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/log_message/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/log_message/params.ex
deleted file mode 100644
index 466b99ee..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/log_message/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.LogMessage.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype message: string(), type: Types.Message.Type
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/lsp_object.ex b/apps/protocol/lib/generated/lexical/protocol/types/lsp_object.ex
deleted file mode 100644
index 01261f0e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/lsp_object.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.LSPObject do
- alias Lexical.Proto
- use Proto
- deftype []
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/markdown/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/markdown/client_capabilities.ex
deleted file mode 100644
index 788157e7..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/markdown/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Markdown.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype allowed_tags: optional(list_of(string())), parser: string(), version: optional(string())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/marked_string.ex b/apps/protocol/lib/generated/lexical/protocol/types/marked_string.ex
deleted file mode 100644
index b223e5ca..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/marked_string.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.MarkedString do
- alias Lexical.Proto
-
- defmodule MarkedString do
- use Proto
- deftype language: string(), value: string()
- end
-
- use Proto
- defalias one_of([string(), Lexical.Protocol.Types.MarkedString.MarkedString])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/markup/content.ex b/apps/protocol/lib/generated/lexical/protocol/types/markup/content.ex
deleted file mode 100644
index 0cb1272e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/markup/content.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Markup.Content do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype kind: Types.Markup.Kind, value: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/markup/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/markup/kind.ex
deleted file mode 100644
index 08597716..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/markup/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Markup.Kind do
- alias Lexical.Proto
- use Proto
- defenum plain_text: "plaintext", markdown: "markdown"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/message/action_item.ex b/apps/protocol/lib/generated/lexical/protocol/types/message/action_item.ex
deleted file mode 100644
index 21331da8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/message/action_item.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Message.ActionItem do
- alias Lexical.Proto
- use Proto
- deftype title: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/message/type.ex b/apps/protocol/lib/generated/lexical/protocol/types/message/type.ex
deleted file mode 100644
index e3a791c0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/message/type.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Message.Type do
- alias Lexical.Proto
- use Proto
- defenum error: 1, warning: 2, info: 3, log: 4
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/moniker/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/moniker/client_capabilities.ex
deleted file mode 100644
index 224cde61..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/moniker/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Moniker.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/moniker/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/moniker/options.ex
deleted file mode 100644
index e1a4bb32..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/moniker/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Moniker.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/moniker/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/moniker/registration/options.ex
deleted file mode 100644
index a7785274..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/moniker/registration/options.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Moniker.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/notebook/cell/text_document/filter.ex b/apps/protocol/lib/generated/lexical/protocol/types/notebook/cell/text_document/filter.ex
deleted file mode 100644
index e77dab90..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/notebook/cell/text_document/filter.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Notebook.Cell.TextDocument.Filter do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype language: optional(string()),
- notebook: one_of([string(), Types.Notebook.Document.Filter])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/client_capabilities.ex
deleted file mode 100644
index b397338c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/client_capabilities.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Notebook.Document.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype synchronization: Types.Notebook.Document.Sync.ClientCapabilities
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/filter.ex b/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/filter.ex
deleted file mode 100644
index b24dcbf9..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/filter.ex
+++ /dev/null
@@ -1,27 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Notebook.Document.Filter do
- alias Lexical.Proto
-
- defmodule NotebookDocumentFilter do
- use Proto
- deftype notebook_type: string(), pattern: optional(string()), scheme: optional(string())
- end
-
- defmodule NotebookDocumentFilter1 do
- use Proto
- deftype notebook_type: optional(string()), pattern: optional(string()), scheme: string()
- end
-
- defmodule NotebookDocumentFilter2 do
- use Proto
- deftype notebook_type: optional(string()), pattern: string(), scheme: optional(string())
- end
-
- use Proto
-
- defalias one_of([
- Lexical.Protocol.Types.Notebook.Document.Filter.NotebookDocumentFilter,
- Lexical.Protocol.Types.Notebook.Document.Filter.NotebookDocumentFilter1,
- Lexical.Protocol.Types.Notebook.Document.Filter.NotebookDocumentFilter2
- ])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/client_capabilities.ex
deleted file mode 100644
index 4f3c9dad..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/client_capabilities.ex
+++ /dev/null
@@ -1,8 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Notebook.Document.Sync.ClientCapabilities do
- alias Lexical.Proto
- use Proto
-
- deftype dynamic_registration: optional(boolean()),
- execution_summary_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/options.ex
deleted file mode 100644
index cf3e7eba..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/options.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Notebook.Document.Sync.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule Cells do
- use Proto
- deftype language: string()
- end
-
- defmodule Cells1 do
- use Proto
- deftype language: string()
- end
-
- defmodule NotebookSelector do
- use Proto
-
- deftype cells: optional(list_of(Lexical.Protocol.Types.Notebook.Document.Sync.Options.Cells)),
- notebook: one_of([string(), Types.Notebook.Document.Filter])
- end
-
- defmodule NotebookSelector1 do
- use Proto
-
- deftype cells: list_of(Lexical.Protocol.Types.Notebook.Document.Sync.Options.Cells1),
- notebook: optional(one_of([string(), Types.Notebook.Document.Filter]))
- end
-
- use Proto
-
- deftype notebook_selector:
- list_of(
- one_of([
- Lexical.Protocol.Types.Notebook.Document.Sync.Options.NotebookSelector,
- Lexical.Protocol.Types.Notebook.Document.Sync.Options.NotebookSelector1
- ])
- ),
- save: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/registration/options.ex
deleted file mode 100644
index 6cae4f8a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/notebook/document/sync/registration/options.ex
+++ /dev/null
@@ -1,45 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Notebook.Document.Sync.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule Cells2 do
- use Proto
- deftype language: string()
- end
-
- defmodule Cells3 do
- use Proto
- deftype language: string()
- end
-
- defmodule NotebookSelector2 do
- use Proto
-
- deftype cells:
- optional(
- list_of(Lexical.Protocol.Types.Notebook.Document.Sync.Registration.Options.Cells2)
- ),
- notebook: one_of([string(), Types.Notebook.Document.Filter])
- end
-
- defmodule NotebookSelector3 do
- use Proto
-
- deftype cells:
- list_of(Lexical.Protocol.Types.Notebook.Document.Sync.Registration.Options.Cells3),
- notebook: optional(one_of([string(), Types.Notebook.Document.Filter]))
- end
-
- use Proto
-
- deftype id: optional(string()),
- notebook_selector:
- list_of(
- one_of([
- Lexical.Protocol.Types.Notebook.Document.Sync.Registration.Options.NotebookSelector2,
- Lexical.Protocol.Types.Notebook.Document.Sync.Registration.Options.NotebookSelector3
- ])
- ),
- save: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/pattern.ex b/apps/protocol/lib/generated/lexical/protocol/types/pattern.ex
deleted file mode 100644
index 126e359d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/pattern.ex
+++ /dev/null
@@ -1,4 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Pattern do
- @type t :: String.t()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/position.ex b/apps/protocol/lib/generated/lexical/protocol/types/position.ex
deleted file mode 100644
index c7e7b310..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/position.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Position do
- alias Lexical.Proto
- use Proto
- deftype character: integer(), line: integer()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/position/encoding/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/position/encoding/kind.ex
deleted file mode 100644
index bfcf5045..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/position/encoding/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Position.Encoding.Kind do
- alias Lexical.Proto
- use Proto
- defenum utf8: "utf-8", utf16: "utf-16", utf32: "utf-32"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/prepare_support_default_behavior.ex b/apps/protocol/lib/generated/lexical/protocol/types/prepare_support_default_behavior.ex
deleted file mode 100644
index 2692f46a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/prepare_support_default_behavior.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.PrepareSupportDefaultBehavior do
- alias Lexical.Proto
- use Proto
- defenum identifier: 1
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/progress/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/progress/params.ex
deleted file mode 100644
index 14cecd69..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/progress/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Progress.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype token: Types.Progress.Token, value: any()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/progress/token.ex b/apps/protocol/lib/generated/lexical/protocol/types/progress/token.ex
deleted file mode 100644
index 641828f5..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/progress/token.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Progress.Token do
- alias Lexical.Proto
- use Proto
- defalias one_of([integer(), string()])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/publish_diagnostics/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/publish_diagnostics/client_capabilities.ex
deleted file mode 100644
index 43c94ba4..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/publish_diagnostics/client_capabilities.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.PublishDiagnostics.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule TagSupport do
- use Proto
- deftype value_set: list_of(Types.Diagnostic.Tag)
- end
-
- use Proto
-
- deftype code_description_support: optional(boolean()),
- data_support: optional(boolean()),
- related_information: optional(boolean()),
- tag_support:
- optional(Lexical.Protocol.Types.PublishDiagnostics.ClientCapabilities.TagSupport),
- version_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/publish_diagnostics/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/publish_diagnostics/params.ex
deleted file mode 100644
index 02f99a40..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/publish_diagnostics/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.PublishDiagnostics.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype diagnostics: list_of(Types.Diagnostic), uri: string(), version: optional(integer())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/range.ex b/apps/protocol/lib/generated/lexical/protocol/types/range.ex
deleted file mode 100644
index d808c15c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/range.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Range do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype end: Types.Position, start: Types.Position
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/reference/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/reference/client_capabilities.ex
deleted file mode 100644
index dfbc9156..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/reference/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Reference.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/reference/context.ex b/apps/protocol/lib/generated/lexical/protocol/types/reference/context.ex
deleted file mode 100644
index 070cdb00..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/reference/context.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Reference.Context do
- alias Lexical.Proto
- use Proto
- deftype include_declaration: boolean()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/reference/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/reference/options.ex
deleted file mode 100644
index 5d60fe29..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/reference/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Reference.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/reference/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/reference/params.ex
deleted file mode 100644
index 6125d763..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/reference/params.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Reference.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype context: Types.Reference.Context,
- partial_result_token: optional(Types.Progress.Token),
- position: Types.Position,
- text_document: Types.TextDocument.Identifier,
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/registration.ex b/apps/protocol/lib/generated/lexical/protocol/types/registration.ex
deleted file mode 100644
index 17c6442a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/registration.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Registration do
- alias Lexical.Proto
- use Proto
- deftype id: string(), method: string(), register_options: optional(any())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/registration/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/registration/params.ex
deleted file mode 100644
index c7eab2e3..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/registration/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Registration.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype registrations: list_of(Types.Registration)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/regular_expressions/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/regular_expressions/client_capabilities.ex
deleted file mode 100644
index 99f134df..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/regular_expressions/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.RegularExpressions.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype engine: string(), version: optional(string())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/relative_pattern.ex b/apps/protocol/lib/generated/lexical/protocol/types/relative_pattern.ex
deleted file mode 100644
index 83257e93..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/relative_pattern.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.RelativePattern do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype base_uri: one_of([Types.Workspace.Folder, string()]), pattern: Types.Pattern
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/rename/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/rename/client_capabilities.ex
deleted file mode 100644
index 8a89a2c7..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/rename/client_capabilities.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Rename.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype dynamic_registration: optional(boolean()),
- honors_change_annotations: optional(boolean()),
- prepare_support: optional(boolean()),
- prepare_support_default_behavior: optional(Types.PrepareSupportDefaultBehavior)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/rename/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/rename/options.ex
deleted file mode 100644
index 13c416d0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/rename/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Rename.Options do
- alias Lexical.Proto
- use Proto
- deftype prepare_provider: optional(boolean()), work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/rename_file.ex b/apps/protocol/lib/generated/lexical/protocol/types/rename_file.ex
deleted file mode 100644
index 40a179c7..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/rename_file.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.RenameFile do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype annotation_id: optional(Types.ChangeAnnotation.Identifier),
- kind: literal("rename"),
- new_uri: string(),
- old_uri: string(),
- options: optional(Types.RenameFile.Options)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/rename_file/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/rename_file/options.ex
deleted file mode 100644
index a1f369da..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/rename_file/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.RenameFile.Options do
- alias Lexical.Proto
- use Proto
- deftype ignore_if_exists: optional(boolean()), overwrite: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/resource_operation/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/resource_operation/kind.ex
deleted file mode 100644
index 82e88371..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/resource_operation/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ResourceOperation.Kind do
- alias Lexical.Proto
- use Proto
- defenum create: "create", rename: "rename", delete: "delete"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/save/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/save/options.ex
deleted file mode 100644
index 3a7751d7..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/save/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Save.Options do
- alias Lexical.Proto
- use Proto
- deftype include_text: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/selection_range/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/selection_range/client_capabilities.ex
deleted file mode 100644
index 74d4e3c4..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/selection_range/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SelectionRange.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/selection_range/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/selection_range/options.ex
deleted file mode 100644
index fb73ff03..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/selection_range/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SelectionRange.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/selection_range/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/selection_range/registration/options.ex
deleted file mode 100644
index 2ed5b687..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/selection_range/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SelectionRange.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/client_capabilities.ex
deleted file mode 100644
index 6be4c38e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/client_capabilities.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SemanticTokens.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule Full do
- use Proto
- deftype delta: optional(boolean())
- end
-
- defmodule Range do
- use Proto
- deftype []
- end
-
- defmodule Requests do
- use Proto
-
- deftype full:
- optional(
- one_of([boolean(), Lexical.Protocol.Types.SemanticTokens.ClientCapabilities.Full])
- ),
- range:
- optional(
- one_of([boolean(), Lexical.Protocol.Types.SemanticTokens.ClientCapabilities.Range])
- )
- end
-
- use Proto
-
- deftype augments_syntax_tokens: optional(boolean()),
- dynamic_registration: optional(boolean()),
- formats: list_of(Types.TokenFormat),
- multiline_token_support: optional(boolean()),
- overlapping_token_support: optional(boolean()),
- requests: Lexical.Protocol.Types.SemanticTokens.ClientCapabilities.Requests,
- server_cancel_support: optional(boolean()),
- token_modifiers: list_of(string()),
- token_types: list_of(string())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/legend.ex b/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/legend.ex
deleted file mode 100644
index 02777812..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/legend.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SemanticTokens.Legend do
- alias Lexical.Proto
- use Proto
- deftype token_modifiers: list_of(string()), token_types: list_of(string())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/options.ex
deleted file mode 100644
index 9202051f..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/options.ex
+++ /dev/null
@@ -1,23 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SemanticTokens.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule Full do
- use Proto
- deftype delta: optional(boolean())
- end
-
- defmodule Range do
- use Proto
- deftype []
- end
-
- use Proto
-
- deftype full: optional(one_of([boolean(), Lexical.Protocol.Types.SemanticTokens.Options.Full])),
- legend: Types.SemanticTokens.Legend,
- range:
- optional(one_of([boolean(), Lexical.Protocol.Types.SemanticTokens.Options.Range])),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/registration/options.ex
deleted file mode 100644
index 66563f3e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/registration/options.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SemanticTokens.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule Full1 do
- use Proto
- deftype delta: optional(boolean())
- end
-
- defmodule Range1 do
- use Proto
- deftype []
- end
-
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- full:
- optional(
- one_of([boolean(), Lexical.Protocol.Types.SemanticTokens.Registration.Options.Full1])
- ),
- id: optional(string()),
- legend: Types.SemanticTokens.Legend,
- range:
- optional(
- one_of([
- boolean(),
- Lexical.Protocol.Types.SemanticTokens.Registration.Options.Range1
- ])
- ),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/workspace/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/workspace/client_capabilities.ex
deleted file mode 100644
index 02791fe8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/semantic_tokens/workspace/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SemanticTokens.Workspace.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype refresh_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/show_document/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/show_document/client_capabilities.ex
deleted file mode 100644
index 60f05bec..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/show_document/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ShowDocument.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype support: boolean()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/show_message/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/show_message/params.ex
deleted file mode 100644
index b7c9cd1a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/show_message/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ShowMessage.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype message: string(), type: Types.Message.Type
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/show_message_request/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/show_message_request/client_capabilities.ex
deleted file mode 100644
index 8c5544b3..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/show_message_request/client_capabilities.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ShowMessageRequest.ClientCapabilities do
- alias Lexical.Proto
-
- defmodule MessageActionItem do
- use Proto
- deftype additional_properties_support: optional(boolean())
- end
-
- use Proto
-
- deftype message_action_item:
- optional(
- Lexical.Protocol.Types.ShowMessageRequest.ClientCapabilities.MessageActionItem
- )
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/show_message_request/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/show_message_request/params.ex
deleted file mode 100644
index 191ae489..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/show_message_request/params.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.ShowMessageRequest.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype actions: optional(list_of(Types.Message.ActionItem)),
- message: string(),
- type: Types.Message.Type
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/signature_help/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/signature_help/client_capabilities.ex
deleted file mode 100644
index fbee0ccd..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/signature_help/client_capabilities.ex
+++ /dev/null
@@ -1,28 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SignatureHelp.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule ParameterInformation do
- use Proto
- deftype label_offset_support: optional(boolean())
- end
-
- defmodule SignatureInformation do
- use Proto
-
- deftype active_parameter_support: optional(boolean()),
- documentation_format: optional(list_of(Types.Markup.Kind)),
- parameter_information:
- optional(
- Lexical.Protocol.Types.SignatureHelp.ClientCapabilities.ParameterInformation
- )
- end
-
- use Proto
-
- deftype context_support: optional(boolean()),
- dynamic_registration: optional(boolean()),
- signature_information:
- optional(Lexical.Protocol.Types.SignatureHelp.ClientCapabilities.SignatureInformation)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/signature_help/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/signature_help/options.ex
deleted file mode 100644
index cd61fcb0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/signature_help/options.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.SignatureHelp.Options do
- alias Lexical.Proto
- use Proto
-
- deftype retrigger_characters: optional(list_of(string())),
- trigger_characters: optional(list_of(string())),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/symbol/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/symbol/kind.ex
deleted file mode 100644
index bfd82a51..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/symbol/kind.ex
+++ /dev/null
@@ -1,32 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Symbol.Kind do
- alias Lexical.Proto
- use Proto
-
- defenum file: 1,
- module: 2,
- namespace: 3,
- package: 4,
- class: 5,
- method: 6,
- property: 7,
- field: 8,
- constructor: 9,
- enum: 10,
- interface: 11,
- function: 12,
- variable: 13,
- constant: 14,
- string: 15,
- number: 16,
- boolean: 17,
- array: 18,
- object: 19,
- key: 20,
- null: 21,
- enum_member: 22,
- struct: 23,
- event: 24,
- operator: 25,
- type_parameter: 26
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/symbol/tag.ex b/apps/protocol/lib/generated/lexical/protocol/types/symbol/tag.ex
deleted file mode 100644
index 2be1d71d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/symbol/tag.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Symbol.Tag do
- alias Lexical.Proto
- use Proto
- defenum deprecated: 1
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/client_capabilities.ex
deleted file mode 100644
index d7129c26..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/client_capabilities.ex
+++ /dev/null
@@ -1,37 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype call_hierarchy: optional(Types.CallHierarchy.ClientCapabilities),
- code_action: optional(Types.CodeAction.ClientCapabilities),
- code_lens: optional(Types.CodeLens.ClientCapabilities),
- color_provider: optional(Types.Document.Color.ClientCapabilities),
- completion: optional(Types.Completion.ClientCapabilities),
- declaration: optional(Types.Declaration.ClientCapabilities),
- definition: optional(Types.Definition.ClientCapabilities),
- diagnostic: optional(Types.Diagnostic.ClientCapabilities),
- document_highlight: optional(Types.Document.Highlight.ClientCapabilities),
- document_link: optional(Types.Document.Link.ClientCapabilities),
- document_symbol: optional(Types.Document.Symbol.ClientCapabilities),
- folding_range: optional(Types.FoldingRange.ClientCapabilities),
- formatting: optional(Types.Document.Formatting.ClientCapabilities),
- hover: optional(Types.Hover.ClientCapabilities),
- implementation: optional(Types.Implementation.ClientCapabilities),
- inlay_hint: optional(Types.InlayHint.ClientCapabilities),
- inline_value: optional(Types.InlineValue.ClientCapabilities),
- linked_editing_range: optional(Types.LinkedEditingRange.ClientCapabilities),
- moniker: optional(Types.Moniker.ClientCapabilities),
- on_type_formatting: optional(Types.Document.OnTypeFormatting.ClientCapabilities),
- publish_diagnostics: optional(Types.PublishDiagnostics.ClientCapabilities),
- range_formatting: optional(Types.Document.RangeFormatting.ClientCapabilities),
- references: optional(Types.Reference.ClientCapabilities),
- rename: optional(Types.Rename.ClientCapabilities),
- selection_range: optional(Types.SelectionRange.ClientCapabilities),
- semantic_tokens: optional(Types.SemanticTokens.ClientCapabilities),
- signature_help: optional(Types.SignatureHelp.ClientCapabilities),
- synchronization: optional(Types.TextDocument.Sync.ClientCapabilities),
- type_definition: optional(Types.TypeDefinition.ClientCapabilities),
- type_hierarchy: optional(Types.TypeHierarchy.ClientCapabilities)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/content_change_event.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/content_change_event.ex
deleted file mode 100644
index 5abd1fc9..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/content_change_event.ex
+++ /dev/null
@@ -1,22 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.ContentChangeEvent do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule TextDocumentContentChangeEvent do
- use Proto
- deftype range: Types.Range, range_length: optional(integer()), text: string()
- end
-
- defmodule TextDocumentContentChangeEvent1 do
- use Proto
- deftype text: string()
- end
-
- use Proto
-
- defalias one_of([
- Lexical.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent,
- Lexical.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent1
- ])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/edit.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/edit.ex
deleted file mode 100644
index 68e9933e..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/edit.ex
+++ /dev/null
@@ -1,9 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.Edit do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype edits: list_of(one_of([Types.TextEdit, Types.TextEdit.Annotated])),
- text_document: Types.TextDocument.OptionalVersioned.Identifier
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/filter.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/filter.ex
deleted file mode 100644
index 3acfaf93..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/filter.ex
+++ /dev/null
@@ -1,27 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.Filter do
- alias Lexical.Proto
-
- defmodule TextDocumentFilter do
- use Proto
- deftype language: string(), pattern: optional(string()), scheme: optional(string())
- end
-
- defmodule TextDocumentFilter1 do
- use Proto
- deftype language: optional(string()), pattern: optional(string()), scheme: string()
- end
-
- defmodule TextDocumentFilter2 do
- use Proto
- deftype language: optional(string()), pattern: string(), scheme: optional(string())
- end
-
- use Proto
-
- defalias one_of([
- Lexical.Protocol.Types.TextDocument.Filter.TextDocumentFilter,
- Lexical.Protocol.Types.TextDocument.Filter.TextDocumentFilter1,
- Lexical.Protocol.Types.TextDocument.Filter.TextDocumentFilter2
- ])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/identifier.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/identifier.ex
deleted file mode 100644
index 031f7c1c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/identifier.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.Identifier do
- alias Lexical.Proto
- use Proto
- deftype uri: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/item.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/item.ex
deleted file mode 100644
index ad6ee9ae..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/item.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.Item do
- alias Lexical.Proto
- use Proto
- deftype language_id: string(), text: string(), uri: string(), version: integer()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/optional_versioned/identifier.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/optional_versioned/identifier.ex
deleted file mode 100644
index 1ac0d95d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/optional_versioned/identifier.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.OptionalVersioned.Identifier do
- alias Lexical.Proto
- use Proto
- deftype uri: string(), version: one_of([integer(), nil])
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/client_capabilities.ex
deleted file mode 100644
index 7c4a3eb3..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/client_capabilities.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.Sync.ClientCapabilities do
- alias Lexical.Proto
- use Proto
-
- deftype did_save: optional(boolean()),
- dynamic_registration: optional(boolean()),
- will_save: optional(boolean()),
- will_save_wait_until: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/kind.ex
deleted file mode 100644
index aa2f6e4d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.Sync.Kind do
- alias Lexical.Proto
- use Proto
- defenum none: 0, full: 1, incremental: 2
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/options.ex
deleted file mode 100644
index ffe35773..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/sync/options.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.Sync.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype change: optional(Types.TextDocument.Sync.Kind),
- open_close: optional(boolean()),
- save: optional(one_of([boolean(), Types.Save.Options])),
- will_save: optional(boolean()),
- will_save_wait_until: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_document/versioned/identifier.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_document/versioned/identifier.ex
deleted file mode 100644
index abeee4e5..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_document/versioned/identifier.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextDocument.Versioned.Identifier do
- alias Lexical.Proto
- use Proto
- deftype uri: string(), version: integer()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_edit.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_edit.ex
deleted file mode 100644
index 1c586b18..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_edit.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextEdit do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype new_text: string(), range: Types.Range
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/text_edit/annotated.ex b/apps/protocol/lib/generated/lexical/protocol/types/text_edit/annotated.ex
deleted file mode 100644
index 73ea069a..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/text_edit/annotated.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TextEdit.Annotated do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype annotation_id: Types.ChangeAnnotation.Identifier, new_text: string(), range: Types.Range
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/token_format.ex b/apps/protocol/lib/generated/lexical/protocol/types/token_format.ex
deleted file mode 100644
index a4024144..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/token_format.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TokenFormat do
- alias Lexical.Proto
- use Proto
- defenum relative: "relative"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/trace_values.ex b/apps/protocol/lib/generated/lexical/protocol/types/trace_values.ex
deleted file mode 100644
index 5f4d721b..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/trace_values.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TraceValues do
- alias Lexical.Proto
- use Proto
- defenum off: "off", messages: "messages", verbose: "verbose"
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/type_definition/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/type_definition/client_capabilities.ex
deleted file mode 100644
index 558126e0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/type_definition/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TypeDefinition.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean()), link_support: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/type_definition/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/type_definition/options.ex
deleted file mode 100644
index 3af0d7fd..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/type_definition/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TypeDefinition.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/type_definition/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/type_definition/registration/options.ex
deleted file mode 100644
index 448ed520..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/type_definition/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TypeDefinition.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/client_capabilities.ex
deleted file mode 100644
index 7f913f20..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/client_capabilities.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TypeHierarchy.ClientCapabilities do
- alias Lexical.Proto
- use Proto
- deftype dynamic_registration: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/options.ex
deleted file mode 100644
index cbb70e29..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TypeHierarchy.Options do
- alias Lexical.Proto
- use Proto
- deftype work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/registration/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/registration/options.ex
deleted file mode 100644
index da2b498d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/type_hierarchy/registration/options.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.TypeHierarchy.Registration.Options do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype document_selector: one_of([Types.Document.Selector, nil]),
- id: optional(string()),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/watch/kind.ex b/apps/protocol/lib/generated/lexical/protocol/types/watch/kind.ex
deleted file mode 100644
index 429d7886..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/watch/kind.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Watch.Kind do
- alias Lexical.Proto
- use Proto
- defenum create: 1, change: 2, delete: 4
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/window/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/window/client_capabilities.ex
deleted file mode 100644
index 793c695d..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/window/client_capabilities.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Window.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype show_document: optional(Types.ShowDocument.ClientCapabilities),
- show_message: optional(Types.ShowMessageRequest.ClientCapabilities),
- work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/create/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/create/params.ex
deleted file mode 100644
index 07e51dfb..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/create/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.WorkDone.Progress.Create.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype token: Types.Progress.Token
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/end.ex b/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/end.ex
deleted file mode 100644
index 5a087912..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/end.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.WorkDone.Progress.End do
- alias Lexical.Proto
- use Proto
- deftype kind: literal("end"), message: optional(string())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/params.ex
deleted file mode 100644
index 576ebbc8..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/params.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.WorkDone.Progress.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
- deftype work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/report.ex b/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/report.ex
deleted file mode 100644
index cd584b98..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/work_done/progress/report.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.WorkDone.Progress.Report do
- alias Lexical.Proto
- use Proto
-
- deftype cancellable: optional(boolean()),
- kind: literal("report"),
- message: optional(string()),
- percentage: optional(integer())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/client_capabilities.ex
deleted file mode 100644
index 4e9ac193..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/client_capabilities.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype apply_edit: optional(boolean()),
- code_lens: optional(Types.CodeLens.Workspace.ClientCapabilities),
- configuration: optional(boolean()),
- diagnostics: optional(Types.Diagnostic.Workspace.ClientCapabilities),
- did_change_configuration: optional(Types.DidChangeConfiguration.ClientCapabilities),
- did_change_watched_files: optional(Types.DidChangeWatchedFiles.ClientCapabilities),
- execute_command: optional(Types.ExecuteCommand.ClientCapabilities),
- file_operations: optional(Types.FileOperation.ClientCapabilities),
- inlay_hint: optional(Types.InlayHintWorkspace.ClientCapabilities),
- inline_value: optional(Types.InlineValue.Workspace.ClientCapabilities),
- semantic_tokens: optional(Types.SemanticTokens.Workspace.ClientCapabilities),
- symbol: optional(Types.Workspace.Symbol.ClientCapabilities),
- workspace_edit: optional(Types.Workspace.Edit.ClientCapabilities),
- workspace_folders: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/edit.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/edit.ex
deleted file mode 100644
index 291ccdf0..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/edit.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.Edit do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype change_annotations: optional(map_of(Types.ChangeAnnotation)),
- changes: optional(map_of(list_of(Types.TextEdit))),
- document_changes:
- optional(
- list_of(
- one_of([
- Types.TextDocument.Edit,
- Types.CreateFile,
- Types.RenameFile,
- Types.DeleteFile
- ])
- )
- )
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/edit/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/edit/client_capabilities.ex
deleted file mode 100644
index 1e53f144..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/edit/client_capabilities.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.Edit.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule ChangeAnnotationSupport do
- use Proto
- deftype groups_on_label: optional(boolean())
- end
-
- use Proto
-
- deftype change_annotation_support:
- optional(
- Lexical.Protocol.Types.Workspace.Edit.ClientCapabilities.ChangeAnnotationSupport
- ),
- document_changes: optional(boolean()),
- failure_handling: optional(Types.FailureHandling.Kind),
- normalizes_line_endings: optional(boolean()),
- resource_operations: optional(list_of(Types.ResourceOperation.Kind))
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/folder.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/folder.ex
deleted file mode 100644
index ee84e8db..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/folder.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.Folder do
- alias Lexical.Proto
- use Proto
- deftype name: string(), uri: string()
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/folders_server_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/folders_server_capabilities.ex
deleted file mode 100644
index e34a3c83..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/folders_server_capabilities.ex
+++ /dev/null
@@ -1,8 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.FoldersServerCapabilities do
- alias Lexical.Proto
- use Proto
-
- deftype change_notifications: optional(one_of([string(), boolean()])),
- supported: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol.ex
deleted file mode 100644
index 4938ed1c..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.Symbol do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule Location do
- use Proto
- deftype uri: string()
- end
-
- use Proto
-
- deftype container_name: optional(string()),
- data: optional(any()),
- kind: Types.Symbol.Kind,
- location: one_of([Types.Location, Lexical.Protocol.Types.Workspace.Symbol.Location]),
- name: string(),
- tags: optional(list_of(Types.Symbol.Tag))
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/client_capabilities.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/client_capabilities.ex
deleted file mode 100644
index 56e3e7ef..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/client_capabilities.ex
+++ /dev/null
@@ -1,30 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.Symbol.ClientCapabilities do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
-
- defmodule ResolveSupport do
- use Proto
- deftype properties: list_of(string())
- end
-
- defmodule SymbolKind do
- use Proto
- deftype value_set: optional(list_of(Types.Symbol.Kind))
- end
-
- defmodule TagSupport do
- use Proto
- deftype value_set: list_of(Types.Symbol.Tag)
- end
-
- use Proto
-
- deftype dynamic_registration: optional(boolean()),
- resolve_support:
- optional(Lexical.Protocol.Types.Workspace.Symbol.ClientCapabilities.ResolveSupport),
- symbol_kind:
- optional(Lexical.Protocol.Types.Workspace.Symbol.ClientCapabilities.SymbolKind),
- tag_support:
- optional(Lexical.Protocol.Types.Workspace.Symbol.ClientCapabilities.TagSupport)
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/options.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/options.ex
deleted file mode 100644
index a836f2a9..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/options.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.Symbol.Options do
- alias Lexical.Proto
- use Proto
- deftype resolve_provider: optional(boolean()), work_done_progress: optional(boolean())
-end
diff --git a/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/params.ex b/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/params.ex
deleted file mode 100644
index 288eac72..00000000
--- a/apps/protocol/lib/generated/lexical/protocol/types/workspace/symbol/params.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file's contents are auto-generated. Do not edit.
-defmodule Lexical.Protocol.Types.Workspace.Symbol.Params do
- alias Lexical.Proto
- alias Lexical.Protocol.Types
- use Proto
-
- deftype partial_result_token: optional(Types.Progress.Token),
- query: string(),
- work_done_token: optional(Types.Progress.Token)
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.changes.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.changes.ex
deleted file mode 100644
index 71409905..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.changes.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Document.Changes do
- alias Lexical.Document
-
- def to_lsp(%Document.Changes{} = changes) do
- Lexical.Convertible.to_lsp(changes.edits)
- end
-
- def to_native(%Document.Changes{} = changes, _) do
- {:ok, changes}
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.edit.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.edit.ex
deleted file mode 100644
index 8c10ed34..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.edit.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Document.Edit do
- alias Lexical.Document
- alias Lexical.Protocol.Conversions
- alias Lexical.Protocol.Types
-
- def to_lsp(%Document.Edit{range: nil} = edit) do
- {:ok, Types.TextEdit.new(new_text: edit.text, range: nil)}
- end
-
- def to_lsp(%Document.Edit{} = edit) do
- with {:ok, range} <- Conversions.to_lsp(edit.range) do
- {:ok, Types.TextEdit.new(new_text: edit.text, range: range)}
- end
- end
-
- def to_native(%Document.Edit{} = edit, _context_document) do
- {:ok, edit}
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.location.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.location.ex
deleted file mode 100644
index 3722867a..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.location.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Document.Location do
- alias Lexical.Document
- alias Lexical.Protocol.Conversions
- alias Lexical.Protocol.Types
-
- def to_lsp(%Document.Location{} = location) do
- with {:ok, range} <- Conversions.to_lsp(location.range) do
- uri = Document.Location.uri(location)
- {:ok, Types.Location.new(uri: uri, range: range)}
- end
- end
-
- def to_native(%Document.Location{} = location, _context_document) do
- {:ok, location}
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.position.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.position.ex
deleted file mode 100644
index 5a6b44cf..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.position.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Document.Position do
- alias Lexical.Document
- alias Lexical.Protocol.Conversions
-
- def to_lsp(%Document.Position{} = position) do
- Conversions.to_lsp(position)
- end
-
- def to_native(%Document.Position{} = position, _context_document) do
- {:ok, position}
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.range.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.range.ex
deleted file mode 100644
index 019f6900..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.document.range.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Document.Range do
- alias Lexical.Document
- alias Lexical.Protocol.Conversions
-
- def to_lsp(%Document.Range{} = range) do
- Conversions.to_lsp(range)
- end
-
- def to_native(%Document.Range{} = range, _context_document) do
- {:ok, range}
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.location.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.location.ex
deleted file mode 100644
index b9f78828..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.location.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Protocol.Types.Location do
- alias Lexical.Document
- alias Lexical.Document.Container
- alias Lexical.Protocol.Conversions
- alias Lexical.Protocol.Types
-
- def to_lsp(%Types.Location{} = location) do
- with {:ok, range} <- Conversions.to_lsp(location.range) do
- {:ok, %Types.Location{location | range: range}}
- end
- end
-
- def to_native(%Types.Location{} = location, context_document) do
- context_document = Container.context_document(location, context_document)
-
- with {:ok, range} <- Conversions.to_elixir(location.range, context_document) do
- {:ok, Document.Location.new(range, context_document.uri)}
- end
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.position.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.position.ex
deleted file mode 100644
index b9821b03..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.position.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Protocol.Types.Position do
- alias Lexical.Protocol.Conversions
- alias Lexical.Protocol.Types
-
- def to_lsp(%Types.Position{} = position) do
- Conversions.to_lsp(position)
- end
-
- def to_native(%Types.Position{} = position, context_document) do
- Conversions.to_elixir(position, context_document)
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.range.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.range.ex
deleted file mode 100644
index 0c1e9ec6..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.range.ex
+++ /dev/null
@@ -1,23 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Protocol.Types.Range do
- alias Lexical.Protocol.Conversions
- alias Lexical.Protocol.Types
-
- def to_lsp(%Types.Range{} = range) do
- Conversions.to_lsp(range)
- end
-
- def to_native(
- %Types.Range{
- start: %Types.Position{line: start_line, character: start_character},
- end: %Types.Position{line: end_line, character: end_character}
- } = range,
- _context_document
- )
- when start_line < 0 or start_character < 0 or end_line < 0 or end_character < 0 do
- {:error, {:invalid_range, range}}
- end
-
- def to_native(%Types.Range{} = range, context_document) do
- Conversions.to_elixir(range, context_document)
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.text_document.content_change_event1.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.text_document.content_change_event1.ex
deleted file mode 100644
index 11d9f743..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.text_document.content_change_event1.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-alias Lexical.Convertible
-alias Lexical.Document.Edit
-alias Lexical.Protocol.Types.TextDocument.ContentChangeEvent
-
-defimpl Convertible, for: ContentChangeEvent.TextDocumentContentChangeEvent1 do
- def to_lsp(event) do
- {:ok, event}
- end
-
- def to_native(%ContentChangeEvent.TextDocumentContentChangeEvent1{} = event, _context_document) do
- {:ok, Edit.new(event.text)}
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.text_edit.ex b/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.text_edit.ex
deleted file mode 100644
index 026bf2d5..00000000
--- a/apps/protocol/lib/lexical/protocol/convertibles/lexical.protocol.types.text_edit.ex
+++ /dev/null
@@ -1,22 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Protocol.Types.TextEdit do
- alias Lexical.Document
- alias Lexical.Protocol.Conversions
- alias Lexical.Protocol.Types
-
- def to_lsp(%Types.TextEdit{} = text_edit) do
- with {:ok, range} <- Conversions.to_lsp(text_edit.range) do
- {:ok, %Types.TextEdit{text_edit | range: range}}
- end
- end
-
- def to_native(%Types.TextEdit{range: nil} = text_edit, _context_document) do
- {:ok, Document.Edit.new(text_edit.new_text)}
- end
-
- def to_native(%Types.TextEdit{} = text_edit, context_document) do
- with {:ok, %Document.Range{} = range} <-
- Conversions.to_elixir(text_edit.range, context_document) do
- {:ok, Document.Edit.new(text_edit.new_text, range)}
- end
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/document_containers/lexical.document.changes.ex b/apps/protocol/lib/lexical/protocol/document_containers/lexical.document.changes.ex
deleted file mode 100644
index 153925d8..00000000
--- a/apps/protocol/lib/lexical/protocol/document_containers/lexical.document.changes.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-defimpl Lexical.Document.Container, for: Lexical.Document.Changes do
- alias Lexical.Document
-
- def context_document(%Document.Changes{} = edits, prior_context_document) do
- edits.document || prior_context_document
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/document_containers/lexical.document.location.ex b/apps/protocol/lib/lexical/protocol/document_containers/lexical.document.location.ex
deleted file mode 100644
index 6cf06881..00000000
--- a/apps/protocol/lib/lexical/protocol/document_containers/lexical.document.location.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-defimpl Lexical.Document.Container, for: Lexical.Document.Location do
- alias Lexical.Document
- alias Lexical.Document.Location
-
- def context_document(%Location{document: %Document{} = context_document}, _) do
- context_document
- end
-
- def context_document(%Location{uri: uri}, context_document) when is_binary(uri) do
- case Document.Store.fetch(uri) do
- {:ok, %Document{} = document} ->
- document
-
- _ ->
- context_document
- end
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/document_containers/lexical.notifications.publish_diagnostics.ex b/apps/protocol/lib/lexical/protocol/document_containers/lexical.notifications.publish_diagnostics.ex
deleted file mode 100644
index ef3fd8d7..00000000
--- a/apps/protocol/lib/lexical/protocol/document_containers/lexical.notifications.publish_diagnostics.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defimpl Lexical.Document.Container, for: Lexical.Protocol.Notifications.PublishDiagnostics do
- alias Lexical.Document
- alias Lexical.Protocol.Notifications.PublishDiagnostics
- require Logger
-
- def context_document(%PublishDiagnostics{uri: uri} = publish, context_document)
- when is_binary(uri) do
- case Document.Store.open_temporary(publish.uri) do
- {:ok, source_doc} ->
- source_doc
-
- error ->
- Logger.error("Failed to open #{uri} temporarily because #{inspect(error)}")
- context_document
- end
- end
-
- def context_document(_, context_doc) do
- context_doc
- end
-end
diff --git a/apps/protocol/lib/lexical/protocol/document_containers/lexical.protocol.types.location.ex b/apps/protocol/lib/lexical/protocol/document_containers/lexical.protocol.types.location.ex
deleted file mode 100644
index f73b52c5..00000000
--- a/apps/protocol/lib/lexical/protocol/document_containers/lexical.protocol.types.location.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-defimpl Lexical.Document.Container, for: Lexical.Protocol.Types.Location do
- alias Lexical.Document
- alias Lexical.Protocol.Types
-
- def context_document(%Types.Location{} = location, parent_document) do
- case Document.Store.fetch(location.uri) do
- {:ok, doc} -> doc
- _ -> parent_document
- end
- end
-end
diff --git a/apps/protocol/mix.exs b/apps/protocol/mix.exs
index f0257a68..06877c05 100644
--- a/apps/protocol/mix.exs
+++ b/apps/protocol/mix.exs
@@ -1,4 +1,4 @@
-defmodule Lexical.Protocol.MixProject do
+defmodule Expert.Protocol.MixProject do
use Mix.Project
Code.require_file("../../mix_includes.exs")
@@ -29,7 +29,7 @@ defmodule Lexical.Protocol.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- {:common, path: "../common", env: Mix.env()},
+ {:forge, path: "../forge", env: Mix.env()},
Mix.Credo.dependency(),
Mix.Dialyzer.dependency(),
{:jason, "~> 1.4", optional: true},
diff --git a/apps/protocol/test/lexical/proto_test.exs b/apps/protocol/test/expert/proto_test.exs
similarity index 98%
rename from apps/protocol/test/lexical/proto_test.exs
rename to apps/protocol/test/expert/proto_test.exs
index 6ffc9c40..7580a80c 100644
--- a/apps/protocol/test/lexical/proto_test.exs
+++ b/apps/protocol/test/expert/proto_test.exs
@@ -1,10 +1,10 @@
-defmodule Lexical.ProtoTest do
- alias Lexical.Document
- alias Lexical.Proto
- alias Lexical.Proto.Convert
- alias Lexical.Proto.LspTypes
- alias Lexical.Protocol.Types
- alias Lexical.Test.Protocol.Fixtures.LspProtocol
+defmodule Expert.ProtoTest do
+ alias Expert.Proto
+ alias Expert.Proto.Convert
+ alias Expert.Proto.LspTypes
+ alias Expert.Protocol.Types
+ alias Expert.Test.Protocol.Fixtures.LspProtocol
+ alias Forge.Document
import LspProtocol
diff --git a/apps/protocol/test/lexical/protocol/conversions_test.exs b/apps/protocol/test/expert/protocol/conversions_test.exs
similarity index 93%
rename from apps/protocol/test/lexical/protocol/conversions_test.exs
rename to apps/protocol/test/expert/protocol/conversions_test.exs
index c6ae2f30..d3f483de 100644
--- a/apps/protocol/test/lexical/protocol/conversions_test.exs
+++ b/apps/protocol/test/expert/protocol/conversions_test.exs
@@ -1,8 +1,8 @@
-defmodule Lexical.Protocol.ConversionsTest do
- alias Lexical.Document
- alias Lexical.Document.Position, as: ExPosition
- alias Lexical.Protocol.Conversions
- alias Lexical.Protocol.Types.Position, as: LSPosition
+defmodule Expert.Protocol.ConversionsTest do
+ alias Expert.Protocol.Conversions
+ alias Expert.Protocol.Types.Position, as: LSPosition
+ alias Forge.Document
+ alias Forge.Document.Position, as: ExPosition
use ExUnit.Case
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.document.changes_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.document.changes_test.exs
new file mode 100644
index 00000000..c592659b
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.document.changes_test.exs
@@ -0,0 +1,33 @@
+defmodule Forge.Convertible.Document.ChangesTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "converts to a list of text edits", %{uri: uri, document: document} do
+ edit = Document.Edit.new("hi", valid_range(:native, document))
+ document_edits = Document.Changes.new(document, [edit])
+
+ assert {:ok, [%Types.TextEdit{}]} = to_lsp(document_edits, uri)
+ end
+
+ test "uses the uri from the document edits", %{uri: uri} do
+ # make a file with a couple lines and place a range on something other than the first line,
+ # the default file only has one line so if this succeeds, we will ensure that we're using
+ # the document edits' file.
+ {:ok, _uri, document} = open_file("file:///other.ex", "several\nlines\nhere")
+
+ edit =
+ Document.Edit.new(
+ "hi",
+ range(:native, position(:native, document, 2, 1), position(:native, document, 2, 3))
+ )
+
+ document_edits = Document.Changes.new(document, [edit])
+
+ assert {:ok, [%Types.TextEdit{}]} = to_lsp(document_edits, uri)
+ end
+ end
+
+ # to_native isn't tested because this is a native-only struct and can't come from the language server
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.document.edit_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.document.edit_test.exs
new file mode 100644
index 00000000..842519dc
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.document.edit_test.exs
@@ -0,0 +1,57 @@
+defmodule Expert.Protocol.Convertibles.EditTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ defmodule Inner do
+ defstruct [:position]
+ end
+
+ defmodule Outer do
+ defstruct [:inner]
+ end
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "leaves protocol text edits alone", %{uri: uri} do
+ lsp_text_edit = Types.TextEdit.new(new_text: "hi", range: valid_range(:lsp))
+ {:ok, ^lsp_text_edit} = to_lsp(lsp_text_edit, uri)
+ end
+
+ test "converts with no range", %{uri: uri} do
+ assert {:ok, %Types.TextEdit{new_text: "hi"}} = to_lsp(Document.Edit.new("hi"), uri)
+ end
+
+ test "converts with a filled in range", %{uri: uri, document: document} do
+ ex_range =
+ range(:native, valid_position(:native, document), position(:native, document, 1, 3))
+
+ assert {:ok, %Types.TextEdit{} = text_edit} = to_lsp(Document.Edit.new("hi", ex_range), uri)
+
+ assert text_edit.new_text == "hi"
+ assert %Types.Range{} = range = text_edit.range
+ assert range.start.line == 0
+ assert range.start.character == 0
+ assert range.end.line == 0
+ assert range.end.character == 2
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "converts a text edit with no range", %{uri: uri} do
+ proto_edit = Types.TextEdit.new(new_text: "hi", range: nil)
+
+ assert {:ok, %Document.Edit{} = edit} = to_native(proto_edit, uri)
+
+ assert edit.text == "hi"
+ assert edit.range == nil
+ end
+
+ test "leaves native text edits alone", %{uri: uri, document: document} do
+ native_text_edit = Document.Edit.new("hi", valid_range(:native, document))
+
+ assert {:ok, ^native_text_edit} = to_native(native_text_edit, uri)
+ end
+ end
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.document.location_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.document.location_test.exs
new file mode 100644
index 00000000..195db11c
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.document.location_test.exs
@@ -0,0 +1,47 @@
+defmodule Expert.Protocol.Convertibles.LocationTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "converts a location with a native range", %{uri: uri, document: document} do
+ location = Document.Location.new(valid_range(:native, document), uri)
+
+ assert {:ok, converted} = to_lsp(location, uri)
+ assert converted.uri == uri
+ assert %Types.Range{} = converted.range
+ assert %Types.Position{} = converted.range.start
+ assert %Types.Position{} = converted.range.end
+ end
+
+ test "converts a location with a source file", %{uri: uri, document: document} do
+ location = Document.Location.new(valid_range(:native, document), document)
+
+ assert {:ok, converted} = to_lsp(location, uri)
+ assert converted.uri == document.uri
+ assert %Types.Range{} = converted.range
+ assert %Types.Position{} = converted.range.start
+ assert %Types.Position{} = converted.range.end
+ end
+
+ test "uses the location's uri", %{uri: uri, document: document} do
+ other_uri = "file:///other.ex"
+ {:ok, _, _doc} = open_file(other_uri, "goodbye")
+
+ location = Document.Location.new(valid_range(:native, document), other_uri)
+ assert {:ok, converted} = to_lsp(location, uri)
+ assert converted.uri == other_uri
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "leaves the location alone", %{uri: uri, document: document} do
+ location = Document.Location.new(valid_range(:native, document), uri)
+
+ assert {:ok, converted} = to_native(location, uri)
+ assert location == converted
+ end
+ end
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.document.position_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.document.position_test.exs
new file mode 100644
index 00000000..0e054b72
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.document.position_test.exs
@@ -0,0 +1,68 @@
+defmodule Expert.Protocol.Convertibles.PositionTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ defmodule Inner do
+ defstruct [:position]
+ end
+
+ defmodule Outer do
+ defstruct [:inner]
+ end
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "converts valid positions", %{uri: uri, document: document} do
+ assert {:ok, %Types.Position{} = pos} = to_lsp(valid_position(:native, document), uri)
+ assert pos.line == 0
+ assert pos.character == 0
+ end
+
+ test "converts positions in other structs", %{uri: uri, document: document} do
+ nested = %Outer{
+ inner: %Inner{position: valid_position(:native, document)}
+ }
+
+ assert {:ok, converted} = to_lsp(nested, uri)
+
+ assert %Types.Position{} = converted.inner.position
+ end
+
+ test "leaves protocol positions alone", %{uri: uri} do
+ lsp_pos = valid_position(:lsp)
+ assert {:ok, ^lsp_pos} = to_lsp(lsp_pos, uri)
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "converts valid positions", %{uri: uri} do
+ assert {:ok, %Document.Position{} = pos} =
+ :lsp
+ |> position(1, 0)
+ |> to_native(uri)
+
+ assert pos.line == 2
+ assert pos.character == 1
+ end
+
+ test "converts positions in other structs", %{uri: uri} do
+ nested = %Outer{
+ inner: %Inner{position: valid_position(:lsp)}
+ }
+
+ assert {:ok, converted} = to_native(nested, uri)
+ assert %Document.Position{} = converted.inner.position
+ end
+
+ test "leaves native positions alone", %{uri: uri, document: document} do
+ native_pos = valid_position(:native, document)
+
+ assert {:ok, ^native_pos} =
+ :native
+ |> valid_position(document)
+ |> to_native(uri)
+ end
+ end
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.document.range_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.document.range_test.exs
new file mode 100644
index 00000000..dfc845bf
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.document.range_test.exs
@@ -0,0 +1,50 @@
+defmodule Expert.Protocol.Convertibles.RangeTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "converts ranges", %{uri: uri, document: document} do
+ native_range =
+ range(:native, position(:native, document, 1, 1), position(:native, document, 1, 3))
+
+ assert {:ok, %Types.Range{} = range} = to_lsp(native_range, uri)
+ assert %Types.Position{} = range.start
+ assert %Types.Position{} = range.end
+ end
+
+ test "leaves protocol ranges alone", %{uri: uri} do
+ lsp_range = valid_range(:lsp)
+
+ assert {:ok, ^lsp_range} = to_lsp(lsp_range, uri)
+ end
+
+ test "converts native positions inside lsp ranges", %{uri: uri, document: document} do
+ lsp_range = range(:lsp, valid_position(:native, document), valid_position(:lsp))
+
+ assert %Document.Position{} = lsp_range.start
+
+ assert {:ok, %Types.Range{} = converted} = to_lsp(lsp_range, uri)
+ assert %Types.Position{} = converted.start
+ assert %Types.Position{} = converted.end
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "converts ranges", %{uri: uri} do
+ proto_range = range(:lsp, position(:lsp, 0, 0), position(:lsp, 0, 3))
+
+ assert {:ok, %Document.Range{} = range} = to_native(proto_range, uri)
+
+ assert %Document.Position{} = range.start
+ assert %Document.Position{} = range.end
+ end
+
+ test "leaves native ranges alone", %{uri: uri, document: document} do
+ native_range = valid_range(:native, document)
+ assert {:ok, ^native_range} = to_native(native_range, uri)
+ end
+ end
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.location_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.location_test.exs
new file mode 100644
index 00000000..c8065353
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.location_test.exs
@@ -0,0 +1,80 @@
+defmodule Expert.Protocol.Types.Convertibles.LocationTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "converts a native location", %{uri: uri, document: document} do
+ native_loc = Document.Location.new(valid_range(:native, document), uri)
+
+ assert {:ok, %Types.Location{} = converted} = to_lsp(native_loc, uri)
+
+ assert converted.uri == uri
+ assert %Types.Range{} = converted.range
+ assert %Types.Position{} = converted.range.start
+ assert %Types.Position{} = converted.range.end
+ end
+
+ test "uses the location's uri", %{uri: uri, document: document} do
+ other_uri = "file:///other.ex"
+
+ native_loc = Document.Location.new(valid_range(:native, document), other_uri)
+ assert {:ok, %Types.Location{} = converted} = to_lsp(native_loc, uri)
+
+ assert converted.uri == other_uri
+ end
+
+ test "converts a lsp location with a native range", %{uri: uri, document: document} do
+ lsp_loc = Types.Location.new(range: valid_range(:native, document), uri: uri)
+
+ assert {:ok, %Types.Location{} = converted} = to_lsp(lsp_loc, uri)
+
+ assert converted.uri == uri
+ assert %Types.Range{} = converted.range
+ assert %Types.Position{} = converted.range.start
+ assert %Types.Position{} = converted.range.end
+ end
+
+ test "converts a lsp location with a native position", %{uri: uri, document: document} do
+ lsp_loc =
+ Types.Location.new(
+ range: range(:lsp, valid_position(:native, document), valid_position(:lsp)),
+ uri: uri
+ )
+
+ assert {:ok, %Types.Location{} = converted} = to_lsp(lsp_loc, uri)
+
+ assert converted.uri == uri
+ assert %Types.Range{} = converted.range
+ assert %Types.Position{} = converted.range.start
+ assert %Types.Position{} = converted.range.end
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "converts an lsp location", %{uri: uri} do
+ lsp_loc = Types.Location.new(range: valid_range(:lsp), uri: uri)
+
+ assert {:ok, %Document.Location{} = converted} = to_native(lsp_loc, uri)
+
+ assert converted.uri == uri
+ assert %Document.Range{} = converted.range
+ assert %Document.Position{} = converted.range.start
+ assert %Document.Position{} = converted.range.end
+ end
+
+ test "uses the location's uri", %{uri: uri} do
+ other_uri = "file:///other.ex"
+ lsp_loc = Types.Location.new(range: valid_range(:lsp), uri: other_uri)
+
+ assert {:ok, %Document.Location{} = converted} = to_native(lsp_loc, uri)
+
+ assert converted.uri == uri
+ assert %Document.Range{} = converted.range
+ assert %Document.Position{} = converted.range.start
+ assert %Document.Position{} = converted.range.end
+ end
+ end
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.position_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.position_test.exs
new file mode 100644
index 00000000..570a62d1
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.position_test.exs
@@ -0,0 +1,33 @@
+defmodule Expert.Protocol.Types.Convertibles.PositionTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "converts native position", %{uri: uri, document: document} do
+ native_pos = valid_position(:native, document)
+ assert {:ok, %Types.Position{}} = to_lsp(native_pos, uri)
+ end
+
+ test "leaves protocol ranges alone", %{uri: uri} do
+ lsp_pos = valid_position(:lsp)
+
+ assert {:ok, ^lsp_pos} = to_lsp(lsp_pos, uri)
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "converts lsp ranges", %{uri: uri} do
+ proto_range = valid_position(:lsp)
+
+ assert {:ok, %Document.Position{}} = to_native(proto_range, uri)
+ end
+
+ test "leaves native ranges alone", %{uri: uri, document: document} do
+ native_range = valid_range(:native, document)
+ assert {:ok, ^native_range} = to_native(native_range, uri)
+ end
+ end
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.range_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.range_test.exs
new file mode 100644
index 00000000..62124eb0
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.range_test.exs
@@ -0,0 +1,68 @@
+defmodule Expert.Protocol.Types.Convertibles.RangeTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ describe "to_lsp/2" do
+ setup [:with_an_open_file]
+
+ test "converts native ranges", %{uri: uri, document: document} do
+ native_range =
+ range(:native, position(:native, document, 1, 1), position(:native, document, 1, 3))
+
+ assert {:ok, %Types.Range{} = range} = to_lsp(native_range, uri)
+
+ assert %Types.Position{} = range.start
+ assert %Types.Position{} = range.end
+ end
+
+ test "leaves protocol ranges alone", %{uri: uri} do
+ lsp_range = valid_range(:lsp)
+ assert {:ok, ^lsp_range} = to_lsp(lsp_range, uri)
+ end
+
+ test "converts native positions inside lsp ranges", %{uri: uri, document: document} do
+ lsp_range = range(:lsp, valid_position(:native, document), valid_position(:lsp))
+
+ assert %Document.Position{} = lsp_range.start
+
+ assert {:ok, converted} = to_lsp(lsp_range, uri)
+ assert %Types.Position{} = converted.start
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "converts lsp ranges", %{uri: uri} do
+ lsp_range = range(:lsp, position(:lsp, 0, 0), position(:lsp, 0, 3))
+ assert {:ok, %Document.Range{} = range} = to_native(lsp_range, uri)
+
+ assert %Document.Position{} = range.start
+ assert %Document.Position{} = range.end
+ end
+
+ test "leaves native ranges alone", %{uri: uri, document: document} do
+ native_range = valid_range(:native, document)
+ assert {:ok, ^native_range} = to_native(native_range, uri)
+ end
+
+ test "errors on invalid start position line", %{uri: uri} do
+ invalid_range = range(:lsp, position(:lsp, -1, 0), valid_position(:lsp))
+ assert {:error, {:invalid_range, ^invalid_range}} = to_native(invalid_range, uri)
+ end
+
+ test "errors on invalid end position line", %{uri: uri} do
+ invalid_range = range(:lsp, valid_position(:lsp), position(:lsp, -1, 0))
+ assert {:error, {:invalid_range, ^invalid_range}} = to_native(invalid_range, uri)
+ end
+
+ test "errors on invalid start position character", %{uri: uri} do
+ invalid_range = range(:lsp, position(:lsp, 0, -1), valid_position(:lsp))
+ assert {:error, {:invalid_range, ^invalid_range}} = to_native(invalid_range, uri)
+ end
+
+ test "errors on invalid end position character", %{uri: uri} do
+ invalid_range = range(:lsp, valid_position(:lsp), position(:lsp, 0, -1))
+ assert {:error, {:invalid_range, ^invalid_range}} = to_native(invalid_range, uri)
+ end
+ end
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.text_document.content_change_event1_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.text_document.content_change_event1_test.exs
new file mode 100644
index 00000000..deec6372
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.text_document.content_change_event1_test.exs
@@ -0,0 +1,28 @@
+defmodule Expert.Protocol.Convertibles.ContentChangeEvent.TextDocumentContentChangeEvent1Test do
+ alias Expert.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent1,
+ as: TextOnlyEvent
+
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ describe "to_lsp/2)" do
+ setup [:with_an_open_file]
+
+ test "is a no-op", %{uri: uri} do
+ native_text_edit = TextOnlyEvent.new(text: "hi")
+ assert {:ok, ^native_text_edit} = to_lsp(native_text_edit, uri)
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "converts to a single replace edit", %{uri: uri} do
+ replacement_text = "This is the replacement text"
+ event = TextOnlyEvent.new(text: replacement_text)
+ {:ok, %Document.Edit{} = converted} = to_native(event, uri)
+
+ assert converted.text == replacement_text
+ assert converted.range == nil
+ end
+ end
+end
diff --git a/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.text_edit_test.exs b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.text_edit_test.exs
new file mode 100644
index 00000000..ef8668f8
--- /dev/null
+++ b/apps/protocol/test/expert/protocol/convertibles/expert.protocol.types.text_edit_test.exs
@@ -0,0 +1,60 @@
+defmodule Expert.Protocol.Convertibles.TextEditTest do
+ use Expert.Test.Protocol.ConvertibleSupport
+
+ describe "to_lsp/2)" do
+ setup [:with_an_open_file]
+
+ test "leaves text edits alone", %{uri: uri} do
+ native_text_edit = Types.TextEdit.new(new_text: "hi", range: valid_range(:lsp))
+ assert {:ok, ^native_text_edit} = to_lsp(native_text_edit, uri)
+ end
+
+ test "converts native positions in text edits", %{uri: uri, document: document} do
+ text_edit =
+ Types.TextEdit.new(
+ new_text: "hi",
+ range: range(:lsp, valid_position(:native, document), valid_position(:lsp))
+ )
+
+ assert %Document.Position{} = text_edit.range.start
+ assert {:ok, converted} = to_lsp(text_edit, uri)
+ assert %Types.Position{} = converted.range.start
+ end
+
+ test "converts native ranges in text edits", %{uri: uri, document: document} do
+ text_edit = Types.TextEdit.new(new_text: "hi", range: valid_range(:native, document))
+ assert %Document.Range{} = text_edit.range
+
+ assert {:ok, converted} = to_lsp(text_edit, uri)
+ assert %Types.Range{} = converted.range
+ assert %Types.Position{} = converted.range.start
+ assert %Types.Position{} = converted.range.end
+ end
+ end
+
+ describe "to_native/2" do
+ setup [:with_an_open_file]
+
+ test "converts to edits", %{uri: uri} do
+ text_edit = Types.TextEdit.new(new_text: "Hi", range: valid_range(:lsp))
+
+ {:ok, converted} = to_native(text_edit, uri)
+
+ assert %Document.Edit{} = converted
+ assert %Document.Range{} = converted.range
+ assert %Document.Position{} = converted.range.start
+ assert %Document.Position{} = converted.range.end
+ assert "Hi" == converted.text
+ end
+
+ test "fails conversion gracefully", %{uri: uri} do
+ edit =
+ Types.TextEdit.new(
+ new_text: "hi",
+ range: range(:lsp, position(:lsp, -1, 0), valid_position(:lsp))
+ )
+
+ assert {:error, {:invalid_range, _}} = to_native(edit, uri)
+ end
+ end
+end
diff --git a/apps/protocol/test/lexical/protocol/integration/initialize_test.exs b/apps/protocol/test/expert/protocol/integration/initialize_test.exs
similarity index 96%
rename from apps/protocol/test/lexical/protocol/integration/initialize_test.exs
rename to apps/protocol/test/expert/protocol/integration/initialize_test.exs
index 3292fcad..93c65724 100644
--- a/apps/protocol/test/lexical/protocol/integration/initialize_test.exs
+++ b/apps/protocol/test/expert/protocol/integration/initialize_test.exs
@@ -1,8 +1,8 @@
-defmodule Lexical.Protocol.Integrations.InitializeTest do
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Types
- alias Lexical.Protocol.Types.Completion
- alias Lexical.Protocol.Types.TextDocument
+defmodule Expert.Protocol.Integrations.InitializeTest do
+ alias Expert.Protocol.Requests
+ alias Expert.Protocol.Types
+ alias Expert.Protocol.Types.Completion
+ alias Expert.Protocol.Types.TextDocument
use ExUnit.Case
@@ -220,13 +220,13 @@ defmodule Lexical.Protocol.Integrations.InitializeTest do
"version": "0.10.0"
},
"processId": 81648,
- "rootPath": "/Users/scottming/Code/lexical",
- "rootUri": "file:///Users/scottming/Code/lexical",
+ "rootPath": "/Users/scottming/Code/Expert",
+ "rootUri": "file:///Users/scottming/Code/Expert",
"trace": "off",
"workspaceFolders": [
{
- "name": "/Users/scottming/Code/lexical",
- "uri": "file:///Users/scottming/Code/lexical"
+ "name": "/Users/scottming/Code/Expert",
+ "uri": "file:///Users/scottming/Code/Expert"
}
]
}
diff --git a/apps/protocol/test/lexical/protocol/notifications_test.exs b/apps/protocol/test/expert/protocol/notifications_test.exs
similarity index 92%
rename from apps/protocol/test/lexical/protocol/notifications_test.exs
rename to apps/protocol/test/expert/protocol/notifications_test.exs
index 015f5c38..b7547b81 100644
--- a/apps/protocol/test/lexical/protocol/notifications_test.exs
+++ b/apps/protocol/test/expert/protocol/notifications_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Protocol.NotificationsTest do
- alias Lexical.Protocol.Notifications
+defmodule Expert.Protocol.NotificationsTest do
+ alias Expert.Protocol.Notifications
import Notifications
use ExUnit.Case
diff --git a/apps/protocol/test/lexical/protocol/requests_test.exs b/apps/protocol/test/expert/protocol/requests_test.exs
similarity index 93%
rename from apps/protocol/test/lexical/protocol/requests_test.exs
rename to apps/protocol/test/expert/protocol/requests_test.exs
index bc6c44ae..c217b678 100644
--- a/apps/protocol/test/lexical/protocol/requests_test.exs
+++ b/apps/protocol/test/expert/protocol/requests_test.exs
@@ -1,5 +1,5 @@
-defmodule Lexical.Protocol.RequestsTest do
- alias Lexical.Protocol.Requests
+defmodule Expert.Protocol.RequestsTest do
+ alias Expert.Protocol.Requests
import Requests
use ExUnit.Case
diff --git a/apps/protocol/test/lexical/protocol/response_test.exs b/apps/protocol/test/expert/protocol/response_test.exs
similarity index 89%
rename from apps/protocol/test/lexical/protocol/response_test.exs
rename to apps/protocol/test/expert/protocol/response_test.exs
index 5d9ba829..01552456 100644
--- a/apps/protocol/test/lexical/protocol/response_test.exs
+++ b/apps/protocol/test/expert/protocol/response_test.exs
@@ -1,8 +1,8 @@
-defmodule Lexical.Protocol.ResponseTest do
- alias Lexical.Document
- alias Lexical.Proto
- alias Lexical.Proto.Convert
- alias Lexical.Protocol.Types
+defmodule Expert.Protocol.ResponseTest do
+ alias Expert.Proto
+ alias Expert.Proto.Convert
+ alias Expert.Protocol.Types
+ alias Forge.Document
use ExUnit.Case
@@ -24,7 +24,7 @@ defmodule Lexical.Protocol.ResponseTest do
setup [:with_open_document]
defmodule TextDocumentAndPosition do
- alias Lexical.Protocol.Types
+ alias Expert.Protocol.Types
use Proto
deftype text_document: Types.TextDocument.Identifier,
@@ -51,7 +51,7 @@ defmodule Lexical.Protocol.ResponseTest do
end
defmodule Locations do
- alias Lexical.Protocol.Types
+ alias Expert.Protocol.Types
use Proto
defresponse list_of(Types.Location)
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.changes_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.document.changes_test.exs
deleted file mode 100644
index 2bb29b35..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.changes_test.exs
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule Lexical.Convertible.Document.ChangesTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "converts to a list of text edits", %{uri: uri, document: document} do
- edit = Document.Edit.new("hi", valid_range(:native, document))
- document_edits = Document.Changes.new(document, [edit])
-
- assert {:ok, [%Types.TextEdit{}]} = to_lsp(document_edits, uri)
- end
-
- test "uses the uri from the document edits", %{uri: uri} do
- # make a file with a couple lines and place a range on something other than the first line,
- # the default file only has one line so if this succeeds, we will ensure that we're using
- # the document edits' file.
- {:ok, _uri, document} = open_file("file:///other.ex", "several\nlines\nhere")
-
- edit =
- Document.Edit.new(
- "hi",
- range(:native, position(:native, document, 2, 1), position(:native, document, 2, 3))
- )
-
- document_edits = Document.Changes.new(document, [edit])
-
- assert {:ok, [%Types.TextEdit{}]} = to_lsp(document_edits, uri)
- end
- end
-
- # to_native isn't tested because this is a native-only struct and can't come from the language server
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.edit_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.document.edit_test.exs
deleted file mode 100644
index b574968f..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.edit_test.exs
+++ /dev/null
@@ -1,57 +0,0 @@
-defmodule Lexical.Protocol.Convertibles.EditTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- defmodule Inner do
- defstruct [:position]
- end
-
- defmodule Outer do
- defstruct [:inner]
- end
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "leaves protocol text edits alone", %{uri: uri} do
- lsp_text_edit = Types.TextEdit.new(new_text: "hi", range: valid_range(:lsp))
- {:ok, ^lsp_text_edit} = to_lsp(lsp_text_edit, uri)
- end
-
- test "converts with no range", %{uri: uri} do
- assert {:ok, %Types.TextEdit{new_text: "hi"}} = to_lsp(Document.Edit.new("hi"), uri)
- end
-
- test "converts with a filled in range", %{uri: uri, document: document} do
- ex_range =
- range(:native, valid_position(:native, document), position(:native, document, 1, 3))
-
- assert {:ok, %Types.TextEdit{} = text_edit} = to_lsp(Document.Edit.new("hi", ex_range), uri)
-
- assert text_edit.new_text == "hi"
- assert %Types.Range{} = range = text_edit.range
- assert range.start.line == 0
- assert range.start.character == 0
- assert range.end.line == 0
- assert range.end.character == 2
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "converts a text edit with no range", %{uri: uri} do
- proto_edit = Types.TextEdit.new(new_text: "hi", range: nil)
-
- assert {:ok, %Document.Edit{} = edit} = to_native(proto_edit, uri)
-
- assert edit.text == "hi"
- assert edit.range == nil
- end
-
- test "leaves native text edits alone", %{uri: uri, document: document} do
- native_text_edit = Document.Edit.new("hi", valid_range(:native, document))
-
- assert {:ok, ^native_text_edit} = to_native(native_text_edit, uri)
- end
- end
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.location_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.document.location_test.exs
deleted file mode 100644
index ffb24f0a..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.location_test.exs
+++ /dev/null
@@ -1,47 +0,0 @@
-defmodule Lexical.Protocol.Convertibles.LocationTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "converts a location with a native range", %{uri: uri, document: document} do
- location = Document.Location.new(valid_range(:native, document), uri)
-
- assert {:ok, converted} = to_lsp(location, uri)
- assert converted.uri == uri
- assert %Types.Range{} = converted.range
- assert %Types.Position{} = converted.range.start
- assert %Types.Position{} = converted.range.end
- end
-
- test "converts a location with a source file", %{uri: uri, document: document} do
- location = Document.Location.new(valid_range(:native, document), document)
-
- assert {:ok, converted} = to_lsp(location, uri)
- assert converted.uri == document.uri
- assert %Types.Range{} = converted.range
- assert %Types.Position{} = converted.range.start
- assert %Types.Position{} = converted.range.end
- end
-
- test "uses the location's uri", %{uri: uri, document: document} do
- other_uri = "file:///other.ex"
- {:ok, _, _doc} = open_file(other_uri, "goodbye")
-
- location = Document.Location.new(valid_range(:native, document), other_uri)
- assert {:ok, converted} = to_lsp(location, uri)
- assert converted.uri == other_uri
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "leaves the location alone", %{uri: uri, document: document} do
- location = Document.Location.new(valid_range(:native, document), uri)
-
- assert {:ok, converted} = to_native(location, uri)
- assert location == converted
- end
- end
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.position_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.document.position_test.exs
deleted file mode 100644
index 787bdea9..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.position_test.exs
+++ /dev/null
@@ -1,68 +0,0 @@
-defmodule Lexical.Protocol.Convertibles.PositionTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- defmodule Inner do
- defstruct [:position]
- end
-
- defmodule Outer do
- defstruct [:inner]
- end
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "converts valid positions", %{uri: uri, document: document} do
- assert {:ok, %Types.Position{} = pos} = to_lsp(valid_position(:native, document), uri)
- assert pos.line == 0
- assert pos.character == 0
- end
-
- test "converts positions in other structs", %{uri: uri, document: document} do
- nested = %Outer{
- inner: %Inner{position: valid_position(:native, document)}
- }
-
- assert {:ok, converted} = to_lsp(nested, uri)
-
- assert %Types.Position{} = converted.inner.position
- end
-
- test "leaves protocol positions alone", %{uri: uri} do
- lsp_pos = valid_position(:lsp)
- assert {:ok, ^lsp_pos} = to_lsp(lsp_pos, uri)
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "converts valid positions", %{uri: uri} do
- assert {:ok, %Document.Position{} = pos} =
- :lsp
- |> position(1, 0)
- |> to_native(uri)
-
- assert pos.line == 2
- assert pos.character == 1
- end
-
- test "converts positions in other structs", %{uri: uri} do
- nested = %Outer{
- inner: %Inner{position: valid_position(:lsp)}
- }
-
- assert {:ok, converted} = to_native(nested, uri)
- assert %Document.Position{} = converted.inner.position
- end
-
- test "leaves native positions alone", %{uri: uri, document: document} do
- native_pos = valid_position(:native, document)
-
- assert {:ok, ^native_pos} =
- :native
- |> valid_position(document)
- |> to_native(uri)
- end
- end
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.range_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.document.range_test.exs
deleted file mode 100644
index 450c265c..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.document.range_test.exs
+++ /dev/null
@@ -1,50 +0,0 @@
-defmodule Lexical.Protocol.Convertibles.RangeTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "converts ranges", %{uri: uri, document: document} do
- native_range =
- range(:native, position(:native, document, 1, 1), position(:native, document, 1, 3))
-
- assert {:ok, %Types.Range{} = range} = to_lsp(native_range, uri)
- assert %Types.Position{} = range.start
- assert %Types.Position{} = range.end
- end
-
- test "leaves protocol ranges alone", %{uri: uri} do
- lsp_range = valid_range(:lsp)
-
- assert {:ok, ^lsp_range} = to_lsp(lsp_range, uri)
- end
-
- test "converts native positions inside lsp ranges", %{uri: uri, document: document} do
- lsp_range = range(:lsp, valid_position(:native, document), valid_position(:lsp))
-
- assert %Document.Position{} = lsp_range.start
-
- assert {:ok, %Types.Range{} = converted} = to_lsp(lsp_range, uri)
- assert %Types.Position{} = converted.start
- assert %Types.Position{} = converted.end
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "converts ranges", %{uri: uri} do
- proto_range = range(:lsp, position(:lsp, 0, 0), position(:lsp, 0, 3))
-
- assert {:ok, %Document.Range{} = range} = to_native(proto_range, uri)
-
- assert %Document.Position{} = range.start
- assert %Document.Position{} = range.end
- end
-
- test "leaves native ranges alone", %{uri: uri, document: document} do
- native_range = valid_range(:native, document)
- assert {:ok, ^native_range} = to_native(native_range, uri)
- end
- end
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.location_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.location_test.exs
deleted file mode 100644
index 0bd46e2f..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.location_test.exs
+++ /dev/null
@@ -1,80 +0,0 @@
-defmodule Lexical.Protocol.Types.Convertibles.LocationTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "converts a native location", %{uri: uri, document: document} do
- native_loc = Document.Location.new(valid_range(:native, document), uri)
-
- assert {:ok, %Types.Location{} = converted} = to_lsp(native_loc, uri)
-
- assert converted.uri == uri
- assert %Types.Range{} = converted.range
- assert %Types.Position{} = converted.range.start
- assert %Types.Position{} = converted.range.end
- end
-
- test "uses the location's uri", %{uri: uri, document: document} do
- other_uri = "file:///other.ex"
-
- native_loc = Document.Location.new(valid_range(:native, document), other_uri)
- assert {:ok, %Types.Location{} = converted} = to_lsp(native_loc, uri)
-
- assert converted.uri == other_uri
- end
-
- test "converts a lsp location with a native range", %{uri: uri, document: document} do
- lsp_loc = Types.Location.new(range: valid_range(:native, document), uri: uri)
-
- assert {:ok, %Types.Location{} = converted} = to_lsp(lsp_loc, uri)
-
- assert converted.uri == uri
- assert %Types.Range{} = converted.range
- assert %Types.Position{} = converted.range.start
- assert %Types.Position{} = converted.range.end
- end
-
- test "converts a lsp location with a native position", %{uri: uri, document: document} do
- lsp_loc =
- Types.Location.new(
- range: range(:lsp, valid_position(:native, document), valid_position(:lsp)),
- uri: uri
- )
-
- assert {:ok, %Types.Location{} = converted} = to_lsp(lsp_loc, uri)
-
- assert converted.uri == uri
- assert %Types.Range{} = converted.range
- assert %Types.Position{} = converted.range.start
- assert %Types.Position{} = converted.range.end
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "converts an lsp location", %{uri: uri} do
- lsp_loc = Types.Location.new(range: valid_range(:lsp), uri: uri)
-
- assert {:ok, %Document.Location{} = converted} = to_native(lsp_loc, uri)
-
- assert converted.uri == uri
- assert %Document.Range{} = converted.range
- assert %Document.Position{} = converted.range.start
- assert %Document.Position{} = converted.range.end
- end
-
- test "uses the location's uri", %{uri: uri} do
- other_uri = "file:///other.ex"
- lsp_loc = Types.Location.new(range: valid_range(:lsp), uri: other_uri)
-
- assert {:ok, %Document.Location{} = converted} = to_native(lsp_loc, uri)
-
- assert converted.uri == uri
- assert %Document.Range{} = converted.range
- assert %Document.Position{} = converted.range.start
- assert %Document.Position{} = converted.range.end
- end
- end
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.position_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.position_test.exs
deleted file mode 100644
index 60275f71..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.position_test.exs
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule Lexical.Protocol.Types.Convertibles.PositionTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "converts native position", %{uri: uri, document: document} do
- native_pos = valid_position(:native, document)
- assert {:ok, %Types.Position{}} = to_lsp(native_pos, uri)
- end
-
- test "leaves protocol ranges alone", %{uri: uri} do
- lsp_pos = valid_position(:lsp)
-
- assert {:ok, ^lsp_pos} = to_lsp(lsp_pos, uri)
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "converts lsp ranges", %{uri: uri} do
- proto_range = valid_position(:lsp)
-
- assert {:ok, %Document.Position{}} = to_native(proto_range, uri)
- end
-
- test "leaves native ranges alone", %{uri: uri, document: document} do
- native_range = valid_range(:native, document)
- assert {:ok, ^native_range} = to_native(native_range, uri)
- end
- end
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.range_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.range_test.exs
deleted file mode 100644
index 77add300..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.range_test.exs
+++ /dev/null
@@ -1,68 +0,0 @@
-defmodule Lexical.Protocol.Types.Convertibles.RangeTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "converts native ranges", %{uri: uri, document: document} do
- native_range =
- range(:native, position(:native, document, 1, 1), position(:native, document, 1, 3))
-
- assert {:ok, %Types.Range{} = range} = to_lsp(native_range, uri)
-
- assert %Types.Position{} = range.start
- assert %Types.Position{} = range.end
- end
-
- test "leaves protocol ranges alone", %{uri: uri} do
- lsp_range = valid_range(:lsp)
- assert {:ok, ^lsp_range} = to_lsp(lsp_range, uri)
- end
-
- test "converts native positions inside lsp ranges", %{uri: uri, document: document} do
- lsp_range = range(:lsp, valid_position(:native, document), valid_position(:lsp))
-
- assert %Document.Position{} = lsp_range.start
-
- assert {:ok, converted} = to_lsp(lsp_range, uri)
- assert %Types.Position{} = converted.start
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "converts lsp ranges", %{uri: uri} do
- lsp_range = range(:lsp, position(:lsp, 0, 0), position(:lsp, 0, 3))
- assert {:ok, %Document.Range{} = range} = to_native(lsp_range, uri)
-
- assert %Document.Position{} = range.start
- assert %Document.Position{} = range.end
- end
-
- test "leaves native ranges alone", %{uri: uri, document: document} do
- native_range = valid_range(:native, document)
- assert {:ok, ^native_range} = to_native(native_range, uri)
- end
-
- test "errors on invalid start position line", %{uri: uri} do
- invalid_range = range(:lsp, position(:lsp, -1, 0), valid_position(:lsp))
- assert {:error, {:invalid_range, ^invalid_range}} = to_native(invalid_range, uri)
- end
-
- test "errors on invalid end position line", %{uri: uri} do
- invalid_range = range(:lsp, valid_position(:lsp), position(:lsp, -1, 0))
- assert {:error, {:invalid_range, ^invalid_range}} = to_native(invalid_range, uri)
- end
-
- test "errors on invalid start position character", %{uri: uri} do
- invalid_range = range(:lsp, position(:lsp, 0, -1), valid_position(:lsp))
- assert {:error, {:invalid_range, ^invalid_range}} = to_native(invalid_range, uri)
- end
-
- test "errors on invalid end position character", %{uri: uri} do
- invalid_range = range(:lsp, valid_position(:lsp), position(:lsp, 0, -1))
- assert {:error, {:invalid_range, ^invalid_range}} = to_native(invalid_range, uri)
- end
- end
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.text_document.content_change_event1_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.text_document.content_change_event1_test.exs
deleted file mode 100644
index 14390fca..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.text_document.content_change_event1_test.exs
+++ /dev/null
@@ -1,28 +0,0 @@
-defmodule Lexical.Protocol.Convertibles.ContentChangeEvent.TextDocumentContentChangeEvent1Test do
- alias Lexical.Protocol.Types.TextDocument.ContentChangeEvent.TextDocumentContentChangeEvent1,
- as: TextOnlyEvent
-
- use Lexical.Test.Protocol.ConvertibleSupport
-
- describe "to_lsp/2)" do
- setup [:with_an_open_file]
-
- test "is a no-op", %{uri: uri} do
- native_text_edit = TextOnlyEvent.new(text: "hi")
- assert {:ok, ^native_text_edit} = to_lsp(native_text_edit, uri)
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "converts to a single replace edit", %{uri: uri} do
- replacement_text = "This is the replacement text"
- event = TextOnlyEvent.new(text: replacement_text)
- {:ok, %Document.Edit{} = converted} = to_native(event, uri)
-
- assert converted.text == replacement_text
- assert converted.range == nil
- end
- end
-end
diff --git a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.text_edit_test.exs b/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.text_edit_test.exs
deleted file mode 100644
index b246ea22..00000000
--- a/apps/protocol/test/lexical/protocol/convertibles/lexical.protocol.types.text_edit_test.exs
+++ /dev/null
@@ -1,60 +0,0 @@
-defmodule Lexical.Protocol.Convertibles.TextEditTest do
- use Lexical.Test.Protocol.ConvertibleSupport
-
- describe "to_lsp/2)" do
- setup [:with_an_open_file]
-
- test "leaves text edits alone", %{uri: uri} do
- native_text_edit = Types.TextEdit.new(new_text: "hi", range: valid_range(:lsp))
- assert {:ok, ^native_text_edit} = to_lsp(native_text_edit, uri)
- end
-
- test "converts native positions in text edits", %{uri: uri, document: document} do
- text_edit =
- Types.TextEdit.new(
- new_text: "hi",
- range: range(:lsp, valid_position(:native, document), valid_position(:lsp))
- )
-
- assert %Document.Position{} = text_edit.range.start
- assert {:ok, converted} = to_lsp(text_edit, uri)
- assert %Types.Position{} = converted.range.start
- end
-
- test "converts native ranges in text edits", %{uri: uri, document: document} do
- text_edit = Types.TextEdit.new(new_text: "hi", range: valid_range(:native, document))
- assert %Document.Range{} = text_edit.range
-
- assert {:ok, converted} = to_lsp(text_edit, uri)
- assert %Types.Range{} = converted.range
- assert %Types.Position{} = converted.range.start
- assert %Types.Position{} = converted.range.end
- end
- end
-
- describe "to_native/2" do
- setup [:with_an_open_file]
-
- test "converts to edits", %{uri: uri} do
- text_edit = Types.TextEdit.new(new_text: "Hi", range: valid_range(:lsp))
-
- {:ok, converted} = to_native(text_edit, uri)
-
- assert %Document.Edit{} = converted
- assert %Document.Range{} = converted.range
- assert %Document.Position{} = converted.range.start
- assert %Document.Position{} = converted.range.end
- assert "Hi" == converted.text
- end
-
- test "fails conversion gracefully", %{uri: uri} do
- edit =
- Types.TextEdit.new(
- new_text: "hi",
- range: range(:lsp, position(:lsp, -1, 0), valid_position(:lsp))
- )
-
- assert {:error, {:invalid_range, _}} = to_native(edit, uri)
- end
- end
-end
diff --git a/apps/protocol/test/support/lexical/test/convertible_support.ex b/apps/protocol/test/support/test/convertible_support.ex
similarity index 89%
rename from apps/protocol/test/support/lexical/test/convertible_support.ex
rename to apps/protocol/test/support/test/convertible_support.ex
index 7fa68435..90993dc0 100644
--- a/apps/protocol/test/support/lexical/test/convertible_support.ex
+++ b/apps/protocol/test/support/test/convertible_support.ex
@@ -1,13 +1,13 @@
-defmodule Lexical.Test.Protocol.ConvertibleSupport do
- alias Lexical.Convertible
- alias Lexical.Document
+defmodule Expert.Test.Protocol.ConvertibleSupport do
+ alias Forge.Convertible
+ alias Forge.Document
use ExUnit.CaseTemplate
using do
quote location: :keep do
- alias Lexical.Protocol.Types
- use Lexical.Test.DocumentSupport
+ alias Expert.Protocol.Types
+ use Forge.Test.DocumentSupport
def open_file_contents do
"hello"
diff --git a/apps/protocol/test/support/lexical/test/protocol/fixtures/lsp_protocol.ex b/apps/protocol/test/support/test/protocol/fixtures/lsp_protocol.ex
similarity index 99%
rename from apps/protocol/test/support/lexical/test/protocol/fixtures/lsp_protocol.ex
rename to apps/protocol/test/support/test/protocol/fixtures/lsp_protocol.ex
index 7aaa346f..59ce3ce0 100644
--- a/apps/protocol/test/support/lexical/test/protocol/fixtures/lsp_protocol.ex
+++ b/apps/protocol/test/support/test/protocol/fixtures/lsp_protocol.ex
@@ -1,4 +1,4 @@
-defmodule Lexical.Test.Protocol.Fixtures.LspProtocol do
+defmodule Expert.Test.Protocol.Fixtures.LspProtocol do
def build(module_to_build, opts \\ []) do
true = Code.ensure_loaded?(module_to_build)
diff --git a/apps/remote_control/.credo.exs b/apps/remote_control/.credo.exs
deleted file mode 100644
index fc32fd65..00000000
--- a/apps/remote_control/.credo.exs
+++ /dev/null
@@ -1,3 +0,0 @@
-Code.require_file("../../mix_credo.exs")
-
-Mix.Credo.config(excluded: ["test/fixtures/**/*.ex"])
diff --git a/apps/remote_control/.formatter.exs b/apps/remote_control/.formatter.exs
deleted file mode 100644
index 5427d1aa..00000000
--- a/apps/remote_control/.formatter.exs
+++ /dev/null
@@ -1,32 +0,0 @@
-# Used by "mix format"
-current_directory = Path.dirname(__ENV__.file)
-
-import_deps = [:common]
-
-impossible_to_format = [
- Path.join([
- current_directory,
- "test",
- "fixtures",
- "compilation_errors",
- "lib",
- "compilation_errors.ex"
- ]),
- Path.join([current_directory, "test", "fixtures", "parse_errors", "lib", "parse_errors.ex"])
-]
-
-locals_without_parens = [with_progress: 2, with_progress: 3, defkey: 2, defkey: 3, with_wal: 2]
-
-[
- locals_without_parens: locals_without_parens,
- export: [locals_without_parens: locals_without_parens],
- import_deps: import_deps,
- inputs:
- Enum.flat_map(
- [
- Path.join(current_directory, "*.exs"),
- Path.join(current_directory, "{lib,test}/**/*.{ex,exs}")
- ],
- &Path.wildcard(&1, match_dot: true)
- ) -- impossible_to_format
-]
diff --git a/apps/remote_control/.gitignore b/apps/remote_control/.gitignore
deleted file mode 100644
index 1e24e82d..00000000
--- a/apps/remote_control/.gitignore
+++ /dev/null
@@ -1,26 +0,0 @@
-# The directory Mix will write compiled artifacts to.
-/_build/
-
-# If you run "mix test --cover", coverage assets end up here.
-/cover/
-
-# The directory Mix downloads your dependencies sources to.
-/deps/
-
-# Where third-party dependencies like ExDoc output generated docs.
-/doc/
-
-# Ignore .fetch files in case you like to edit your project deps locally.
-/.fetch
-
-# If the VM crashes, it generates a dump, let's ignore it too.
-erl_crash.dump
-
-# Also ignore archive artifacts (built via "mix archive.build").
-*.ez
-
-# Ignore package tarball (built via "mix hex.build").
-remote_control-*.tar
-
-# Temporary files, for example, from tests.
-/tmp/
diff --git a/apps/remote_control/README.md b/apps/remote_control/README.md
deleted file mode 100644
index 97f8cc8c..00000000
--- a/apps/remote_control/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Lexical.RemoteControl
-
-An application that is injected into the project's virtual machine to provide support for the language server
diff --git a/apps/remote_control/lib/lexical/remote_control.ex b/apps/remote_control/lib/lexical/remote_control.ex
deleted file mode 100644
index 01af7517..00000000
--- a/apps/remote_control/lib/lexical/remote_control.ex
+++ /dev/null
@@ -1,248 +0,0 @@
-defmodule Lexical.RemoteControl do
- @moduledoc """
- The remote control boots another elixir application in a separate VM, injects
- the remote control application into it and allows the language server to execute tasks in the
- context of the remote VM.
- """
-
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Proxy
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.RemoteControl.CodeIntelligence
- alias Lexical.RemoteControl.ProjectNode
-
- require Logger
-
- @excluded_apps [:patch, :nimble_parsec]
- @allowed_apps [:remote_control | Mix.Project.deps_apps()] -- @excluded_apps
-
- defdelegate schedule_compile(force?), to: Proxy
-
- defdelegate compile_document(document), to: Proxy
-
- defdelegate format(document), to: Proxy
-
- defdelegate reindex, to: Proxy
-
- defdelegate index_running?, to: Proxy
-
- defdelegate broadcast(message), to: Proxy
-
- defdelegate expand_alias(segments_or_module, analysis, position), to: RemoteControl.Analyzer
-
- defdelegate list_modules, to: :code, as: :all_available
-
- defdelegate code_actions(document, range, diagnostics, kinds, trigger_kind),
- to: CodeAction,
- as: :for_range
-
- defdelegate complete(env), to: RemoteControl.Completion, as: :elixir_sense_expand
-
- defdelegate complete_struct_fields(analysis, position),
- to: RemoteControl.Completion,
- as: :struct_fields
-
- defdelegate definition(document, position), to: CodeIntelligence.Definition
-
- defdelegate references(analysis, position, include_definitions?),
- to: CodeIntelligence.References
-
- defdelegate modules_with_prefix(prefix), to: RemoteControl.Modules, as: :with_prefix
-
- defdelegate modules_with_prefix(prefix, predicate), to: RemoteControl.Modules, as: :with_prefix
-
- defdelegate docs(module, opts \\ []), to: CodeIntelligence.Docs, as: :for_module
-
- defdelegate register_listener(listener_pid, message_types), to: RemoteControl.Dispatch
-
- defdelegate resolve_entity(analysis, position), to: CodeIntelligence.Entity, as: :resolve
-
- defdelegate struct_definitions, to: CodeIntelligence.Structs, as: :for_project
-
- defdelegate document_symbols(document), to: CodeIntelligence.Symbols, as: :for_document
-
- defdelegate workspace_symbols(query), to: CodeIntelligence.Symbols, as: :for_workspace
-
- def start_link(%Project{} = project) do
- :ok = ensure_epmd_started()
- start_net_kernel(project)
-
- apps_to_start = [:elixir | @allowed_apps] ++ [:runtime_tools]
- node = Project.node_name(project)
-
- with {:ok, node_pid} <- ProjectNode.start(project, glob_paths()),
- :ok <- ensure_apps_started(node, apps_to_start) do
- {:ok, node, node_pid}
- end
- end
-
- def deps_paths do
- case :persistent_term.get({__MODULE__, :deps_paths}, :error) do
- :error ->
- {:ok, deps_paths} =
- RemoteControl.Mix.in_project(fn _ ->
- Mix.Task.run("loadpaths")
- Mix.Project.deps_paths()
- end)
-
- :persistent_term.put({__MODULE__, :deps_paths}, deps_paths)
- deps_paths
-
- deps_paths ->
- deps_paths
- end
- end
-
- def with_lock(lock_type, func) do
- :global.trans({lock_type, self()}, func, [Node.self()])
- end
-
- def project_node? do
- !!:persistent_term.get({__MODULE__, :project}, false)
- end
-
- def get_project do
- :persistent_term.get({__MODULE__, :project}, nil)
- end
-
- def set_project(%Project{} = project) do
- :persistent_term.put({__MODULE__, :project}, project)
- end
-
- defdelegate stop(project), to: ProjectNode
-
- def call(%Project{} = project, m, f, a \\ []) do
- project
- |> Project.node_name()
- |> :erpc.call(m, f, a)
- end
-
- def manager_node_name(%Project{} = project) do
- :"manager-#{Project.name(project)}-#{Project.entropy(project)}@127.0.0.1"
- end
-
- defp start_net_kernel(%Project{} = project) do
- manager = manager_node_name(project)
- :net_kernel.start(manager, %{name_domain: :longnames})
- end
-
- defp ensure_apps_started(node, app_names) do
- Enum.reduce_while(app_names, :ok, fn app_name, _ ->
- case :rpc.call(node, :application, :ensure_all_started, [app_name]) do
- {:ok, _} -> {:cont, :ok}
- error -> {:halt, error}
- end
- end)
- end
-
- defp glob_paths do
- for entry <- :code.get_path(),
- entry_string = List.to_string(entry),
- entry_string != ".",
- Enum.any?(app_globs(), &PathGlob.match?(entry_string, &1, match_dot: true)) do
- entry
- end
- end
-
- def elixir_executable(%Project{} = project) do
- root_path = Project.root_path(project)
-
- {path_result, env} =
- with nil <- version_manager_path_and_env("asdf", root_path),
- nil <- version_manager_path_and_env("mise", root_path),
- nil <- version_manager_path_and_env("rtx", root_path) do
- {File.cd!(root_path, fn -> System.find_executable("elixir") end), System.get_env()}
- end
-
- case path_result do
- nil ->
- {:error, :no_elixir}
-
- executable when is_binary(executable) ->
- {:ok, executable, env}
- end
- end
-
- defp app_globs do
- app_globs = Enum.map(@allowed_apps, fn app_name -> "/**/#{app_name}*/ebin" end)
- ["/**/priv" | app_globs]
- end
-
- defp ensure_epmd_started do
- case System.cmd("epmd", ~w(-daemon)) do
- {"", 0} ->
- :ok
-
- _ ->
- {:error, :epmd_failed}
- end
- end
-
- defp version_manager_path_and_env(manager, root_path) do
- with true <- is_binary(System.find_executable(manager)),
- env = reset_env(manager, root_path),
- {path, 0} <- System.cmd(manager, ~w(which elixir), cd: root_path, env: env) do
- {String.trim(path), env}
- else
- _ ->
- nil
- end
- end
-
- # We launch lexical by asking the version managers to provide an environment,
- # which contains path munging. This initial environment is present in the running
- # VM, and needs to be undone so we can find the correct elixir executable in the project.
- defp reset_env("asdf", _root_path) do
- orig_path = System.get_env("PATH_SAVE", System.get_env("PATH"))
-
- Enum.map(System.get_env(), fn
- {"ASDF_ELIXIR_VERSION", _} -> {"ASDF_ELIXIR_VERSION", nil}
- {"ASDF_ERLANG_VERSION", _} -> {"ASDF_ERLANG_VERSION", nil}
- {"PATH", _} -> {"PATH", orig_path}
- other -> other
- end)
- end
-
- defp reset_env("rtx", root_path) do
- {env, _} = System.cmd("rtx", ~w(env -s bash), cd: root_path)
-
- env
- |> String.trim()
- |> String.split("\n")
- |> Enum.map(fn
- "export " <> key_and_value ->
- [key, value] =
- key_and_value
- |> String.split("=", parts: 2)
- |> Enum.map(&String.trim/1)
-
- {key, value}
-
- _ ->
- nil
- end)
- |> Enum.reject(&is_nil/1)
- end
-
- defp reset_env("mise", root_path) do
- {env, _} = System.cmd("mise", ~w(env -s bash), cd: root_path)
-
- env
- |> String.trim()
- |> String.split("\n")
- |> Enum.map(fn
- "export " <> key_and_value ->
- [key, value] =
- key_and_value
- |> String.split("=", parts: 2)
- |> Enum.map(&String.trim/1)
-
- {key, value}
-
- _ ->
- nil
- end)
- |> Enum.reject(&is_nil/1)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/analyzer/aliases.ex b/apps/remote_control/lib/lexical/remote_control/analyzer/aliases.ex
deleted file mode 100644
index 39987779..00000000
--- a/apps/remote_control/lib/lexical/remote_control/analyzer/aliases.ex
+++ /dev/null
@@ -1,62 +0,0 @@
-defmodule Lexical.RemoteControl.Analyzer.Aliases do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Alias
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Document.Position
-
- @spec at(Analysis.t(), Position.t()) :: %{atom() => module()}
- def at(%Analysis{} = analysis, %Position{} = position) do
- case Analysis.scopes_at(analysis, position) do
- [%Scope{} = scope | _] ->
- scope
- |> Scope.alias_map(position)
- |> Map.new(fn {as, %Alias{} = alias} ->
- {as, Alias.to_module(alias)}
- end)
-
- [] ->
- %{}
- end
- end
-
- @doc """
- Resolves an alias in the context of a line and a scope
- (used internally when calculating imports)
- """
- def resolve_at(%Scope{} = scope, module, line) do
- aliases = Scope.alias_map(scope, line)
-
- case module do
- # unquote(__MODULE__).SubModule
- [{:unquote, _, [{:__MODULE__, _, _}]} | suffix] ->
- resolve_current_module(aliases, suffix)
-
- [{:__MODULE__, _, _} | suffix] ->
- resolve_current_module(aliases, suffix)
-
- [prefix | suffix] ->
- case aliases do
- %{^prefix => _} ->
- current_module = resolve_alias(aliases, prefix, suffix)
-
- Module.concat([current_module | suffix])
-
- _ ->
- Module.concat(module)
- end
- end
- end
-
- defp resolve_current_module(aliases, suffix) do
- resolve_alias(aliases, :__MODULE__, suffix)
- end
-
- defp resolve_alias(aliases, prefix, suffix) do
- current_module =
- aliases
- |> Map.get(prefix)
- |> Alias.to_module()
-
- Module.concat([current_module | suffix])
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/analyzer/requires.ex b/apps/remote_control/lib/lexical/remote_control/analyzer/requires.ex
deleted file mode 100644
index 2faebdf7..00000000
--- a/apps/remote_control/lib/lexical/remote_control/analyzer/requires.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-defmodule Lexical.RemoteControl.Analyzer.Requires do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Require
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Document.Position
-
- def at(%Analysis{} = analysis, %Position{} = position) do
- case Analysis.scopes_at(analysis, position) do
- [%Scope{} = scope | _] ->
- scope.requires
- |> Enum.filter(fn %Require{} = require ->
- require_end = require.range.end
-
- if require_end.line == position.line do
- require_end.character <= position.character
- else
- require_end.line < position.line
- end
- end)
- |> Enum.uniq_by(& &1.as)
-
- _ ->
- []
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/api.ex b/apps/remote_control/lib/lexical/remote_control/api.ex
deleted file mode 100644
index c995646a..00000000
--- a/apps/remote_control/lib/lexical/remote_control/api.ex
+++ /dev/null
@@ -1,138 +0,0 @@
-defmodule Lexical.RemoteControl.Api do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Env
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeIntelligence
-
- require Logger
-
- def schedule_compile(%Project{} = project, force?) do
- RemoteControl.call(project, RemoteControl, :schedule_compile, [force?])
- end
-
- def compile_document(%Project{} = project, %Document{} = document) do
- RemoteControl.call(project, RemoteControl, :compile_document, [document])
- end
-
- def expand_alias(
- %Project{} = project,
- segments_or_module,
- %Analysis{} = analysis,
- %Position{} = position
- ) do
- RemoteControl.call(project, RemoteControl, :expand_alias, [
- segments_or_module,
- analysis,
- position
- ])
- end
-
- def list_modules(%Project{} = project) do
- RemoteControl.call(project, RemoteControl, :list_modules)
- end
-
- def format(%Project{} = project, %Document{} = document) do
- RemoteControl.call(project, RemoteControl, :format, [document])
- end
-
- def code_actions(
- %Project{} = project,
- %Document{} = document,
- %Range{} = range,
- diagnostics,
- kinds,
- trigger_kind
- ) do
- RemoteControl.call(project, RemoteControl, :code_actions, [
- document,
- range,
- diagnostics,
- kinds,
- trigger_kind
- ])
- end
-
- def complete(%Project{} = project, %Env{} = env) do
- Logger.info("Completion for #{inspect(env.position)}")
- RemoteControl.call(project, RemoteControl, :complete, [env])
- end
-
- def complete_struct_fields(%Project{} = project, %Analysis{} = analysis, %Position{} = position) do
- RemoteControl.call(project, RemoteControl, :complete_struct_fields, [
- analysis,
- position
- ])
- end
-
- def definition(%Project{} = project, %Document{} = document, %Position{} = position) do
- RemoteControl.call(project, RemoteControl, :definition, [document, position])
- end
-
- def references(
- %Project{} = project,
- %Analysis{} = analysis,
- %Position{} = position,
- include_definitions?
- ) do
- RemoteControl.call(project, RemoteControl, :references, [
- analysis,
- position,
- include_definitions?
- ])
- end
-
- def modules_with_prefix(%Project{} = project, prefix)
- when is_binary(prefix) or is_atom(prefix) do
- RemoteControl.call(project, RemoteControl, :modules_with_prefix, [prefix])
- end
-
- def modules_with_prefix(%Project{} = project, prefix, predicate)
- when is_binary(prefix) or is_atom(prefix) do
- RemoteControl.call(project, RemoteControl, :modules_with_prefix, [prefix, predicate])
- end
-
- @spec docs(Project.t(), module()) :: {:ok, CodeIntelligence.Docs.t()} | {:error, any()}
- def docs(%Project{} = project, module, opts \\ []) when is_atom(module) do
- RemoteControl.call(project, RemoteControl, :docs, [module, opts])
- end
-
- def register_listener(%Project{} = project, listener_pid, message_types)
- when is_pid(listener_pid) and is_list(message_types) do
- RemoteControl.call(project, RemoteControl, :register_listener, [
- listener_pid,
- message_types
- ])
- end
-
- def broadcast(%Project{} = project, message) do
- RemoteControl.call(project, RemoteControl, :broadcast, [message])
- end
-
- def reindex(%Project{} = project) do
- RemoteControl.call(project, RemoteControl, :reindex, [])
- end
-
- def index_running?(%Project{} = project) do
- RemoteControl.call(project, RemoteControl, :index_running?, [])
- end
-
- def resolve_entity(%Project{} = project, %Analysis{} = analysis, %Position{} = position) do
- RemoteControl.call(project, RemoteControl, :resolve_entity, [analysis, position])
- end
-
- def struct_definitions(%Project{} = project) do
- RemoteControl.call(project, RemoteControl, :struct_definitions, [])
- end
-
- def document_symbols(%Project{} = project, %Document{} = document) do
- RemoteControl.call(project, RemoteControl, :document_symbols, [document])
- end
-
- def workspace_symbols(%Project{} = project, query) do
- RemoteControl.call(project, RemoteControl, :workspace_symbols, [query])
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/application.ex b/apps/remote_control/lib/lexical/remote_control/application.ex
deleted file mode 100644
index 907fc43d..00000000
--- a/apps/remote_control/lib/lexical/remote_control/application.ex
+++ /dev/null
@@ -1,37 +0,0 @@
-defmodule Lexical.RemoteControl.Application do
- @moduledoc false
-
- alias Lexical.RemoteControl
-
- use Application
- require Logger
-
- @impl true
- def start(_type, _args) do
- children =
- if RemoteControl.project_node?() do
- [
- RemoteControl.Api.Proxy,
- RemoteControl.Commands.Reindex,
- RemoteControl.Module.Loader,
- {RemoteControl.Dispatch, progress: true},
- RemoteControl.ModuleMappings,
- RemoteControl.Build,
- RemoteControl.Build.CaptureServer,
- RemoteControl.Plugin.Runner.Supervisor,
- RemoteControl.Plugin.Runner.Coordinator,
- RemoteControl.Search.Store.Backends.Ets,
- {RemoteControl.Search.Store,
- [
- &RemoteControl.Search.Indexer.create_index/1,
- &RemoteControl.Search.Indexer.update_index/2
- ]}
- ]
- else
- []
- end
-
- opts = [strategy: :one_for_one, name: Lexical.RemoteControl.Supervisor]
- Supervisor.start_link(children, opts)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/document.ex b/apps/remote_control/lib/lexical/remote_control/build/document.ex
deleted file mode 100644
index d1e3a41f..00000000
--- a/apps/remote_control/lib/lexical/remote_control/build/document.ex
+++ /dev/null
@@ -1,23 +0,0 @@
-defmodule Lexical.RemoteControl.Build.Document do
- alias Lexical.Document
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.Document.Compilers
- alias Lexical.RemoteControl.Build.Isolation
-
- @compilers [Compilers.Config, Compilers.Elixir, Compilers.EEx, Compilers.HEEx, Compilers.NoOp]
-
- def compile(%Document{} = document) do
- compiler = Enum.find(@compilers, & &1.recognizes?(document))
- compile_fun = fn -> compiler.compile(document) end
-
- case Isolation.invoke(compile_fun) do
- {:ok, result} ->
- result
-
- {:error, {exception, stack}} ->
- diagnostic = Build.Error.error_to_diagnostic(document, exception, stack, nil)
- diagnostics = Build.Error.refine_diagnostics([diagnostic])
- {:error, diagnostics}
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/no_op.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/no_op.ex
deleted file mode 100644
index c04bc91a..00000000
--- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/no_op.ex
+++ /dev/null
@@ -1,14 +0,0 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.NoOp do
- @moduledoc """
- A no-op, catch-all compiler. Always enabled, recognizes everything and returns no errors
- """
- alias Lexical.RemoteControl.Build.Document
-
- @behaviour Document.Compiler
-
- def recognizes?(_), do: true
-
- def enabled?, do: true
-
- def compile(_), do: {:ok, []}
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/quoted.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/quoted.ex
deleted file mode 100644
index 94551b50..00000000
--- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/quoted.ex
+++ /dev/null
@@ -1,282 +0,0 @@
-defmodule Lexical.RemoteControl.Build.Document.Compilers.Quoted do
- alias Elixir.Features
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.ModuleMappings
-
- import Lexical.RemoteControl.Build.CaptureIO, only: [capture_io: 2]
-
- def compile(%Document{} = document, quoted_ast, compiler_name) do
- prepare_compile(document.path)
-
- quoted_ast =
- if document.language_id == "elixir-script" do
- wrap_top_level_forms(quoted_ast)
- else
- quoted_ast
- end
-
- {status, diagnostics} =
- if Features.with_diagnostics?() do
- do_compile(quoted_ast, document)
- else
- do_compile_and_capture_io(quoted_ast, document)
- end
-
- {status, Enum.map(diagnostics, &replace_source(&1, compiler_name))}
- end
-
- defp do_compile(quoted_ast, document) do
- old_modules = ModuleMappings.modules_in_file(document.path)
-
- case compile_quoted_with_diagnostics(quoted_ast, document.path) do
- {{:ok, modules}, []} ->
- purge_removed_modules(old_modules, modules)
- {:ok, []}
-
- {{:ok, modules}, all_errors_and_warnings} ->
- purge_removed_modules(old_modules, modules)
-
- diagnostics =
- document
- |> Build.Error.diagnostics_from_mix(all_errors_and_warnings)
- |> Build.Error.refine_diagnostics()
-
- {:ok, diagnostics}
-
- {{:exception, exception, stack, quoted_ast}, all_errors_and_warnings} ->
- converted = Build.Error.error_to_diagnostic(document, exception, stack, quoted_ast)
- maybe_diagnostics = Build.Error.diagnostics_from_mix(document, all_errors_and_warnings)
-
- diagnostics =
- [converted | maybe_diagnostics]
- |> Enum.reverse()
- |> Build.Error.refine_diagnostics()
-
- {:error, diagnostics}
- end
- end
-
- defp do_compile_and_capture_io(quoted_ast, document) do
- # credo:disable-for-next-line Credo.Check.Design.TagTODO
- # TODO: remove this function once we drop support for Elixir 1.14
- old_modules = ModuleMappings.modules_in_file(document.path)
- compile = fn -> safe_compile_quoted(quoted_ast, document.path) end
-
- case capture_io(:stderr, compile) do
- {captured_messages, {:error, {:exception, {exception, _inner_stack}, stack}}} ->
- error = Build.Error.error_to_diagnostic(document, exception, stack, [])
- diagnostics = Build.Error.message_to_diagnostic(document, captured_messages)
-
- {:error, [error | diagnostics]}
-
- {captured_messages, {:exception, exception, stack, quoted_ast}} ->
- error = Build.Error.error_to_diagnostic(document, exception, stack, quoted_ast)
- diagnostics = Build.Error.message_to_diagnostic(document, captured_messages)
- refined = Build.Error.refine_diagnostics([error | diagnostics])
-
- {:error, refined}
-
- {"", {:ok, modules}} ->
- purge_removed_modules(old_modules, modules)
- {:ok, []}
-
- {captured_warnings, {:ok, modules}} ->
- purge_removed_modules(old_modules, modules)
-
- diagnostics =
- document
- |> Build.Error.message_to_diagnostic(captured_warnings)
- |> Build.Error.refine_diagnostics()
-
- {:ok, diagnostics}
- end
- end
-
- defp prepare_compile(path) do
- # If we're compiling a mix.exs file, the after compile callback from
- # `use Mix.Project` will blow up if we add the same project to the project stack
- # twice. Preemptively popping it prevents that error from occurring.
- if Path.basename(path) == "mix.exs" do
- Mix.ProjectStack.pop()
- end
-
- Mix.Task.run(:loadconfig)
- end
-
- @dialyzer {:nowarn_function, compile_quoted_with_diagnostics: 2}
-
- defp compile_quoted_with_diagnostics(quoted_ast, path) do
- # Using apply to prevent a compile warning on elixir < 1.15
- # credo:disable-for-next-line
- apply(Code, :with_diagnostics, [fn -> safe_compile_quoted(quoted_ast, path) end])
- end
-
- defp safe_compile_quoted(quoted_ast, path) do
- try do
- {:ok, Code.compile_quoted(quoted_ast, path)}
- rescue
- exception ->
- {filled_exception, stack} = Exception.blame(:error, exception, __STACKTRACE__)
- {:exception, filled_exception, stack, quoted_ast}
- end
- end
-
- defp purge_removed_modules(old_modules, new_modules) do
- new_modules = MapSet.new(new_modules, fn {module, _bytecode} -> module end)
- old_modules = MapSet.new(old_modules)
-
- old_modules
- |> MapSet.difference(new_modules)
- |> Enum.each(fn to_remove ->
- :code.purge(to_remove)
- :code.delete(to_remove)
- end)
- end
-
- defp replace_source(result, source) do
- Map.put(result, :source, source)
- end
-
- @doc false
- def wrap_top_level_forms({:__block__, meta, nodes}) do
- {chunks, _vars} =
- nodes
- |> Enum.chunk_by(&should_wrap?/1)
- |> Enum.with_index()
- |> Enum.flat_map_reduce([], fn {[node | _] = nodes, i}, vars ->
- if should_wrap?(node) do
- {wrapped, vars} = wrap_nodes(nodes, vars, i)
- {[wrapped], vars}
- else
- {nodes, vars}
- end
- end)
-
- {:__block__, meta, chunks}
- end
-
- def wrap_top_level_forms(ast) do
- wrap_top_level_forms({:__block__, [], [ast]})
- end
-
- defp wrap_nodes(nodes, vars, i) do
- module_name = :"lexical_wrapper_#{i}"
- {nodes, new_vars} = suppress_and_extract_vars(nodes)
-
- quoted =
- quote do
- defmodule unquote(module_name) do
- def __lexical_wrapper__([unquote_splicing(vars)]) do
- (unquote_splicing(nodes))
- end
- end
- end
-
- {quoted, new_vars ++ vars}
- end
-
- @allowed_top_level [:defmodule, :alias, :import, :require, :use]
- defp should_wrap?({allowed, _, _}) when allowed in @allowed_top_level, do: false
- defp should_wrap?(_), do: true
-
- @doc false
- # This function replaces all unused variables with `_` in order
- # to suppress warnings while accumulating those vars. The approach
- # here is bottom-up, starting from the last expression and working
- # back to the beginning:
- #
- # - If the expression is an assignment, collect vars from the LHS,
- # replacing them with `_` if they haven't been referenced, then
- # collect references from the RHS.
- # - If the expression isn't an assignment, just collect references.
- # - Note that pinned vars on the LHS of an assignment are references.
- #
- def suppress_and_extract_vars(quoted)
-
- def suppress_and_extract_vars(list) when is_list(list) do
- list
- |> Enum.reverse()
- |> do_suppress_and_extract_vars()
- end
-
- def suppress_and_extract_vars({:__block__, meta, nodes}) do
- {nodes, vars} = suppress_and_extract_vars(nodes)
- {{:__block__, meta, nodes}, vars}
- end
-
- def suppress_and_extract_vars(expr) do
- {[expr], vars} = suppress_and_extract_vars([expr])
- {expr, vars}
- end
-
- defp do_suppress_and_extract_vars(list, acc \\ [], references \\ [], vars \\ [])
-
- defp do_suppress_and_extract_vars([expr | rest], acc, references, vars) do
- {expr, new_vars} = suppress_and_extract_vars_from_expr(expr, references)
- new_references = extract_references_from_expr(expr)
-
- do_suppress_and_extract_vars(
- rest,
- [expr | acc],
- new_references ++ references,
- new_vars ++ vars
- )
- end
-
- defp do_suppress_and_extract_vars([], acc, _references, vars) do
- {acc, vars}
- end
-
- defp suppress_and_extract_vars_from_expr({:=, meta, [left, right]}, references) do
- {left, left_vars} =
- Ast.prewalk_vars(left, [], fn
- {:^, _, _} = pinned, acc ->
- {pinned, acc}
-
- {name, meta, context} = var, acc ->
- if Ast.has_var?(references, name, context) do
- {var, [{name, [], context} | acc]}
- else
- {{:_, meta, nil}, [var | acc]}
- end
- end)
-
- {right, right_vars} = suppress_and_extract_vars_from_expr(right, references)
-
- {{:=, meta, [left, right]}, left_vars ++ right_vars}
- end
-
- defp suppress_and_extract_vars_from_expr(other, _references) do
- {other, []}
- end
-
- defp extract_references_from_expr({:=, _, [left, right]}) do
- {_, left_references} =
- Ast.prewalk_vars(left, [], fn
- {:^, _, [referenced_var]}, acc ->
- {:ok, [referenced_var | acc]}
-
- node, acc ->
- {node, acc}
- end)
-
- right_references = extract_references_from_expr(right)
-
- left_references ++ right_references
- end
-
- defp extract_references_from_expr(expr) do
- {_, references} =
- Ast.prewalk_vars(expr, [], fn
- {:^, _, _}, acc ->
- {:ok, acc}
-
- var, acc ->
- {:ok, [var | acc]}
- end)
-
- references
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/error/location.ex b/apps/remote_control/lib/lexical/remote_control/build/error/location.ex
deleted file mode 100644
index d8f4e92c..00000000
--- a/apps/remote_control/lib/lexical/remote_control/build/error/location.ex
+++ /dev/null
@@ -1,97 +0,0 @@
-defmodule Lexical.RemoteControl.Build.Error.Location do
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Plugin.V1.Diagnostic.Result
-
- require Logger
-
- def stack_to_position([{_, target, _, _} | rest])
- when target not in [:__FILE__, :__MODULE__] do
- stack_to_position(rest)
- end
-
- def stack_to_position([{_, target, _, context} | _rest])
- when target in [:__FILE__, :__MODULE__] do
- context_to_position(context)
- end
-
- def stack_to_position([]) do
- nil
- end
-
- def context_to_position(context) do
- case {context[:line], context[:column]} do
- {nil, nil} ->
- Logger.error("Invalid context: #{inspect(context)}")
- nil
-
- {line, nil} ->
- line
-
- {line, column} ->
- position(line, column)
- end
- end
-
- def position(line) do
- line
- end
-
- def position(line, column) do
- {line, column}
- end
-
- def fetch_range(%Document{} = document, context) do
- case {context[:end_line], context[:end_column]} do
- {nil, _} ->
- :error
-
- {end_line, end_column} ->
- {line, column} = {context[:line], context[:column]}
- {:ok, range(document, line, column, end_line, end_column)}
- end
- end
-
- def range(%Document{} = document, {line, column}, {end_line, end_column}) do
- range(document, line, column, end_line, end_column)
- end
-
- def range(%Document{} = document, line, column, end_line, end_column) do
- start_position = Position.new(document, line, column)
- end_position = Position.new(document, end_line, end_column)
- Range.new(start_position, end_position)
- end
-
- def uniq(diagnostics) do
- exacts = Enum.filter(diagnostics, fn diagnostic -> match?(%Range{}, diagnostic.position) end)
-
- extract_line = fn
- %Result{position: {line, _column}} -> line
- %Result{position: line} -> line
- end
-
- # Note: Sometimes error and warning appear on one line at the same time
- # So we need to uniq by line and severity,
- # and :error is always more important than :warning
- extract_line_and_severity = &{extract_line.(&1), &1.severity}
-
- filtered =
- diagnostics
- |> Enum.filter(fn diagnostic -> not match?(%Range{}, diagnostic.position) end)
- |> Enum.sort_by(extract_line_and_severity)
- |> Enum.uniq_by(extract_line)
- |> reject_zeroth_line()
-
- exacts ++ filtered
- end
-
- defp reject_zeroth_line(diagnostics) do
- # Since 1.15, Elixir has some nonsensical error on line 0,
- # e.g.: Can't compile this file
- # We can simply ignore it, as there is a more accurate one
- Enum.reject(diagnostics, fn diagnostic ->
- diagnostic.position == 0
- end)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/error/parse.ex b/apps/remote_control/lib/lexical/remote_control/build/error/parse.ex
deleted file mode 100644
index e0241601..00000000
--- a/apps/remote_control/lib/lexical/remote_control/build/error/parse.ex
+++ /dev/null
@@ -1,198 +0,0 @@
-defmodule Lexical.RemoteControl.Build.Error.Parse do
- alias Lexical.Document
- alias Lexical.Document.Range
- alias Lexical.Plugin.V1.Diagnostic.Result
- alias Lexical.RemoteControl.Build.Error.Location
-
- @elixir_source "Elixir"
-
- # Parse errors happen during Code.string_to_quoted and are raised as SyntaxErrors, and TokenMissingErrors.
- def to_diagnostics(
- %Document{} = source,
- context,
- {_error, detail} = message_info,
- token
- )
- when is_binary(detail) do
- # NOTE: mainly for `unexpected token` errors `< 1.16`,
- # its details consist of multiple lines, so it is a tuple.
- detail_diagnostics = detail_diagnostics(source, detail)
- error = message_info_to_binary(message_info, token)
- error_diagnostics = to_diagnostics(source, context, error, token)
- Location.uniq(detail_diagnostics ++ error_diagnostics)
- end
-
- def to_diagnostics(%Document{} = source, context, message_info, token)
- when is_exception(message_info) do
- to_diagnostics(source, context, Exception.message(message_info), token)
- end
-
- def to_diagnostics(%Document{} = source, context, message_info, token) do
- {start_line_fn, end_line_fn} =
- if Features.details_in_context?() do
- {&build_end_line_diagnostics_from_context/4, &build_start_line_diagnostics_from_context/4}
- else
- {&build_start_line_diagnostics/4, &build_end_line_diagnostics/4}
- end
-
- parse_error_diagnostic_functions = [
- end_line_fn,
- start_line_fn,
- &build_hint_diagnostics/4
- ]
-
- parse_error_diagnostic_functions
- |> Enum.flat_map(& &1.(source, context, message_info, token))
- |> Location.uniq()
- end
-
- @missing_terminator_pattern ~r/missing terminator: \w+/
- defp build_end_line_diagnostics_from_context(
- %Document{} = source,
- context,
- message_info,
- token
- ) do
- message =
- cond do
- String.starts_with?(message_info, "unexpected") ->
- ~s/#{message_info}#{token}, expected `#{context[:expected_delimiter]}`/
-
- Regex.match?(@missing_terminator_pattern, message_info) ->
- [message] = Regex.run(@missing_terminator_pattern, message_info)
- message
-
- true ->
- "#{message_info}#{token}"
- end
-
- case Location.fetch_range(source, context) do
- {:ok, %Range{end: end_pos}} ->
- [
- Result.new(
- source.uri,
- {end_pos.line, end_pos.character},
- message,
- :error,
- @elixir_source
- )
- ]
-
- :error ->
- []
- end
- end
-
- defp build_end_line_diagnostics(%Document{} = source, context, message_info, token) do
- [end_line_message | _] = String.split(message_info, "\n")
-
- message =
- if String.ends_with?(end_line_message, token) do
- end_line_message
- else
- end_line_message <> token
- end
-
- diagnostic =
- Result.new(source.uri, Location.context_to_position(context), message, :error, "Elixir")
-
- [diagnostic]
- end
-
- defp build_start_line_diagnostics_from_context(
- %Document{} = source,
- context,
- message_info,
- token
- ) do
- opening_delimiter = context[:opening_delimiter]
-
- if opening_delimiter do
- build_opening_delimiter_diagnostics(source, context, opening_delimiter)
- else
- build_syntax_error_diagnostic(source, context, message_info, token)
- end
- end
-
- defp build_opening_delimiter_diagnostics(%Document{} = source, context, opening_delimiter) do
- message =
- ~s/The `#{opening_delimiter}` here is missing terminator `#{context[:expected_delimiter]}`/
-
- opening_delimiter_length = opening_delimiter |> Atom.to_string() |> String.length()
-
- pos =
- Location.range(
- source,
- context[:line],
- context[:column],
- context[:line],
- context[:column] + opening_delimiter_length
- )
-
- result = Result.new(source.uri, pos, message, :error, @elixir_source)
- [result]
- end
-
- defp build_syntax_error_diagnostic(%Document{} = source, context, message_info, token) do
- message = "#{message_info}#{token}"
- pos = Location.position(context[:line], context[:column])
- result = Result.new(source.uri, pos, message, :error, @elixir_source)
- [result]
- end
-
- @start_line_regex ~r/(\w+) \(for (.*) starting at line (\d+)\)/
- defp build_start_line_diagnostics(%Document{} = source, _context, message_info, _token) do
- case Regex.run(@start_line_regex, message_info) do
- [_, missing, token, start_line] ->
- message =
- ~s[The #{format_token(token)} here is missing terminator #{format_token(missing)}]
-
- position = String.to_integer(start_line)
- result = Result.new(source.uri, position, message, :error, @elixir_source)
- [result]
-
- _ ->
- []
- end
- end
-
- @hint_regex ~r/(HINT:|hint:\e\[0m|hint:)( .*on line (\d+).*)/m
- defp build_hint_diagnostics(%Document{} = source, _context, message_info, _token) do
- case Regex.run(@hint_regex, message_info) do
- [_whole_message, _hint, message, hint_line] ->
- message = "HINT:" <> String.replace(message, ~r/on line \d+/, "here")
- position = String.to_integer(hint_line)
- result = Result.new(source.uri, position, message, :error, @elixir_source)
- [result]
-
- _ ->
- []
- end
- end
-
- defp message_info_to_binary({header, footer}, token) do
- header <> token <> footer
- end
-
- @detail_location_re ~r/at line (\d+)/
- defp detail_diagnostics(%Document{} = source, detail) do
- case Regex.scan(@detail_location_re, detail) do
- [[matched, line_number]] ->
- line_number = String.to_integer(line_number)
- message = String.replace(detail, matched, "here")
- result = Result.new(source.uri, line_number, message, :error, @elixir_source)
- [result]
-
- _ ->
- []
- end
- end
-
- defp format_token(token) when is_binary(token) do
- if String.contains?(token, "\"") do
- String.replace(token, "\"", "`")
- else
- "`#{token}`"
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/project.ex b/apps/remote_control/lib/lexical/remote_control/build/project.ex
deleted file mode 100644
index 3f72999f..00000000
--- a/apps/remote_control/lib/lexical/remote_control/build/project.ex
+++ /dev/null
@@ -1,130 +0,0 @@
-defmodule Lexical.RemoteControl.Build.Project do
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.Isolation
- alias Lexical.RemoteControl.Plugin
- alias Mix.Task.Compiler.Diagnostic
-
- use RemoteControl.Progress
- require Logger
-
- def compile(%Project{} = project, initial?) do
- RemoteControl.Mix.in_project(fn _ ->
- Mix.Task.clear()
-
- prepare_for_project_build(initial?)
-
- compile_fun = fn ->
- Mix.Task.clear()
-
- with_progress building_label(project), fn ->
- result = compile_in_isolation()
- Mix.Task.run(:loadpaths)
- result
- end
- end
-
- case compile_fun.() do
- {:error, diagnostics} ->
- diagnostics =
- diagnostics
- |> List.wrap()
- |> Build.Error.refine_diagnostics()
-
- {:error, diagnostics}
-
- {status, diagnostics} when status in [:ok, :noop] ->
- Logger.info(
- "Compile completed with status #{status} " <>
- "Produced #{length(diagnostics)} diagnostics " <>
- inspect(diagnostics)
- )
-
- Build.Error.refine_diagnostics(diagnostics)
- end
- end)
- end
-
- defp compile_in_isolation do
- compile_fun = fn -> Mix.Task.run(:compile, mix_compile_opts()) end
-
- case Isolation.invoke(compile_fun) do
- {:ok, result} ->
- result
-
- {:error, {exception, [{_mod, _fun, _arity, meta} | _]}} ->
- diagnostic = %Diagnostic{
- file: Keyword.get(meta, :file),
- severity: :error,
- message: Exception.message(exception),
- compiler_name: "Elixir",
- position: Keyword.get(meta, :line, 1)
- }
-
- {:error, [diagnostic]}
- end
- end
-
- defp prepare_for_project_build(false = _initial?) do
- :ok
- end
-
- defp prepare_for_project_build(true = _initial?) do
- if connected_to_internet?() do
- with_progress "mix local.hex", fn ->
- Mix.Task.run("local.hex", ~w(--force --if-missing))
- end
-
- with_progress "mix local.rebar", fn ->
- Mix.Task.run("local.rebar", ~w(--force --if-missing))
- end
-
- with_progress "mix deps.get", fn ->
- Mix.Task.run("deps.get")
- end
- else
- Logger.warning("Could not connect to hex.pm, dependencies will not be fetched")
- end
-
- with_progress "mix loadconfig", fn ->
- Mix.Task.run(:loadconfig)
- end
-
- unless Elixir.Features.compile_keeps_current_directory?() do
- with_progress "mix deps.compile", fn ->
- Mix.Task.run("deps.safe_compile", ~w(--skip-umbrella-children))
- end
- end
-
- with_progress "loading plugins", fn ->
- Plugin.Discovery.run()
- end
- end
-
- defp connected_to_internet? do
- # While there's no perfect way to check if a computer is connected to the internet,
- # it seems reasonable to gate pulling dependenices on a resolution check for hex.pm.
- # Yes, it's entirely possible that the DNS server is local, and that the entry is in cache,
- # but that's an edge case, and the build will just time out anyways.
- case :inet_res.getbyname(~c"hex.pm", :a, 250) do
- {:ok, _} -> true
- _ -> false
- end
- end
-
- def building_label(%Project{} = project) do
- "Building #{Project.display_name(project)}"
- end
-
- defp mix_compile_opts do
- ~w(
- --return-errors
- --ignore-module-conflict
- --all-warnings
- --docs
- --debug-info
- --no-protocol-consolidation
- )
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/build/state.ex b/apps/remote_control/lib/lexical/remote_control/build/state.ex
deleted file mode 100644
index baa7aa35..00000000
--- a/apps/remote_control/lib/lexical/remote_control/build/state.ex
+++ /dev/null
@@ -1,261 +0,0 @@
-defmodule Lexical.RemoteControl.Build.State do
- alias Elixir.Features
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Plugin
- alias Lexical.VM.Versions
-
- require Logger
-
- import Messages
-
- use RemoteControl.Progress
-
- defstruct project: nil,
- build_number: 0,
- uri_to_document: %{},
- project_compile: :none
-
- def new(%Project{} = project) do
- %__MODULE__{project: project}
- end
-
- def on_timeout(%__MODULE__{} = state) do
- new_state =
- case state.project_compile do
- :none -> state
- :force -> compile_project(state, true)
- :normal -> compile_project(state, false)
- end
-
- # We need to compile the individual documents even after the project is
- # compiled because they might have unsaved changes, and we want that state
- # to be the latest state of the project.
- new_state =
- Enum.reduce(new_state.uri_to_document, state, fn {_uri, document}, state ->
- compile_file(state, document)
- end)
-
- %__MODULE__{new_state | uri_to_document: %{}, project_compile: :none}
- end
-
- def on_file_compile(%__MODULE__{} = state, %Document{} = document) do
- %__MODULE__{
- state
- | uri_to_document: Map.put(state.uri_to_document, document.uri, document)
- }
- end
-
- def on_project_compile(%__MODULE__{} = state, force?) do
- if force? do
- %__MODULE__{state | project_compile: :force}
- else
- %__MODULE__{state | project_compile: :normal}
- end
- end
-
- def ensure_build_directory(%__MODULE__{} = state) do
- # If the project directory isn't there, for some reason the main build fails, so we create it here
- # to ensure that the build will succeed.
- project = state.project
- build_path = RemoteControl.Build.path(project)
-
- unless Versions.compatible?(build_path) do
- Logger.info("Build path #{build_path} was compiled on a previous erlang version. Deleting")
-
- if File.exists?(build_path) do
- File.rm_rf(build_path)
- end
- end
-
- maybe_delete_old_builds(project)
-
- unless File.exists?(build_path) do
- File.mkdir_p!(build_path)
- Versions.write(build_path)
- end
- end
-
- defp compile_project(%__MODULE__{} = state, initial?) do
- state = increment_build_number(state)
- project = state.project
-
- Build.with_lock(fn ->
- compile_requested_message =
- project_compile_requested(project: project, build_number: state.build_number)
-
- RemoteControl.broadcast(compile_requested_message)
- {elapsed_us, result} = :timer.tc(fn -> Build.Project.compile(project, initial?) end)
- elapsed_ms = to_ms(elapsed_us)
-
- {compile_message, diagnostics} =
- case result do
- :ok ->
- message = project_compiled(status: :success, project: project, elapsed_ms: elapsed_ms)
-
- {message, []}
-
- {:ok, diagnostics} ->
- message = project_compiled(status: :success, project: project, elapsed_ms: elapsed_ms)
-
- {message, List.wrap(diagnostics)}
-
- {:error, diagnostics} ->
- message = project_compiled(status: :error, project: project, elapsed_ms: elapsed_ms)
-
- {message, List.wrap(diagnostics)}
- end
-
- diagnostics_message =
- project_diagnostics(
- project: project,
- build_number: state.build_number,
- diagnostics: diagnostics
- )
-
- RemoteControl.broadcast(compile_message)
- RemoteControl.broadcast(diagnostics_message)
- Plugin.diagnose(project, state.build_number)
- end)
-
- state
- end
-
- def compile_file(%__MODULE__{} = state, %Document{} = document) do
- state = increment_build_number(state)
- project = state.project
-
- Build.with_lock(fn ->
- RemoteControl.broadcast(file_compile_requested(uri: document.uri))
-
- safe_compile_func = fn ->
- RemoteControl.Mix.in_project(fn _ -> Build.Document.compile(document) end)
- end
-
- {elapsed_us, result} = :timer.tc(fn -> safe_compile_func.() end)
-
- elapsed_ms = to_ms(elapsed_us)
-
- {compile_message, diagnostics} =
- case result do
- {:ok, diagnostics} ->
- message =
- file_compiled(
- project: project,
- build_number: state.build_number,
- status: :success,
- uri: document.uri,
- elapsed_ms: elapsed_ms
- )
-
- {message, diagnostics}
-
- {:error, diagnostics} ->
- message =
- file_compiled(
- project: project,
- build_number: state.build_number,
- status: :error,
- uri: document.uri,
- elapsed_ms: elapsed_ms
- )
-
- {message, diagnostics}
- end
-
- diagnostics =
- file_diagnostics(
- project: project,
- build_number: state.build_number,
- uri: document.uri,
- diagnostics: List.wrap(diagnostics)
- )
-
- RemoteControl.broadcast(compile_message)
- RemoteControl.broadcast(diagnostics)
- Plugin.diagnose(project, state.build_number, document)
- end)
-
- state
- end
-
- def set_compiler_options do
- Code.compiler_options(
- parser_options: parser_options(),
- tracers: [RemoteControl.Compilation.Tracer]
- )
-
- :ok
- end
-
- def mix_compile_opts(initial?) do
- opts = ~w(
- --return-errors
- --ignore-module-conflict
- --all-warnings
- --docs
- --debug-info
- --no-protocol-consolidation
- )
-
- if initial? do
- ["--force " | opts]
- else
- opts
- end
- end
-
- def building_label(%Project{} = project) do
- "Building #{Project.display_name(project)}"
- end
-
- defp to_ms(microseconds) do
- microseconds / 1000
- end
-
- defp parser_options do
- [columns: true, token_metadata: true]
- end
-
- defp increment_build_number(%__MODULE__{} = state) do
- %__MODULE__{state | build_number: state.build_number + 1}
- end
-
- @two_month_seconds 86_400 * 31 * 2
- defp maybe_delete_old_builds(%Project{} = project) do
- build_root = Project.build_path(project)
- two_months_ago = System.system_time(:second) - @two_month_seconds
-
- case File.ls(build_root) do
- {:ok, entries} ->
- for file_name <- entries,
- absolute_path = Path.join(build_root, file_name),
- File.dir?(absolute_path),
- newest_beam_mtime(absolute_path) <=
- two_months_ago do
- File.rm_rf!(absolute_path)
- end
-
- _ ->
- :ok
- end
- end
-
- defp newest_beam_mtime(directory) do
- directory
- |> Path.join("**/*.beam")
- |> Path.wildcard()
- |> then(fn
- [] ->
- 0
-
- beam_files ->
- beam_files
- |> Enum.map(&File.stat!(&1, time: :posix).mtime)
- |> Enum.max()
- end)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action.ex b/apps/remote_control/lib/lexical/remote_control/code_action.ex
deleted file mode 100644
index 6b0f0735..00000000
--- a/apps/remote_control/lib/lexical/remote_control/code_action.ex
+++ /dev/null
@@ -1,66 +0,0 @@
-defmodule Lexical.RemoteControl.CodeAction do
- alias Lexical.Document
- alias Lexical.Document.Changes
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.CodeAction.Diagnostic
- alias Lexical.RemoteControl.CodeAction.Handlers
-
- defstruct [:title, :kind, :changes, :uri]
-
- @type code_action_kind ::
- :empty
- | :quick_fix
- | :refactor
- | :refactor_extract
- | :refactor_inline
- | :refactor_rewrite
- | :source
- | :source_organize_imports
- | :source_fix_all
-
- @type trigger_kind :: :invoked | :automatic
-
- @type t :: %__MODULE__{
- title: String.t(),
- kind: code_action_kind,
- changes: Changes.t(),
- uri: Lexical.uri()
- }
-
- @handlers [
- Handlers.ReplaceRemoteFunction,
- Handlers.ReplaceWithUnderscore,
- Handlers.OrganizeAliases,
- Handlers.AddAlias,
- Handlers.RemoveUnusedAlias,
- Handlers.Refactorex
- ]
-
- @spec new(Lexical.uri(), String.t(), code_action_kind(), Changes.t()) :: t()
- def new(uri, title, kind, changes) do
- %__MODULE__{uri: uri, title: title, changes: changes, kind: kind}
- end
-
- @spec for_range(
- Document.t(),
- Range.t(),
- [Diagnostic.t()],
- [code_action_kind] | :all,
- trigger_kind
- ) :: [t()]
- def for_range(%Document{} = doc, %Range{} = range, diagnostics, kinds, trigger_kind) do
- Enum.flat_map(@handlers, fn handler ->
- if handle_kinds?(handler, kinds) and handle_trigger_kind?(handler, trigger_kind) do
- handler.actions(doc, range, diagnostics)
- else
- []
- end
- end)
- end
-
- defp handle_kinds?(_handler, :all), do: true
- defp handle_kinds?(handler, kinds), do: kinds -- handler.kinds() != kinds
-
- defp handle_trigger_kind?(handler, trigger_kind),
- do: handler.trigger_kind() in [trigger_kind, :all]
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/diagnostic.ex b/apps/remote_control/lib/lexical/remote_control/code_action/diagnostic.ex
deleted file mode 100644
index 78b6ee83..00000000
--- a/apps/remote_control/lib/lexical/remote_control/code_action/diagnostic.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-defmodule Lexical.RemoteControl.CodeAction.Diagnostic do
- alias Lexical.Document.Range
-
- defstruct [:range, :message, :source]
- @type message :: String.t()
- @type source :: String.t()
- @type t :: %__MODULE__{
- range: Range.t(),
- message: message() | nil,
- source: source() | nil
- }
-
- @spec new(Range.t(), message(), source() | nil) :: t
- def new(%Range{} = range, message, source) do
- %__MODULE__{range: range, message: message, source: source}
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/handler.ex b/apps/remote_control/lib/lexical/remote_control/code_action/handler.ex
deleted file mode 100644
index 2951626a..00000000
--- a/apps/remote_control/lib/lexical/remote_control/code_action/handler.ex
+++ /dev/null
@@ -1,10 +0,0 @@
-defmodule Lexical.RemoteControl.CodeAction.Handler do
- alias Lexical.Document
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.RemoteControl.CodeAction.Diagnostic
-
- @callback actions(Document.t(), Range.t(), [Diagnostic.t()]) :: [CodeAction.t()]
- @callback kinds() :: [CodeAction.code_action_kind()]
- @callback trigger_kind() :: CodeAction.trigger_kind() | :all
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/docs/entry.ex b/apps/remote_control/lib/lexical/remote_control/code_intelligence/docs/entry.ex
deleted file mode 100644
index 098af162..00000000
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/docs/entry.ex
+++ /dev/null
@@ -1,56 +0,0 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Docs.Entry do
- @moduledoc """
- A documentation entry for a named entity within a module.
- """
-
- defstruct [
- :module,
- :kind,
- :name,
- :arity,
- :signature,
- :doc,
- :metadata,
- defs: []
- ]
-
- @type t(kind) :: %__MODULE__{
- module: module(),
- kind: kind,
- name: atom(),
- arity: arity(),
- signature: [String.t()],
- doc: content(),
- metadata: metadata(),
- defs: [String.t()]
- }
-
- @type content :: String.t() | :none | :hidden
-
- @type metadata :: %{
- optional(:defaults) => pos_integer(),
- optional(:since) => String.t(),
- optional(:guard) => boolean(),
- optional(:opaque) => boolean(),
- optional(:deprecated) => boolean()
- }
-
- @known_metadata [:defaults, :since, :guard, :opaque, :deprecated]
-
- @doc false
- def from_docs_v1(module, {{kind, name, arity}, _anno, signature, doc, meta}) do
- %__MODULE__{
- module: module,
- kind: kind,
- name: name,
- arity: arity,
- signature: signature,
- doc: parse_doc(doc),
- metadata: Map.take(meta, @known_metadata)
- }
- end
-
- @doc false
- def parse_doc(%{"en" => doc}), do: doc
- def parse_doc(atom) when atom in [:none, :hidden], do: atom
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/structs.ex b/apps/remote_control/lib/lexical/remote_control/code_intelligence/structs.ex
deleted file mode 100644
index 8ef9e238..00000000
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/structs.ex
+++ /dev/null
@@ -1,27 +0,0 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Structs do
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Module.Loader
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store
-
- def for_project do
- if Mix.Project.get() do
- {:ok, structs_from_index()}
- else
- RemoteControl.Mix.in_project(fn _ -> structs_from_index() end)
- end
- end
-
- defp structs_from_index do
- case Store.exact(type: :struct, subtype: :definition) do
- {:ok, entries} ->
- for %Entry{subject: struct_module} <- entries,
- Loader.ensure_loaded?(struct_module) do
- struct_module
- end
-
- _ ->
- []
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols/document.ex b/apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols/document.ex
deleted file mode 100644
index 8009244e..00000000
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/symbols/document.ex
+++ /dev/null
@@ -1,99 +0,0 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Symbols.Document do
- alias Lexical.Document
- alias Lexical.Formats
- alias Lexical.RemoteControl.Search.Indexer.Entry
-
- defstruct [:name, :type, :range, :detail_range, :detail, :original_type, :subject, children: []]
-
- def from(%Document{} = document, %Entry{} = entry, children \\ []) do
- case name_and_type(entry.type, entry, document) do
- {name, type} ->
- range = entry.block_range || entry.range
-
- {:ok,
- %__MODULE__{
- name: name,
- type: type,
- range: range,
- detail_range: entry.range,
- children: children,
- original_type: entry.type,
- subject: entry.subject
- }}
-
- _ ->
- :error
- end
- end
-
- @do_regex ~r/\s*do\s*$/
-
- defp name_and_type({:function, type}, %Entry{} = entry, %Document{} = document)
- when type in [:public, :private, :delegate] do
- fragment =
- document
- |> Document.fragment(entry.range.start, entry.range.end)
- |> remove_line_breaks_and_multiple_spaces()
-
- prefix =
- case type do
- :public -> "def "
- :private -> "defp "
- :delegate -> "defdelegate "
- end
-
- {prefix <> fragment, entry.type}
- end
-
- @ignored_attributes ~w[spec doc moduledoc derive impl tag]
- @type_name_regex ~r/@type\s+[^\s]+/
-
- defp name_and_type(:module_attribute, %Entry{} = entry, document) do
- case String.split(entry.subject, "@") do
- [_, name] when name in @ignored_attributes ->
- nil
-
- [_, "type"] ->
- type_text = Document.fragment(document, entry.range.start, entry.range.end)
-
- name =
- case Regex.scan(@type_name_regex, type_text) do
- [[match]] -> match
- _ -> "@type ??"
- end
-
- {name, :type}
-
- [_, name] ->
- {"@#{name}", :module_attribute}
- end
- end
-
- defp name_and_type(ex_unit, %Entry{} = entry, document)
- when ex_unit in [:ex_unit_describe, :ex_unit_setup, :ex_unit_test] do
- name =
- document
- |> Document.fragment(entry.range.start, entry.range.end)
- |> String.trim()
- |> String.replace(@do_regex, "")
-
- {name, ex_unit}
- end
-
- defp name_and_type(:struct, %Entry{} = entry, _document) do
- module_name = Formats.module(entry.subject)
- {"%#{module_name}{}", :struct}
- end
-
- defp name_and_type(type, %Entry{subject: name}, _document) when is_atom(name) do
- {Formats.module(name), type}
- end
-
- defp name_and_type(type, %Entry{} = entry, _document) do
- {to_string(entry.subject), type}
- end
-
- defp remove_line_breaks_and_multiple_spaces(string) do
- string |> String.split(~r/\s/) |> Enum.reject(&match?("", &1)) |> Enum.join(" ")
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_intelligence/variable.ex b/apps/remote_control/lib/lexical/remote_control/code_intelligence/variable.ex
deleted file mode 100644
index 24168457..00000000
--- a/apps/remote_control/lib/lexical/remote_control/code_intelligence/variable.ex
+++ /dev/null
@@ -1,249 +0,0 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.Variable do
- alias Lexical.Ast.Analysis
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.Search.Indexer
- alias Lexical.RemoteControl.Search.Indexer.Entry
-
- require Logger
-
- @extractors [Indexer.Extractors.Variable]
-
- @spec definition(Analysis.t(), Position.t(), atom()) :: {:ok, Entry.t()} | :error
- def definition(%Analysis{} = analysis, %Position{} = position, variable_name) do
- with {:ok, block_structure, entries} <- index_variables(analysis),
- {:ok, %Entry{} = definition_entry} <-
- do_find_definition(variable_name, block_structure, entries, position) do
- {:ok, definition_entry}
- else
- _ ->
- :error
- end
- end
-
- @spec references(Analysis.t(), Position.t(), charlist(), boolean()) :: [Range.t()]
- def references(
- %Analysis{} = analysis,
- %Position{} = position,
- variable_name,
- include_definitions? \\ false
- ) do
- with {:ok, block_structure, entries} <- index_variables(analysis),
- {:ok, %Entry{} = definition_entry} <-
- do_find_definition(variable_name, block_structure, entries, position) do
- references = search_for_references(entries, definition_entry, block_structure)
-
- entries =
- if include_definitions? do
- [definition_entry | references]
- else
- references
- end
-
- Enum.sort_by(entries, fn %Entry{} = entry ->
- {entry.range.start.line, entry.range.start.character}
- end)
- else
- _ ->
- []
- end
- end
-
- defp index_variables(%Analysis{} = analysis) do
- with {:ok, entries} <- Indexer.Quoted.index(analysis, @extractors),
- {[block_structure], entries} <- Enum.split_with(entries, &(&1.type == :metadata)) do
- {:ok, block_structure.subject, entries}
- end
- end
-
- defp do_find_definition(variable_name, block_structure, entries, position) do
- with {:ok, entry} <- fetch_entry(entries, variable_name, position) do
- search_for_definition(entries, entry, block_structure)
- end
- end
-
- defp fetch_entry(entries, variable_name, position) do
- entries
- |> Enum.find(fn %Entry{} = entry ->
- entry.subject == variable_name and entry.type == :variable and
- Range.contains?(entry.range, position)
- end)
- |> case do
- %Entry{} = entry ->
- {:ok, entry}
-
- _ ->
- :error
- end
- end
-
- defp search_for_references(entries, %Entry{} = definition_entry, block_structure) do
- block_id_to_children = block_id_to_children(block_structure)
-
- definition_children = Map.get(block_id_to_children, definition_entry.block_id, [])
-
- # The algorithm here is to first clean up the entries so they either are definitions or references to a
- # variable with the given name. We sort them by their occurrence in the file, working backwards on a line, so
- # definitions earlier in the line shadow definitions later in the line.
- # Then we start at the definition entry, and then for each entry after that,
- # if it's a definition, we mark the state as being shadowed, but reset the state if the block
- # id isn't in the children of the current block id. If we're not in a child of the current block
- # id, then we're no longer shadowed
- #
- # Note, this algorithm doesn't work when we have a block definition whose result rebinds a variable.
- # For example:
- # entries = [4, 5, 6]
- # entries =
- # if something() do
- # [1 | entries]
- # else
- # entries
- # end
- # Searching for the references to the initial variable won't find anything inside the block, but
- # searching for the rebound variable will.
-
- {entries, _, _} =
- entries
- |> Enum.filter(fn %Entry{} = entry ->
- after_definition? = Position.compare(entry.range.start, definition_entry.range.end) == :gt
-
- variable_type? = entry.type == :variable
- correct_subject? = entry.subject == definition_entry.subject
- child_of_definition_block? = entry.block_id in definition_children
-
- variable_type? and correct_subject? and child_of_definition_block? and after_definition?
- end)
- |> Enum.sort_by(fn %Entry{} = entry ->
- start = entry.range.start
- {start.line, -start.character, entry.block_id}
- end)
- |> Enum.reduce({[], false, definition_entry.block_id}, fn
- %Entry{subtype: :definition} = entry, {entries, _, _} ->
- # we have a definition that's shadowing our definition entry
- {entries, true, entry.block_id}
-
- %Entry{subtype: :reference} = entry, {entries, true, current_block_id} ->
- shadowed? = entry.block_id in Map.get(block_id_to_children, current_block_id, [])
-
- entries =
- if shadowed? do
- entries
- else
- [entry | entries]
- end
-
- {entries, shadowed?, entry.block_id}
-
- %Entry{} = entry, {entries, false, _} ->
- # we're a reference and we're not being shadowed; collect it and move on.
- {[entry | entries], false, entry.block_id}
- end)
-
- entries
- end
-
- defp search_for_definition(entries, %Entry{} = entry, block_structure) do
- block_id_to_parents = collect_parents(block_structure)
- block_path = Map.get(block_id_to_parents, entry.block_id)
- entries_by_block_id = entries_by_block_id(entries)
-
- Enum.reduce_while([entry.block_id | block_path], :error, fn block_id, _ ->
- block_entries =
- entries_by_block_id
- |> Map.get(block_id, [])
- |> then(fn entries ->
- # In the current block, reject all entries that come after the entry whose definition
- # we're searching for. This prevents us from finding definitions who are shadowing
- # our entry. For example, the definition on the left of the equals in: `param = param + 1`.
-
- if block_id == entry.block_id do
- Enum.drop_while(entries, &(&1.id != entry.id))
- else
- entries
- end
- end)
-
- case Enum.find(block_entries, &definition_of?(entry, &1)) do
- %Entry{} = definition ->
- {:halt, {:ok, definition}}
-
- nil ->
- {:cont, :error}
- end
- end)
- end
-
- defp definition_of?(%Entry{} = needle, %Entry{} = compare) do
- compare.type == :variable and compare.subtype == :definition and
- compare.subject == needle.subject
- end
-
- defp entries_by_block_id(entries) do
- entries
- |> Enum.reduce(%{}, fn %Entry{} = entry, acc ->
- Map.update(acc, entry.block_id, [entry], &[entry | &1])
- end)
- |> Map.new(fn {block_id, entries} ->
- entries =
- Enum.sort_by(
- entries,
- fn %Entry{} = entry ->
- {entry.range.start.line, -entry.range.start.character}
- end,
- :desc
- )
-
- {block_id, entries}
- end)
- end
-
- def block_id_to_parents(hierarchy) do
- hierarchy
- |> flatten_hierarchy()
- |> Enum.reduce(%{}, fn {parent_id, child_id}, acc ->
- old_parents = [parent_id | Map.get(acc, parent_id, [])]
- Map.update(acc, child_id, old_parents, &Enum.concat(&1, old_parents))
- end)
- |> Map.put(:root, [])
- end
-
- def block_id_to_children(hierarchy) do
- # Note: Parent ids are included in their children list in order to simplify
- # checks for "is this id in one of its children"
-
- hierarchy
- |> flatten_hierarchy()
- |> Enum.reverse()
- |> Enum.reduce(%{root: [:root]}, fn {parent_id, child_id}, current_mapping ->
- current_children = [child_id | Map.get(current_mapping, child_id, [parent_id])]
-
- current_mapping
- |> Map.put_new(child_id, [child_id])
- |> Map.update(parent_id, current_children, &Enum.concat(&1, current_children))
- end)
- end
-
- def flatten_hierarchy(hierarchy) do
- Enum.flat_map(hierarchy, fn
- {k, v} when is_map(v) and map_size(v) > 0 ->
- v
- |> Map.keys()
- |> Enum.map(&{k, &1})
- |> Enum.concat(flatten_hierarchy(v))
-
- _ ->
- []
- end)
- end
-
- defp collect_parents(block_structure) do
- do_collect_parents(block_structure, %{}, [])
- end
-
- defp do_collect_parents(hierarchy, parent_map, path) do
- Enum.reduce(hierarchy, parent_map, fn {block_id, children}, acc ->
- parent_map = Map.put(acc, block_id, path)
- do_collect_parents(children, parent_map, [block_id | path])
- end)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/code_mod/aliases.ex b/apps/remote_control/lib/lexical/remote_control/code_mod/aliases.ex
deleted file mode 100644
index 105aed95..00000000
--- a/apps/remote_control/lib/lexical/remote_control/code_mod/aliases.ex
+++ /dev/null
@@ -1,256 +0,0 @@
-defmodule Lexical.RemoteControl.CodeMod.Aliases do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Analysis.Alias
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.Document
- alias Lexical.Document.Edit
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.RemoteControl
- alias Sourceror.Zipper
-
- @doc """
- Returns the aliases that are in scope at the given range.
- """
- @spec in_scope(Analysis.t(), Range.t()) :: [Alias.t()]
- def in_scope(%Analysis{} = analysis, %Range{} = range) do
- analysis
- |> Analysis.module_scope(range)
- |> aliases_in_scope()
- end
-
- @doc """
- Sorts the given aliases according to our rules
- """
- @spec sort(Enumerable.t(Alias.t())) :: [Alias.t()]
- def sort(aliases) do
- Enum.sort_by(aliases, fn %Alias{} = scope_alias ->
- Enum.map(scope_alias.module, fn elem -> elem |> to_string() |> String.downcase() end)
- end)
- end
-
- @doc """
- Returns the position in the document where aliases should be inserted
- Since a document can have multiple module definitions, the cursor position is used to
- determine the initial starting point.
-
- This function also returns a string that should be appended to the end of the
- edits that are performed.
- """
- @spec insert_position(Analysis.t(), Position.t()) :: {Position.t(), String.t() | nil}
- def insert_position(%Analysis{} = analysis, %Position{} = cursor_position) do
- range = Range.new(cursor_position, cursor_position)
- current_aliases = in_scope(analysis, range)
- do_insert_position(analysis, current_aliases, range)
- end
-
- @doc """
- Turns a list of aliases into aliases into edits
- """
- @spec to_edits([Alias.t()], Position.t(), trailer :: String.t() | nil) :: [Edit.t()]
-
- def to_edits(aliases, position, trailer \\ nil)
- def to_edits([], _, _), do: []
-
- def to_edits(aliases, %Position{} = insert_position, trailer) do
- aliases = sort(aliases)
- initial_spaces = insert_position.character - 1
-
- alias_text =
- aliases
- # get rid of duplicate aliases
- |> Enum.uniq_by(& &1.module)
- |> Enum.map_join("\n", fn %Alias{} = a ->
- text =
- if List.last(a.module) == a.as do
- "alias #{join(a.module)}"
- else
- "alias #{join(a.module)}, as: #{join(List.wrap(a.as))}"
- end
-
- indent(text, initial_spaces)
- end)
- |> String.trim_trailing()
-
- zeroed = put_in(insert_position.character, 1)
- new_alias_range = Range.new(zeroed, zeroed)
-
- alias_text =
- if is_binary(trailer) do
- alias_text <> trailer
- else
- alias_text
- end
-
- edits = remove_old_aliases(aliases)
-
- edits ++
- [Edit.new(alias_text, new_alias_range)]
- end
-
- defp aliases_in_scope(%Scope{} = scope) do
- scope.aliases
- |> Enum.filter(fn %Alias{} = scope_alias ->
- scope_alias.explicit? and Range.contains?(scope.range, scope_alias.range.start)
- end)
- |> sort()
- end
-
- defp join(module) do
- Enum.join(module, ".")
- end
-
- defp indent(text, spaces) do
- String.duplicate(" ", spaces) <> text
- end
-
- defp remove_old_aliases(aliases) do
- ranges =
- aliases
- # Reject new aliases that don't have a range
- |> Enum.reject(&is_nil(&1.range))
- # iterating back to start means we won't have prior edits
- # clobber subsequent edits
- |> Enum.sort_by(& &1.range.start.line, :desc)
- |> Enum.uniq_by(& &1.range)
- |> Enum.map(fn %Alias{} = alias ->
- orig_range = alias.range
-
- orig_range
- |> put_in([:start, :character], 1)
- |> update_in([:end], fn %Position{} = pos ->
- %Position{pos | character: 1, line: pos.line + 1}
- end)
- end)
-
- first_alias_index = length(ranges) - 1
-
- ranges
- |> Enum.with_index()
- |> Enum.map(fn
- {range, ^first_alias_index} ->
- # add a new line where the first alias was to make space
- # for the rewritten aliases
- Edit.new("\n", range)
-
- {range, _} ->
- Edit.new("", range)
- end)
- |> merge_adjacent_edits()
- end
-
- defp merge_adjacent_edits([]), do: []
- defp merge_adjacent_edits([_] = edit), do: edit
-
- defp merge_adjacent_edits([edit | rest]) do
- rest
- |> Enum.reduce([edit], fn %Edit{} = current, [%Edit{} = last | rest] = edits ->
- with {same_text, same_text} <- {last.text, current.text},
- {same, same} <- {to_tuple(current.range.end), to_tuple(last.range.start)} do
- collapsed = put_in(current.range.end, last.range.end)
-
- [collapsed | rest]
- else
- _ ->
- [current | edits]
- end
- end)
- |> Enum.reverse()
- end
-
- defp to_tuple(%Position{} = position) do
- {position.line, position.character}
- end
-
- defp do_insert_position(%Analysis{}, [%Alias{} | _] = aliases, _) do
- first = Enum.min_by(aliases, &{&1.range.start.line, &1.range.start.character})
- {first.range.start, nil}
- end
-
- defp do_insert_position(%Analysis{} = analysis, _, range) do
- case Analysis.module_scope(analysis, range) do
- %Scope{id: :global} = scope ->
- {scope.range.start, "\n"}
-
- %Scope{} = scope ->
- scope_start = scope.range.start
- # we use the end position here because the start position is right after
- # the do for modules, which puts it well into the line. The end position
- # is before the end, which is equal to the indent of the scope.
-
- initial_position =
- scope_start
- |> put_in([:line], scope_start.line + 1)
- |> put_in([:character], scope.range.end.character)
- |> constrain_to_range(scope.range)
-
- position =
- case Ast.zipper_at(analysis.document, scope_start) do
- {:ok, zipper} ->
- {_, position} =
- Zipper.traverse(zipper, initial_position, fn
- %Zipper{node: {:@, _, [{:moduledoc, _, _}]}} = zipper, _acc ->
- # If we detect a moduledoc node, place the alias after it
- range = Sourceror.get_range(zipper.node)
-
- {zipper, after_node(analysis.document, scope.range, range)}
-
- zipper, acc ->
- {zipper, acc}
- end)
-
- position
-
- _ ->
- initial_position
- end
-
- maybe_move_cursor_to_token_start(position, analysis)
- end
- end
-
- defp after_node(%Document{} = document, %Range{} = scope_range, %{
- start: start_pos,
- end: end_pos
- }) do
- document
- |> Position.new(end_pos[:line] + 1, start_pos[:column])
- |> constrain_to_range(scope_range)
- end
-
- defp constrain_to_range(%Position{} = position, %Range{} = scope_range) do
- cond do
- position.line == scope_range.end.line ->
- character = min(scope_range.end.character, position.character)
- %Position{position | character: character}
-
- position.line > scope_range.end.line ->
- %Position{scope_range.end | character: 1}
-
- true ->
- position
- end
- end
-
- defp maybe_move_cursor_to_token_start(%Position{} = position, %Analysis{} = analysis) do
- project = RemoteControl.get_project()
-
- with {:ok, env} <- Ast.Env.new(project, analysis, position),
- false <- String.last(env.prefix) in [" ", ""] do
- # ` en|d` -> `2`
- # `en|d` -> `2`
- non_empty_characters_count = env.prefix |> String.trim_leading() |> String.length()
-
- new_position = %Position{
- position
- | character: position.character - non_empty_characters_count
- }
-
- {new_position, "\n"}
- else
- _ ->
- {position, "\n"}
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/completion.ex b/apps/remote_control/lib/lexical/remote_control/completion.ex
deleted file mode 100644
index fedd3a55..00000000
--- a/apps/remote_control/lib/lexical/remote_control/completion.ex
+++ /dev/null
@@ -1,150 +0,0 @@
-defmodule Lexical.RemoteControl.Completion do
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Env
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeMod.Format
- alias Lexical.RemoteControl.Completion.Candidate
-
- import Document.Line
- import Lexical.Logging
-
- def elixir_sense_expand(%Env{} = env) do
- {doc_string, position} = strip_struct_operator(env)
-
- line = position.line
- character = position.character
- hint = ElixirSense.Core.Source.prefix(doc_string, line, character)
-
- if String.trim(hint) == "" do
- []
- else
- {_formatter, opts} =
- timed_log("formatter for file", fn ->
- Format.formatter_for_file(env.project, env.document.path)
- end)
-
- locals_without_parens = Keyword.fetch!(opts, :locals_without_parens)
-
- for suggestion <-
- timed_log("ES suggestions", fn ->
- ElixirSense.suggestions(doc_string, line, character)
- end),
- candidate =
- timed_log("from_elixir_sense", fn ->
- from_elixir_sense(suggestion, locals_without_parens)
- end),
- candidate != nil do
- candidate
- end
- end
- end
-
- defp from_elixir_sense(suggestion, locals_without_parens) do
- suggestion
- |> Candidate.from_elixir_sense()
- |> maybe_suppress_parens(locals_without_parens)
- end
-
- defp maybe_suppress_parens(%struct{} = candidate, locals_without_parens)
- when struct in [Candidate.Function, Candidate.Macro, Candidate.Typespec] do
- atom_name = String.to_atom(candidate.name)
- suppress_parens? = local_without_parens?(atom_name, candidate.arity, locals_without_parens)
-
- %{candidate | parens?: not suppress_parens?}
- end
-
- defp maybe_suppress_parens(candidate, _), do: candidate
-
- defp local_without_parens?(fun, arity, locals_without_parens) do
- arity > 0 and
- Enum.any?(locals_without_parens, fn
- {^fun, :*} -> true
- {^fun, ^arity} -> true
- _ -> false
- end)
- end
-
- def struct_fields(%Analysis{} = analysis, %Position{} = position) do
- container_struct_module =
- analysis
- |> Lexical.Ast.cursor_path(position)
- |> container_struct_module()
-
- with {:ok, struct_module} <-
- RemoteControl.Analyzer.expand_alias(container_struct_module, analysis, position),
- true <- function_exported?(struct_module, :__struct__, 0) do
- struct_module
- |> struct()
- |> Map.from_struct()
- |> Map.keys()
- |> Enum.map(&Candidate.StructField.new(&1, struct_module))
- else
- _ -> []
- end
- end
-
- defp container_struct_module(cursor_path) do
- Enum.find_value(cursor_path, fn
- # current module struct: `%__MODULE__{|}`
- {:%, _, [{:__MODULE__, _, _} | _]} -> [:__MODULE__]
- # struct leading by current module: `%__MODULE__.Struct{|}`
- {:%, _, [{:__aliases__, _, [{:__MODULE__, _, _} | tail]} | _]} -> [:__MODULE__ | tail]
- # Struct leading by alias or just a aliased Struct: `%Struct{|}`, `%Project.Struct{|}`
- {:%, _, [{:__aliases__, _, aliases} | _]} -> aliases
- _ -> nil
- end)
- end
-
- # HACK: This fixes ElixirSense struct completions for certain cases.
- # We should try removing when we update or remove ElixirSense.
- defp strip_struct_operator(%Env{} = env) do
- with true <- Env.in_context?(env, :struct_reference),
- {:ok, completion_length} <- fetch_struct_completion_length(env) do
- column = env.position.character
- percent_position = column - (completion_length + 1)
-
- new_line_start = String.slice(env.line, 0, percent_position - 1)
- new_line_end = String.slice(env.line, percent_position..-1//1)
- new_line = [new_line_start, new_line_end]
- new_position = Position.new(env.document, env.position.line, env.position.character - 1)
- line_to_replace = env.position.line
-
- stripped_text =
- env.document.lines
- |> Enum.with_index(1)
- |> Enum.reduce([], fn
- {line(ending: ending), ^line_to_replace}, acc ->
- [acc, new_line, ending]
-
- {line(text: line_text, ending: ending), _}, acc ->
- [acc, line_text, ending]
- end)
- |> IO.iodata_to_binary()
-
- {stripped_text, new_position}
- else
- _ ->
- doc_string = Document.to_string(env.document)
- {doc_string, env.position}
- end
- end
-
- defp fetch_struct_completion_length(env) do
- case Code.Fragment.cursor_context(env.prefix) do
- {:struct, {:dot, {:alias, struct_name}, []}} ->
- # add one because of the trailing period
- {:ok, length(struct_name) + 1}
-
- {:struct, {:local_or_var, local_name}} ->
- {:ok, length(local_name)}
-
- {:struct, struct_name} ->
- {:ok, length(struct_name)}
-
- {:local_or_var, local_name} ->
- {:ok, length(local_name)}
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/dispatch/handler.ex b/apps/remote_control/lib/lexical/remote_control/dispatch/handler.ex
deleted file mode 100644
index 7675f780..00000000
--- a/apps/remote_control/lib/lexical/remote_control/dispatch/handler.ex
+++ /dev/null
@@ -1,105 +0,0 @@
-defmodule Lexical.RemoteControl.Dispatch.Handler do
- @moduledoc """
- Defines a handler that selectively receives events emitted from a remote control node.
-
- ## Usage
-
- Define a handler, specifying the events to be handled and implementing `on_event/2`:
-
- defmodule MyHandler do
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Dispatch.Handler
-
- import Messages
-
- use Handler, [project_compiled()]
-
- def on_event(project_compiled(), state) do
- ...do something with the message
- {:ok, state}
- end
- end
-
- Register the handler with dispatch:
-
- # The second argument here will be passed to the `init/1` callback
- Lexical.RemoteControl.Dispatch.add_handler(MyHandler, init_arg)
-
- """
- @type event :: tuple()
- @type handler_state :: term()
-
- @callback on_event(event(), handler_state) :: {:ok, handler_state} | {:error, any()}
- @callback init(term()) :: {:ok, handler_state()}
- @optional_callbacks init: 1
-
- defmacro __using__(event_types) do
- event_types = List.wrap(event_types)
-
- handler_bodies =
- if Enum.member?(event_types, :all) do
- [all_handler()]
- else
- handler_bodies(event_types)
- end
-
- quote do
- @behaviour unquote(__MODULE__)
-
- def init(arg) do
- {:ok, arg}
- end
-
- def on(event, state) do
- {:ok, event, state}
- end
-
- def handle_call(_, state) do
- {:ok, state}
- end
-
- def handle_info(_, state) do
- {:ok, state}
- end
-
- unquote_splicing(handler_bodies)
-
- # handlers only respond to on or info, not calls.
- defoverridable init: 1, handle_info: 2, on: 2
- end
- end
-
- def handler_bodies(event_types) do
- results =
- Enum.map(event_types, fn {event_name, _, _} ->
- event_handler(event_name)
- end)
-
- results ++ [ignore_handler()]
- end
-
- defp event_handler(event_name) do
- quote do
- def handle_event(event, state)
- when is_tuple(event) and elem(event, 0) == unquote(event_name) do
- on_event(event, state)
- end
- end
- end
-
- defp all_handler do
- quote do
- def handle_event(event, state) do
- on_event(event, state)
- end
- end
- end
-
- defp ignore_handler do
- quote do
- def handle_event(event, state) do
- {:ok, state}
- end
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/dispatch/handlers/indexing.ex b/apps/remote_control/lib/lexical/remote_control/dispatch/handlers/indexing.ex
deleted file mode 100644
index f20ec4bd..00000000
--- a/apps/remote_control/lib/lexical/remote_control/dispatch/handlers/indexing.ex
+++ /dev/null
@@ -1,36 +0,0 @@
-defmodule Lexical.RemoteControl.Dispatch.Handlers.Indexing do
- alias Lexical.Document
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Commands
- alias Lexical.RemoteControl.Dispatch
- alias Lexical.RemoteControl.Search
-
- require Logger
- import Messages
-
- use Dispatch.Handler, [file_compile_requested(), filesystem_event()]
-
- def on_event(file_compile_requested(uri: uri), state) do
- reindex(uri)
- {:ok, state}
- end
-
- def on_event(filesystem_event(uri: uri, event_type: :deleted), state) do
- delete_path(uri)
- {:ok, state}
- end
-
- def on_event(filesystem_event(), state) do
- {:ok, state}
- end
-
- defp reindex(uri) do
- Commands.Reindex.uri(uri)
- end
-
- def delete_path(uri) do
- uri
- |> Document.Path.ensure_path()
- |> Search.Store.clear()
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/plugin.ex b/apps/remote_control/lib/lexical/remote_control/plugin.ex
deleted file mode 100644
index f99fdba5..00000000
--- a/apps/remote_control/lib/lexical/remote_control/plugin.ex
+++ /dev/null
@@ -1,48 +0,0 @@
-defmodule Lexical.RemoteControl.Plugin do
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Plugin.Runner
-
- import Messages
-
- def diagnose(%Project{} = project, build_number) do
- on_complete = fn
- [] ->
- :ok
-
- [_ | _] = diagnostics ->
- message =
- project_diagnostics(
- project: project,
- build_number: build_number,
- diagnostics: diagnostics
- )
-
- RemoteControl.broadcast(message)
- end
-
- Runner.diagnose(project, on_complete)
- end
-
- def diagnose(%Project{} = project, build_number, %Document{} = document) do
- on_complete = fn
- [] ->
- :ok
-
- [_ | _] = diagnostics ->
- message =
- file_diagnostics(
- project: project,
- build_number: build_number,
- uri: document.uri,
- diagnostics: diagnostics
- )
-
- RemoteControl.broadcast(message)
- end
-
- Runner.diagnose(document, on_complete)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/plugin/runner/coordinator/state.ex b/apps/remote_control/lib/lexical/remote_control/plugin/runner/coordinator/state.ex
deleted file mode 100644
index a30a4617..00000000
--- a/apps/remote_control/lib/lexical/remote_control/plugin/runner/coordinator/state.ex
+++ /dev/null
@@ -1,102 +0,0 @@
-defmodule Lexical.RemoteControl.Plugin.Runner.Coordinator.State do
- @moduledoc false
-
- alias Lexical.RemoteControl.Plugin.Runner
-
- defstruct tasks: [], failures: %{}
-
- @max_plugin_errors 10
-
- require Logger
-
- def new do
- %__MODULE__{}
- end
-
- def run_all(%__MODULE__{} = state, subject, plugin_type, timeout) do
- tasks_to_plugin_modules =
- plugin_type
- |> Runner.plugins_of_type()
- |> Enum.map(&Runner.Supervisor.async(&1, subject))
- |> Map.new()
-
- await_results(state, tasks_to_plugin_modules, timeout)
- end
-
- def failure_count(%__MODULE__{} = state, plugin_module) do
- Map.get(state.failures, plugin_module, 0)
- end
-
- def remove_task(%__MODULE__{} = state, ref) do
- new_tasks = Enum.reject(state.tasks, &(&1.ref == ref))
- %__MODULE__{state | tasks: new_tasks}
- end
-
- defp await_results(%__MODULE__{} = state, tasks_to_plugin_modules, timeout) do
- raw_result =
- tasks_to_plugin_modules
- |> Map.keys()
- |> Task.yield_many(timeout)
-
- {successes, failed} =
- raw_result
- |> Enum.reduce({[], []}, fn
- {_task, {:ok, {:ok, results}}}, {successes, failures} when is_list(results) ->
- {[results | successes], failures}
-
- {task, {:ok, {:ok, _not_list}}}, {successes, failures} ->
- reason = "it did not return a list of results"
- failure = {:log, task, reason}
- {successes, [failure | failures]}
-
- {task, {:ok, reason}}, {successes, failures} ->
- failure = {:log, task, reason}
- {successes, [failure | failures]}
-
- {task, {:exit, reason}}, {successes, failures} ->
- failure = {:log, task, reason}
- {successes, [failure | failures]}
-
- {task, nil}, {successes, failures} ->
- failure = {:shutdown, task}
- {successes, [failure | failures]}
- end)
-
- new_state =
- Enum.reduce(failed, state, fn
- {:log, %Task{} = task, reason}, state ->
- plugin_module = Map.get(tasks_to_plugin_modules, task)
- Logger.error("Task #{plugin_module} failed because #{inspect(reason)}")
- mark_failed(state, plugin_module)
-
- {:shutdown, %Task{} = task}, state ->
- plugin_module = Map.get(tasks_to_plugin_modules, task)
- Logger.error("Task #{plugin_module} did not complete in #{timeout}ms ")
- Task.shutdown(task, :brutal_kill)
- mark_failed(state, plugin_module)
- end)
-
- results =
- successes
- |> Enum.reverse()
- |> List.flatten()
-
- {results, new_state}
- end
-
- defp mark_failed(%__MODULE__{} = state, plugin_module) do
- new_failures = Map.update(state.failures, plugin_module, 1, &(&1 + 1))
- maybe_shutdown(plugin_module, new_failures)
- %__MODULE__{state | failures: new_failures}
- end
-
- defp maybe_shutdown(plugin_module, failure_map) do
- case Map.get(failure_map, plugin_module, 0) do
- count when count >= @max_plugin_errors ->
- Runner.disable(plugin_module)
-
- _ ->
- :ok
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/plugin/supervisor.ex b/apps/remote_control/lib/lexical/remote_control/plugin/supervisor.ex
deleted file mode 100644
index ad950873..00000000
--- a/apps/remote_control/lib/lexical/remote_control/plugin/supervisor.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-defmodule Lexical.RemoteControl.Plugin.Runner.Supervisor do
- @moduledoc false
-
- def child_spec(_) do
- %{
- id: __MODULE__,
- start: {Task.Supervisor, :start_link, [[name: name()]]}
- }
- end
-
- @spec async(module(), term()) :: {Task.t(), module()}
- def async(plugin_module, subject) do
- task = Task.Supervisor.async_nolink(name(), plugin_module, :diagnose, [subject])
- {task, plugin_module}
- end
-
- defp name do
- Module.concat(__MODULE__, TaskSupervisor)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/progress.ex b/apps/remote_control/lib/lexical/remote_control/progress.ex
deleted file mode 100644
index 3448609c..00000000
--- a/apps/remote_control/lib/lexical/remote_control/progress.ex
+++ /dev/null
@@ -1,68 +0,0 @@
-defmodule Lexical.RemoteControl.Progress do
- alias Lexical.RemoteControl
-
- import Lexical.RemoteControl.Api.Messages
-
- @type label :: String.t()
- @type message :: String.t()
-
- @type delta :: pos_integer()
- @type on_complete_callback :: (-> any())
- @type report_progress_callback :: (delta(), message() -> any())
-
- defmacro __using__(_) do
- quote do
- import unquote(__MODULE__), only: [with_progress: 2]
- end
- end
-
- @spec with_progress(label(), (-> any())) :: any()
- def with_progress(label, func) when is_function(func, 0) do
- on_complete = begin_progress(label)
-
- try do
- func.()
- after
- on_complete.()
- end
- end
-
- @spec with_percent_progress(label(), pos_integer(), (report_progress_callback() -> any())) ::
- any()
- def with_percent_progress(label, max, func) when is_function(func, 1) do
- {report_progress, on_complete} = begin_percent(label, max)
-
- try do
- func.(report_progress)
- after
- on_complete.()
- end
- end
-
- @spec begin_progress(label :: label()) :: on_complete_callback()
- def begin_progress(label) do
- RemoteControl.broadcast(project_progress(label: label, stage: :begin))
-
- fn ->
- RemoteControl.broadcast(project_progress(label: label, stage: :complete))
- end
- end
-
- @spec begin_percent(label(), pos_integer()) ::
- {report_progress_callback(), on_complete_callback()}
- def begin_percent(label, max) do
- RemoteControl.broadcast(percent_progress(label: label, max: max, stage: :begin))
-
- report_progress = fn delta, message ->
- RemoteControl.broadcast(
- percent_progress(label: label, message: message, delta: delta, stage: :report)
- )
- end
-
- complete = fn ->
- RemoteControl.broadcast(percent_progress(label: label, stage: :complete))
- end
-
- {report_progress, complete}
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/entry.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/entry.ex
deleted file mode 100644
index cb17f9f0..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/entry.ex
+++ /dev/null
@@ -1,155 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Entry do
- @type function_type :: :public | :private | :delegated | :usage
- @type protocol_type :: :implementation | :definition
-
- @type entry_type ::
- :ex_unit_describe
- | :ex_unit_test
- | :module
- | :module_attribute
- | :struct
- | :variable
- | {:protocol, protocol_type()}
- | {:function, function_type()}
-
- @type subject :: String.t()
- @type entry_subtype :: :reference | :definition
- @type version :: String.t()
- @type entry_id :: pos_integer() | nil
- @type block_id :: pos_integer() | :root
- @type subject_query :: subject() | :_
- @type entry_type_query :: entry_type() | :_
- @type entry_subtype_query :: entry_subtype() | :_
- @type constraint :: {:type, entry_type_query()} | {:subtype, entry_subtype_query()}
- @type constraints :: [constraint()]
-
- defstruct [
- :application,
- :id,
- :block_id,
- :block_range,
- :path,
- :range,
- :subject,
- :subtype,
- :type,
- :metadata
- ]
-
- @type t :: %__MODULE__{
- application: module(),
- subject: subject(),
- block_id: block_id(),
- block_range: Lexical.Document.Range.t() | nil,
- path: Path.t(),
- range: Lexical.Document.Range.t(),
- subtype: entry_subtype(),
- type: entry_type(),
- metadata: nil | map()
- }
- @type datetime_format :: :erl | :unix | :datetime
- @type date_type :: :calendar.datetime() | integer() | DateTime.t()
-
- alias Lexical.Identifier
- alias Lexical.RemoteControl.Search.Indexer.Source.Block
- alias Lexical.StructAccess
-
- use StructAccess
-
- defguard is_structure(entry) when entry.type == :metadata and entry.subtype == :block_structure
- defguard is_block(entry) when entry.id == entry.block_id
-
- @doc """
- Creates a new entry by copying the passed-in entry.
-
- The returned entry will have the same fields set as the one passed in,
- but a different id.
- You can also pass in a keyword list of overrides, which will overwrit values in
- the returned struct.
- """
- def copy(%__MODULE__{} = orig, overrides \\ []) when is_list(overrides) do
- %__MODULE__{orig | id: Identifier.next_global!()}
- |> struct(overrides)
- end
-
- def block_structure(path, structure) do
- %__MODULE__{
- path: path,
- subject: structure,
- type: :metadata,
- subtype: :block_structure
- }
- end
-
- def reference(path, %Block{} = block, subject, type, range, application) do
- new(path, Identifier.next_global!(), block.id, subject, type, :reference, range, application)
- end
-
- def definition(path, %Block{} = block, subject, type, range, application) do
- new(path, Identifier.next_global!(), block.id, subject, type, :definition, range, application)
- end
-
- def block_definition(
- path,
- %Block{} = block,
- subject,
- type,
- block_range,
- detail_range,
- application
- ) do
- definition =
- definition(
- path,
- block.id,
- block.parent_id,
- subject,
- type,
- detail_range,
- application
- )
-
- %__MODULE__{definition | block_range: block_range}
- end
-
- defp definition(path, id, block_id, subject, type, range, application) do
- new(path, id, block_id, subject, type, :definition, range, application)
- end
-
- defp new(path, id, block_id, subject, type, subtype, range, application) do
- %__MODULE__{
- application: application,
- block_id: block_id,
- id: id,
- path: path,
- range: range,
- subject: subject,
- subtype: subtype,
- type: type
- }
- end
-
- def block?(%__MODULE__{} = entry) do
- is_block(entry)
- end
-
- @spec updated_at(t()) :: date_type()
- @spec updated_at(t(), datetime_format) :: date_type()
- def updated_at(entry, format \\ :erl)
-
- def updated_at(%__MODULE__{id: id} = entry, format) when is_integer(id) do
- case format do
- :erl -> Identifier.to_erl(entry.id)
- :unix -> Identifier.to_unix(id)
- :datetime -> Identifier.to_datetime(id)
- end
- end
-
- def updated_at(%__MODULE__{}, _format) do
- nil
- end
-
- def put_metadata(%__MODULE__{} = entry, metadata) do
- %__MODULE__{entry | metadata: metadata}
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module.ex
deleted file mode 100644
index e010c3c3..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module.ex
+++ /dev/null
@@ -1,344 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.Module do
- @moduledoc """
- Extracts module references and definitions from AST
- """
-
- alias Lexical.Ast
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.ProcessCache
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Metadata
- alias Lexical.RemoteControl.Search.Indexer.Source.Block
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
- alias Lexical.RemoteControl.Search.Subject
-
- require Logger
-
- @definition_mappings %{
- defmodule: :module,
- defprotocol: {:protocol, :definition}
- }
- @module_definitions Map.keys(@definition_mappings)
-
- # extract a module definition
- def extract(
- {definition, defmodule_meta,
- [{:__aliases__, module_name_meta, module_name}, module_block]} = defmodule_ast,
- %Reducer{} = reducer
- )
- when definition in @module_definitions do
- %Block{} = block = Reducer.current_block(reducer)
-
- case resolve_alias(reducer, module_name) do
- {:ok, aliased_module} ->
- module_position = Metadata.position(module_name_meta)
- detail_range = to_range(reducer, module_name, module_position)
-
- entry =
- Entry.block_definition(
- reducer.analysis.document.path,
- block,
- Subject.module(aliased_module),
- @definition_mappings[definition],
- block_range(reducer.analysis.document, defmodule_ast),
- detail_range,
- Application.get_application(aliased_module)
- )
-
- module_name_meta = Reducer.skip(module_name_meta)
-
- elem =
- {:defmodule, defmodule_meta,
- [{:__aliases__, module_name_meta, module_name}, module_block]}
-
- {:ok, entry, elem}
-
- _ ->
- :ignored
- end
- end
-
- # defimpl MyProtocol, for: MyStruct do ...
- def extract(
- {:defimpl, _, [{:__aliases__, _, module_name}, [for_block], _impl_body]} = defimpl_ast,
- %Reducer{} = reducer
- ) do
- %Block{} = block = Reducer.current_block(reducer)
-
- with {:ok, protocol_module} <- resolve_alias(reducer, module_name),
- {:ok, for_target} <- resolve_for_block(reducer, for_block) do
- detail_range = defimpl_range(reducer, defimpl_ast)
- implemented_module = Module.concat(protocol_module, for_target)
-
- implementation_entry =
- Entry.block_definition(
- reducer.analysis.document.path,
- block,
- Subject.module(protocol_module),
- {:protocol, :implementation},
- block_range(reducer.analysis.document, defimpl_ast),
- detail_range,
- Application.get_application(protocol_module)
- )
-
- module_entry =
- Entry.copy(implementation_entry,
- subject: Subject.module(implemented_module),
- type: :module
- )
-
- {:ok, [implementation_entry, module_entry]}
- else
- _ ->
- :ignored
- end
- end
-
- # This matches an elixir module reference
- def extract({:__aliases__, metadata, maybe_module}, %Reducer{} = reducer)
- when is_list(maybe_module) do
- case module(reducer, maybe_module) do
- {:ok, module} ->
- start = Metadata.position(metadata)
- range = to_range(reducer, maybe_module, start)
- %Block{} = current_block = Reducer.current_block(reducer)
-
- entry =
- Entry.reference(
- reducer.analysis.document.path,
- current_block,
- Subject.module(module),
- :module,
- range,
- Application.get_application(module)
- )
-
- {:ok, entry, nil}
-
- _ ->
- :ignored
- end
- end
-
- @module_length String.length("__MODULE__")
- # This matches __MODULE__ references
- def extract({:__MODULE__, metadata, _} = ast, %Reducer{} = reducer) do
- line = Sourceror.get_line(ast)
- pos = Position.new(reducer.analysis.document, line - 1, 1)
-
- case RemoteControl.Analyzer.current_module(reducer.analysis, pos) do
- {:ok, current_module} ->
- {start_line, start_col} = Metadata.position(metadata)
- start_pos = Position.new(reducer.analysis.document, start_line, start_col)
-
- end_pos =
- Position.new(
- reducer.analysis.document,
- start_line,
- start_col + @module_length
- )
-
- range = Range.new(start_pos, end_pos)
- %Block{} = current_block = Reducer.current_block(reducer)
-
- entry =
- Entry.reference(
- reducer.analysis.document.path,
- current_block,
- Subject.module(current_module),
- :module,
- range,
- Application.get_application(current_module)
- )
-
- {:ok, entry}
-
- _ ->
- :ignored
- end
- end
-
- # This matches an erlang module, which is just an atom
- def extract({:__block__, metadata, [atom_literal]}, %Reducer{} = reducer)
- when is_atom(atom_literal) do
- case module(reducer, atom_literal) do
- {:ok, module} ->
- start = Metadata.position(metadata)
- %Block{} = current_block = Reducer.current_block(reducer)
- range = to_range(reducer, module, start)
-
- entry =
- Entry.reference(
- reducer.analysis.document.path,
- current_block,
- Subject.module(module),
- :module,
- range,
- Application.get_application(module)
- )
-
- {:ok, entry}
-
- :error ->
- :ignored
- end
- end
-
- # Function capture with arity: &OtherModule.foo/3
- def extract(
- {:&, _,
- [
- {:/, _,
- [
- {{:., _, [{:__aliases__, start_metadata, maybe_module}, _function_name]}, _, []},
- _
- ]}
- ]},
- %Reducer{} = reducer
- ) do
- case module(reducer, maybe_module) do
- {:ok, module} ->
- start = Metadata.position(start_metadata)
- range = to_range(reducer, maybe_module, start)
- %Block{} = current_block = Reducer.current_block(reducer)
-
- entry =
- Entry.reference(
- reducer.analysis.document.path,
- current_block,
- Subject.module(module),
- :module,
- range,
- Application.get_application(module)
- )
-
- {:ok, entry}
-
- _ ->
- :ignored
- end
- end
-
- def extract(_, _) do
- :ignored
- end
-
- defp defimpl_range(%Reducer{} = reducer, {_, protocol_meta, _} = protocol_ast) do
- start = Sourceror.get_start_position(protocol_ast)
- {finish_line, finish_column} = Metadata.position(protocol_meta, :do)
- # add two to include the do
- finish_column = finish_column + 2
- document = reducer.analysis.document
-
- Range.new(
- Position.new(document, start[:line], start[:column]),
- Position.new(document, finish_line, finish_column)
- )
- end
-
- defp resolve_for_block(
- %Reducer{} = reducer,
- {{:__block__, _, [:for]}, {:__aliases__, _, for_target}}
- ) do
- resolve_alias(reducer, for_target)
- end
-
- defp resolve_for_block(_, _), do: :error
-
- defp resolve_alias(%Reducer{} = reducer, unresolved_alias) do
- position = Reducer.position(reducer)
-
- RemoteControl.Analyzer.expand_alias(unresolved_alias, reducer.analysis, position)
- end
-
- defp module(%Reducer{} = reducer, maybe_module) when is_list(maybe_module) do
- with true <- Enum.all?(maybe_module, &module_part?/1),
- {:ok, resolved} <- resolve_alias(reducer, maybe_module) do
- {:ok, resolved}
- else
- _ ->
- human_location = Reducer.human_location(reducer)
-
- Logger.warning(
- "Could not expand module #{inspect(maybe_module)}. Please report this (at #{human_location})"
- )
-
- :error
- end
- end
-
- defp module(%Reducer{}, maybe_erlang_module) when is_atom(maybe_erlang_module) do
- if available_module?(maybe_erlang_module) do
- {:ok, maybe_erlang_module}
- else
- :error
- end
- end
-
- defp module(_, _), do: :error
-
- @protocol_module_attribue_names [:protocol, :for]
-
- @starts_with_capital ~r/[A-Z]+/
- defp module_part?(part) when is_atom(part) do
- Regex.match?(@starts_with_capital, Atom.to_string(part))
- end
-
- defp module_part?({:@, _, [{type, _, _} | _]}) when type in @protocol_module_attribue_names,
- do: true
-
- defp module_part?({:__MODULE__, _, context}) when is_atom(context), do: true
-
- defp module_part?(_), do: false
-
- defp available_module?(potential_module) do
- MapSet.member?(all_modules(), potential_module)
- end
-
- defp all_modules do
- ProcessCache.trans(:all_modules, fn ->
- MapSet.new(:code.all_available(), fn {module_charlist, _, _} ->
- List.to_atom(module_charlist)
- end)
- end)
- end
-
- # handles @protocol and @for in defimpl blocks
- defp to_range(%Reducer{} = reducer, [{:@, _, [{type, _, _} | _]} = attribute | segments], _)
- when type in @protocol_module_attribue_names do
- range = Sourceror.get_range(attribute)
-
- document = reducer.analysis.document
- module_length = segments |> Ast.Module.name() |> String.length()
- # add one because we're off by the @ sign
- end_column = range.end[:column] + module_length + 1
-
- Range.new(
- Position.new(document, range.start[:line], range.start[:column]),
- Position.new(document, range.end[:line], end_column)
- )
- end
-
- defp to_range(%Reducer{} = reducer, module_name, {line, column}) do
- document = reducer.analysis.document
-
- module_length =
- module_name
- |> Ast.Module.name()
- |> String.length()
-
- Range.new(
- Position.new(document, line, column),
- Position.new(document, line, column + module_length)
- )
- end
-
- defp block_range(document, ast) do
- case Ast.Range.fetch(ast, document) do
- {:ok, range} -> range
- _ -> nil
- end
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module_attribute.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module_attribute.ex
deleted file mode 100644
index f182e832..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/module_attribute.ex
+++ /dev/null
@@ -1,107 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.ModuleAttribute do
- @moduledoc """
- Extracts module attribute definitions and references from AST
- """
-
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
- alias Lexical.RemoteControl.Search.Subject
-
- require Logger
-
- # Finds module attribute usages
- def extract({:@, _, [{attr_name, _, nil}]}, %Reducer{} = reducer) do
- block = Reducer.current_block(reducer)
-
- case Analyzer.current_module(reducer.analysis, Reducer.position(reducer)) do
- {:ok, current_module} ->
- reference =
- Entry.reference(
- reducer.analysis.document.path,
- block,
- Subject.module_attribute(current_module, attr_name),
- :module_attribute,
- reference_range(reducer, attr_name),
- Application.get_application(current_module)
- )
-
- {:ok, reference}
-
- :error ->
- :ignored
- end
- end
-
- # an attribute being typed above an already existing attribute will have the name `@`, which we ignore
- # example:
- # @|
- # @callback foo() :: :ok
- def extract({:@, _, [{:@, _, _attr_value}]}, %Reducer{}) do
- :ignored
- end
-
- # Finds module attribute definitions @foo 3
- def extract({:@, _, [{attr_name, _, _attr_value}]} = attr, %Reducer{} = reducer) do
- block = Reducer.current_block(reducer)
-
- case Analyzer.current_module(reducer.analysis, Reducer.position(reducer)) do
- {:ok, current_module} ->
- definition =
- Entry.definition(
- reducer.analysis.document.path,
- block,
- Subject.module_attribute(current_module, attr_name),
- :module_attribute,
- definition_range(reducer, attr),
- Application.get_application(current_module)
- )
-
- {:ok, definition}
-
- _ ->
- :ignored
- end
- end
-
- def extract(_, _) do
- :ignored
- end
-
- defp reference_range(%Reducer{} = reducer, attr_name) do
- document = reducer.analysis.document
-
- name_length =
- attr_name
- |> Atom.to_string()
- |> String.length()
-
- {start_line, start_column} = reducer.position
-
- # add 1 to include the @ character
- end_column = start_column + name_length + 1
-
- Range.new(
- Position.new(document, start_line, start_column),
- Position.new(document, start_line, end_column)
- )
- end
-
- defp definition_range(%Reducer{} = reducer, attr_ast) do
- document = reducer.analysis.document
-
- [line: start_line, column: start_column] = Sourceror.get_start_position(attr_ast)
-
- end_line = Sourceror.get_end_line(attr_ast)
- {:ok, line_text} = Lexical.Document.fetch_text_at(document, end_line)
- # add one because lsp positions are one-based
- end_column = String.length(line_text) + 1
-
- Range.new(
- Position.new(document, start_line, start_column),
- Position.new(document, end_line, end_column)
- )
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/struct_definition.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/struct_definition.ex
deleted file mode 100644
index b8663a30..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/struct_definition.ex
+++ /dev/null
@@ -1,35 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.StructDefinition do
- alias Lexical.Ast
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
-
- def extract({:defstruct, _, [_fields]} = definition, %Reducer{} = reducer) do
- document = reducer.analysis.document
- block = Reducer.current_block(reducer)
-
- case Analyzer.current_module(reducer.analysis, Reducer.position(reducer)) do
- {:ok, current_module} ->
- range = Ast.Range.fetch!(definition, document)
-
- entry =
- Entry.definition(
- document.path,
- block,
- current_module,
- :struct,
- range,
- Application.get_application(current_module)
- )
-
- {:ok, entry}
-
- _ ->
- :ignored
- end
- end
-
- def extract(_, _) do
- :ignored
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/struct_reference.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/struct_reference.ex
deleted file mode 100644
index 46b77018..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/struct_reference.ex
+++ /dev/null
@@ -1,93 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.StructReference do
- alias Lexical.Ast
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
- alias Lexical.RemoteControl.Search.Subject
-
- require Logger
-
- @struct_fn_names [:struct, :struct!]
-
- # Handles usages via an alias, e.g. x = %MyStruct{...} or %__MODULE__{...}
- def extract(
- {:%, _, [struct_alias, {:%{}, _, _struct_args}]} = reference,
- %Reducer{} = reducer
- ) do
- case expand_alias(struct_alias, reducer) do
- {:ok, struct_module} ->
- {:ok, entry(reducer, struct_module, reference)}
-
- _ ->
- :ignored
- end
- end
-
- # Call to Kernel.struct with a fully qualified module e.g. Kernel.struct(MyStruct, ...)
- def extract(
- {{:., _, [kernel_alias, struct_fn_name]}, _, [struct_alias | _]} = reference,
- %Reducer{} = reducer
- )
- when struct_fn_name in @struct_fn_names do
- with {:ok, Kernel} <- expand_alias(kernel_alias, reducer),
- {:ok, struct_module} <- expand_alias(struct_alias, reducer) do
- {:ok, entry(reducer, struct_module, reference)}
- else
- _ ->
- :ignored
- end
- end
-
- # handles calls to Kernel.struct e.g. struct(MyModule) or struct(MyModule, foo: 3)
- def extract({struct_fn_name, _, [struct_alias | _] = args} = reference, %Reducer{} = reducer)
- when struct_fn_name in @struct_fn_names do
- reducer_position = Reducer.position(reducer)
- imports = Analyzer.imports_at(reducer.analysis, reducer_position)
- arity = length(args)
-
- with true <- Enum.member?(imports, {Kernel, struct_fn_name, arity}),
- {:ok, struct_module} <- expand_alias(struct_alias, reducer) do
- {:ok, entry(reducer, struct_module, reference)}
- else
- _ ->
- :ignored
- end
- end
-
- def extract(_, _) do
- :ignored
- end
-
- defp entry(%Reducer{} = reducer, struct_module, reference) do
- document = reducer.analysis.document
- block = Reducer.current_block(reducer)
- subject = Subject.module(struct_module)
-
- Entry.reference(
- document.path,
- block,
- subject,
- :struct,
- Ast.Range.fetch!(reference, document),
- Application.get_application(struct_module)
- )
- end
-
- defp expand_alias({:__aliases__, _, struct_alias}, %Reducer{} = reducer) do
- Analyzer.expand_alias(struct_alias, reducer.analysis, Reducer.position(reducer))
- end
-
- defp expand_alias({:__MODULE__, _, _}, %Reducer{} = reducer) do
- Analyzer.current_module(reducer.analysis, Reducer.position(reducer))
- end
-
- defp expand_alias(alias, %Reducer{} = reducer) do
- {line, column} = reducer.position
-
- Logger.error(
- "Could not expand alias: #{inspect(alias)} at #{reducer.analysis.document.path} #{line}:#{column}"
- )
-
- :error
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/variable.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/variable.ex
deleted file mode 100644
index bd433aee..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/extractors/variable.ex
+++ /dev/null
@@ -1,249 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.Variable do
- alias Lexical.Ast
- alias Lexical.RemoteControl.Analyzer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
-
- @defs [:def, :defmacro, :defp, :defmacrop]
-
- def extract(
- {def, _, [{:when, _, [{_fn_name, _, params} | when_args]}, body]},
- %Reducer{} = reducer
- )
- when def in @defs do
- entries = extract_definitions(params, reducer) ++ extract_references(when_args, reducer)
- {:ok, entries, body}
- end
-
- def extract({def, _, [{_fn_name, _, params}, body]}, %Reducer{} = reducer)
- when def in @defs do
- entries = extract_definitions(params, reducer)
-
- {:ok, entries, body}
- end
-
- # Stab operator x -> body
- def extract({:->, _, [params, body]}, %Reducer{} = reducer) do
- entries = extract_definitions(params, reducer) ++ extract_in_definitions(params, reducer)
-
- {:ok, entries, List.wrap(body)}
- end
-
- # with match operator. with {:ok, var} <- something()
- def extract({:<-, _, [left, right]}, %Reducer{} = reducer) do
- entries = extract_definitions(left, reducer)
-
- {:ok, entries, List.wrap(right)}
- end
-
- # Match operator left = right
- def extract({:=, _, [left, right]}, %Reducer{} = reducer) do
- definitions = extract_definitions(left, reducer)
-
- {:ok, definitions, List.wrap(right)}
- end
-
- # String interpolations "#{foo}"
- def extract(
- {:<<>>, _, [{:"::", _, [{{:., _, [Kernel, :to_string]}, _, body}, {:binary, _, _}]}]},
- %Reducer{}
- ) do
- {:ok, [], body}
- end
-
- # Test declarations
- def extract(
- {:test, _metadata,
- [
- {:__block__, [delimiter: "\"", line: 3, column: 8], ["my test"]},
- args,
- body
- ]},
- %Reducer{} = reducer
- ) do
- entries = extract_definitions(args, reducer)
- {:ok, entries, body}
- end
-
- def extract({:binary, _, _}, %Reducer{}) do
- :ignored
- end
-
- def extract({:@, _, _}, %Reducer{}) do
- {:ok, nil, nil}
- end
-
- # Generic variable reference
- def extract({var_name, _, _} = ast, %Reducer{} = reducer) when is_atom(var_name) do
- case extract_reference(ast, reducer, get_current_app(reducer)) do
- %Entry{} = entry -> {:ok, entry}
- _ -> :ignored
- end
- end
-
- # Pin operator ^pinned_variable
- def extract({:^, _, [reference]}, %Reducer{} = reducer) do
- reference = extract_reference(reference, reducer, get_current_app(reducer))
-
- {:ok, reference, nil}
- end
-
- def extract(_ast, _reducer) do
- :ignored
- end
-
- defp extract_definitions(ast, reducer) do
- current_app = get_current_app(reducer)
-
- {_ast, entries} =
- Macro.prewalk(ast, [], fn ast, acc ->
- case extract_definition(ast, reducer, current_app) do
- %Entry{} = entry ->
- {ast, [entry | acc]}
-
- {%Entry{} = entry, ast} ->
- {ast, [entry | acc]}
-
- {entries, ast} when is_list(entries) ->
- {ast, entries ++ acc}
-
- {_, ast} ->
- {ast, acc}
-
- _ ->
- {ast, acc}
- end
- end)
-
- Enum.reverse(entries)
- end
-
- # the pin operator is always on the left side of a pattern match, but it's
- # not defining a variable, just referencing one.
- defp extract_definition({:^, _, [reference]}, %Reducer{} = reducer, current_app) do
- reference = extract_reference(reference, reducer, current_app)
-
- {reference, nil}
- end
-
- # unquote(expression)
- defp extract_definition({:unquote, _, [expr]}, %Reducer{} = reducer, current_app) do
- reference = extract_reference(expr, reducer, current_app)
- {reference, nil}
- end
-
- defp extract_definition({:@, _, _}, %Reducer{}, _current_app) do
- {nil, []}
- end
-
- # when clauses actually contain parameters and references
- defp extract_definition({:when, _, when_args}, %Reducer{} = reducer, _current_app) do
- {definitions, references} =
- Enum.split_with(when_args, fn {_, _, context} -> is_atom(context) end)
-
- definitions = extract_definitions(definitions, reducer)
- references = extract_references(references, reducer)
-
- {Enum.reverse(definitions ++ references), nil}
- end
-
- # This is an effect of string interpolation
- defp extract_definition({:binary, _metadata, nil}, _reducer, _current_app) do
- nil
- end
-
- defp extract_definition({var_name, _metadata, nil} = ast, reducer, current_app) do
- if used_variable?(var_name) do
- document = reducer.analysis.document
- block = Reducer.current_block(reducer)
-
- Entry.definition(
- document.path,
- block,
- var_name,
- :variable,
- Ast.Range.fetch!(ast, document),
- current_app
- )
- end
- end
-
- defp extract_definition(_, _reducer, _current_app), do: nil
-
- defp extract_references(ast, reducer) do
- current_app = get_current_app(reducer)
-
- {_ast, entries} =
- Macro.prewalk(ast, [], fn ast, acc ->
- case extract_reference(ast, reducer, current_app) do
- %Entry{} = entry ->
- {ast, [entry | acc]}
-
- _ ->
- {ast, acc}
- end
- end)
-
- Enum.reverse(entries)
- end
-
- defp extract_reference({var_name, _metadata, nil} = ast, reducer, current_app) do
- if used_variable?(var_name) do
- document = reducer.analysis.document
- block = Reducer.current_block(reducer)
-
- Entry.reference(
- document.path,
- block,
- var_name,
- :variable,
- Ast.Range.fetch!(ast, document),
- current_app
- )
- end
- end
-
- defp extract_reference(_, _, _) do
- nil
- end
-
- # extracts definitions like e in SomeException ->
- defp extract_in_definitions(ast, %Reducer{} = reducer) do
- current_app = get_current_app(reducer)
-
- {_ast, entries} =
- Macro.prewalk(ast, [], fn ast, acc ->
- case extract_in_definition(ast, reducer, current_app) do
- %Entry{} = entry ->
- {ast, [entry | acc]}
-
- _ ->
- {ast, acc}
- end
- end)
-
- Enum.reverse(entries)
- end
-
- defp extract_in_definition(
- [[{:in, _, [definition, _right]}], _body],
- %Reducer{} = reducer,
- current_app
- ) do
- extract_definition(definition, reducer, current_app)
- end
-
- defp extract_in_definition(_ast, %Reducer{}, _current_app), do: nil
-
- defp get_current_app(%Reducer{} = reducer) do
- with {:ok, module} <- Analyzer.current_module(reducer.analysis, Reducer.position(reducer)) do
- Application.get_application(module)
- end
- end
-
- defp used_variable?(variable_name) do
- not (variable_name
- |> Atom.to_string()
- |> String.starts_with?("_"))
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/module.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/module.ex
deleted file mode 100644
index 55632bf9..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/module.ex
+++ /dev/null
@@ -1,27 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Module do
- alias Lexical.RemoteControl.Search.Indexer
-
- def index(module) do
- with true <- indexable?(module),
- {:ok, path, source} <- source_file_path(module) do
- Indexer.Source.index(path, source)
- else
- _ ->
- nil
- end
- end
-
- defp source_file_path(module) do
- with {:ok, file_path} <- Keyword.fetch(module.__info__(:compile), :source),
- {:ok, contents} <- File.read(file_path) do
- {:ok, file_path, contents}
- end
- end
-
- defp indexable?(Kernel.SpecialForms), do: false
-
- defp indexable?(module) do
- module_string = to_string(module)
- String.starts_with?(module_string, "Elixir.")
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/quoted.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/quoted.ex
deleted file mode 100644
index 18cac388..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/quoted.ex
+++ /dev/null
@@ -1,29 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Quoted do
- alias Lexical.Ast.Analysis
- alias Lexical.ProcessCache
- alias Lexical.RemoteControl.Search.Indexer.Source.Reducer
-
- require ProcessCache
-
- def index_with_cleanup(%Analysis{} = analysis) do
- ProcessCache.with_cleanup do
- index(analysis)
- end
- end
-
- def index(analysis, extractors \\ nil)
-
- def index(%Analysis{valid?: true} = analysis, extractors) do
- {_, reducer} =
- Macro.prewalk(analysis.ast, Reducer.new(analysis, extractors), fn elem, reducer ->
- {reducer, elem} = Reducer.reduce(reducer, elem)
- {elem, reducer}
- end)
-
- {:ok, Reducer.entries(reducer)}
- end
-
- def index(%Analysis{valid?: false}, _extractors) do
- {:ok, []}
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/indexer/source.ex b/apps/remote_control/lib/lexical/remote_control/search/indexer/source.ex
deleted file mode 100644
index cd21c009..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/indexer/source.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Source do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.RemoteControl.Search.Indexer
-
- require Logger
-
- def index(path, source, extractors \\ nil) do
- path
- |> Document.new(source, 1)
- |> index_document(extractors)
- end
-
- def index_document(%Document{} = document, extractors \\ nil) do
- document
- |> Ast.analyze()
- |> Indexer.Quoted.index(extractors)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store.ex b/apps/remote_control/lib/lexical/remote_control/search/store.ex
deleted file mode 100644
index 0ad31fb2..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/store.ex
+++ /dev/null
@@ -1,294 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Store do
- @moduledoc """
- A persistent store for search entries
- """
-
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store
- alias Lexical.RemoteControl.Search.Store.State
-
- @type index_state :: :empty | :stale
- @type existing_entries :: [Entry.t()]
- @type new_entries :: [Entry.t()]
- @type updated_entries :: [Entry.t()]
- @type paths_to_delete :: [Path.t()]
- @typedoc """
- A function that creates indexes when none is detected
- """
- @type create_index ::
- (project :: Project.t() ->
- {:ok, new_entries} | {:error, term()})
-
- @typedoc """
- A function that takes existing entries and refreshes them if necessary
- """
- @type refresh_index ::
- (project :: Project.t(), entries :: existing_entries ->
- {:ok, new_entries, paths_to_delete} | {:error, term()})
-
- @backend Application.compile_env(:remote_control, :search_store_backend, Store.Backends.Ets)
- @flush_interval_ms Application.compile_env(
- :remote_control,
- :search_store_quiescent_period_ms,
- 2500
- )
-
- import Api.Messages
- use GenServer
- require Logger
-
- def stop do
- GenServer.stop(__MODULE__)
- end
-
- def loaded? do
- GenServer.call(__MODULE__, :loaded?)
- end
-
- def replace(entries) do
- GenServer.call(__MODULE__, {:replace, entries})
- end
-
- @spec exact(Entry.subject_query(), Entry.constraints()) :: {:ok, [Entry.t()]} | {:error, term()}
- def exact(subject \\ :_, constraints) do
- call_or_default({:exact, subject, constraints}, [])
- end
-
- @spec prefix(String.t(), Entry.constraints()) :: {:ok, [Entry.t()]} | {:error, term()}
- def prefix(prefix, constraints) do
- call_or_default({:prefix, prefix, constraints}, [])
- end
-
- @spec parent(Entry.t()) :: {:ok, Entry.t()} | {:error, term()}
- def parent(%Entry{} = entry) do
- call_or_default({:parent, entry}, nil)
- end
-
- @spec siblings(Entry.t()) :: {:ok, [Entry.t()]} | {:error, term()}
- def siblings(%Entry{} = entry) do
- call_or_default({:siblings, entry}, [])
- end
-
- @spec fuzzy(Entry.subject(), Entry.constraints()) :: {:ok, [Entry.t()]} | {:error, term()}
- def fuzzy(subject, constraints) do
- call_or_default({:fuzzy, subject, constraints}, [])
- end
-
- def clear(path) do
- GenServer.call(__MODULE__, {:update, path, []})
- end
-
- def update(path, entries) do
- GenServer.call(__MODULE__, {:update, path, entries})
- end
-
- def destroy do
- GenServer.call(__MODULE__, :destroy)
- end
-
- def enable do
- GenServer.call(__MODULE__, :enable)
- end
-
- @spec start_link(Project.t(), create_index, refresh_index, module()) :: GenServer.on_start()
- def start_link(%Project{} = project, create_index, refresh_index, backend) do
- GenServer.start_link(__MODULE__, [project, create_index, refresh_index, backend],
- name: __MODULE__
- )
- end
-
- def child_spec(init_args) when is_list(init_args) do
- %{
- id: __MODULE__,
- start: {__MODULE__, :start_link, normalize_init_args(init_args)}
- }
- end
-
- defp normalize_init_args([create_index, refresh_index]) do
- normalize_init_args([Lexical.RemoteControl.get_project(), create_index, refresh_index])
- end
-
- defp normalize_init_args([%Project{} = project, create_index, refresh_index]) do
- normalize_init_args([project, create_index, refresh_index, backend()])
- end
-
- defp normalize_init_args([%Project{}, create_index, refresh_index, backend] = args)
- when is_function(create_index, 1) and is_function(refresh_index, 2) and is_atom(backend) do
- args
- end
-
- @impl GenServer
- def init([%Project{} = project, create_index, update_index, backend]) do
- Process.flag(:fullsweep_after, 5)
- schedule_gc()
- # I've found that if indexing happens before the first compile, for some reason
- # the compilation is 4x slower than if indexing happens after it. I was
- # unable to figure out why this is the case, and I looked extensively, so instead
- # we have this bandaid. We wait for the first compilation to complete, and then
- # the search store enables itself, at which point we index the code.
-
- RemoteControl.register_listener(self(), project_compiled())
- state = State.new(project, create_index, update_index, backend)
- {:ok, state}
- end
-
- @impl GenServer
- # enable ourselves when the project is force compiled
- def handle_info(project_compiled(), %State{} = state) do
- {:noreply, enable(state)}
- end
-
- def handle_info(project_compiled(), {_, _} = state) do
- # we're already enabled, no need to do anything
- {:noreply, state}
- end
-
- # handle the result from `State.async_load/1`
- def handle_info({ref, result}, {update_ref, %State{async_load_ref: ref} = state}) do
- {:noreply, {update_ref, State.async_load_complete(state, result)}}
- end
-
- def handle_info(:flush_updates, {_, %State{} = state}) do
- {:ok, state} = State.flush_buffered_updates(state)
- ref = schedule_flush()
- {:noreply, {ref, state}}
- end
-
- def handle_info(:gc, state) do
- :erlang.garbage_collect()
- schedule_gc()
- {:noreply, state}
- end
-
- def handle_info(_, state) do
- {:noreply, state}
- end
-
- @impl GenServer
- def handle_call(:enable, _from, %State{} = state) do
- {:reply, :ok, enable(state)}
- end
-
- def handle_call(:enable, _from, state) do
- {:reply, :ok, state}
- end
-
- def handle_call({:replace, entities}, _from, {ref, %State{} = state}) do
- {reply, new_state} =
- case State.replace(state, entities) do
- {:ok, new_state} ->
- {:ok, State.drop_buffered_updates(new_state)}
-
- {:error, _} = error ->
- {error, state}
- end
-
- {:reply, reply, {ref, new_state}}
- end
-
- def handle_call({:exact, subject, constraints}, _from, {ref, %State{} = state}) do
- {:reply, State.exact(state, subject, constraints), {ref, state}}
- end
-
- def handle_call({:prefix, prefix, constraints}, _from, {ref, %State{} = state}) do
- {:reply, State.prefix(state, prefix, constraints), {ref, state}}
- end
-
- def handle_call({:fuzzy, subject, constraints}, _from, {ref, %State{} = state}) do
- {:reply, State.fuzzy(state, subject, constraints), {ref, state}}
- end
-
- def handle_call({:update, path, entries}, _from, {ref, %State{} = state}) do
- {reply, new_ref, new_state} = do_update(state, ref, path, entries)
-
- {:reply, reply, {new_ref, new_state}}
- end
-
- def handle_call({:parent, entry}, _from, {_, %State{} = state} = orig_state) do
- parent = State.parent(state, entry)
- {:reply, parent, orig_state}
- end
-
- def handle_call({:siblings, entry}, _from, {_, %State{} = state} = orig_state) do
- siblings = State.siblings(state, entry)
- {:reply, siblings, orig_state}
- end
-
- def handle_call(:on_stop, _, {ref, %State{} = state}) do
- {:ok, state} = State.flush_buffered_updates(state)
-
- State.drop(state)
- {:reply, :ok, {ref, state}}
- end
-
- def handle_call(:loaded?, _, {ref, %State{loaded?: loaded?} = state}) do
- {:reply, loaded?, {ref, state}}
- end
-
- def handle_call(:loaded?, _, %State{loaded?: loaded?} = state) do
- # We're not enabled yet, but we can still reply to the query
- {:reply, loaded?, state}
- end
-
- def handle_call(:destroy, _, {ref, %State{} = state}) do
- new_state = State.destroy(state)
- {:reply, :ok, {ref, new_state}}
- end
-
- def handle_call(message, _from, %State{} = state) do
- Logger.warning("Received #{inspect(message)}, but the search store isn't enabled yet.")
- {:reply, {:error, {:not_enabled, message}}, state}
- end
-
- @impl GenServer
- def terminate(_reason, {_, state}) do
- {:ok, state} = State.flush_buffered_updates(state)
- {:noreply, state}
- end
-
- defp backend do
- @backend
- end
-
- defp do_update(state, old_ref, path, entries) do
- {:ok, schedule_flush(old_ref), State.buffer_updates(state, path, entries)}
- end
-
- defp schedule_flush(ref) when is_reference(ref) do
- Process.cancel_timer(ref)
- schedule_flush()
- end
-
- defp schedule_flush(_) do
- schedule_flush()
- end
-
- defp schedule_flush do
- Process.send_after(self(), :flush_updates, @flush_interval_ms)
- end
-
- defp enable(%State{} = state) do
- state = State.async_load(state)
- :persistent_term.put({__MODULE__, :enabled?}, true)
- {nil, state}
- end
-
- defp schedule_gc do
- Process.send_after(self(), :gc, :timer.seconds(5))
- end
-
- defp call_or_default(call, default) do
- if enabled?() do
- GenServer.call(__MODULE__, call)
- else
- default
- end
- end
-
- defp enabled? do
- :persistent_term.get({__MODULE__, :enabled?}, false)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/legacy_v0.ex b/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/legacy_v0.ex
deleted file mode 100644
index 57253244..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/schemas/legacy_v0.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.Schemas.LegacyV0 do
- @moduledoc """
- A legacy version of the schema.
-
- We pushed the initial indexer to main before we added schemas and versioning.
- This represents that schema type, hence the non-versioned name.
- """
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schema
-
- use Schema, version: 0
-
- def index_file_name do
- "source.index.ets"
- end
-
- def to_rows(_) do
- []
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/state.ex b/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/state.ex
deleted file mode 100644
index 08891fa3..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/store/backends/ets/state.ex
+++ /dev/null
@@ -1,297 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Store.Backends.Ets.State do
- @moduledoc """
- An ETS based search backend
-
- This backend uses an ETS table to store its data using a schema defined in the schemas submodule.
-
- """
- alias Lexical.Project
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schema
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Schemas
- alias Lexical.RemoteControl.Search.Store.Backends.Ets.Wal
-
- @schema_order [
- Schemas.LegacyV0,
- Schemas.V1,
- Schemas.V2,
- Schemas.V3
- ]
-
- import Wal, only: :macros
- import Entry, only: :macros
-
- import Schemas.V3,
- only: [
- by_block_id: 1,
- query_by_id: 1,
- query_by_path: 1,
- query_structure: 1,
- query_by_subject: 1,
- structure: 1,
- to_subject: 1
- ]
-
- defstruct [:project, :table_name, :leader?, :leader_pid, :wal_state]
-
- def new_leader(%Project{} = project) do
- %__MODULE__{project: project, leader?: true, leader_pid: self()}
- end
-
- def new_follower(%Project{} = project, leader_pid) do
- %__MODULE__{project: project, leader?: false, leader_pid: leader_pid}
- end
-
- def prepare(%__MODULE__{leader?: true} = state) do
- {:ok, wal, table_name, result} = Schema.load(state.project, @schema_order)
-
- {{:ok, result}, %__MODULE__{state | table_name: table_name, wal_state: wal}}
- end
-
- def prepare(%__MODULE__{leader?: false}) do
- {:error, :not_leader}
- end
-
- def drop(%__MODULE__{leader?: true} = state) do
- Wal.truncate(state.wal_state)
- :ets.delete_all_objects(state.table_name)
- end
-
- def insert(%__MODULE__{leader?: true} = state, entries) do
- rows = Schema.entries_to_rows(entries, current_schema())
-
- with_wal state.wal_state do
- true = :ets.insert(state.table_name, rows)
- end
-
- :ok
- end
-
- def reduce(%__MODULE__{} = state, acc, reducer_fun) do
- ets_reducer = fn
- {{:by_id, _, _, _}, entries}, acc when is_list(entries) ->
- Enum.reduce(entries, acc, reducer_fun)
-
- {{:by_id, _, _, _}, %Entry{} = entry}, acc ->
- reducer_fun.(entry, acc)
-
- _, acc ->
- acc
- end
-
- :ets.foldl(ets_reducer, acc, state.table_name)
- end
-
- def find_by_subject(%__MODULE__{} = state, subject, type, subtype) do
- match_pattern =
- query_by_subject(
- subject: to_subject(subject),
- type: type,
- subtype: subtype
- )
-
- state.table_name
- |> :ets.match_object({match_pattern, :_})
- |> Enum.flat_map(fn {_, id_keys} ->
- id_keys
- end)
- |> MapSet.new()
- |> Enum.flat_map(&:ets.lookup_element(state.table_name, &1, 2))
- end
-
- def find_by_prefix(%__MODULE__{} = state, subject, type, subtype) do
- match_pattern =
- query_by_subject(
- subject: to_prefix(subject),
- type: type,
- subtype: subtype
- )
-
- state.table_name
- |> :ets.select([{{match_pattern, :_}, [], [:"$_"]}])
- |> Stream.flat_map(fn {_, id_keys} -> id_keys end)
- |> Stream.uniq()
- |> Enum.flat_map(&:ets.lookup_element(state.table_name, &1, 2))
- end
-
- @dialyzer {:nowarn_function, to_prefix: 1}
-
- defp to_prefix(prefix) when is_binary(prefix) do
- # what we really want to do here is convert the prefix to a improper list
- # like this: `'abc' -> [97, 98, 99 | :_]`, it's different from `'abc' ++ [:_]`
- # this is the required format for the `:ets.select` function.
- {last_char, others} = prefix |> String.to_charlist() |> List.pop_at(-1)
- others ++ [last_char | :_]
- end
-
- def siblings(%__MODULE__{} = state, %Entry{} = entry) do
- key = by_block_id(block_id: entry.block_id, path: entry.path)
-
- siblings =
- state.table_name
- |> :ets.lookup_element(key, 2)
- |> Enum.map(&:ets.lookup_element(state.table_name, &1, 2))
- |> List.flatten()
- |> Enum.filter(fn sibling ->
- case {is_block(entry), is_block(sibling)} do
- {same, same} -> true
- _ -> false
- end
- end)
- |> Enum.sort_by(& &1.id)
- |> Enum.uniq()
-
- {:ok, siblings}
- rescue
- ArgumentError ->
- :error
- end
-
- def parent(%__MODULE__{} = state, %Entry{} = entry) do
- with {:ok, structure} <- structure_for_path(state, entry.path),
- {:ok, child_path} <- child_path(structure, entry.block_id) do
- child_path =
- if is_block(entry) do
- # if we're a block, finding the first block will find us, so pop
- # our id off the path.
- tl(child_path)
- else
- child_path
- end
-
- find_first_by_block_id(state, child_path)
- end
- end
-
- def parent(%__MODULE__{}, :root) do
- :error
- end
-
- def find_by_ids(%__MODULE__{} = state, ids, type, subtype)
- when is_list(ids) do
- for id <- ids,
- match_pattern = match_id_key(id, type, subtype),
- {_key, entry} <- :ets.match_object(state.table_name, match_pattern) do
- entry
- end
- |> List.flatten()
- end
-
- def replace_all(%__MODULE__{leader?: true} = state, entries) do
- rows = Schema.entries_to_rows(entries, current_schema())
-
- {:ok, _, result} =
- with_wal state.wal_state do
- true = :ets.delete_all_objects(state.table_name)
- true = :ets.insert(state.table_name, rows)
- :ok
- end
-
- # When we replace everything, the old checkpoint is invalidated
- # so it makes sense to force a new one.
- Wal.checkpoint(state.wal_state)
- result
- end
-
- def delete_by_path(%__MODULE__{leader?: true} = state, path) do
- ids_to_delete =
- state.table_name
- |> :ets.match({query_by_path(path: path), :"$0"})
- |> List.flatten()
-
- with_wal state.wal_state do
- :ets.match_delete(state.table_name, {query_by_subject(path: path), :_})
- :ets.match_delete(state.table_name, {query_by_path(path: path), :_})
- :ets.match_delete(state.table_name, {query_structure(path: path), :_})
- end
-
- Enum.each(ids_to_delete, fn id ->
- with_wal state.wal_state do
- :ets.delete(state.table_name, id)
- end
- end)
-
- {:ok, ids_to_delete}
- end
-
- def destroy_all(%Project{} = project) do
- Wal.destroy_all(project)
- end
-
- def destroy(%__MODULE__{leader?: true, wal_state: %Wal{}} = state) do
- Wal.destroy(state.wal_state)
- end
-
- def destroy(%__MODULE__{leader?: true}) do
- :ok
- end
-
- def terminate(%__MODULE__{wal_state: %Wal{}} = state) do
- Wal.close(state.wal_state)
- end
-
- def terminate(%__MODULE__{}) do
- :ok
- end
-
- defp child_path(structure, child_id) do
- path =
- Enum.reduce_while(structure, [], fn
- {^child_id, _children}, children ->
- {:halt, [child_id | children]}
-
- {_, children}, path when map_size(children) == 0 ->
- {:cont, path}
-
- {current_id, children}, path ->
- case child_path(children, child_id) do
- {:ok, child_path} -> {:halt, [current_id | path] ++ Enum.reverse(child_path)}
- :error -> {:cont, path}
- end
- end)
-
- case path do
- [] -> :error
- path -> {:ok, Enum.reverse(path)}
- end
- end
-
- defp find_first_by_block_id(%__MODULE__{} = state, block_ids) do
- Enum.reduce_while(block_ids, :error, fn block_id, failure ->
- case find_entry_by_id(state, block_id) do
- {:ok, _} = success ->
- {:halt, success}
-
- _ ->
- {:cont, failure}
- end
- end)
- end
-
- def find_entry_by_id(%__MODULE__{} = state, id) do
- case find_by_ids(state, [id], :_, :_) do
- [entry] -> {:ok, entry}
- _ -> :error
- end
- end
-
- def structure_for_path(%__MODULE__{} = state, path) do
- key = structure(path: path)
-
- case :ets.lookup_element(state.table_name, key, 2) do
- [structure] -> {:ok, structure}
- _ -> :error
- end
- rescue
- ArgumentError ->
- :error
- end
-
- defp match_id_key(id, type, subtype) do
- {query_by_id(id: id, type: type, subtype: subtype), :_}
- end
-
- defp current_schema do
- List.last(@schema_order)
- end
-end
diff --git a/apps/remote_control/lib/lexical/remote_control/search/store/state.ex b/apps/remote_control/lib/lexical/remote_control/search/store/state.ex
deleted file mode 100644
index 9742282f..00000000
--- a/apps/remote_control/lib/lexical/remote_control/search/store/state.ex
+++ /dev/null
@@ -1,260 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Store.State do
- alias Lexical.Project
- alias Lexical.RemoteControl.Api.Messages
- alias Lexical.RemoteControl.Dispatch
- alias Lexical.RemoteControl.Search.Fuzzy
- alias Lexical.RemoteControl.Search.Indexer.Entry
-
- require Logger
- import Messages
-
- defstruct [
- :project,
- :backend,
- :create_index,
- :update_index,
- :loaded?,
- :fuzzy,
- :async_load_ref,
- :update_buffer
- ]
-
- def new(%Project{} = project, create_index, update_index, backend) do
- %__MODULE__{
- backend: backend,
- create_index: create_index,
- project: project,
- loaded?: false,
- update_index: update_index,
- update_buffer: %{},
- fuzzy: Fuzzy.from_entries([])
- }
- end
-
- def drop(%__MODULE__{} = state) do
- state.backend.drop()
- end
-
- def destroy(%__MODULE__{} = state) do
- state.backend.destroy(state)
- end
-
- @doc """
- Asynchronously loads the search state.
-
- This function returns prior to creating or refreshing the index, which
- occurs in a separate process. The caller should listen for a message
- of the shape `{ref, result}`, where `ref` matches the state's
- `:async_load_ref`. Once received, that result should be passed to
- `async_load_complete/2`.
- """
- def async_load(%__MODULE__{loaded?: false, async_load_ref: nil} = state) do
- {:ok, backend_result} = state.backend.new(state.project)
- prepare_backend_async(state, backend_result)
- end
-
- def async_load(%__MODULE__{} = state) do
- {:ok, state}
- end
-
- def async_load_complete(%__MODULE__{} = state, result) do
- new_state = %__MODULE__{state | loaded?: true, async_load_ref: nil}
-
- response =
- case result do
- {:create_index, result} ->
- create_index_complete(new_state, result)
-
- {:update_index, result} ->
- update_index_complete(new_state, result)
-
- :initialize_fuzzy ->
- initialize_fuzzy(new_state)
- end
-
- Dispatch.broadcast(project_index_ready(project: state.project))
- response
- end
-
- def replace(%__MODULE__{} = state, entries) do
- with :ok <- state.backend.replace_all(entries),
- :ok <- maybe_sync(state) do
- {:ok, %__MODULE__{state | fuzzy: Fuzzy.from_backend(state.backend)}}
- end
- end
-
- def exact(%__MODULE__{} = state, subject, constraints) do
- type = Keyword.get(constraints, :type, :_)
- subtype = Keyword.get(constraints, :subtype, :_)
-
- case state.backend.find_by_subject(subject, type, subtype) do
- l when is_list(l) -> {:ok, l}
- error -> error
- end
- end
-
- def prefix(%__MODULE__{} = state, prefix, constraints) do
- type = Keyword.get(constraints, :type, :_)
- subtype = Keyword.get(constraints, :subtype, :_)
-
- case state.backend.find_by_prefix(prefix, type, subtype) do
- l when is_list(l) ->
- {:ok, l}
-
- error ->
- error
- end
- end
-
- def fuzzy(%__MODULE__{} = state, subject, constraints) do
- case Fuzzy.match(state.fuzzy, subject) do
- [] ->
- {:ok, []}
-
- ids ->
- type = Keyword.get(constraints, :type, :_)
- subtype = Keyword.get(constraints, :subtype, :_)
-
- case state.backend.find_by_ids(ids, type, subtype) do
- l when is_list(l) -> {:ok, l}
- error -> error
- end
- end
- end
-
- def siblings(%__MODULE__{} = state, entry) do
- case state.backend.siblings(entry) do
- l when is_list(l) -> {:ok, l}
- error -> error
- end
- end
-
- def parent(%__MODULE__{} = state, entry) do
- case state.backend.parent(entry) do
- %Entry{} = entry -> {:ok, entry}
- error -> error
- end
- end
-
- def buffer_updates(%__MODULE__{} = state, path, entries) do
- %__MODULE__{state | update_buffer: Map.put(state.update_buffer, path, entries)}
- end
-
- def drop_buffered_updates(%__MODULE__{} = state) do
- %__MODULE__{state | update_buffer: %{}}
- end
-
- def flush_buffered_updates(%__MODULE__{update_buffer: buffer} = state)
- when map_size(buffer) == 0 do
- maybe_sync(state)
- {:ok, state}
- end
-
- def flush_buffered_updates(%__MODULE__{} = state) do
- result =
- Enum.reduce_while(state.update_buffer, state, fn {path, entries}, state ->
- case update_nosync(state, path, entries) do
- {:ok, new_state} ->
- {:cont, new_state}
-
- error ->
- {:halt, error}
- end
- end)
-
- with %__MODULE__{} = state <- result,
- :ok <- maybe_sync(state) do
- {:ok, drop_buffered_updates(state)}
- end
- end
-
- def update_nosync(%__MODULE__{} = state, path, entries) do
- with {:ok, deleted_ids} <- state.backend.delete_by_path(path),
- :ok <- state.backend.insert(entries) do
- fuzzy =
- state.fuzzy
- |> Fuzzy.drop_values(deleted_ids)
- |> Fuzzy.add(entries)
-
- {:ok, %__MODULE__{state | fuzzy: fuzzy}}
- end
- end
-
- require Logger
-
- defp prepare_backend_async(%__MODULE__{async_load_ref: nil} = state, backend_result) do
- task =
- Task.async(fn ->
- case state.backend.prepare(backend_result) do
- {:ok, :empty} ->
- Logger.info("backend reports empty")
- {:create_index, state.create_index.(state.project)}
-
- {:ok, :stale} ->
- Logger.info("backend reports stale")
- {:update_index, state.update_index.(state.project, state.backend)}
-
- {:error, :not_leader} ->
- :initialize_fuzzy
-
- error ->
- Logger.error("Could not initialize index due to #{inspect(error)}")
- error
- end
- end)
-
- %__MODULE__{state | async_load_ref: task.ref}
- end
-
- defp create_index_complete(%__MODULE__{} = state, {:ok, entries}) do
- case replace(state, entries) do
- {:ok, state} ->
- state
-
- {:error, _} ->
- Logger.warning("Could not replace entries")
- state
- end
- end
-
- defp create_index_complete(%__MODULE__{} = state, {:error, _} = error) do
- Logger.warning("Could not create index, got: #{inspect(error)}")
- state
- end
-
- defp update_index_complete(%__MODULE__{} = state, {:ok, updated_entries, deleted_paths}) do
- starting_state = initialize_fuzzy(%__MODULE__{state | loaded?: true})
-
- new_state =
- updated_entries
- |> Enum.group_by(& &1.path)
- |> Enum.reduce(starting_state, fn {path, entry_list}, state ->
- {:ok, new_state} = update_nosync(state, path, entry_list)
- new_state
- end)
-
- Enum.reduce(deleted_paths, new_state, fn path, state ->
- {:ok, new_state} = update_nosync(state, path, [])
- new_state
- end)
- end
-
- defp update_index_complete(%__MODULE__{} = state, {:error, _} = error) do
- Logger.warning("Could not update index, got: #{inspect(error)}")
- state
- end
-
- defp maybe_sync(%__MODULE__{} = state) do
- if function_exported?(state.backend, :sync, 1) do
- state.backend.sync(state.project)
- else
- :ok
- end
- end
-
- defp initialize_fuzzy(%__MODULE__{} = state) do
- fuzzy = Fuzzy.from_backend(state.backend)
-
- %__MODULE__{state | fuzzy: fuzzy}
- end
-end
diff --git a/apps/remote_control/lib/mix/tasks/namespace/module.ex b/apps/remote_control/lib/mix/tasks/namespace/module.ex
deleted file mode 100644
index 54833af6..00000000
--- a/apps/remote_control/lib/mix/tasks/namespace/module.ex
+++ /dev/null
@@ -1,83 +0,0 @@
-defmodule Mix.Tasks.Namespace.Module do
- alias Mix.Tasks.Namespace
-
- @namespace_prefix "LX"
-
- def apply(module_name) do
- cond do
- prefixed?(module_name) ->
- module_name
-
- module_name in Namespace.app_names() ->
- :"lx_#{module_name}"
-
- true ->
- module_name
- |> Atom.to_string()
- |> apply_namespace()
- end
- end
-
- def prefixed?(module) when is_atom(module) do
- module
- |> Atom.to_string()
- |> prefixed?()
- end
-
- def prefixed?("Elixir." <> rest),
- do: prefixed?(rest)
-
- def prefixed?(@namespace_prefix <> _),
- do: true
-
- def prefixed?("lx_" <> _),
- do: true
-
- def prefixed?([?l, ?x, ?_ | _]), do: true
- def prefixed?([?E, ?l, ?i, ?x, ?i, ?r, ?., ?L, ?X | _]), do: true
- def prefixed?([?L, ?X | _]), do: true
-
- def prefixed?(_),
- do: false
-
- defp apply_namespace("Elixir." <> rest) do
- Namespace.root_modules()
- |> Enum.map(fn module -> module |> Module.split() |> List.first() end)
- |> Enum.reduce_while(rest, fn root_module, module ->
- if has_root_module?(root_module, module) do
- namespaced_module =
- module
- |> String.replace(root_module, namespace(root_module), global: false)
- |> String.to_atom()
-
- {:halt, namespaced_module}
- else
- {:cont, module}
- end
- end)
- |> List.wrap()
- |> Module.concat()
- end
-
- defp apply_namespace(erlang_module) do
- String.to_atom(erlang_module)
- end
-
- defp has_root_module?(root_module, root_module), do: true
-
- defp has_root_module?(root_module, candidate) do
- String.contains?(candidate, append_trailing_period(root_module))
- end
-
- defp namespace("Lexical") do
- "#{@namespace_prefix}ical"
- end
-
- defp namespace(orig) do
- @namespace_prefix <> orig
- end
-
- defp append_trailing_period(str) do
- str <> "."
- end
-end
diff --git a/apps/remote_control/mix.exs b/apps/remote_control/mix.exs
deleted file mode 100644
index ed954516..00000000
--- a/apps/remote_control/mix.exs
+++ /dev/null
@@ -1,61 +0,0 @@
-defmodule Lexical.RemoteControl.MixProject do
- use Mix.Project
- Code.require_file("../../mix_includes.exs")
-
- def project do
- [
- app: :remote_control,
- version: "0.7.2",
- elixir: "~> 1.15",
- start_permanent: Mix.env() == :prod,
- deps: deps(),
- dialyzer: Mix.Dialyzer.config(),
- elixirc_paths: elixirc_paths(Mix.env()),
- aliases: aliases(),
- preferred_cli_env: [benchmark: :test]
- ]
- end
-
- def application do
- [
- extra_applications: [:logger, :sasl, :eex, :path_glob],
- mod: {Lexical.RemoteControl.Application, []}
- ]
- end
-
- # cli/0 is new for elixir 1.15, prior, we need to set `preferred_cli_env` in the project
- def cli do
- [
- preferred_envs: [benchmark: :test]
- ]
- end
-
- defp elixirc_paths(:test) do
- ~w(lib test/support)
- end
-
- defp elixirc_paths(_) do
- ~w(lib)
- end
-
- defp deps do
- [
- {:benchee, "~> 1.3", only: :test},
- {:common, path: "../common", env: Mix.env()},
- Mix.Credo.dependency(),
- Mix.Dialyzer.dependency(),
- {:elixir_sense,
- github: "elixir-lsp/elixir_sense", ref: "73ce7e0d239342fb9527d7ba567203e77dbb9b25"},
- {:patch, "~> 0.15", only: [:dev, :test], optional: true, runtime: false},
- {:path_glob, "~> 0.2", optional: true},
- {:phoenix_live_view, "~> 1.0", only: [:test], optional: true, runtime: false},
- {:sourceror, "~> 1.9"},
- {:stream_data, "~> 1.1", only: [:test], runtime: false},
- {:refactorex, "~> 0.1.51"}
- ]
- end
-
- defp aliases do
- [test: "test --no-start", benchmark: "run"]
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/analyzer/aliases_test.exs b/apps/remote_control/test/lexical/remote_control/analyzer/aliases_test.exs
deleted file mode 100644
index 956d77d1..00000000
--- a/apps/remote_control/test/lexical/remote_control/analyzer/aliases_test.exs
+++ /dev/null
@@ -1,730 +0,0 @@
-defmodule Lexical.RemoteControl.Analyzer.AliasesTest do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.RemoteControl.Analyzer
-
- import Lexical.Test.CursorSupport
- import Lexical.Test.CodeSigil
- import Lexical.Test.RangeSupport
-
- use ExUnit.Case
-
- def aliases_at_cursor(text) do
- {position, document} = pop_cursor(text, as: :document)
-
- document
- |> Ast.analyze()
- |> Analyzer.aliases_at(position)
- end
-
- defp scope_aliases(text) do
- {position, document} = pop_cursor(text, as: :document)
-
- aliases =
- document
- |> Ast.analyze()
- |> Ast.Analysis.scopes_at(position)
- |> Enum.flat_map(& &1.aliases)
- |> Map.new(&{&1.as, &1})
-
- {aliases, document}
- end
-
- describe "top level aliases" do
- test "a useless alias" do
- aliases =
- ~q[
- alias Foo
- |
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Foo] == Foo
- end
-
- test "an alias outside of a module" do
- aliases =
- ~q[
- alias Foo.Bar.Baz
- defmodule Parent do
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Baz] == Foo.Bar.Baz
- end
-
- test "an alias inside the body of a module" do
- aliases =
- ~q[
- defmodule Basic do
- alias Foo.Bar
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases == %{__MODULE__: Basic, Bar: Foo.Bar, Basic: Basic}
- end
-
- test "an alias using as" do
- aliases =
- ~q[
- defmodule TopLevel do
- alias Foo.Bar, as: FooBar
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:__MODULE__] == TopLevel
- assert aliases[:FooBar] == Foo.Bar
- end
-
- test "an alias using warn" do
- aliases =
- ~q[
- defmodule TopLevel do
- alias Foo.Bar, warn: false
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Bar] == Foo.Bar
- end
-
- test "an alias using warn and as" do
- aliases =
- ~q[
- defmodule TopLevel do
- alias Foo.Bar, warn: false, as: FooBar
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:FooBar] == Foo.Bar
- end
-
- test "multiple aliases off of single alias" do
- aliases =
- ~q[
- defmodule TopLevel do
- alias Foo.{First, Second, Third.Fourth}
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:First] == Foo.First
- assert aliases[:Second] == Foo.Second
- assert aliases[:Fourth] == Foo.Third.Fourth
- end
-
- test "multiple aliases off of nested alias" do
- aliases =
- ~q[
- defmodule TopLevel do
- alias Foo.Bar.{First, Second, Third.Fourth}
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:First] == Foo.Bar.First
- assert aliases[:Second] == Foo.Bar.Second
- assert aliases[:Fourth] == Foo.Bar.Third.Fourth
- end
-
- test "aliasing __MODULE__" do
- aliases =
- ~q[
- defmodule Something.Is.Nested do
- alias __MODULE__|
-
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Nested] == Something.Is.Nested
- end
-
- test "multiple aliases leading by current module" do
- aliases =
- ~q[
- defmodule TopLevel do
- alias __MODULE__.{First, Second}
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:First] == TopLevel.First
- assert aliases[:Second] == TopLevel.Second
- end
-
- test "multiple aliases leading by current module's child" do
- aliases =
- ~q[
- defmodule TopLevel do
- alias __MODULE__.Child.{First, Second}
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:First] == TopLevel.Child.First
- assert aliases[:Second] == TopLevel.Child.Second
- end
-
- test "aliases expanding other aliases" do
- aliases =
- ~q[
- alias Foo.Bar.Baz
- alias Baz.Quux|
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Baz] == Foo.Bar.Baz
- assert aliases[:Quux] == Foo.Bar.Baz.Quux
- end
-
- test "aliases expanding current module" do
- aliases = ~q[
- defmodule TopLevel do
- alias __MODULE__.Foo|
- end
- ] |> aliases_at_cursor()
-
- assert aliases[:Foo] == TopLevel.Foo
- end
-
- test "aliases expanding current module using as" do
- aliases = ~q[
- defmodule TopLevel do
- alias __MODULE__.Foo, as: OtherAlias|
- end
- ] |> aliases_at_cursor()
-
- assert aliases[:OtherAlias] == TopLevel.Foo
- end
-
- test "can be overridden" do
- aliases =
- ~q[
- alias Foo.Bar.Baz
- alias Other.Baz
- |
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Baz] == Other.Baz
- end
-
- test "can be accessed before being overridden" do
- aliases =
- ~q[
- alias Foo.Bar.Baz
- |
- alias Other.Baz
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Baz] == Foo.Bar.Baz
- end
-
- test "aliases used to define a module" do
- aliases =
- ~q[
- alias Something.Else
- defmodule Else.Other do
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Else] == Something.Else
- end
-
- test "in a protocol implementation" do
- aliases =
- ~q[
- defimpl MyProtocol, for: Atom do
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:"@protocol"] == MyProtocol
- assert aliases[:"@for"] == Atom
- end
- end
-
- describe "alias ranges" do
- test "for a simple alias" do
- {aliases, doc} =
- ~q[
- defmodule Parent do
- alias Foo.Bar.Baz|
- end
- ]
- |> scope_aliases()
-
- assert decorate(doc, aliases[:Baz].range) =~ " «alias Foo.Bar.Baz»"
- end
-
- test "for a multiple alias on one line" do
- {aliases, doc} =
- ~q[
- defmodule Parent do
- alias Foo.Bar.{Baz, Quux}|
- end
- ]
- |> scope_aliases()
-
- assert decorate(doc, aliases[:Baz].range) =~ " «alias Foo.Bar.{Baz, Quux}»"
- assert decorate(doc, aliases[:Quux].range) =~ " «alias Foo.Bar.{Baz, Quux}»"
- end
-
- test "for a multiple alias on multiple lines" do
- {aliases, doc} =
- ~q[
- defmodule Parent do
- alias Foo.Bar.{
- Baz,
- Quux,
- Other
- }|
- end
- ]
- |> scope_aliases()
-
- for name <- [:Baz, :Quux, :Other] do
- assert decorate(doc, aliases[name].range) =~
- " «alias Foo.Bar.{\n Baz,\n Quux,\n Other\n}»"
- end
- end
-
- def column_after_do(%Document{} = doc, line) do
- with {:ok, text} <- Document.fetch_text_at(doc, line),
- {:ok, column} <- find_do_position(text, 0) do
- column + 2
- else
- _ ->
- :not_found
- end
- end
-
- def find_do_position("do" <> _, position) do
- {:ok, position}
- end
-
- def find_do_position(<<_c::utf8, rest::binary>>, position) do
- find_do_position(rest, position + 1)
- end
-
- def find_do_position(<<>>, _) do
- :not_found
- end
-
- test "__MODULE__ implicit aliases don't have a visible range" do
- {aliases, doc} =
- ~q[
- defmodule MyModule do
- |
- end
- ]
- |> scope_aliases()
-
- module_range = aliases[:__MODULE__].range
-
- refute aliases[:__MODULE__].explicit?
- assert module_range.start.line == 1
- assert module_range.start.character == column_after_do(doc, 1)
- assert module_range.start == module_range.end
- end
-
- test "implicit parent alias doesn't have a range" do
- {aliases, doc} =
- ~q[
- defmodule Parent do
- defmodule Child do
- |
- end
- end
- ]
- |> scope_aliases()
-
- parent_range = aliases[:Parent].range
-
- refute aliases[:Parent].explicit?
- assert parent_range.start.line == 1
- assert parent_range.start.character == column_after_do(doc, 1)
- assert parent_range.start == parent_range.end
- end
-
- test "protocol implicit aliases doesn't have a visible range" do
- {aliases, doc} =
- ~q[
- defimpl MyThing, for: MyProtocol do
- |
- end
- ]
- |> scope_aliases()
-
- # the implicit aliases don't have any text in their range
-
- for_range = aliases[:"@for"].range
- refute aliases[:"@for"].explicit?
- assert for_range.start.line == 1
- assert for_range.start.character == column_after_do(doc, 1)
- assert for_range.start == for_range.end
-
- protocol_range = aliases[:"@protocol"].range
- refute aliases[:"@protocol"].explicit?
- assert protocol_range.start.line == 1
- assert protocol_range.start.character == column_after_do(doc, 1)
- assert protocol_range.start == protocol_range.end
- end
- end
-
- describe "nested modules" do
- test "no aliases are defined for modules with dots" do
- aliases =
- ~q[
- defmodule GrandParent.Parent.Child do
- |
- end
- ]
- |> aliases_at_cursor()
-
- refute Map.has_key?(aliases, :Child)
- end
-
- test "with children get their parents name" do
- aliases =
- ~q[
- defmodule Grandparent.Parent do
- defmodule Child do
- |
- end
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Child] == Grandparent.Parent.Child
- assert aliases[:__MODULE__] == Grandparent.Parent.Child
- end
-
- test "with a child that has an explicit parent" do
- aliases =
- ~q[
- defmodule Parent do
- defmodule __MODULE__.Child do
- |
- end
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:__MODULE__] == Parent.Child
- end
- end
-
- describe "alias scopes" do
- test "aliases are removed when leaving a module" do
- aliases =
- ~q[
- defmodule Basic do
- alias Foo.Bar
- end|
- ]
- |> aliases_at_cursor()
-
- assert aliases == %{Basic: Basic}
- end
-
- test "aliases inside of nested modules" do
- aliases =
- ~q[
- defmodule Parent do
- alias Foo.Grandparent
-
- defmodule Child do
- alias Foo.Something
- |
- end
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Grandparent] == Foo.Grandparent
- assert aliases[:Something] == Foo.Something
- assert aliases[:__MODULE__] == Parent.Child
- assert aliases[:Child] == Parent.Child
- end
-
- test "multiple nested module are aliased after definition" do
- aliases =
- ~q[
- defmodule Parent do
- alias Foo.Grandparent
-
- defmodule Child do
- alias Foo.Something
- end
-
- defmodule AnotherChild do
- alias Foo.Something
- end
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:AnotherChild] == Parent.AnotherChild
- assert aliases[:Child] == Parent.Child
- end
-
- test "an alias defined in a named function" do
- aliases =
- ~q[
- defmodule Parent do
- def fun do
- alias Foo.Parent
- |
- end
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Parent] == Foo.Parent
- end
-
- test "an alias defined in a named function doesn't leak" do
- aliases =
- ~q[
- defmodule Parent do
- def fun do
- alias Foo.Parent
- end|
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Parent] == Parent
- end
-
- test "an alias defined in a private named function" do
- aliases =
- ~q[
- defmodule Parent do
- defp fun do
- alias Foo.Parent
- |
- end
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Parent] == Foo.Parent
- end
-
- test "an alias defined in a private named function doesn't leak" do
- aliases =
- ~q[
- defmodule Parent do
- defp fun do
- alias Foo.InFun
- end|
- end
- ]
- |> aliases_at_cursor()
-
- refute aliases[:InFun]
- end
-
- test "an alias defined in a DSL" do
- aliases =
- ~q[
- defmodule Parent do
- my_dsl do
- alias Foo.Parent
- |
- end
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Parent] == Foo.Parent
- end
-
- test "an alias defined in a DSL does not leak" do
- aliases =
- ~q[
- defmodule Parent do
- my_dsl do
- alias Foo.InDSL
- end
- |
- end
- ]
- |> aliases_at_cursor()
-
- refute aliases[InDsl]
- end
-
- test "an alias defined in an if statement" do
- aliases =
- ~q[
- if test() do
- alias Foo.Something
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Something]
- end
-
- test "an alias defined in an if statement does not leak" do
- aliases =
- ~q[
- if test() do
- alias Foo.Something
- end
- |
- ]
- |> aliases_at_cursor()
-
- refute aliases[:Something]
- end
-
- test "an alias defined in an cond statement" do
- aliases =
- ~q[
- cond do
- something() ->
- alias Foo.Something
- |Else
- true ->
- :ok
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Something]
- end
-
- test "an alias defined in an cond statement shouldn't leak" do
- aliases =
- ~q[
- cond do
- something() ->
- alias Foo.Something
- true ->
- |
- :ok
- end
- ]
- |> aliases_at_cursor()
-
- refute aliases[:Something]
-
- aliases =
- ~q[
- cond do
- something() ->
- alias Foo.Something
- true ->
- :ok
- end
- |
- ]
- |> aliases_at_cursor()
-
- refute aliases[:Something]
- end
-
- test "an alias defined in an with statement" do
- aliases =
- ~q[
- with {:ok, val} <- some_function() do
- alias Foo.Something
- |
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Something]
- end
-
- test "an alias defined in an with statement shouldn't leak" do
- aliases =
- ~q[
- with {:ok, val} <- some_function() do
- alias Foo.Something
- end
- |
- ]
- |> aliases_at_cursor()
-
- refute aliases[:Something]
- end
-
- test "sibling modules with nested blocks" do
- aliases =
- ~q[
- defmodule First do
- defstuff do
- field :x
- end
- end
-
- defmodule Second do
- defstuff do
- field :y
- end
- end
- |
- ]
- |> aliases_at_cursor()
-
- assert aliases[:First] == First
- assert aliases[:Second] == Second
- end
-
- test "an alias defined in a anonymous function" do
- aliases =
- ~q[
- fn x ->
- alias Foo.Bar
- Bar|
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Bar] == Foo.Bar
- end
-
- test "an alias defined in a anonymous function doesn't leak" do
- aliases =
- ~q[
- fn
- x ->
- alias Foo.Bar
- Bar.bar(x)
- y ->
- alias Baz.Buzz
- |Buzz
- end
- ]
- |> aliases_at_cursor()
-
- assert aliases[:Buzz] == Baz.Buzz
- refute aliases[:Bar]
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/build/state_test.exs b/apps/remote_control/test/lexical/remote_control/build/state_test.exs
deleted file mode 100644
index d4894e9b..00000000
--- a/apps/remote_control/test/lexical/remote_control/build/state_test.exs
+++ /dev/null
@@ -1,153 +0,0 @@
-defmodule Lexical.RemoteControl.Build.StateTest do
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Build
- alias Lexical.RemoteControl.Build.State
- alias Lexical.RemoteControl.Plugin
-
- import Lexical.Test.Fixtures
-
- use ExUnit.Case, async: false
- use Patch
-
- setup do
- start_supervised!(RemoteControl.Dispatch)
- start_supervised!(RemoteControl.Api.Proxy)
- start_supervised!(Build.CaptureServer)
- start_supervised!(RemoteControl.ModuleMappings)
- start_supervised!(Plugin.Runner.Coordinator)
- start_supervised!(Plugin.Runner.Supervisor)
- :ok
- end
-
- def document(%State{} = state, filename \\ "file.ex", source_code) do
- sequence = System.unique_integer([:monotonic, :positive])
-
- uri =
- state.project
- |> Project.root_path()
- |> Path.join(to_string(sequence))
- |> Path.join(filename)
- |> Document.Path.to_uri()
-
- Document.new(uri, source_code, 0)
- end
-
- def with_project_state(project_name) do
- test = self()
-
- patch(RemoteControl.Dispatch, :broadcast, &send(test, &1))
-
- project_name = to_string(project_name)
- fixture_dir = Path.join(fixtures_path(), project_name)
- project = Project.new("file://#{fixture_dir}")
- state = State.new(project)
-
- RemoteControl.set_project(project)
- {:ok, state}
- end
-
- def with_metadata_project(_) do
- {:ok, state} = with_project_state(:project_metadata)
- {:ok, state: state}
- end
-
- def with_a_valid_document(%{state: state}) do
- source = ~S[
- defmodule Testing.ValidSource do
- def add(a, b) do
- a + b
- end
- end
- ]
-
- document = document(state, source)
- {:ok, document: document}
- end
-
- def with_patched_compilation(_) do
- patch(Build.Document, :compile, :ok)
- patch(Build.Project, :compile, :ok)
- :ok
- end
-
- describe "throttled document compilation" do
- setup [:with_metadata_project, :with_a_valid_document, :with_patched_compilation]
-
- test "it doesn't compile immediately", %{state: state, document: document} do
- State.on_file_compile(state, document)
-
- refute_called(Build.Document.compile(document))
- refute_called(Build.Project.compile(_, _))
- end
-
- test "it compiles files when on_timeout is called", %{state: state, document: document} do
- state
- |> State.on_file_compile(document)
- |> State.on_timeout()
-
- assert_called(Build.Document.compile(document))
- refute_called(Build.Project.compile(_, _))
- end
- end
-
- describe "throttled project compilation" do
- setup [:with_metadata_project, :with_a_valid_document, :with_patched_compilation]
-
- test "doesn't compile immediately if forced", %{state: state} do
- State.on_project_compile(state, true)
- refute_called(Build.Project.compile(_, _))
- end
-
- test "doesn't compile immediately", %{state: state} do
- State.on_project_compile(state, false)
- refute_called(Build.Project.compile(_, _))
- end
-
- test "compiles if force is true after on_timeout is called", %{state: state} do
- state
- |> State.on_project_compile(true)
- |> State.on_timeout()
-
- assert_called(Build.Project.compile(_, true))
- end
-
- test "compiles after on_timeout is called", %{state: state} do
- state
- |> State.on_project_compile(false)
- |> State.on_timeout()
-
- assert_called(Build.Project.compile(_, false))
- end
- end
-
- describe "mixed compilation" do
- setup [:with_metadata_project, :with_a_valid_document, :with_patched_compilation]
-
- test "doesn't compile if both documents and projects are added", %{
- state: state,
- document: document
- } do
- state
- |> State.on_project_compile(false)
- |> State.on_file_compile(document)
-
- refute_called(Build.Document.compile(_))
- refute_called(Build.Project.compile(_, _))
- end
-
- test "compiles when on_timeout is called if both documents and projects are added", %{
- state: state,
- document: document
- } do
- state
- |> State.on_project_compile(false)
- |> State.on_file_compile(document)
- |> State.on_timeout()
-
- assert_called(Build.Document.compile(_))
- assert_called(Build.Project.compile(_, _))
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/code_action/handlers/add_alias_test.exs b/apps/remote_control/test/lexical/remote_control/code_action/handlers/add_alias_test.exs
deleted file mode 100644
index a0eb1c40..00000000
--- a/apps/remote_control/test/lexical/remote_control/code_action/handlers/add_alias_test.exs
+++ /dev/null
@@ -1,307 +0,0 @@
-defmodule Lexical.RemoteControl.CodeAction.Handlers.AddAliasTest do
- alias Lexical.Ast.Analysis.Scope
- alias Lexical.CodeUnit
- alias Lexical.Document
- alias Lexical.Document.Line
- alias Lexical.Document.Range
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeAction.Handlers.AddAlias
- alias Lexical.RemoteControl.Search.Store
-
- import Lexical.Test.CursorSupport
- import Lexical.Test.CodeSigil
-
- use Lexical.Test.CodeMod.Case, enable_ast_conversion: false
- use Patch
-
- setup do
- start_supervised!({Document.Store, derive: [analysis: &Lexical.Ast.analyze/1]})
- :ok
- end
-
- def apply_code_mod(text, _ast, options) do
- range = options[:range]
- uri = "file:///file.ex"
- :ok = Document.Store.open(uri, text, 1)
- {:ok, document} = Document.Store.fetch(uri)
-
- edits =
- case AddAlias.actions(document, range, []) do
- [action] -> action.changes.edits
- _ -> []
- end
-
- {:ok, edits}
- end
-
- def add_alias(original_text, modules_to_return) do
- {position, stripped_text} = pop_cursor(original_text)
- patch_fuzzy_search(modules_to_return)
- range = Range.new(position, position)
- modify(stripped_text, range: range)
- end
-
- def patch_fuzzy_search(modules_to_return) do
- all_modules =
- Enum.map(modules_to_return, fn module ->
- {Atom.to_charlist(module), :code.which(module), :code.is_loaded(module)}
- end)
-
- patch(AddAlias, :all_modules, all_modules)
- end
-
- describe "in an existing module with no aliases" do
- test "aliases are added at the top of the module" do
- patch(RemoteControl, :get_project, %Lexical.Project{})
-
- {:ok, added} =
- ~q[
- defmodule MyModule do
- def my_fn do
- Line|
- end
- end
- ]
- |> add_alias([Line])
-
- expected = ~q[
- defmodule MyModule do
- alias Lexical.Document.Line
- def my_fn do
- Line
- end
- end
- ]t
- assert added =~ expected
- end
- end
-
- describe "in an existing module" do
- end
-
- describe "in the root context" do
- end
-
- describe "adding an alias" do
- test "does nothing on an invalid document" do
- {:ok, added} = add_alias("%Lexical.RemoteControl.Search.", [Lexical.RemoteControl.Search])
-
- assert added == "%Lexical.RemoteControl.Search."
- end
-
- test "outside of a module with aliases" do
- {:ok, added} =
- ~q[
- alias ZZ.XX.YY
- Line|
- ]
- |> add_alias([Line])
-
- expected = ~q[
- alias Lexical.Document.Line
- alias ZZ.XX.YY
- Line
- ]t
-
- assert added == expected
- end
-
- test "when a full module name is given" do
- {:ok, added} =
- ~q[
- Lexical.RemoteControl.Search.Store.Backend|
- ]
- |> add_alias([Store.Backend])
-
- expected = ~q[
- alias Lexical.RemoteControl.Search.Store.Backend
- Backend
- ]t
-
- assert added == expected
- end
-
- test "when a full module name is given in a module function" do
- patch(RemoteControl, :get_project, %Lexical.Project{})
-
- {:ok, added} =
- ~q[
- defmodule MyModule do
- def my_fun do
- result = Lexical.RemoteControl.Search.Store|
- end
- end
- ]
- |> add_alias([Store])
-
- expected = ~q[
- defmodule MyModule do
- alias Lexical.RemoteControl.Search.Store
- def my_fun do
- result = Store
- end
- end
- ]t
-
- assert added =~ expected
- end
-
- test "outside of a module with no aliases" do
- {:ok, added} =
- ~q[Line|]
- |> add_alias([Line])
-
- expected = ~q[
- alias Lexical.Document.Line
- Line
- ]t
-
- assert added == expected
- end
-
- test "in a module with no aliases" do
- patch(RemoteControl, :get_project, %Lexical.Project{})
-
- {:ok, added} =
- ~q[
- defmodule MyModule do
- def my_fun do
- Line|
- end
- end
- ]
- |> add_alias([Line])
-
- expected = ~q[
- defmodule MyModule do
- alias Lexical.Document.Line
- def my_fun do
- Line
- end
- end
- ]t
-
- assert added =~ expected
- end
-
- test "outside of functions" do
- {:ok, added} =
- ~q[
- defmodule MyModule do
- alias Something.Else
- Line|
- end
- ]
- |> add_alias([Line])
-
- expected = ~q[
- defmodule MyModule do
- alias Lexical.Document.Line
- alias Something.Else
- Line
- end
- ]
-
- assert expected =~ added
- end
-
- test "inside a function" do
- {:ok, added} =
- ~q[
- defmodule MyModule do
- alias Something.Else
- def my_fn do
- Line|
- end
- end
- ]
- |> add_alias([Line])
-
- expected = ~q[
- defmodule MyModule do
- alias Lexical.Document.Line
- alias Something.Else
- def my_fn do
- Line
- end
- end
- ]
- assert expected =~ added
- end
-
- test "inside a nested module" do
- {:ok, added} =
- ~q[
- defmodule Parent do
- alias Top.Level
- defmodule Child do
- alias Some.Other
- Line|
- end
- end
- ]
- |> add_alias([Line])
-
- expected = ~q[
- defmodule Parent do
- alias Top.Level
- defmodule Child do
- alias Lexical.Document.Line
- alias Some.Other
- Line
- end
- end
- ]t
-
- assert added =~ expected
- end
-
- test "aliases for struct references don't include non-struct modules" do
- {:ok, added} = add_alias("%Scope|{}", [Lexical.Ast, Scope])
-
- expected = ~q[
- alias Lexical.Ast.Analysis.Scope
- %Scope
- ]t
-
- assert added =~ expected
- end
-
- test "only modules with a similarly named function will be included in aliases" do
- {:ok, added} = add_alias("Document.fetch|", [Document, RemoteControl])
-
- expected = ~q[
- alias Lexical.Document
- Document.fetch
- ]t
-
- assert added =~ expected
- end
-
- test "protocols are excluded" do
- {:ok, added} = add_alias("Co|", [Collectable, CodeUnit])
- expected = ~q[
- alias Lexical.CodeUnit
- Co
- ]t
-
- assert added =~ expected
- end
-
- test "protocol implementations are excluded" do
- {:ok, added} =
- add_alias("Lin|", [Lexical.Document.Lines, Enumerable.Lexical.Document.Lines])
-
- expected = ~q[
- alias Lexical.Document.Lines
- Lin
- ]t
- assert added =~ expected
- end
-
- test "erlang modules are excluded" do
- {:ok, added} = add_alias(":ets|", [:ets])
- assert added =~ ":ets"
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/code_intelligence/variable_test.exs b/apps/remote_control/test/lexical/remote_control/code_intelligence/variable_test.exs
deleted file mode 100644
index 4ede9e69..00000000
--- a/apps/remote_control/test/lexical/remote_control/code_intelligence/variable_test.exs
+++ /dev/null
@@ -1,490 +0,0 @@
-defmodule Lexical.RemoteControl.CodeIntelligence.VariableTest do
- alias Lexical.Ast
- alias Lexical.RemoteControl.CodeIntelligence.Variable
-
- use ExUnit.Case
-
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
- import Lexical.Test.RangeSupport
-
- def find_definition(code) do
- {position, document} = pop_cursor(code, as: :document)
- analysis = Ast.analyze(document)
- {:ok, {:local_or_var, var_name}} = Ast.cursor_context(analysis, position)
-
- case Variable.definition(analysis, position, List.to_atom(var_name)) do
- {:ok, entry} -> {:ok, entry.range, document}
- error -> error
- end
- end
-
- def find_references(code, include_definition? \\ false) do
- {position, document} = pop_cursor(code, as: :document)
- analysis = Ast.analyze(document)
- {:ok, {:local_or_var, var_name}} = Ast.cursor_context(analysis, position)
-
- ranges =
- analysis
- |> Variable.references(position, List.to_atom(var_name), include_definition?)
- |> Enum.map(& &1.range)
-
- {:ok, ranges, document}
- end
-
- describe "definitions in a single scope" do
- test "are returned if it is selected" do
- {:ok, range, doc} =
- ~q[
- def foo(param|) do
- param
- end
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "def foo(«param») do"
- end
-
- test "are found in a parameter" do
- {:ok, range, doc} =
- ~q[
- def foo(param) do
- param|
- end
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "def foo(«param») do"
- end
-
- test "are found in a parameter list" do
- {:ok, range, doc} =
- ~q[
- def foo(other_param, param) do
- param|
- end
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "def foo(other_param, «param») do"
- end
-
- test "are found when shadowed" do
- {:ok, range, doc} =
- ~q[
- def foo(param) do
- param = param + 1
- param|
- end
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "«param» = param + 1"
- end
-
- test "are found when shadowing a parameter" do
- {:ok, range, doc} =
- ~q[
- def foo(param) do
- param = param| + 1
- end
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "def foo(«param») do"
- end
-
- test "when there are multiple definitions on one line" do
- {:ok, range, doc} =
- ~q[
- param = 3
- foo = param = param + 1
- param|
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "= «param» = param + 1"
- end
-
- test "when the definition is in a map key" do
- {:ok, range, doc} =
- ~q[
- %{key: value} = map
- value|
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "%{key: «value»} = map"
- end
- end
-
- describe "definitions across scopes" do
- test "works in an if in a function" do
- {:ok, range, doc} =
- ~q[
- def my_fun do
- foo = 3
- if something do
- foo|
- end
- end
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "«foo» = 3"
- end
-
- test "works for variables defined in a module" do
- {:ok, range, doc} =
- ~q[
- defmodule Parent do
- x = 3
- def fun do
- unquote(x|)
- end
- end
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "«x» = 3"
- end
-
- test "works for variables defined outside module" do
- {:ok, range, doc} =
- ~q[
- x = 3
- defmodule Parent do
- def fun do
- unquote(x|)
- end
- end
- ]
- |> find_definition()
-
- assert decorate(doc, range) =~ "«x» = 3"
- end
- end
-
- describe "references" do
- test "in a function parameter" do
- {:ok, [range], doc} =
- ~q[
- def something(param|) do
- param
- end
- ]
- |> find_references()
-
- assert decorate(doc, range) =~ "«param»"
- end
-
- test "can include definitions" do
- {:ok, [definition, reference], doc} =
- ~q[
- def something(param|) do
- param
- end
- ]
- |> find_references(true)
-
- assert decorate(doc, definition) =~ "def something(«param») do"
- assert decorate(doc, reference) =~ " «param»"
- end
-
- test "can be found via a usage" do
- {:ok, [first, second, third], doc} =
- ~q[
- def something(param) do
- y = param + 3
- z = param + 4
- param| + y + z
- end
- ]
- |> find_references()
-
- assert decorate(doc, first) =~ " y = «param» + 3"
- assert decorate(doc, second) =~ " z = «param» + 4"
- assert decorate(doc, third) =~ " «param» + y + z"
- end
-
- test "are found in a function body" do
- {:ok, [first, second, third, fourth, fifth], doc} =
- ~q[
- def something(param|) do
- x = param + param + 3
- y = param + x
- z = 10 + param
- x + y + z + param
- end
- ]
- |> find_references()
-
- assert decorate(doc, first) =~ " x = «param» + param + 3"
- assert decorate(doc, second) =~ " x = param + «param» + 3"
- assert decorate(doc, third) =~ " y = «param» + x"
- assert decorate(doc, fourth) =~ " z = 10 + «param»"
- assert decorate(doc, fifth) =~ " x + y + z + «param»"
- end
-
- test "are constrained to their definition function" do
- {:ok, [range], doc} =
- ~q[
- def something(param|) do
- param
- end
-
- def other_fn(param) do
- param + 1
- end
- ]
- |> find_references()
-
- assert decorate(doc, range) =~ "«param»"
- end
-
- test "are visible across blocks" do
- {:ok, [first, second], doc} =
- ~q[
- def something(param|) do
- if something() do
- param + 1
- else
- param + 2
- end
- end
- ]
- |> find_references()
-
- assert decorate(doc, first) =~ " «param» + 1"
- assert decorate(doc, second) =~ " «param» + 2"
- end
-
- test "dont leak out of blocks" do
- {:ok, [range], doc} =
- ~q[
- def something(param) do
-
- if something() do
- param| = 3
- param + 1
- end
- param + 1
- end
- ]
- |> find_references()
-
- assert decorate(doc, range) =~ "«param»"
- end
-
- test "are found in the head of a case statement" do
- {:ok, [range], doc} =
- ~q[
- def something(param|) do
- case param do
- _ -> :ok
- end
- end
- ]
- |> find_references()
-
- assert decorate(doc, range) =~ " case «param» do"
- end
-
- test "are constrained to a single arm of a case statement" do
- {:ok, [guard_range, usage_range], doc} =
- ~q[
- def something(param) do
- case param do
- param| when is_number(param) -> param + 1
- param -> 0
- end
- end
- ]
- |> find_references()
-
- assert decorate(doc, guard_range) =~ " param when is_number(«param») -> param + 1"
- assert decorate(doc, usage_range) =~ " param when is_number(param) -> «param» + 1"
- end
-
- test "are found in a module body" do
- {:ok, [range], doc} =
- ~q[
- defmodule Outer do
- something| = 3
- def foo(unquote(something)) do
- end
- end
- ]
- |> find_references()
-
- assert decorate(doc, range) =~ "def foo(unquote(«something»)) do"
- end
-
- test "are found in anonymous function parameters" do
- {:ok, [first, second], doc} =
- ~q[
- def outer do
- fn param| ->
- y = param + 1
- x = param + 2
- x + y
- end
- end
- ]
- |> find_references()
-
- assert decorate(doc, first) =~ "y = «param» + 1"
- assert decorate(doc, second) =~ "x = «param» + 2"
- end
-
- test "are found in a pin operator" do
- {:ok, [ref], doc} =
- ~q[
- def outer(param|) do
- fn ^param ->
- nil
- end
- end
- ]
- |> find_references()
-
- assert decorate(doc, ref) =~ "fn ^«param» ->"
- end
-
- test "are found inside of string interpolation" do
- {:ok, [ref], doc} =
- ~S[
- name| = "Stinky"
- "#{name} Stinkman"
- ]
- |> find_references()
-
- assert decorate(doc, ref) =~ "\#{«name»} Stinkman"
- end
-
- # Note: This test needs to pass before we can implement renaming variables reliably
- @tag :skip
- test "works for variables defined outside of an if while being shadowed" do
- {:ok, [first, second], doc} =
- ~q{
- entries| = [1, 2, 3]
- entries =
- if something() do
- [4 | entries]
- else
- entries
- end
- }
- |> find_references()
-
- assert decorate(doc, first) =~ "[4 | «entries»]"
- assert decorate(doc, second) =~ "«entries»"
- end
-
- test "finds variables defined in anonymous function arms" do
- {:ok, [first, second], doc} =
- ~q"
- shadowed? = false
- fn
- {:foo, entries|} ->
- if shadowed? do
- [1, entries]
- else
- entries
- end
- {:bar, entries} ->
- entries
- end
- "
- |> find_references()
-
- assert decorate(doc, first) =~ "[1, «entries»]"
- assert decorate(doc, second) =~ "«entries»"
- end
- end
-
- describe "reference shadowing" do
- test "on a single line" do
- {:ok, [], _doc} =
- ~q[
- def something(param) do
- other = other = other| = param
- end
- ]
- |> find_references()
- end
-
- test "in a function body" do
- {:ok, [], _doc} =
- ~q[
- def something(param|) do
- param = 3
- param
- end
- ]
- |> find_references()
- end
-
- test "in anonymous function arguments" do
- {:ok, [], _doc} =
- ~q[
- def something(param|) do
- fn param ->
- param + 1
- end
- :ok
- end
- ]
- |> find_references()
- end
-
- test "inside of a block" do
- {:ok, [range], doc} =
- ~q[
- def something do
- shadow| = 4
- if true do
- shadow = shadow + 1
- shadow
- end
- end
- ]
- |> find_references()
-
- assert decorate(doc, range) == " shadow = «shadow» + 1"
- end
-
- test "exiting a block" do
- {:ok, [range], doc} =
- ~q[
- def something do
- shadow| = 4
- if true do
- shadow = :ok
- shadow
- end
- shadow + 1
- end
- ]
- |> find_references()
-
- assert decorate(doc, range) == " «shadow» + 1"
- end
-
- test "exiting nested blocks" do
- {:ok, [range], doc} =
- ~q[
- def something(param| = arg) do
- case arg do
- param when is_number(n) ->
- param + 4
- end
- param + 5
- end
- ]
- |> find_references()
-
- assert decorate(doc, range) == " «param» + 5"
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/code_mod/aliases_test.exs b/apps/remote_control/test/lexical/remote_control/code_mod/aliases_test.exs
deleted file mode 100644
index 9a8875fb..00000000
--- a/apps/remote_control/test/lexical/remote_control/code_mod/aliases_test.exs
+++ /dev/null
@@ -1,204 +0,0 @@
-defmodule Lexical.RemoteControl.CodeMod.AliasesTest do
- alias Lexical.Ast
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeMod.Aliases
-
- import Lexical.Test.CursorSupport
- use Lexical.Test.CodeMod.Case
- use Patch
-
- setup do
- patch(RemoteControl, :get_project, %Lexical.Project{})
- :ok
- end
-
- def insert_position(orig) do
- {cursor, document} = pop_cursor(orig, as: :document)
- analysis = Ast.analyze(document)
- {position, _trailer} = Aliases.insert_position(analysis, cursor)
-
- {:ok, document, position}
- end
-
- describe "insert_position" do
- test "is directly after a module's definition if there are no aliases present" do
- {:ok, document, position} =
- ~q[
- defmodule MyModule do|
- end
- ]
- |> insert_position()
-
- assert decorate_cursor(document, position) =~ ~q[
- defmodule MyModule do
- |end
- ]
- end
-
- test "is after the moduledoc if no aliases are present" do
- {:ok, document, position} =
- ~q[
- defmodule MyModule do|
- @moduledoc """
- This is my funny moduledoc
- """
- end
- ]
- |> insert_position()
-
- assert decorate_cursor(document, position) =~ ~q[
- defmodule MyModule do
- @moduledoc """
- This is my funny moduledoc
- """
- |end
- ]
- end
-
- test "is before use statements" do
- {:ok, document, position} =
- ~q[
- defmodule MyModule do|
- use Something.That.Exists
- end
- ]
- |> insert_position()
-
- expected = ~q[
- defmodule MyModule do
- |use Something.That.Exists
- end
- ]
- assert decorate_cursor(document, position) =~ expected
- end
-
- test "is before require statements" do
- {:ok, document, position} =
- ~q[
- defmodule MyModule do|
- require Something.That.Exists
- end
- ]
- |> insert_position()
-
- expected = ~q[
- defmodule MyModule do
- |require Something.That.Exists
- end
- ]
- assert decorate_cursor(document, position) =~ expected
- end
-
- test "is before import statements" do
- {:ok, document, position} =
- ~q[
- defmodule MyModule do|
- import Something.That.Exists
- end
- ]
- |> insert_position()
-
- expected = ~q[
- defmodule MyModule do
- |import Something.That.Exists
- end
- ]
- assert decorate_cursor(document, position) =~ expected
- end
-
- test "is where existing aliases are" do
- {:ok, document, position} =
- ~q[
- defmodule MyModule do|
- alias Something.That.Exists
- end
- ]
- |> insert_position()
-
- expected = ~q[
- defmodule MyModule do
- |alias Something.That.Exists
- end
- ]
- assert decorate_cursor(document, position) =~ expected
- end
-
- test "in nested empty modules" do
- {:ok, document, position} =
- ~q[
- defmodule Outer do
- defmodule Inner do|
- end
- end
- ]
- |> insert_position()
-
- expected = ~q[
- defmodule Outer do
- defmodule Inner do
- |end
- end
- ]t
-
- assert decorate_cursor(document, position) =~ expected
- end
-
- test "in nested modules that both have existing aliases" do
- {:ok, document, position} =
- ~q[
- defmodule Outer do
- alias First.Thing
-
- defmodule Inner do|
- alias Second.Person
- end
- end
- ]
- |> insert_position()
-
- expected = ~q[
- defmodule Outer do
- alias First.Thing
-
- defmodule Inner do
- |alias Second.Person
- end
- end
- ]t
-
- assert decorate_cursor(document, position) =~ expected
- end
-
- test "is after moduledocs in nested modules" do
- {:ok, document, position} =
- ~q[
- defmodule Outer do
- alias First.Thing
-
- defmodule Inner do|
- @moduledoc """
- This is my documentation, it
- spans multiple lines
- """
- end
- end
- ]
- |> insert_position()
-
- expected = ~q[
- defmodule Outer do
- alias First.Thing
-
- defmodule Inner do
- @moduledoc """
- This is my documentation, it
- spans multiple lines
- """
- |end
- end
- ]t
-
- assert decorate_cursor(document, position) =~ expected
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/completion_test.exs b/apps/remote_control/test/lexical/remote_control/completion_test.exs
deleted file mode 100644
index b4e35ee7..00000000
--- a/apps/remote_control/test/lexical/remote_control/completion_test.exs
+++ /dev/null
@@ -1,225 +0,0 @@
-defmodule Lexical.RemoteControl.CompletionTest do
- alias Lexical.Ast
- alias Lexical.Ast.Env
- alias Lexical.Document
- alias Lexical.RemoteControl.Completion
-
- import Lexical.Test.CodeSigil
- import Lexical.Test.CursorSupport
- import Lexical.Test.Fixtures
- import Lexical.Test.Quiet
-
- use ExUnit.Case, async: true
- use Patch
-
- describe "struct_fields/2" do
- test "returns the field completion for current module" do
- source = ~q<
- defmodule Project.Issue do
- defstruct [:message]
- @type t :: %__MODULE__{|}
- end
- >
-
- [message] = struct_fields(source)
- assert message.name == :message
- end
-
- test "returns the field completions for aliased module" do
- source = ~q<
- defmodule Project.Issue do
- alias Project.Issue
- defstruct [:message]
- @type t :: %Issue{|}
- end
- >
-
- [message] = struct_fields(source)
- assert message.name == :message
- end
-
- test "returns [] when cursor is not in a struct" do
- source = ~q<
- defmodule Project.Issue do
- alias Project.Issue
- defstruct [:message]
- @type t :: %Issue{}|
- end
- >
-
- assert struct_fields(source) == []
- end
-
- test "returns the field completions when cursor is in the current module child's arguments" do
- source = ~q<
- defmodule Project do
- defmodule Issue do
- @type t :: %Issue{}
- defstruct [:message]
- end
-
- def message(%__MODULE__.Issue{|} = issue) do
- issue.message
- end
- end
- >
-
- [message] = struct_fields(source)
- assert message.name == :message
- end
-
- test "returns the field completion when cursor is in an alias child's arguments" do
- source = ~q<
- defmodule Project do
- defmodule Issue do
- defstruct [:message]
- end
- end
-
- defmodule MyModule do
- alias Project
-
- def message(%Project.Issue{|} = issue) do
- issue.message
- end
- end
- >
-
- [message] = struct_fields(source)
- assert message.name == :message
- end
- end
-
- def expose_strip_struct_operator(_) do
- Patch.expose(Completion, strip_struct_operator: 1)
- :ok
- end
-
- describe "strip_struct_operator/1" do
- setup [:expose_strip_struct_operator]
-
- test "with a reference followed by __" do
- {doc, _position} =
- "%__"
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == "__"
- end
-
- test "with a reference followed by a module name" do
- {doc, _position} =
- "%Module"
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == "Module"
- end
-
- test "with a reference followed by a module and a dot" do
- {doc, _position} =
- "%Module."
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == "Module."
- end
-
- test "with a reference followed by a nested module" do
- {doc, _position} =
- "%Module.Sub"
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == "Module.Sub"
- end
-
- test "with a reference followed by an alias" do
- code = ~q[
- alias Something.Else
- %El|
- ]t
-
- {doc, _position} =
- code
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == "alias Something.Else\nEl"
- end
-
- test "on a line with two references, replacing the first" do
- {doc, _position} =
- "%First{} = %Se"
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == "%First{} = Se"
- end
-
- test "on a line with two references, replacing the second" do
- {doc, _position} =
- "%Fir| = %Second{}"
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == "Fir = %Second{}"
- end
-
- test "with a plain module" do
- env = new_env("Module")
- {doc, _position} = private(Completion.strip_struct_operator(env))
-
- assert doc == Document.to_string(env.document)
- end
-
- test "with a plain module strip_struct_reference a dot" do
- env = new_env("Module.")
- {doc, _position} = private(Completion.strip_struct_operator(env))
-
- assert doc == Document.to_string(env.document)
- end
-
- test "leaves leading spaces in place" do
- {doc, _position} =
- " %Some"
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == " Some"
- end
-
- test "works in a function definition" do
- {doc, _position} =
- "def my_function(%Lo|)"
- |> new_env()
- |> private(Completion.strip_struct_operator())
-
- assert doc == "def my_function(Lo)"
- end
- end
-
- defp struct_fields(source) do
- {position, document} = pop_cursor(source, as: :document)
- text = Document.to_string(document)
-
- quiet(:stderr, fn ->
- Code.compile_string(text)
- end)
-
- analysis =
- document
- |> Ast.analyze()
- |> Ast.reanalyze_to(position)
-
- Completion.struct_fields(analysis, position)
- end
-
- def new_env(text) do
- project = project()
- {position, document} = pop_cursor(text, as: :document)
- analysis = Ast.analyze(document)
- {:ok, env} = Env.new(project, analysis, position)
- env
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/dispatch/handlers/indexer_test.exs b/apps/remote_control/test/lexical/remote_control/dispatch/handlers/indexer_test.exs
deleted file mode 100644
index 60eed4e7..00000000
--- a/apps/remote_control/test/lexical/remote_control/dispatch/handlers/indexer_test.exs
+++ /dev/null
@@ -1,146 +0,0 @@
-defmodule Lexical.RemoteControl.Dispatch.Handlers.IndexingTest do
- alias Lexical.Document
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api
- alias Lexical.RemoteControl.Commands
- alias Lexical.RemoteControl.Dispatch.Handlers.Indexing
- alias Lexical.RemoteControl.Search
-
- import Api.Messages
- import Lexical.Test.CodeSigil
- import Lexical.Test.EventualAssertions
- import Lexical.Test.Fixtures
-
- use ExUnit.Case
- use Patch
-
- setup do
- project = project()
- RemoteControl.set_project(project)
- create_index = &Search.Indexer.create_index/1
- update_index = &Search.Indexer.update_index/2
-
- start_supervised!(RemoteControl.Dispatch)
- start_supervised!(Commands.Reindex)
- start_supervised!(Search.Store.Backends.Ets)
- start_supervised!({Search.Store, [project, create_index, update_index]})
- start_supervised!({Document.Store, derive: [analysis: &Lexical.Ast.analyze/1]})
-
- Search.Store.enable()
- assert_eventually(Search.Store.loaded?(), 1500)
-
- {:ok, state} = Indexing.init([])
- {:ok, state: state, project: project}
- end
-
- def set_document!(source) do
- uri = "file:///file.ex"
-
- :ok =
- case Document.Store.fetch(uri) do
- {:ok, _} ->
- Document.Store.update(uri, fn doc ->
- edit = Document.Edit.new(source)
- Document.apply_content_changes(doc, doc.version + 1, [edit])
- end)
-
- {:error, :not_open} ->
- Document.Store.open(uri, source, 1)
- end
-
- {uri, source}
- end
-
- describe "handling file_quoted events" do
- test "should add new entries to the store", %{state: state} do
- {uri, _source} =
- ~q[
- defmodule NewModule do
- end
- ]
- |> set_document!()
-
- assert {:ok, _} = Indexing.on_event(file_compile_requested(uri: uri), state)
-
- assert_eventually {:ok, [entry]} = Search.Store.exact("NewModule", [])
-
- assert entry.subject == NewModule
- end
-
- test "should update entries in the store", %{state: state} do
- {uri, source} =
- ~q[
- defmodule OldModule
- end
- ]
- |> set_document!()
-
- {:ok, _} = Search.Indexer.Source.index(uri, source)
-
- {^uri, _source} =
- ~q[
- defmodule UpdatedModule do
- end
- ]
- |> set_document!()
-
- assert {:ok, _} = Indexing.on_event(file_compile_requested(uri: uri), state)
-
- assert_eventually {:ok, [entry]} = Search.Store.exact("UpdatedModule", [])
- assert entry.subject == UpdatedModule
- assert {:ok, []} = Search.Store.exact("OldModule", [])
- end
-
- test "only updates entries if the version of the document is the same as the version in the document store",
- %{state: state} do
- Document.Store.open("file:///file.ex", "defmodule Newer do \nend", 3)
-
- {uri, _source} =
- ~q[
- defmodule Stale do
- end
- ]
- |> set_document!()
-
- assert {:ok, _} = Indexing.on_event(file_compile_requested(uri: uri), state)
- assert {:ok, []} = Search.Store.exact("Stale", [])
- end
- end
-
- describe "a file is deleted" do
- test "its entries should be deleted", %{project: project, state: state} do
- {uri, source} =
- ~q[
- defmodule ToDelete do
- end
- ]
- |> set_document!()
-
- {:ok, entries} = Search.Indexer.Source.index(uri, source)
- Search.Store.update(uri, entries)
-
- assert_eventually {:ok, [_]} = Search.Store.exact("ToDelete", [])
-
- Indexing.on_event(
- filesystem_event(project: project, uri: uri, event_type: :deleted),
- state
- )
-
- assert_eventually {:ok, []} = Search.Store.exact("ToDelete", [])
- end
- end
-
- describe "a file is created" do
- test "is a no op", %{project: project, state: state} do
- spy(Search.Store)
- spy(Search.Indexer)
-
- event = filesystem_event(project: project, uri: "file:///another.ex", event_type: :created)
-
- assert {:ok, _} = Indexing.on_event(event, state)
-
- assert history(Search.Store) == []
- assert history(Search.Indexer) == []
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/dispatch/handlers/indexing_test.exs b/apps/remote_control/test/lexical/remote_control/dispatch/handlers/indexing_test.exs
deleted file mode 100644
index 383f3275..00000000
--- a/apps/remote_control/test/lexical/remote_control/dispatch/handlers/indexing_test.exs
+++ /dev/null
@@ -1,2 +0,0 @@
-defmodule Lexical.RemoteControl.Dispatch.Handlers.IndexerTest do
-end
diff --git a/apps/remote_control/test/lexical/remote_control/plugin/runner/coordinator/state_test.exs b/apps/remote_control/test/lexical/remote_control/plugin/runner/coordinator/state_test.exs
deleted file mode 100644
index 0c5518dd..00000000
--- a/apps/remote_control/test/lexical/remote_control/plugin/runner/coordinator/state_test.exs
+++ /dev/null
@@ -1,147 +0,0 @@
-defmodule Lexical.RemoteControl.Plugin.Coordinator.StateTest do
- alias Lexical.Document
- alias Lexical.Plugin.V1
- alias Lexical.RemoteControl.Plugin.Runner
- alias Lexical.RemoteControl.Plugin.Runner.Coordinator.State
-
- use ExUnit.Case
-
- setup do
- start_supervised!(Runner.Supervisor)
-
- on_exit(fn ->
- Runner.clear_config()
- end)
-
- {:ok, state: State.new()}
- end
-
- test "with no configured plugins" do
- assert {[], _} = State.run_all(%State{}, nil, :diagnostic, 50)
- end
-
- defmodule FailsInit do
- use V1.Diagnostic, name: :fails_init
-
- def init do
- {:error, :failed}
- end
-
- def diagnose(subject) do
- {:ok, [subject]}
- end
- end
-
- test "a plugin is deactivated if it fails to initialize" do
- assert :error = Runner.register(FailsInit)
- refute :fails_init in Runner.enabled_plugins()
- end
-
- defmodule Echo do
- use V1.Diagnostic, name: :echo
-
- def diagnose(subject) do
- {:ok, [subject]}
- end
- end
-
- defmodule MultipleResults do
- use V1.Diagnostic, name: :multiple_results
-
- def diagnose(subject) do
- {:ok, [subject, subject]}
- end
- end
-
- describe "plugins completing successfully" do
- test "results are returned for a single plugin", %{state: state} do
- Runner.register(Echo)
- doc = %Document{}
- assert {[^doc], _} = State.run_all(state, doc, :diagnostic, 50)
- end
-
- test "results are aggregated for multiple plugins", %{state: state} do
- Runner.register(Echo)
- Runner.register(MultipleResults)
-
- doc = %Document{}
- assert {[^doc, ^doc, ^doc], _} = State.run_all(state, doc, :diagnostic, 50)
- end
- end
-
- describe "failure modes" do
- defmodule TimesOut do
- use V1.Diagnostic, name: :times_out
-
- def diagnose(subject) do
- Process.sleep(5000)
- {:ok, [subject]}
- end
- end
-
- defmodule Crashes do
- use V1.Diagnostic, name: :crashes
-
- def diagnose(subject) do
- 45 = subject
- {:ok, [subject]}
- end
- end
-
- defmodule Errors do
- use V1.Diagnostic, name: :errors
-
- def diagnose(_) do
- {:error, :invalid_subject}
- end
- end
-
- defmodule BadReturn do
- use V1.Diagnostic, name: :bad_return
-
- def diagnose(subject) do
- {:ok, subject}
- end
- end
-
- test "timeouts are logged", %{state: state} do
- Runner.register(TimesOut)
-
- assert {[], state} = State.run_all(state, %Document{}, :diagnostic, 50)
- assert State.failure_count(state, TimesOut) == 1
- end
-
- test "crashing plugins are logged", %{state: state} do
- Runner.register(Crashes)
- assert {[], state} = State.run_all(state, %Document{}, :diagnostic, 50)
- assert State.failure_count(state, Crashes) == 1
- end
-
- test "a plugin that returns an error is logged", %{state: state} do
- Runner.register(Errors)
-
- assert {[], state} = State.run_all(state, %Document{}, :diagnostic, 50)
- assert State.failure_count(state, Errors) == 1
- end
-
- test "a plugin that doesn't return a list is logged", %{state: state} do
- Runner.register(BadReturn)
-
- assert {[], state} = State.run_all(state, %Document{}, :diagnostic, 50)
- assert State.failure_count(state, BadReturn) == 1
- end
-
- test "a plugin is disabled if it fails 10 times", %{state: state} do
- Runner.register(Crashes)
-
- assert :crashes in Runner.enabled_plugins()
-
- Enum.reduce(1..10, state, fn _, state ->
- {_, state} = State.run_all(state, %Document{}, :diagnostic, 50)
- state
- end)
-
- refute :crashes in Runner.enabled_plugins()
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/progress_test.exs b/apps/remote_control/test/lexical/remote_control/progress_test.exs
deleted file mode 100644
index a44c5e83..00000000
--- a/apps/remote_control/test/lexical/remote_control/progress_test.exs
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule Lexical.RemoteControl.ProgressTest do
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Progress
-
- import Lexical.RemoteControl.Api.Messages
-
- use ExUnit.Case
- use Patch
- use Progress
-
- setup do
- test_pid = self()
- patch(RemoteControl.Api.Proxy, :broadcast, &send(test_pid, &1))
- :ok
- end
-
- test "it should send begin/complete event and return the result" do
- result = with_progress "foo", fn -> :ok end
-
- assert result == :ok
- assert_received project_progress(label: "foo", stage: :begin)
- assert_received project_progress(label: "foo", stage: :complete)
- end
-
- test "it should send begin/complete event even there is an exception" do
- assert_raise(Mix.Error, fn ->
- with_progress "compile", fn -> raise Mix.Error, "can't compile" end
- end)
-
- assert_received project_progress(label: "compile", stage: :begin)
- assert_received project_progress(label: "compile", stage: :complete)
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/project_node_test.exs b/apps/remote_control/test/lexical/remote_control/project_node_test.exs
deleted file mode 100644
index d7407411..00000000
--- a/apps/remote_control/test/lexical/remote_control/project_node_test.exs
+++ /dev/null
@@ -1,44 +0,0 @@
-defmodule Lexical.RemoteControl.ProjectNodeTest do
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.ProjectNode
- alias Lexical.RemoteControl.ProjectNodeSupervisor
-
- import Lexical.Test.EventualAssertions
- import Lexical.Test.Fixtures
-
- use ExUnit.Case, async: false
-
- setup do
- project = project()
- start_supervised!({ProjectNodeSupervisor, project})
- {:ok, %{project: project}}
- end
-
- test "it should be able to stop a project node and won't restart", %{project: project} do
- {:ok, _node_name, _} = RemoteControl.start_link(project)
-
- project_alive? = project |> ProjectNode.name() |> Process.whereis() |> Process.alive?()
-
- assert project_alive?
- assert :ok = ProjectNode.stop(project, 1500)
- assert Process.whereis(ProjectNode.name(project)) == nil
- end
-
- test "it should be stopped atomically when the startup process is dead", %{project: project} do
- test_pid = self()
-
- linked_node_process =
- spawn(fn ->
- {:ok, _node_name, _} = RemoteControl.start_link(project)
- send(test_pid, :started)
- end)
-
- assert_receive :started, 1500
-
- node_process_name = ProjectNode.name(project)
-
- assert node_process_name |> Process.whereis() |> Process.alive?()
- Process.exit(linked_node_process, :kill)
- assert_eventually Process.whereis(node_process_name) == nil, 50
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/module_attribute_test.exs b/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/module_attribute_test.exs
deleted file mode 100644
index 153498d3..00000000
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/module_attribute_test.exs
+++ /dev/null
@@ -1,194 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.ModuleAttributeTest do
- alias Lexical.RemoteControl.Search.Subject
- use Lexical.Test.ExtractorCase
-
- def index(source) do
- do_index(source, fn entry ->
- entry.type == :module_attribute
- end)
- end
-
- describe "indexing module attributes" do
- test "finds definitions when defining scalars" do
- {:ok, [attr], doc} =
- ~q[
- defmodule Root do
- @attribute 32
- end
- ]
- |> index()
-
- assert attr.type == :module_attribute
- assert attr.subtype == :definition
- assert attr.subject == Subject.module_attribute(Root, :attribute)
-
- assert decorate(doc, attr.range) =~ "«@attribute 32»"
- end
-
- test "in-progress module attributes are ignored" do
- {:ok, [latter_attribute], _doc} =
- ~q[
- defmodule Root do
- @
- @callback foo() :: :ok
- end
- ]
- |> index()
-
- assert latter_attribute.subject == "@callback"
- end
-
- test "finds multiple definitions of the same attribute" do
- {:ok, [first, second, third], doc} =
- ~q[
- defmodule Parent do
- @tag 1
- def first, do: 1
-
- @tag 2
- def second, do: 1
-
- @tag 3
- def third, do: 1
- end
- ]
- |> index()
-
- assert first.type == :module_attribute
- assert first.subtype == :definition
- assert first.subject == Subject.module_attribute(Parent, :tag)
- assert decorate(doc, first.range) =~ "«@tag 1»"
-
- assert second.type == :module_attribute
- assert second.subtype == :definition
- assert second.subject == Subject.module_attribute(Parent, :tag)
- assert decorate(doc, second.range) =~ "«@tag 2»"
-
- assert third.type == :module_attribute
- assert third.subtype == :definition
- assert third.subject == Subject.module_attribute(Parent, :tag)
- assert decorate(doc, third.range) =~ "«@tag 3»"
- end
-
- test "finds definitions when the definition spans multiple lines" do
- {:ok, [attr], doc} =
- ~q[
- defmodule Parent do
- @number_strings 1..50
- |> Enum.map(& &1 * 2)
- |> Enum.map(&Integer.to_string/1)
- end
- ]
- |> index()
-
- assert attr.type == :module_attribute
- assert attr.subtype == :definition
- assert attr.subject == Subject.module_attribute(Parent, :number_strings)
-
- expected =
- """
- «@number_strings 1..50
- |> Enum.map(& &1 * 2)
- |> Enum.map(&Integer.to_string/1)»
- """
- |> String.trim()
-
- assert decorate(doc, attr.range) =~ expected
- end
-
- test "finds references in other definitions" do
- {:ok, [_def1, def2, reference], doc} =
- ~q[
- defmodule Root do
- @attr 23
-
- @attr2 @attr + 1
- end
- ]
- |> index()
-
- assert def2.type == :module_attribute
- assert def2.subtype == :definition
- assert def2.subject == Subject.module_attribute(Root, :attr2)
- assert decorate(doc, def2.range) =~ "«@attr2 @attr + 1»"
-
- assert reference.type == :module_attribute
- assert reference.subtype == :reference
- assert reference.subject == Subject.module_attribute(Root, :attr)
- assert decorate(doc, reference.range) =~ "@attr2 «@attr» + 1"
- end
-
- test "finds definitions in nested contexts" do
- {:ok, [parent_def, child_def], doc} =
- ~q[
- defmodule Parent do
- @in_parent true
- defmodule Child do
- @in_child true
- end
- end
- ]
- |> index()
-
- assert parent_def.type == :module_attribute
- assert parent_def.subtype == :definition
- assert parent_def.subject == Subject.module_attribute(Parent, :in_parent)
- assert decorate(doc, parent_def.range) =~ "«@in_parent true»"
-
- assert child_def.type == :module_attribute
- assert child_def.subtype == :definition
- assert child_def.subject == Subject.module_attribute(Parent.Child, :in_child)
- assert decorate(doc, child_def.range) =~ "«@in_child true»"
- end
-
- test "finds references in function arguments" do
- {:ok, [_definition, reference], doc} =
- ~q[
- defmodule InArgs do
- @age 95
- def is_old?(@age), do: true
- end
- ]
- |> index()
-
- assert reference.type == :module_attribute
- assert reference.subtype == :reference
- assert reference.subject == Subject.module_attribute(InArgs, :age)
- assert decorate(doc, reference.range) =~ " def is_old?(«@age»)"
- end
-
- test "finds references in map keys" do
- {:ok, [_, key], doc} =
- ~q[
- defmodule InMapKey do
- @foo 3
- def something(%{@foo => 3}) do
- end
- end
- ]
- |> index()
-
- assert key.type == :module_attribute
- assert key.subtype == :reference
- assert key.subject == Subject.module_attribute(InMapKey, :foo)
- assert decorate(doc, key.range) =~ "%{«@foo» => 3}"
- end
-
- test "finds references in map values" do
- {:ok, [_, value], doc} =
- ~q[
- defmodule InMapValue do
- @foo 3
- def something(%{foo: @foo}) do
- end
- end
- ]
- |> index()
-
- assert value.type == :module_attribute
- assert value.subtype == :reference
- assert value.subject == Subject.module_attribute(InMapValue, :foo)
- assert decorate(doc, value.range) =~ "%{foo: «@foo»}"
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/module_test.exs b/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/module_test.exs
deleted file mode 100644
index b1ea0695..00000000
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/module_test.exs
+++ /dev/null
@@ -1,540 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.ModuleTest do
- use Lexical.Test.ExtractorCase
-
- def index(source) do
- do_index(source, &(&1.type == :module))
- end
-
- describe "indexing modules" do
- test "it doesn't confuse a list of atoms for a module" do
- {:ok, [module], _} =
- ~q(
- defmodule Root do
- @attr [:Some, :Other, :Module]
- end
- )
- |> index()
-
- assert module.type == :module
- assert module.subject == Root
- end
-
- test "indexes a flat module with no aliases" do
- {:ok, [entry], doc} =
- ~q[
- defmodule Simple do
- end
- ]
- |> index()
-
- assert entry.type == :module
- assert entry.block_id == :root
- assert entry.subject == Simple
- assert decorate(doc, entry.range) =~ "defmodule «Simple» do"
- end
-
- test "indexes a flat module with a dotted name" do
- {:ok, [entry], doc} =
- ~q[
- defmodule Simple.Module.Path do
- end
- ]
- |> index()
-
- assert entry.subject == Simple.Module.Path
- assert entry.type == :module
- assert entry.block_id == :root
- assert decorate(doc, entry.range) =~ "defmodule «Simple.Module.Path» do"
- end
-
- test "indexes a flat module with an aliased name" do
- {:ok, [_alias, entry], doc} =
- ~q[
- alias Something.Else
- defmodule Else.Other do
- end
- ]
- |> index()
-
- assert entry.subject == Something.Else.Other
- assert decorate(doc, entry.range) == "defmodule «Else.Other» do"
- end
-
- test "can detect an erlang module" do
- {:ok, [module_def, erlang_module], doc} =
- ~q[
- defmodule Root do
- @something :timer
- end
- ]
- |> index()
-
- assert erlang_module.type == :module
- assert erlang_module.block_id == module_def.id
- assert erlang_module.subject == :timer
- assert decorate(doc, erlang_module.range) =~ " @something «:timer»"
- end
-
- test "can detect a module reference in a module attribute" do
- {:ok, [module_def, attribute], doc} =
- ~q[
- defmodule Root do
- @attr Some.Other.Module
- end
- ]
- |> index()
-
- assert attribute.type == :module
- assert attribute.block_id == module_def.id
- assert attribute.subject == Some.Other.Module
- assert decorate(doc, attribute.range) =~ " @attr «Some.Other.Module»"
- end
-
- test "can detect __MODULE__ in a function" do
- {:ok, [_module_def, module_ref], doc} =
- ~q[
- defmodule Root do
- def something do
- __MODULE__
- end
- end
- ]
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Root
- assert decorate(doc, module_ref.range) =~ " «__MODULE__»"
- end
-
- test "can detect a module reference on the left side of a pattern match" do
- {:ok, [_module_def, module_ref], doc} =
- ~q[
- defmodule Root do
- def my_fn(arg) do
- Some.Module = arg
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ "«Some.Module» = arg"
- end
-
- test "can detect an aliased module reference on the left side of a pattern match" do
- {:ok, [_module_def, _alias, module_ref], doc} =
- ~q[
- defmodule Root do
- alias Some.Other.Thing
- def my_fn(arg) do
- Thing.Util = arg
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Other.Thing.Util
- assert decorate(doc, module_ref.range) =~ " «Thing.Util» = arg"
- end
-
- test "can detect a module reference in a nested alias" do
- {:ok, [_top_level, _foo, _first, second, fourth], doc} = ~q[
- defmodule TopLevel do
- alias Foo.{
- First,
- Second,
- Third.Fourth
- }
- end] |> index()
-
- assert second.subject == Foo.Second
- assert decorate(doc, second.range) == " «Second»,"
-
- assert fourth.subject == Foo.Third.Fourth
- assert decorate(doc, fourth.range) == " «Third.Fourth»"
- end
-
- test "can detect a module reference on the right side of a pattern match" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule Root do
- def my_fn(arg) do
- arg = Some.Module
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ "arg = «Some.Module»"
- end
-
- test "can detect an aliased module reference on the right side of a pattern match" do
- {:ok, [_module_def, _alias, module_ref], doc} =
- ~q[
- defmodule Root do
- alias Some.Other.Thing
- def my_fn(arg) do
- arg = Thing.Util
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Other.Thing.Util
- assert decorate(doc, module_ref.range) =~ " arg = «Thing.Util»"
- end
-
- test "can detect a module reference in a remote call" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule RemoteCall do
- def my_fn do
- Some.Module.function()
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ " «Some.Module».function()"
- end
-
- test "can detect a module reference in a remote captured function" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule Capture do
- def my_fn do
- &Some.Module.function/1
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ " &«Some.Module».function/1"
- end
-
- test "can detect a module reference in a remote captured function with multiple arities" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule Capture do
- def my_fn do
- &Some.Module.function(&1, 1)
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ " &«Some.Module».function(&1, 1)"
- end
-
- test "can detect a module reference in an aliased remote captured function" do
- {:ok, [_module, _alias, _aliased, module_ref], doc} = ~q[
- defmodule Capture do
- alias First.Second, as: Third
- def my_fn do
- &Third.function/1
- end
- end
- ]t |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == First.Second
- assert decorate(doc, module_ref.range) =~ " &«Third».function/1"
- end
-
- test "can detect a module reference in a function call's arguments" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule FunCallArgs do
- def my_fn do
- function(Some.Module)
- end
-
- def function(_) do
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ " function(«Some.Module»)"
- end
-
- test "can detect a module reference in a function's pattern match arguments" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule FunCallArgs do
- def my_fn(arg = Some.Module) do
- arg
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ "def my_fn(arg = «Some.Module»)"
- end
-
- test "can detect a module reference in default parameters" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule FunCallArgs do
- def my_fn(module \\ Some.Module) do
- module.foo()
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ ~S[def my_fn(module \\ «Some.Module»)]
- end
-
- test "can detect a module reference in map keys" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule FunCallArgs do
- def my_fn do
- %{Some.Module => 1}
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ "%{«Some.Module» => 1}"
- end
-
- test "can detect a module reference in map values" do
- {:ok, [_module, module_ref], doc} =
- ~q[
- defmodule FunCallArgs do
- def my_fn do
- %{invalid: Some.Module}
- end
- end
- ]t
- |> index()
-
- assert module_ref.type == :module
- assert module_ref.subject == Some.Module
- assert decorate(doc, module_ref.range) =~ "%{invalid: «Some.Module»}"
- end
-
- test "can detect a module reference in an anonymous function call" do
- {:ok, [parent, ref], doc} =
- ~q[
- defmodule Parent do
- def outer_fn do
- fn ->
- Ref.To.Something
- end
- end
- end
- ]
- |> index()
-
- assert ref.type == :module
- assert ref.subject == Ref.To.Something
- refute ref.block_id == parent.id
- assert decorate(doc, ref.range) =~ " «Ref.To.Something»"
- end
-
- test "can detect a @protocol module reference" do
- {:ok, [protocol_def, protocol_ref, for_ref, proto_module_attr], doc} =
- ~q[
- defimpl MyProtocol, for: Atom do
- def something do
- @protocol.Something
- end
- end
- ]
- |> index()
-
- assert protocol_def.type == :module
- assert protocol_def.subtype == :definition
- assert protocol_def.subject == MyProtocol.Atom
- assert decorate(doc, protocol_def.range) == "«defimpl MyProtocol, for: Atom do»"
-
- assert protocol_ref.type == :module
- assert protocol_ref.subtype == :reference
- assert protocol_ref.subject == MyProtocol
- assert decorate(doc, protocol_ref.range) == "defimpl «MyProtocol», for: Atom do"
-
- assert for_ref.type == :module
- assert for_ref.subtype == :reference
- assert for_ref.subject == Atom
- assert decorate(doc, for_ref.range) == "defimpl MyProtocol, for: «Atom» do"
-
- assert proto_module_attr.type == :module
- assert proto_module_attr.subtype == :reference
- assert proto_module_attr.subject == MyProtocol.Something
- assert decorate(doc, proto_module_attr.range) == " «@protocol.Something»"
- end
-
- test "can detect an @for module reference" do
- {:ok, [_, _, _, for_module_attr], doc} =
- ~q[
- defimpl MyProtocol, for: DataStructure do
- def something do
- @for.Something
- end
- end
- ]
- |> index()
-
- assert for_module_attr.type == :module
- assert for_module_attr.subtype == :reference
- assert for_module_attr.subject == DataStructure.Something
- assert decorate(doc, for_module_attr.range) == " «@for.Something»"
- end
- end
-
- describe "multiple modules in one document" do
- test "have different refs" do
- {:ok, [first, second], _} =
- ~q[
- defmodule First do
- end
-
- defmodule Second do
- end
- ]
- |> index()
-
- assert first.block_id == :root
- assert first.type == :module
- assert first.subtype == :definition
- assert first.subject == First
-
- assert second.block_id == :root
- assert second.type == :module
- assert second.subtype == :definition
- assert second.subject == Second
-
- assert second.id != first.id
- end
-
- test "aren't nested" do
- {:ok, [first, second, third, fourth], _} =
- ~q[
- defmodule A.B.C do
- defstruct do
- field(:ok, :boolean)
- end
- end
-
- defmodule D.E.F do
- defstruct do
- field(:ok, :boolean)
- end
- end
-
- defmodule G.H.I do
- defstruct do
- field(:ok, :boolean)
- end
- end
-
- defmodule J.K.L do
- defstruct do
- field(:ok, :boolean)
- end
- end
- ]
- |> index()
-
- assert first.subject == A.B.C
- assert second.subject == D.E.F
- assert third.subject == G.H.I
- assert fourth.subject == J.K.L
- end
- end
-
- describe "nested modules" do
- test "have a parent/child relationship" do
- {:ok, [parent, child], _} =
- ~q[
- defmodule Parent do
- defmodule Child do
- end
- end
- ]
- |> index()
-
- assert parent.block_id == :root
- assert parent.type == :module
- assert parent.subtype == :definition
-
- assert child.block_id == parent.id
- assert child.type == :module
- assert child.subtype == :definition
- end
-
- test "Have aliases resolved correctly" do
- {:ok, [_parent, _parent_alias, child, child_alias], _} =
- ~q[
- defmodule Parent do
- alias Something.Else
-
- defmodule Child do
- alias Else.Other
- end
- end
- ]
- |> index()
-
- assert child_alias.block_id == child.id
- assert child_alias.type == :module
- assert child_alias.subtype == :reference
- assert child_alias.subject == Something.Else.Other
- end
-
- test "works with __MODULE__" do
- {:ok, [parent, child], _} =
- ~q[
- defmodule Parent do
- defmodule __MODULE__.Child do
- end
- end
- ]
- |> index()
-
- assert parent.block_id == :root
- assert parent.type == :module
- assert parent.subtype == :definition
-
- assert child.block_id == parent.id
- assert child.type == :module
- assert child.subtype == :definition
- end
-
- test "works with __MODULE__ alias concatenations" do
- {:ok, [_, child], _} =
- ~q[
- defmodule Parent do
- @child_module __MODULE__.Child
- end
- ]
- |> index()
-
- assert child.type == :module
- assert child.subtype == :reference
- assert child.subject == Parent.Child
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/struct_reference_test.exs b/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/struct_reference_test.exs
deleted file mode 100644
index 4849ca7c..00000000
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/struct_reference_test.exs
+++ /dev/null
@@ -1,392 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.StructReferenceTest do
- alias Lexical.RemoteControl.Search.Subject
- use Lexical.Test.ExtractorCase
-
- def index(source) do
- do_index(source, fn entry ->
- entry.type == :struct and entry.subtype == :reference
- end)
- end
-
- describe "recognizing structs" do
- test "in a naked reference" do
- {:ok, [struct], doc} =
- ~q[%MyStruct{}]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == "«%MyStruct{}»"
- end
-
- test "in a naked reference with fields" do
- {:ok, [struct], doc} =
- ~q[
- %MyStruct{name: "stinky", height: 184}
- ]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S(«%MyStruct{name: "stinky", height: 184}»)
- end
-
- test "in a struct on the left side of a match" do
- {:ok, [struct], doc} =
- ~q[%MyStruct{} = variable]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == "«%MyStruct{}» = variable"
- end
-
- test "in a struct on the right side of a match" do
- {:ok, [struct], doc} =
- ~q[variable = %MyStruct{}]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == "variable = «%MyStruct{}»"
- end
-
- test "in a struct reference in params" do
- {:ok, [struct], doc} =
- ~q[
- def my_fn(%MyStruct{} = first) do
- end
- ]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S[def my_fn(«%MyStruct{}» = first) do]
- end
-
- test "in nested struct references" do
- {:ok, [outer, inner], doc} =
- ~q[
- %OuterStruct{inner: %InnerStruct{}}
- ]
- |> index()
-
- assert outer.type == :struct
- assert outer.subtype == :reference
- assert outer.subject == Subject.module(OuterStruct)
- assert decorate(doc, outer.range) == ~S[«%OuterStruct{inner: %InnerStruct{}}»]
-
- assert inner.type == :struct
- assert inner.subtype == :reference
- assert inner.subject == Subject.module(InnerStruct)
- assert decorate(doc, inner.range) == ~S[%OuterStruct{inner: «%InnerStruct{}»}]
- end
-
- test "in map keys" do
- {:ok, [struct], doc} =
- ~q[%{%MyStruct{} => 3}]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S[%{«%MyStruct{}» => 3}]
- end
-
- test "in map values" do
- {:ok, [struct], doc} =
- ~q[%{cool_struct: %MyStruct{}}]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S[%{cool_struct: «%MyStruct{}»}]
- end
-
- test "in list elements" do
- {:ok, [struct], doc} =
- ~q([1, 2, %MyStruct{}])
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S([1, 2, «%MyStruct{}»])
- end
-
- test "in a imported call to struct/1 with an alias" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct(MyStruct)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S[ struct = «struct(MyStruct)»]
- end
-
- test "in a imported call to struct/1 with __MODULE__" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct(__MODULE__)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(Parent)
- assert decorate(doc, struct.range) == ~S[ struct = «struct(__MODULE__)»]
- end
-
- test "in a imported call to struct!/1 with __MODULE__" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct!(__MODULE__)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(Parent)
- assert decorate(doc, struct.range) == ~S[ struct = «struct!(__MODULE__)»]
- end
-
- test "in a imported call to struct/2 with an alias" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct(MyStruct, foo: 3)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S[ struct = «struct(MyStruct, foo: 3)»]
- end
-
- test "in a imported call to struct!/2 with an alias" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct!(MyStruct, foo: 3)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S[ struct = «struct!(MyStruct, foo: 3)»]
- end
-
- test "in a imported call to struct/2 with __MODULE__" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct(__MODULE__, foo: 3)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(Parent)
- assert decorate(doc, struct.range) == ~S[ struct = «struct(__MODULE__, foo: 3)»]
- end
-
- test "is ignored if struct isn't imported" do
- assert {:ok, _, _} =
- ~q{
- defmodule Parent do
-
- import Kernel, except: [struct: 1]
- struct = struct(MyStruct)
- end
- }
- |> index()
- end
-
- test "in a fully qualified call to Kernel.struct/1" do
- {:ok, [struct], doc} = ~q[struct = Kernel.struct(MyStruct)] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S[struct = «Kernel.struct(MyStruct)»]
- end
-
- test "in a fully qualified call to Kernel.struct/2" do
- {:ok, [struct], doc} = ~q[struct = Kernel.struct(MyStruct, foo: 3)] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyStruct)
- assert decorate(doc, struct.range) == ~S[struct = «Kernel.struct(MyStruct, foo: 3)»]
- end
-
- test "other functions named struct are not counted" do
- {:ok, [], _} = ~q[struct = Macro.struct(MyStruct)] |> index()
- end
- end
-
- describe "handling __MODULE__" do
- test "in a module attribute" do
- {:ok, [struct], doc} =
- ~q[
- defmodule MyModule do
- @attr %__MODULE__{}
- end
- ]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyModule)
- assert decorate(doc, struct.range) == ~S( @attr «%__MODULE__{}»)
- end
-
- test "in handling a submodule" do
- {:ok, [struct], doc} =
- ~q[
- defmodule MyModule do
- @attr %__MODULE__.Submodule{}
- end
- ]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyModule.Submodule)
- assert decorate(doc, struct.range) == ~S( @attr «%__MODULE__.Submodule{}»)
- end
-
- test "in a function definition" do
- {:ok, [struct], doc} =
- ~q[
- defmodule MyModule do
- def my_fn(%__MODULE__{}), do: :ok
- end
- ]
- |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(MyModule)
- assert decorate(doc, struct.range) == ~S[ def my_fn(«%__MODULE__{}»), do: :ok]
- end
-
- test "in a call to Kernel.struct/1" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct(__MODULE__)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(Parent)
- assert decorate(doc, struct.range) == ~S[ struct = «struct(__MODULE__)»]
- end
-
- test "in a call to Kernel.struct!/1" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct!(__MODULE__)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(Parent)
- assert decorate(doc, struct.range) == ~S[ struct = «struct!(__MODULE__)»]
- end
-
- test "in a call to Kernel.struct/2" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct(__MODULE__, foo: 3)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(Parent)
- assert decorate(doc, struct.range) == ~S[ struct = «struct(__MODULE__, foo: 3)»]
- end
-
- test "in a call to Kernel.struct!/2" do
- {:ok, [struct], doc} = ~q[
- defmodule Parent do
- struct = struct!(__MODULE__, foo: 3)
- end
- ] |> index()
-
- assert struct.type == :struct
- assert struct.subtype == :reference
- assert struct.subject == Subject.module(Parent)
- assert decorate(doc, struct.range) == ~S[ struct = «struct!(__MODULE__, foo: 3)»]
- end
- end
-
- describe "when aliases can't be expanded" do
- test "a fully qualified call to Kernel.struct/1 is ignored" do
- assert {:ok, [], _} = ~q[
- defmodule Parent do
- struct = Kernel.struct(unquote(__MODULE__))
- end
- ] |> index()
- end
-
- test "a fully qualified call to Kernel.struct/2 is ignored" do
- assert {:ok, [], _} = ~q[
- defmodule Parent do
- struct = Kernel.struct(unquote(__MODULE__), foo: 3)
- end
- ] |> index()
- end
-
- test "a call to struct!/2 is ignored" do
- assert {:ok, [], _} = ~q[
- defmodule Parent do
- struct = struct!(unquote(__MODULE__), foo: 3)
- end
- ] |> index()
- end
-
- test "a call to struct!/1 is ignored" do
- assert {:ok, [], _} = ~q[
- defmodule Parent do
- struct = struct!(unquote(__MODULE__))
- end
- ] |> index()
- end
-
- test "a call to struct/1 is ignored" do
- assert {:ok, [], _} = ~q[
- defmodule Parent do
- struct = struct(unquote(__MODULE__))
- end
- ] |> index()
- end
-
- test "a call to struct/2 is ignored" do
- assert {:ok, [], _} = ~q[
- defmodule Parent do
- struct = struct(unquote(__MODULE__), foo: 3)
- end
- ] |> index()
- end
-
- test "a reference ignored" do
- assert {:ok, [], _} = ~q[
- defmodule Parent do
- struct = %unquote(__MODULE__){}
- end
- ] |> index()
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/variable_test.exs b/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/variable_test.exs
deleted file mode 100644
index c5053d1f..00000000
--- a/apps/remote_control/test/lexical/remote_control/search/indexer/extractors/variable_test.exs
+++ /dev/null
@@ -1,1015 +0,0 @@
-defmodule Lexical.RemoteControl.Search.Indexer.Extractors.VariableTest do
- alias Lexical.RemoteControl.Search.Indexer.Extractors
-
- use Lexical.Test.ExtractorCase
-
- def index_references(source) do
- do_index(source, fn entry -> entry.type == :variable and entry.subtype == :reference end, [
- Extractors.Variable
- ])
- end
-
- def index_definitions(source) do
- do_index(source, fn entry -> entry.type == :variable and entry.subtype == :definition end, [
- Extractors.Variable
- ])
- end
-
- def index(source) do
- do_index(source, &(&1.type == :variable), [Extractors.Variable])
- end
-
- def assert_definition(entry, variable_name) do
- assert entry.type == :variable
- assert entry.subtype == :definition
- assert entry.subject == variable_name
- end
-
- def assert_reference(entry, variable_name) do
- assert entry.type == :variable
- assert entry.subtype == :reference
- assert entry.subject == variable_name
- end
-
- for def_type <- [:def, :defp, :defmacro, :defmacrop] do
- describe "variable definitions in #{def_type} parameters are extracted" do
- test "in a plain parameter" do
- {:ok, [param], doc} =
- ~q[
- #{unquote(def_type)} my_fun(var) do
- end
- ]
- |> index_definitions()
-
- assert_definition(param, :var)
- assert decorate(doc, param.range) =~ "#{unquote(def_type)} my_fun(«var»)"
- end
-
- test "in a struct value" do
- {:ok, [param], doc} =
- ~q[
- #{unquote(def_type)} my_fun(%Pattern{foo: var}) do
- end
- ]
- |> index_definitions()
-
- assert_definition(param, :var)
- assert decorate(doc, param.range) =~ "#{unquote(def_type)} my_fun(%Pattern{foo: «var»})"
- end
-
- test "on both sides of a pattern match" do
- {:ok, [var_1, var_2], doc} =
- ~q[
- #{unquote(def_type)} my_fun(%Pattern{foo: var} = var_2) do
- end
- ]
- |> index_definitions()
-
- assert_definition(var_1, :var)
-
- assert decorate(doc, var_1.range) =~
- "#{unquote(def_type)} my_fun(%Pattern{foo: «var»} = var_2)"
-
- assert_definition(var_2, :var_2)
-
- assert decorate(doc, var_2.range) =~
- "#{unquote(def_type)} my_fun(%Pattern{foo: var} = «var_2»)"
- end
-
- test "in a struct module" do
- {:ok, [var_1], doc} =
- ~q[
- #{unquote(def_type)} my_fun(%my_module{}) do
- end
- ]
- |> index_definitions()
-
- assert_definition(var_1, :my_module)
- assert decorate(doc, var_1.range) =~ "#{unquote(def_type)} my_fun(%«my_module»{})"
- end
-
- test "in a bitstrings" do
- {:ok, [var], doc} =
- ~q[
- #{unquote(def_type)} my_fun(<>) do
- end
- ]
- |> index_definitions()
-
- assert_definition(var, :foo)
-
- assert decorate(doc, var.range) =~
- "#{unquote(def_type)} my_fun(<<«foo»::binary-size(3)>>) do"
- end
-
- test "in list elements" do
- {:ok, [var_1, var_2], doc} =
- ~q{
- #{unquote(def_type)} my_fun([var_1, var_2]) do
- end
- }
- |> index_definitions()
-
- assert_definition(var_1, :var_1)
- assert decorate(doc, var_1.range) =~ "#{unquote(def_type)} my_fun([«var_1», var_2])"
-
- assert_definition(var_2, :var_2)
- assert decorate(doc, var_2.range) =~ "#{unquote(def_type)} my_fun([var_1, «var_2»])"
- end
-
- test "in the tail of a list" do
- {:ok, [tail], doc} =
- ~q{
- #{unquote(def_type)} my_fun([_ | acc]) do
- end
- }
- |> index_definitions()
-
- assert_definition(tail, :acc)
- assert decorate(doc, tail.range) =~ "#{unquote(def_type)} my_fun([_ | «acc»])"
- end
-
- test "unless it is an alias" do
- {:ok, [], _} =
- ~q[
- #{unquote(def_type)} my_fun(%MyStruct{}) do
- end
- ]
- |> index_definitions()
- end
-
- test "unless it begins with an underscore" do
- {:ok, [], _} =
- ~q[
- #{unquote(def_type)} my_fun(_unused) do
- end
- ]
- |> index_definitions()
-
- {:ok, [], _} =
- ~q[
- #{unquote(def_type)} my_fun(_) do
- end
- ]
- |> index_definitions()
- end
- end
-
- describe "variable definitions in #{def_type} that contain references are extracted" do
- test "when passed through" do
- {:ok, [def, ref], doc} =
- ~q[
- #{unquote(def_type)} my_fun(var) do
- var
- end
- ]
- |> index()
-
- assert_definition(def, :var)
- assert_reference(ref, :var)
-
- assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun(«var») do"
- assert decorate(doc, ref.range) =~ " «var»"
- end
-
- test "when wrapped in a list" do
- {:ok, [def, ref], doc} =
- ~q{
- #{unquote(def_type)} my_fun([var]) do
- [var]
- end
- }
- |> index()
-
- assert_definition(def, :var)
- assert_reference(ref, :var)
-
- assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun([«var»]) do"
- assert decorate(doc, ref.range) =~ " [«var»]"
- end
-
- test "when it's a map value" do
- {:ok, [def, ref], doc} =
- ~q[
- #{unquote(def_type)} my_fun(%{key: var}) do
- %{key: var}
- end
- ]
- |> index()
-
- assert_definition(def, :var)
- assert_reference(ref, :var)
-
- assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun(%{key: «var»}) do"
- assert decorate(doc, ref.range) =~ " %{key: «var»}"
- end
-
- test "when it's a struct module" do
- {:ok, [def, ref], doc} =
- ~q[
- #{unquote(def_type)} my_fun(%{key: var}) do
- %{key: var}
- end
- ]
- |> index()
-
- assert_definition(def, :var)
- assert_reference(ref, :var)
-
- assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun(%{key: «var»}) do"
- assert decorate(doc, ref.range) =~ " %{key: «var»}"
- end
-
- test "when it's a tuple entry " do
- {:ok, [def, ref], doc} =
- ~q[
- #{unquote(def_type)} my_fun({var}) do
- {var}
- end
- ]
- |> index()
-
- assert_definition(def, :var)
- assert_reference(ref, :var)
-
- assert decorate(doc, def.range) =~ "#{unquote(def_type)} my_fun({«var»}) do"
- assert decorate(doc, ref.range) =~ " {«var»}"
- end
-
- test "when it utilizes a pin " do
- {:ok, [first_def, second_def, first_pin, other_def, second_ref, other_ref], doc} =
- ~q"
- #{unquote(def_type)} my_fun({first, second}) do
- [^first, other] = second
- other
- end
- "
- |> index()
-
- assert_definition(first_def, :first)
-
- assert decorate(doc, first_def.range) =~
- "#{unquote(def_type)} my_fun({«first», second}) do"
-
- assert_definition(second_def, :second)
-
- assert decorate(doc, second_def.range) =~
- "#{unquote(def_type)} my_fun({first, «second»}) do"
-
- assert_reference(first_pin, :first)
- assert decorate(doc, first_pin.range) =~ " [^«first», other]"
-
- assert_definition(other_def, :other)
- assert decorate(doc, other_def.range) =~ " [^first, «other»]"
-
- assert_reference(second_ref, :second)
- assert decorate(doc, second_ref.range) =~ " [^first, other] = «second»"
-
- assert_reference(other_ref, :other)
- assert decorate(doc, other_ref.range) =~ " «other»"
- end
- end
- end
-
- describe "variable definitions in anonymous function parameters are extracted" do
- test "when definition on the right side of the equals" do
- {:ok, [ref], doc} =
- ~q[
- fn 1 = a -> a end
- ]
- |> index_references()
-
- assert decorate(doc, ref.range) =~ "fn 1 = a -> «a»"
- end
-
- test "in a plain parameter" do
- {:ok, [param], doc} =
- ~q[
- fn var ->
- nil
- end
- ]
- |> index_definitions()
-
- assert_definition(param, :var)
- assert decorate(doc, param.range) =~ "fn «var» ->"
- end
-
- test "in a struct's values" do
- {:ok, [param], doc} =
- ~q[
- fn %Pattern{foo: var} ->
- nil
- end
- ]
- |> index_definitions()
-
- assert_definition(param, :var)
- assert decorate(doc, param.range) =~ "fn %Pattern{foo: «var»} ->"
- end
-
- test "when they're pinned" do
- {:ok, [param], doc} =
- ~q[
- fn ^pinned ->
- nil
- end
- ]
- |> index_references()
-
- assert_reference(param, :pinned)
- assert decorate(doc, param.range) =~ "fn ^«pinned» ->"
- end
-
- test "on both sides of a pattern match" do
- {:ok, [var_1, var_2], doc} =
- ~q[
- fn %Pattern{foo: var} = var_2 ->
- nil
- end
- ]
- |> index_definitions()
-
- assert_definition(var_1, :var)
- assert decorate(doc, var_1.range) =~ "fn %Pattern{foo: «var»} = var_2 ->"
-
- assert_definition(var_2, :var_2)
- assert decorate(doc, var_2.range) =~ "fn %Pattern{foo: var} = «var_2» ->"
- end
-
- test "in a struct module" do
- {:ok, [var_1], doc} =
- ~q[
- fn %my_module{} ->
- nil
- end
- ]
- |> index_definitions()
-
- assert_definition(var_1, :my_module)
- assert decorate(doc, var_1.range) =~ "fn %«my_module»{} ->"
- end
-
- test "in list elements" do
- {:ok, [var_1, var_2], doc} =
- ~q{
- fn [var_1, var_2] ->
- nil
- end
- }
- |> index_definitions()
-
- assert_definition(var_1, :var_1)
- assert decorate(doc, var_1.range) =~ "fn [«var_1», var_2] ->"
-
- assert_definition(var_2, :var_2)
- assert decorate(doc, var_2.range) =~ "fn [var_1, «var_2»] ->"
- end
-
- test "in the tail of a list" do
- {:ok, [tail], doc} =
- ~q{
- fn [_ | acc] ->
- nil
- end
- }
- |> index_definitions()
-
- assert_definition(tail, :acc)
- assert decorate(doc, tail.range) =~ "fn [_ | «acc»] ->"
- end
-
- test "unless it is an alias" do
- {:ok, [], _} =
- ~q[
- fn %MyStruct{} ->
- nil
- end
- ]
- |> index_definitions()
- end
-
- test "unless it starts with an underscore" do
- {:ok, [], _} =
- ~q[
- fn _unused ->
- nil
- end
- ]
- |> index_definitions()
-
- {:ok, [], _} =
- ~q[
- fn _ ->
- nil
- end
- ]
- |> index_definitions()
- end
- end
-
- describe "variable definitions in code are extracted" do
- test "from full pattern matches" do
- {:ok, [var], doc} = index_definitions(~q[var = 38])
-
- assert_definition(var, :var)
- assert decorate(doc, var.range) =~ "«var» = 38"
- end
-
- test "from tuples elements" do
- {:ok, [first, second], doc} = index_definitions(~q({first, second} = foo))
-
- assert_definition(first, :first)
- assert decorate(doc, first.range) =~ "{«first», second} ="
-
- assert_definition(second, :second)
- assert decorate(doc, second.range) =~ "{first, «second»} ="
- end
-
- test "from list elements" do
- {:ok, [first, second], doc} = index_definitions(~q([first, second] = foo))
-
- assert_definition(first, :first)
- assert decorate(doc, first.range) =~ "[«first», second] ="
-
- assert_definition(second, :second)
- assert decorate(doc, second.range) =~ "[first, «second»] ="
- end
-
- test "from map values" do
- {:ok, [value], doc} = index_definitions(~q(%{key: value} = whatever))
-
- assert_definition(value, :value)
- assert decorate(doc, value.range) =~ "%{key: «value»} = whatever"
- end
-
- test "from struct values" do
- {:ok, [value], doc} = index_definitions(~q(%MyStruct{key: value} = whatever))
-
- assert_definition(value, :value)
- assert decorate(doc, value.range) =~ "%MyStruct{key: «value»} = whatever"
- end
-
- test "from struct modules" do
- {:ok, [module], doc} = index_definitions(~q(%struct_module{} = whatever))
-
- assert_definition(module, :struct_module)
- assert decorate(doc, module.range) =~ "%«struct_module»{} = whatever"
- end
-
- test "in an else block in a with" do
- {:ok, [value], doc} =
- ~q[
- with true <- true do
- :bad
- else var ->
- :ok
- end
- ]
- |> index_definitions()
-
- assert_definition(value, :var)
- assert decorate(doc, value.range) =~ "else «var» ->"
- end
-
- test "from comprehensions" do
- {:ok, [var, thing, field_1, field_2], doc} =
- ~q[
- for var <- things,
- {:ok, thing} = var,
- {:record, field_1, field_2} <- thing do
- end
- ]
- |> index_definitions()
-
- assert_definition(var, :var)
- assert decorate(doc, var.range) =~ "for «var» <- things,"
-
- assert_definition(thing, :thing)
- assert decorate(doc, thing.range) =~ "{:ok, «thing»} = var,"
-
- assert_definition(field_1, :field_1)
- assert decorate(doc, field_1.range) =~ "{:record, «field_1», field_2} <- thing do"
-
- assert_definition(field_2, :field_2)
- assert decorate(doc, field_2.range) =~ "{:record, field_1, «field_2»} <- thing do"
- end
-
- test "in an else block in a try" do
- {:ok, [value], doc} =
- ~q[
- try do
- :ok
- else failure ->
- failure
- end
- ]
- |> index_definitions()
-
- assert_definition(value, :failure)
- assert decorate(doc, value.range) =~ "else «failure» ->"
- end
-
- test "in a catch block in a try" do
- {:ok, [value], doc} =
- ~q[
- try do
- :ok
- catch thrown ->
- thrown
- end
- ]
- |> index_definitions()
-
- assert_definition(value, :thrown)
- assert decorate(doc, value.range) =~ "catch «thrown» ->"
- end
-
- test "in a rescue block in a try" do
- {:ok, [value], doc} =
- ~q[
- try do
- :ok
- rescue ex ->
- ex
- end
- ]
- |> index_definitions()
-
- assert_definition(value, :ex)
- assert decorate(doc, value.range) =~ "rescue «ex» ->"
- end
-
- test "in a rescue block in a try using in" do
- {:ok, [value], doc} =
- ~q[
- try do
- :ok
- rescue ex in RuntimeError ->
- ex
- end
- ]
- |> index_definitions()
-
- assert_definition(value, :ex)
- assert decorate(doc, value.range) =~ "rescue «ex» in RuntimeError ->"
- end
-
- test "from complex, nested mappings" do
- {:ok, [module, list_elem, tuple_first, tuple_second], doc} =
- index_definitions(
- ~q(%struct_module{key: [list_elem, {tuple_first, tuple_second}]} = whatever)
- )
-
- assert_definition(module, :struct_module)
-
- assert decorate(doc, module.range) =~
- "%«struct_module»{key: [list_elem, {tuple_first, tuple_second}]} = whatever"
-
- assert_definition(list_elem, :list_elem)
-
- assert decorate(doc, list_elem.range) =~
- "%struct_module{key: [«list_elem», {tuple_first, tuple_second}]} = whatever"
-
- assert_definition(tuple_first, :tuple_first)
-
- assert decorate(doc, tuple_first.range) =~
- "%struct_module{key: [list_elem, {«tuple_first», tuple_second}]} = whatever"
-
- assert_definition(tuple_second, :tuple_second)
-
- assert decorate(doc, tuple_second.range) =~
- "%struct_module{key: [list_elem, {tuple_first, «tuple_second»}]} = whatever"
- end
-
- test "from test arguments" do
- {:ok, [test_def], doc} =
- ~q[
- defmodule TestCase do
- use ExUnit.Case
- test "my test", %{var: var} do
- var
- end
- end
- ]
- |> index_definitions()
-
- assert_definition(test_def, :var)
- assert decorate(doc, test_def.range) =~ "%{var: «var»} do"
- end
- end
-
- describe "variable references are extracted" do
- test "when by themselves" do
- assert {:ok, [ref], doc} = index_references(~q[variable])
-
- assert_reference(ref, :variable)
- assert decorate(doc, ref.range) =~ "«variable»"
- end
-
- test "from pinned variables" do
- {:ok, [ref], doc} = index_references("^pinned = 3")
-
- assert_reference(ref, :pinned)
- assert decorate(doc, ref.range) =~ "^«pinned» = 3"
- end
-
- test "from pinned variables in a function head" do
- {:ok, [ref], doc} =
- ~q{
- fn [^pinned] ->
- nil
- end
- }
- |> index
-
- assert_reference(ref, :pinned)
- assert decorate(doc, ref.range) =~ "fn [^«pinned»] ->"
- end
-
- test "on the left side of operators" do
- assert {:ok, [ref], doc} = index_references(~q[x + 3])
-
- assert_reference(ref, :x)
- assert decorate(doc, ref.range) =~ "«x» + 3"
- end
-
- test "on the right side of operators" do
- assert {:ok, [ref], doc} = index_references(~q[3 + x])
-
- assert_reference(ref, :x)
- assert decorate(doc, ref.range) =~ "3 + «x»"
- end
-
- test "on the right of pattern matches" do
- assert {:ok, [ref], doc} = index_references(~q[x = other_variable])
-
- assert_reference(ref, :other_variable)
- assert decorate(doc, ref.range) =~ "x = «other_variable»"
- end
-
- test "on the right side of pattern matches with dot notation" do
- assert {:ok, [ref], doc} = index_references(~q[x = foo.bar.baz])
-
- assert_reference(ref, :foo)
- assert decorate(doc, ref.range) =~ "x = «foo».bar.baz"
- end
-
- test "on the right side of a pattern match in a function call" do
- assert {:ok, [ref], doc} = index_references(~q[_ = foo(bar)])
-
- assert_reference(ref, :bar)
- assert decorate(doc, ref.range) =~ "_ = foo(«bar»)"
- end
-
- test "on the left of pattern matches via a pin" do
- assert {:ok, [ref], doc} = index_references(~q[^pin = 49])
-
- assert_reference(ref, :pin)
- assert decorate(doc, ref.range) =~ "^«pin» = 49"
- end
-
- test "from function call arguments" do
- assert {:ok, [ref], doc} = index_references(~q[pow(x, 3)])
-
- assert_reference(ref, :x)
- assert decorate(doc, ref.range) =~ "pow(«x», 3)"
- end
-
- test "when using access syntax" do
- assert {:ok, [ref], doc} = index_references(~q{3 = foo[:bar]})
-
- assert_reference(ref, :foo)
- assert decorate(doc, ref.range) =~ "3 = «foo»[:bar]"
- end
-
- test "when inside brackets" do
- assert {:ok, [ref, access_ref], doc} = index_references(~q{3 = foo[bar]})
-
- assert_reference(ref, :foo)
- assert decorate(doc, ref.range) =~ "3 = «foo»[bar]"
-
- assert_reference(access_ref, :bar)
- assert decorate(doc, access_ref.range) =~ "3 = foo[«bar»]"
- end
-
- test "inside string interpolations" do
- quoted =
- quote file: "foo.ex", line: 1 do
- foo = 3
- "#{foo}"
- end
-
- assert {:ok, [ref], doc} = index_references(quoted)
-
- assert_reference(ref, :foo)
- assert decorate(doc, ref.range) =~ ~S["#{«foo»}"]
- end
-
- test "inside string interpolations that have a statement" do
- quoted =
- quote file: "foo.ex", line: 1 do
- foo = 3
- "#{foo + 3}"
- end
-
- assert {:ok, [ref], doc} = index_references(quoted)
-
- assert_reference(ref, :foo)
- assert decorate(doc, ref.range) =~ ~S["#{«foo» + 3}"]
- end
-
- test "inside string interpolations that have a literal prefix" do
- quoted =
- quote file: "foo.ex", line: 1 do
- foo = 3
- "prefix #{foo}"
- end
-
- assert {:ok, [ref], doc} = index_references(quoted)
-
- assert_reference(ref, :foo)
- assert decorate(doc, ref.range) =~ ~S["prefix #{«foo»}"]
- end
-
- test "inside string interpolations that have a literal suffix" do
- quoted =
- quote file: "foo.ex", line: 1 do
- foo = 3
- "#{foo} suffix"
- end
-
- assert {:ok, [ref], doc} = index_references(quoted)
-
- assert_reference(ref, :foo)
- assert decorate(doc, ref.range) =~ ~S["#{«foo»} suffix"]
- end
-
- test "inside string interpolations that have a literal prefix and suffix" do
- quoted =
- quote file: "foo.ex", line: 1 do
- foo = 3
- "prefix #{foo} suffix"
- end
-
- assert {:ok, [ref], doc} = index_references(quoted)
-
- assert_reference(ref, :foo)
- assert decorate(doc, ref.range) =~ ~S["prefix #{«foo»} suffix"]
- end
-
- test "when inside a rescue block in a try" do
- {:ok, [ref], doc} =
- ~q[
- try do
- :ok
- rescue e in Something ->
- e
- end
- ]
- |> index_references()
-
- assert_reference(ref, :e)
- assert decorate(doc, ref.range) =~ " «e»"
- end
-
- test "when inside a catch block in a try" do
- {:ok, [ref], doc} =
- ~q[
- try do
- :ok
- catch thrown ->
- thrown
- end
- ]
- |> index_references()
-
- assert_reference(ref, :thrown)
- assert decorate(doc, ref.range) =~ " «thrown»"
- end
-
- test "when inside an after block in a try" do
- {:ok, [ref], doc} =
- ~q[
- try do
- :ok
- after ->
- x
- end
- ]
- |> index_references()
-
- assert_reference(ref, :x)
- assert decorate(doc, ref.range) =~ " «x»"
- end
-
- test "when inside an else block in a with" do
- {:ok, [ref], doc} =
- ~q[
- with :ok <- call() do
- else other ->
- other
- end
- ]
- |> index_references()
-
- assert_reference(ref, :other)
- assert decorate(doc, ref.range) =~ " «other»"
- end
-
- test "when in the tail of a list" do
- assert {:ok, [ref], doc} = index_references(~q{[3 | acc]})
-
- assert_reference(ref, :acc)
- assert decorate(doc, ref.range) =~ "[3 | «acc»]"
- end
-
- test "in the body of an anonymous function" do
- {:ok, [ref], doc} =
- ~q[
- fn %Pattern{foo: var} ->
- var
- end
- ]
- |> index_references()
-
- assert_reference(ref, :var)
- assert decorate(doc, ref.range) =~ " «var»"
- end
-
- test "when unquote is used in a function definition" do
- {:ok, [ref], doc} =
- ~q[
- def my_fun(unquote(other_var)) do
- end
- ]
- |> index_references()
-
- assert_reference(ref, :other_var)
- assert decorate(doc, ref.range) =~ "def my_fun(unquote(«other_var»)) do"
- end
-
- test "unless it begins with underscore" do
- assert {:ok, [], _} = index_references("_")
- assert {:ok, [], _} = index_references("_unused")
- assert {:ok, [], _} = index_references("_unused = 3")
- assert {:ok, [], _} = index_references("_unused = foo()")
- end
- end
-
- describe "variable and references are extracted" do
- test "in a multiple match" do
- {:ok, [foo_def, param_def, bar_def, other_ref], doc} =
- ~q[
- foo = param = bar = other
- ]
- |> index()
-
- assert_definition(foo_def, :foo)
- assert decorate(doc, foo_def.range) =~ "«foo» = param = bar = other"
-
- assert_definition(param_def, :param)
- assert decorate(doc, param_def.range) =~ "foo = «param» = bar = other"
-
- assert_definition(bar_def, :bar)
- assert decorate(doc, bar_def.range) =~ "foo = param = «bar» = other"
-
- assert_reference(other_ref, :other)
- assert decorate(doc, other_ref.range) =~ "foo = param = bar = «other»"
- end
-
- test "in an anoymous function" do
- {:ok, [pin_param, var_param, first_def, pin_pin, var_ref, first_ref], doc} =
- ~q{
- fn pin, var ->
- [first, ^pin] = var
- first
- end
- }
- |> index()
-
- assert_definition(pin_param, :pin)
- assert decorate(doc, pin_param.range) =~ "fn «pin», var ->"
-
- assert_definition(var_param, :var)
- assert decorate(doc, var_param.range) =~ "fn pin, «var» ->"
-
- assert_definition(first_def, :first)
- assert decorate(doc, first_def.range) =~ " [«first», ^pin] = var"
-
- assert_reference(pin_pin, :pin)
- assert decorate(doc, pin_pin.range) =~ " [first, ^«pin»] = var"
-
- assert_reference(var_ref, :var)
- assert decorate(doc, var_ref.range) =~ " [first, ^pin] = «var»"
-
- assert_reference(first_ref, :first)
- assert decorate(doc, first_ref.range) =~ " «first»"
- end
-
- test "in the match arms of a with" do
- {:ok, [var_def, var_2_def, var_ref], doc} =
- ~q[
- with {:ok, var} <- something(),
- {:ok, var_2} <- something_else(var) do
- :bad
- end
- ]
- |> index()
-
- assert_definition(var_def, :var)
- assert decorate(doc, var_def.range) =~ "{:ok, «var»} <- something(),"
-
- assert_definition(var_2_def, :var_2)
- assert decorate(doc, var_2_def.range) =~ " {:ok, «var_2»} <- something_else(var) do"
-
- assert_reference(var_ref, :var)
- assert decorate(doc, var_ref.range) =~ " {:ok, var_2} <- something_else(«var») do"
- end
-
- test "in the body of a with" do
- {:ok, [_var_def, var_ref], doc} =
- ~q[
- with {:ok, var} <- something() do
- var + 1
- end
- ]
- |> index()
-
- assert_reference(var_ref, :var)
- assert decorate(doc, var_ref.range) =~ " «var» + 1"
- end
-
- test "in a comprehension" do
- {:ok, extracted, doc} =
- ~q[
- for {:ok, var} <- list,
- {:record, elem_1, elem_2} <- var do
- {:ok, elem_1 + elem_2}
- end
- ]
- |> index()
-
- assert [var_def, list_ref, elem_1_def, elem_2_def, var_ref, elem_1_ref, elem_2_ref] =
- extracted
-
- assert_definition(var_def, :var)
- assert decorate(doc, var_def.range) =~ "for {:ok, «var»} <- list,"
-
- assert_reference(list_ref, :list)
- assert decorate(doc, list_ref.range) =~ "for {:ok, var} <- «list»,"
-
- assert_definition(elem_1_def, :elem_1)
- assert decorate(doc, elem_1_def.range) =~ "{:record, «elem_1», elem_2} <- var do"
-
- assert_definition(elem_2_def, :elem_2)
- assert decorate(doc, elem_2_def.range) =~ "{:record, elem_1, «elem_2»} <- var do"
-
- assert_reference(var_ref, :var)
- assert decorate(doc, var_ref.range) =~ "{:record, elem_1, elem_2} <- «var» do"
-
- assert_reference(elem_1_ref, :elem_1)
- assert decorate(doc, elem_1_ref.range) =~ " {:ok, «elem_1» + elem_2}"
-
- assert_reference(elem_2_ref, :elem_2)
- assert decorate(doc, elem_2_ref.range) =~ " {:ok, elem_1 + «elem_2»}"
- end
-
- test "in guards in def functions" do
- {:ok, [param_def, param_2_def, param_ref], doc} =
- ~q[
- def something(param, param_2) when param > 1 do
- end
- ]
- |> index()
-
- assert_definition(param_def, :param)
- assert decorate(doc, param_def.range) =~ "def something(«param», param_2) when param > 1 do"
-
- assert_definition(param_2_def, :param_2)
-
- assert decorate(doc, param_2_def.range) =~
- "def something(param, «param_2») when param > 1 do"
-
- assert_reference(param_ref, :param)
- assert decorate(doc, param_ref.range) =~ "def something(param, param_2) when «param» > 1 do"
- end
-
- test "in guards in anonymous functions" do
- {:ok, [param_def, param_2_def, param_ref], doc} =
- ~q[
- fn param, param_2 when param > 1 -> :ok end
- ]
- |> index()
-
- assert_definition(param_def, :param)
- assert decorate(doc, param_def.range) =~ "fn «param», param_2 when param > 1 -> :ok end"
-
- assert_definition(param_2_def, :param_2)
- assert decorate(doc, param_2_def.range) =~ "fn param, «param_2» when param > 1 -> :ok end"
-
- assert_reference(param_ref, :param)
- assert decorate(doc, param_ref.range) =~ "fn param, param_2 when «param» > 1 -> :ok end"
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/search/indexer_test.exs b/apps/remote_control/test/lexical/remote_control/search/indexer_test.exs
deleted file mode 100644
index ae19734c..00000000
--- a/apps/remote_control/test/lexical/remote_control/search/indexer_test.exs
+++ /dev/null
@@ -1,166 +0,0 @@
-defmodule Lexical.RemoteControl.Search.IndexerTest do
- alias Lexical.Project
- alias Lexical.RemoteControl.Dispatch
- alias Lexical.RemoteControl.Search.Indexer
- alias Lexical.RemoteControl.Search.Indexer.Entry
-
- use ExUnit.Case
- use Patch
- import Lexical.Test.Fixtures
-
- defmodule FakeBackend do
- def set_entries(entries) when is_list(entries) do
- :persistent_term.put({__MODULE__, :entries}, entries)
- end
-
- def reduce(accumulator, reducer_fun) do
- {__MODULE__, :entries}
- |> :persistent_term.get([])
- |> Enum.reduce(accumulator, fn
- %{id: id} = entry, acc when is_integer(id) -> reducer_fun.(entry, acc)
- _, acc -> acc
- end)
- end
- end
-
- setup do
- project = project()
- start_supervised(Dispatch)
- {:ok, project: project}
- end
-
- describe "create_index/1" do
- test "returns a list of entries", %{project: project} do
- assert {:ok, entry_stream} = Indexer.create_index(project)
- entries = Enum.to_list(entry_stream)
- project_root = Project.root_path(project)
-
- assert length(entries) > 0
- assert Enum.all?(entries, fn entry -> String.starts_with?(entry.path, project_root) end)
- end
-
- test "entries are either .ex or .exs files", %{project: project} do
- assert {:ok, entries} = Indexer.create_index(project)
- assert Enum.all?(entries, fn entry -> Path.extname(entry.path) in [".ex", ".exs"] end)
- end
- end
-
- @ephemeral_file_name "ephemeral.ex"
-
- def with_an_ephemeral_file(%{project: project}, file_contents) do
- file_path = Path.join([Project.root_path(project), "lib", @ephemeral_file_name])
- File.write!(file_path, file_contents)
-
- on_exit(fn ->
- File.rm(file_path)
- end)
-
- {:ok, file_path: file_path}
- end
-
- def with_a_file_with_a_module(context) do
- file_contents = ~s[
- defmodule Ephemeral do
- end
- ]
- with_an_ephemeral_file(context, file_contents)
- end
-
- def with_an_existing_index(%{project: project}) do
- {:ok, entry_stream} = Indexer.create_index(project)
- entries = Enum.to_list(entry_stream)
- FakeBackend.set_entries(entries)
- {:ok, entries: entries}
- end
-
- describe "update_index/2 encounters a new file" do
- setup [:with_an_existing_index, :with_a_file_with_a_module]
-
- test "the ephemeral file is not previously present in the index", %{entries: entries} do
- refute Enum.any?(entries, fn entry -> Path.basename(entry.path) == @ephemeral_file_name end)
- end
-
- test "the ephemeral file is listed in the updated index", %{project: project} do
- {:ok, entry_stream, []} = Indexer.update_index(project, FakeBackend)
- assert [_structure, updated_entry] = Enum.to_list(entry_stream)
- assert Path.basename(updated_entry.path) == @ephemeral_file_name
- assert updated_entry.subject == Ephemeral
- end
- end
-
- def with_an_ephemeral_empty_file(context) do
- with_an_ephemeral_file(context, "")
- end
-
- describe "update_index/2 encounters a zero-length file" do
- setup [:with_an_existing_index, :with_an_ephemeral_empty_file]
-
- test "and does nothing", %{project: project} do
- {:ok, entry_stream, []} = Indexer.update_index(project, FakeBackend)
- assert [] = Enum.to_list(entry_stream)
- end
-
- test "there is no progress", %{project: project} do
- # this ensures we don't emit progress with a total byte size of 0, which will
- # cause an ArithmeticError
-
- Dispatch.register_listener(self(), :all)
- {:ok, entry_stream, []} = Indexer.update_index(project, FakeBackend)
- assert [] = Enum.to_list(entry_stream)
- refute_receive _
- end
- end
-
- describe "update_index/2" do
- setup [:with_a_file_with_a_module, :with_an_existing_index]
-
- test "sees the ephemeral file", %{entries: entries} do
- assert Enum.any?(entries, fn entry -> Path.basename(entry.path) == @ephemeral_file_name end)
- end
-
- test "returns the file paths of deleted files", %{project: project, file_path: file_path} do
- File.rm(file_path)
- assert {:ok, entry_stream, [^file_path]} = Indexer.update_index(project, FakeBackend)
- assert [] = Enum.to_list(entry_stream)
- end
-
- test "updates files that have changed since the last index", %{
- project: project,
- entries: entries,
- file_path: file_path
- } do
- entries = Enum.reject(entries, &is_nil(&1.id))
- path_to_mtime = Map.new(entries, fn entry -> {entry.path, Entry.updated_at(entry)} end)
-
- [entry | _] = entries
- {{year, month, day}, hms} = Entry.updated_at(entry)
- old_mtime = {{year - 1, month, day}, hms}
-
- patch(Indexer, :stat, fn path ->
- {ymd, {hour, minute, second}} =
- Map.get_lazy(path_to_mtime, file_path, &:calendar.universal_time/0)
-
- mtime =
- if path == file_path do
- {ymd, {hour, minute, second + 1}}
- else
- old_mtime
- end
-
- {:ok, %File.Stat{mtime: mtime}}
- end)
-
- new_contents = ~s[
- defmodule Brand.Spanking.New do
- end
- ]
-
- File.write!(file_path, new_contents)
-
- assert {:ok, entry_stream, []} = Indexer.update_index(project, FakeBackend)
- assert [_structure, entry] = Enum.to_list(entry_stream)
- assert entry.path == file_path
- assert entry.subject == Brand.Spanking.New
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control/search/store_test.exs b/apps/remote_control/test/lexical/remote_control/search/store_test.exs
deleted file mode 100644
index 5122efce..00000000
--- a/apps/remote_control/test/lexical/remote_control/search/store_test.exs
+++ /dev/null
@@ -1,398 +0,0 @@
-defmodule Lexical.RemoteControl.Search.StoreTest do
- alias Lexical.RemoteControl.Dispatch
- alias Lexical.RemoteControl.Search.Indexer
- alias Lexical.RemoteControl.Search.Indexer.Entry
- alias Lexical.RemoteControl.Search.Store
- alias Lexical.RemoteControl.Search.Store.Backends.Ets
- alias Lexical.Test.Entry
- alias Lexical.Test.EventualAssertions
- alias Lexical.Test.Fixtures
-
- use ExUnit.Case, async: false
-
- import Entry.Builder
- import EventualAssertions
- import Fixtures
- import Lexical.Test.CodeSigil
-
- @backends [Ets]
-
- setup_all do
- project = project()
- Lexical.RemoteControl.set_project(project)
- # These test cases require an clean slate going into them
- # so we should remove the indexes once when the tests start,
- # and again when tests end, so the next test has a clean slate.
- # Removing the index at the end will also let other test cases
- # start with a clean slate.
-
- destroy_backends(project)
-
- on_exit(fn ->
- destroy_backends(project)
- end)
-
- {:ok, project: project}
- end
-
- def all_entries(backend) do
- []
- |> backend.reduce(fn entry, acc -> [entry | acc] end)
- |> Enum.reverse()
- end
-
- for backend <- @backends,
- backend_name = backend |> Module.split() |> List.last() do
- describe "#{backend_name} :: replace/1" do
- setup %{project: project} do
- with_a_started_store(project, unquote(backend))
- end
-
- test "replaces the entire index" do
- entries = [definition(subject: OtherModule)]
-
- Store.replace(entries)
- assert entries == all_entries(unquote(backend))
- end
- end
-
- describe "#{backend_name} :: querying" do
- setup %{project: project} do
- with_a_started_store(project, unquote(backend))
- end
-
- test "matching can exclude on type" do
- Store.replace([
- definition(id: 1),
- reference(id: 3)
- ])
-
- assert {:ok, [ref]} = Store.exact(subtype: :reference)
- assert ref.subtype == :reference
- end
-
- test "matching with queries can exclude on type" do
- Store.replace([
- reference(subject: Foo.Bar.Baz),
- reference(subject: Other.Module),
- definition(subject: Foo.Bar.Baz)
- ])
-
- assert {:ok, [ref]} = Store.exact("Foo.Bar.Baz", subtype: :reference)
-
- assert ref.subject == Foo.Bar.Baz
- assert ref.type == :module
- assert ref.subtype == :reference
- end
-
- test "matching exact tokens should work" do
- Store.replace([
- definition(id: 1, subject: Foo.Bar.Baz),
- definition(id: 2, subject: Foo.Bar.Bak)
- ])
-
- assert {:ok, [entry]} = Store.exact("Foo.Bar.Baz", type: :module, subtype: :definition)
-
- assert entry.subject == Foo.Bar.Baz
- assert entry.id == 1
- end
-
- test "matching prefix tokens should work" do
- Store.replace([
- definition(id: 1, subject: Foo.Bar),
- definition(id: 2, subject: Foo.Baa.Baa),
- definition(id: 3, subject: Foo.Bar.Baz)
- ])
-
- assert {:ok, [entry1, entry3]} =
- Store.prefix("Foo.Bar", type: :module, subtype: :definition)
-
- assert entry1.subject == Foo.Bar
- assert entry3.subject == Foo.Bar.Baz
-
- assert entry1.id == 1
- assert entry3.id == 3
- end
-
- test "matching fuzzy tokens works" do
- Store.replace([
- definition(id: 1, subject: Foo.Bar.Baz),
- definition(id: 2, subject: Foo.Bar.Bak),
- definition(id: 3, subject: Bad.Times.Now)
- ])
-
- assert {:ok, [entry_1, entry_2]} =
- Store.fuzzy("Foo.Bar.B", type: :module, subtype: :definition)
-
- assert entry_1.subject in [Foo.Bar.Baz, Foo.Bar.Bak]
- assert entry_2.subject in [Foo.Bar.Baz, Foo.Bar.Bak]
- end
- end
-
- describe "#{backend_name} :: updating entries in a file" do
- setup %{project: project} do
- with_a_started_store(project, unquote(backend))
- end
-
- test "old entries with the same path are deleted" do
- path = "/path/to/file.ex"
-
- Store.replace([
- definition(id: 1, subject: Foo.Bar.Baz, path: path),
- definition(id: 2, subject: Foo.Baz.Quux, path: path)
- ])
-
- updated = [
- definition(id: 3, subject: Other.Thing.Entirely, path: path)
- ]
-
- Store.update(path, updated)
-
- assert_eventually [remaining] = all_entries(unquote(backend))
- refute remaining.id in [1, 2]
- end
-
- test "old entries with another path are kept" do
- updated_path = "/path/to/file.ex"
-
- Store.replace([
- definition(id: 1, subject: Foo.Bar.Baz, path: updated_path),
- definition(id: 2, subject: Foo.Bar.Baz.Quus, path: updated_path),
- definition(id: 3, subject: Foo.Bar.Baz, path: "/path/to/another.ex")
- ])
-
- updated = [
- definition(id: 4, subject: Other.Thing.Entirely, path: updated_path)
- ]
-
- Store.update(updated_path, updated)
-
- assert_eventually [first, second] = all_entries(unquote(backend))
-
- assert first.id in [3, 4]
- assert second.id in [3, 4]
- end
-
- test "updated entries are not searchable" do
- path = "/path/to/ex.ex"
-
- Store.replace([
- definition(id: 1, subject: Should.Be.Replaced, path: path)
- ])
-
- Store.update(path, [
- definition(id: 2, subject: Present, path: path)
- ])
-
- assert_eventually {:ok, [found]} =
- Store.fuzzy("Pres", type: :module, subtype: :definition)
-
- assert found.id == 2
- assert found.subject == Present
- end
- end
-
- describe "#{backend_name} :: structure queries " do
- setup %{project: project} do
- with_a_started_store(project, unquote(backend))
- end
-
- test "finding siblings" do
- entries =
- ~q[
- defmodule Parent do
- def function do
- First.Module
- Second.Module
- Third.Module
- end
- end
- ]
- |> entries()
-
- subject_entry = Enum.find(entries, &(&1.subject == Third.Module))
- assert {:ok, [first_ref, second_ref, ^subject_entry]} = Store.siblings(subject_entry)
- assert first_ref.subject == First.Module
- assert second_ref.subject == Second.Module
- end
-
- test "finding siblings of a function" do
- entries =
- ~q[
- defmodule Parent do
- def fun do
- :ok
- end
-
- def fun2(arg) do
- arg + 1
- end
-
- def fun3(arg, arg2) do
- arg + arg2
- end
- end
- ]
- |> entries()
-
- subject_entry = Enum.find(entries, &(&1.subject == "Parent.fun3/2"))
-
- assert {:ok, siblings} = Store.siblings(subject_entry)
- siblings = Enum.filter(siblings, &(&1.subtype == :definition))
-
- assert [first_fun, second_fun, ^subject_entry] = siblings
- assert first_fun.subject == "Parent.fun/0"
- assert second_fun.subject == "Parent.fun2/1"
- end
-
- test "findidng siblings of a non-existent entry" do
- assert :error = Store.siblings(%Indexer.Entry{})
- end
-
- test "finding a parent in a function" do
- entries =
- ~q[
- defmodule Parent do
- def function do
- Module.Ref
- end
- end
- ]
- |> entries()
-
- subject_entry = Enum.find(entries, &(&1.subject == Module.Ref))
- {:ok, parent} = Store.parent(subject_entry)
-
- assert parent.subject == "Parent.function/0"
- assert parent.type == {:function, :public}
- assert parent.subtype == :definition
-
- assert {:ok, parent} = Store.parent(parent)
- assert parent.subject == Parent
-
- assert :error = Store.parent(parent)
- end
-
- test "finding a parent in a comprehension" do
- entries =
- ~q[
- defmodule Parent do
- def fun do
- for n <- 1..10 do
- Module.Ref
- end
- end
- end
- ]
- |> entries()
-
- subject_entry = Enum.find(entries, &(&1.subject == Module.Ref))
- assert {:ok, parent} = Store.parent(subject_entry)
- assert parent.subject == "Parent.fun/0"
- end
-
- test "finding parents in a file with multiple nested modules" do
- entries =
- ~q[
- defmodule Parent do
- defmodule Child do
- def fun do
- end
- end
- end
-
- defmodule Parent2 do
- defmodule Child2 do
- def fun2 do
- Module.Ref
- end
- end
- end
- ]
- |> entries()
-
- subject_entry = Enum.find(entries, &(&1.subject == Module.Ref))
-
- assert {:ok, parent} = Store.parent(subject_entry)
-
- assert parent.subject == "Parent2.Child2.fun2/0"
- assert {:ok, parent} = Store.parent(parent)
- assert parent.subject == Parent2.Child2
-
- assert {:ok, parent} = Store.parent(parent)
- assert parent.subject == Parent2
- end
-
- test "finding a non-existent entry" do
- assert Store.parent(%Indexer.Entry{}) == :error
- end
- end
- end
-
- defp entries(source) do
- document = Lexical.Document.new("file:///file.ex", source, 1)
-
- {:ok, entries} =
- document
- |> Lexical.Ast.analyze()
- |> Indexer.Quoted.index_with_cleanup()
-
- Store.replace(entries)
- entries
- end
-
- defp after_each_test(backend, project) do
- destroy_backend(backend, project)
- end
-
- defp destroy_backends(project) do
- Enum.each(@backends, &destroy_backend(&1, project))
- end
-
- defp destroy_backend(Ets, project) do
- Ets.destroy_all(project)
- end
-
- defp destroy_backend(_, _) do
- :ok
- end
-
- defp default_create(_project) do
- {:ok, []}
- end
-
- defp default_update(_project, _entities) do
- {:ok, [], []}
- end
-
- defp with_a_started_store(project, backend) do
- destroy_backend(backend, project)
-
- start_supervised!(Dispatch)
- start_supervised!(backend)
- start_supervised!({Store, [project, &default_create/1, &default_update/2, backend]})
-
- assert_eventually alive?()
-
- Store.enable()
-
- assert_eventually ready?(), 1500
-
- on_exit(fn ->
- after_each_test(backend, project)
- end)
-
- {:ok, backend: backend}
- end
-
- def ready? do
- alive?() and Store.loaded?()
- end
-
- def alive? do
- case Process.whereis(Store) do
- nil -> false
- pid -> Process.alive?(pid)
- end
- end
-end
diff --git a/apps/remote_control/test/lexical/remote_control_test.exs b/apps/remote_control/test/lexical/remote_control_test.exs
deleted file mode 100644
index fe1a1cdd..00000000
--- a/apps/remote_control/test/lexical/remote_control_test.exs
+++ /dev/null
@@ -1,44 +0,0 @@
-defmodule Lexical.RemoteControlTest do
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.RemoteControl
-
- use ExUnit.Case
- use Lexical.Test.EventualAssertions
- import Lexical.Test.Fixtures
-
- def start_project(%Project{} = project) do
- start_supervised!({Lexical.RemoteControl.ProjectNodeSupervisor, project})
- assert {:ok, _, _} = RemoteControl.start_link(project)
- :ok
- end
-
- def remote_control_cwd(project) do
- RemoteControl.call(project, File, :cwd!, [])
- end
-
- describe "detecting an umbrella app" do
- test "it changes the directory to the root if it's started in a subapp" do
- parent_project = project(:umbrella)
-
- subapp_project =
- [fixtures_path(), "umbrella", "apps", "first"]
- |> Path.join()
- |> Document.Path.to_uri()
- |> Project.new()
-
- start_project(subapp_project)
-
- assert_eventually remote_control_cwd(subapp_project) == Project.root_path(parent_project),
- 250
- end
-
- test "keeps the current directory if it's started in the parent app" do
- parent_project = project(:umbrella)
- start_project(parent_project)
-
- assert_eventually remote_control_cwd(parent_project) == Project.root_path(parent_project),
- 250
- end
- end
-end
diff --git a/apps/remote_control/test/support/lexical/test/fixtures.ex b/apps/remote_control/test/support/lexical/test/fixtures.ex
deleted file mode 100644
index 797bc2a7..00000000
--- a/apps/remote_control/test/support/lexical/test/fixtures.ex
+++ /dev/null
@@ -1,37 +0,0 @@
-defmodule Lexical.Test.Fixtures do
- alias Lexical.Document
- alias Lexical.Project
-
- use ExUnit.CaseTemplate
-
- def fixtures_path do
- [__ENV__.file, "..", "..", "..", "..", "fixtures"]
- |> Path.join()
- |> Path.expand()
- end
-
- def project(project_name) do
- [Path.dirname(__ENV__.file), "..", "..", "..", "fixtures", to_string(project_name)]
- |> Path.join()
- |> Path.expand()
- |> Lexical.Document.Path.to_uri()
- |> Project.new()
- end
-
- def project do
- project(:project)
- end
-
- def file_path(%Project{} = project, path_relative_to_project) do
- project
- |> Project.project_path()
- |> Path.join(path_relative_to_project)
- |> Path.expand()
- end
-
- def file_uri(%Project{} = project, relative_path) do
- project
- |> file_path(relative_path)
- |> Document.Path.ensure_uri()
- end
-end
diff --git a/apps/server/.formatter.exs b/apps/server/.formatter.exs
deleted file mode 100644
index 4e19d224..00000000
--- a/apps/server/.formatter.exs
+++ /dev/null
@@ -1,15 +0,0 @@
-# Used by "mix format"
-imported_deps =
- if Mix.env() == :test do
- [:patch, :common]
- else
- [:common]
- end
-
-locals_without_parens = [with_progress: 3]
-
-[
- locals_without_parens: locals_without_parens,
- inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
- import_deps: imported_deps
-]
diff --git a/apps/server/.iex.exs b/apps/server/.iex.exs
deleted file mode 100644
index 033717f2..00000000
--- a/apps/server/.iex.exs
+++ /dev/null
@@ -1,15 +0,0 @@
-alias Lexical.Project
-alias Lexical.RemoteControl
-
-other_project =
- [
- File.cwd!(),
- "..",
- "..",
- "..",
- "eakins"
- ]
- |> Path.join()
- |> Path.expand()
-
-project = Lexical.Project.new("file://#{other_project}")
diff --git a/apps/server/README.md b/apps/server/README.md
deleted file mode 100644
index 4984638f..00000000
--- a/apps/server/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Lexical.Server
-
-The Lexical Language server implementation
diff --git a/apps/server/bin/start_lexical.sh b/apps/server/bin/start_lexical.sh
deleted file mode 100755
index 4b29a39b..00000000
--- a/apps/server/bin/start_lexical.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env bash
-set -o pipefail
-
-script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
-
-# shellcheck disable=SC1091
-if ! . "$script_dir"/activate_version_manager.sh; then
- echo >&2 "Could not activate a version manager. Trying system installation."
-fi
-
-case $1 in
- iex)
- elixir_command=iex
- ;;
- *)
- elixir_command=elixir
- ;;
-esac
-
-$elixir_command \
- --cookie "lexical" \
- --no-halt \
- "$script_dir/boot.exs"
diff --git a/apps/server/lib/lexical/convertibles/lexical.plugin.diagnostic.result.ex b/apps/server/lib/lexical/convertibles/lexical.plugin.diagnostic.result.ex
deleted file mode 100644
index e6251213..00000000
--- a/apps/server/lib/lexical/convertibles/lexical.plugin.diagnostic.result.ex
+++ /dev/null
@@ -1,94 +0,0 @@
-defimpl Lexical.Convertible, for: Lexical.Plugin.V1.Diagnostic.Result do
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Math
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.Protocol.Conversions
- alias Lexical.Protocol.Types
- alias Lexical.Text
-
- def to_lsp(%Diagnostic.Result{} = diagnostic) do
- with {:ok, lsp_range} <- lsp_range(diagnostic) do
- proto_diagnostic = %Types.Diagnostic{
- message: diagnostic.message,
- range: lsp_range,
- severity: diagnostic.severity,
- source: diagnostic.source
- }
-
- {:ok, proto_diagnostic}
- end
- end
-
- def to_native(%Diagnostic.Result{} = diagnostic, _) do
- {:ok, diagnostic}
- end
-
- defp lsp_range(%Diagnostic.Result{position: %Position{} = position}) do
- with {:ok, lsp_start_pos} <- Conversions.to_lsp(position) do
- range =
- Types.Range.new(
- start: lsp_start_pos,
- end: Types.Position.new(line: lsp_start_pos.line + 1, character: 0)
- )
-
- {:ok, range}
- end
- end
-
- defp lsp_range(%Diagnostic.Result{position: %Range{} = range}) do
- Conversions.to_lsp(range)
- end
-
- defp lsp_range(%Diagnostic.Result{uri: uri} = diagnostic) when is_binary(uri) do
- with {:ok, document} <- Document.Store.open_temporary(uri) do
- position_to_range(document, diagnostic.position)
- end
- end
-
- defp lsp_range(%Diagnostic.Result{}) do
- {:error, :no_uri}
- end
-
- defp position_to_range(%Document{} = document, {start_line, start_column, end_line, end_column}) do
- start_pos = Position.new(document, start_line, max(start_column, 1))
- end_pos = Position.new(document, end_line, max(end_column, 1))
-
- range = Range.new(start_pos, end_pos)
- Conversions.to_lsp(range)
- end
-
- defp position_to_range(%Document{} = document, {line_number, column}) do
- column = max(column, 1)
-
- document
- |> to_lexical_range(line_number, column)
- |> Conversions.to_lsp()
- end
-
- defp position_to_range(document, line_number) when is_integer(line_number) do
- line_number = Math.clamp(line_number, 1, Document.size(document))
-
- with {:ok, line_text} <- Document.fetch_text_at(document, line_number) do
- column = Text.count_leading_spaces(line_text) + 1
-
- document
- |> to_lexical_range(line_number, column)
- |> Conversions.to_lsp()
- end
- end
-
- defp position_to_range(document, nil) do
- position_to_range(document, 1)
- end
-
- defp to_lexical_range(%Document{} = document, line_number, column) do
- line_number = Math.clamp(line_number, 1, Document.size(document) + 1)
-
- Range.new(
- Position.new(document, line_number, column),
- Position.new(document, line_number + 1, 1)
- )
- end
-end
diff --git a/apps/server/lib/lexical/server.ex b/apps/server/lib/lexical/server.ex
deleted file mode 100644
index 1d36d60e..00000000
--- a/apps/server/lib/lexical/server.ex
+++ /dev/null
@@ -1,198 +0,0 @@
-defmodule Lexical.Server do
- alias Lexical.Proto.Convert
- alias Lexical.Protocol.Notifications
- alias Lexical.Protocol.Requests
- alias Lexical.Server.Provider.Handlers
- alias Lexical.Server.State
- alias Lexical.Server.TaskQueue
-
- require Logger
-
- use GenServer
-
- @server_specific_messages [
- Notifications.DidChange,
- Notifications.DidChangeConfiguration,
- Notifications.DidChangeWatchedFiles,
- Notifications.DidClose,
- Notifications.DidOpen,
- Notifications.DidSave,
- Notifications.Exit,
- Notifications.Initialized,
- Requests.Shutdown
- ]
-
- @dialyzer {:nowarn_function, apply_to_state: 2}
-
- @spec server_request(
- Requests.request(),
- (Requests.request(), {:ok, any()} | {:error, term()} -> term())
- ) :: :ok
- def server_request(request, on_response) when is_function(on_response, 2) do
- GenServer.call(__MODULE__, {:server_request, request, on_response})
- end
-
- @spec server_request(Requests.request()) :: :ok
- def server_request(request) do
- server_request(request, fn _, _ -> :ok end)
- end
-
- def start_link(_) do
- GenServer.start_link(__MODULE__, [], name: __MODULE__)
- end
-
- def protocol_message(message) do
- GenServer.cast(__MODULE__, {:protocol_message, message})
- end
-
- def init(_) do
- {:ok, State.new()}
- end
-
- def handle_call({:server_request, request, on_response}, _from, %State{} = state) do
- new_state = State.add_request(state, request, on_response)
- {:reply, :ok, new_state}
- end
-
- def handle_cast({:protocol_message, message}, %State{} = state) do
- new_state =
- case handle_message(message, state) do
- {:ok, new_state} ->
- new_state
-
- error ->
- Logger.error(
- "Could not handle message #{inspect(message.__struct__)} #{inspect(error)}"
- )
-
- state
- end
-
- {:noreply, new_state}
- end
-
- def handle_cast(other, %State{} = state) do
- Logger.info("got other: #{inspect(other)}")
- {:noreply, state}
- end
-
- def handle_info(:default_config, %State{configuration: nil} = state) do
- Logger.warning(
- "Did not receive workspace/didChangeConfiguration notification after 5 seconds. " <>
- "Using default settings."
- )
-
- {:ok, config} = State.default_configuration(state)
- {:noreply, %State{state | configuration: config}}
- end
-
- def handle_info(:default_config, %State{} = state) do
- {:noreply, state}
- end
-
- def handle_message(%Requests.Initialize{} = initialize, %State{} = state) do
- Process.send_after(self(), :default_config, :timer.seconds(5))
-
- case State.initialize(state, initialize) do
- {:ok, _state} = success ->
- success
-
- error ->
- {error, state}
- end
- end
-
- def handle_message(%Requests.Cancel{} = cancel_request, %State{} = state) do
- TaskQueue.cancel(cancel_request)
- {:ok, state}
- end
-
- def handle_message(%Notifications.Cancel{} = cancel_notification, %State{} = state) do
- TaskQueue.cancel(cancel_notification)
- {:ok, state}
- end
-
- def handle_message(%message_module{} = message, %State{} = state)
- when message_module in @server_specific_messages do
- case apply_to_state(state, message) do
- {:ok, _} = success ->
- success
-
- error ->
- Logger.error("Failed to handle #{message.__struct__}, #{inspect(error)}")
- end
- end
-
- def handle_message(nil, %State{} = state) do
- # NOTE: This deals with the response after a request is requested by the server,
- # such as the response of `CreateWorkDoneProgress`.
- {:ok, state}
- end
-
- def handle_message(%_{} = request, %State{} = state) do
- with {:ok, handler} <- fetch_handler(request),
- {:ok, req} <- Convert.to_native(request) do
- TaskQueue.add(request.id, {handler, :handle, [req, state.configuration]})
- else
- {:error, {:unhandled, _}} ->
- Logger.info("Unhandled request: #{request.method}")
-
- _ ->
- :ok
- end
-
- {:ok, state}
- end
-
- def handle_message(%{} = response, %State{} = state) do
- new_state = State.finish_request(state, response)
-
- {:ok, new_state}
- end
-
- defp apply_to_state(%State{} = state, %{} = request_or_notification) do
- case State.apply(state, request_or_notification) do
- {:ok, new_state} -> {:ok, new_state}
- :ok -> {:ok, state}
- error -> {error, state}
- end
- end
-
- # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
- defp fetch_handler(%_{} = request) do
- case request do
- %Requests.FindReferences{} ->
- {:ok, Handlers.FindReferences}
-
- %Requests.Formatting{} ->
- {:ok, Handlers.Formatting}
-
- %Requests.CodeAction{} ->
- {:ok, Handlers.CodeAction}
-
- %Requests.CodeLens{} ->
- {:ok, Handlers.CodeLens}
-
- %Requests.Completion{} ->
- {:ok, Handlers.Completion}
-
- %Requests.GoToDefinition{} ->
- {:ok, Handlers.GoToDefinition}
-
- %Requests.Hover{} ->
- {:ok, Handlers.Hover}
-
- %Requests.ExecuteCommand{} ->
- {:ok, Handlers.Commands}
-
- %Requests.DocumentSymbols{} ->
- {:ok, Handlers.DocumentSymbols}
-
- %Requests.WorkspaceSymbol{} ->
- {:ok, Handlers.WorkspaceSymbol}
-
- %request_module{} ->
- {:error, {:unhandled, request_module}}
- end
- end
-end
diff --git a/apps/server/lib/lexical/server/application.ex b/apps/server/lib/lexical/server/application.ex
deleted file mode 100644
index 3e7964cc..00000000
--- a/apps/server/lib/lexical/server/application.ex
+++ /dev/null
@@ -1,32 +0,0 @@
-defmodule Lexical.Server.Application do
- # See https://hexdocs.pm/elixir/Application.html
- # for more information on OTP Applications
- @moduledoc false
-
- alias Lexical.Document
- alias Lexical.Server
- alias Lexical.Server.TaskQueue
- alias Lexical.Server.Transport
-
- use Application
-
- @impl true
- def start(_type, _args) do
- children = [
- document_store_child_spec(),
- Server,
- {DynamicSupervisor, Server.Project.Supervisor.options()},
- {Task.Supervisor, name: TaskQueue.task_supervisor_name()},
- TaskQueue,
- {Transport.StdIO, [:standard_io, &Server.protocol_message/1]}
- ]
-
- opts = [strategy: :one_for_one, name: Server.Supervisor]
- Supervisor.start_link(children, opts)
- end
-
- @doc false
- def document_store_child_spec do
- {Document.Store, derive: [analysis: &Lexical.Ast.analyze/1]}
- end
-end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion.ex b/apps/server/lib/lexical/server/code_intelligence/completion.ex
deleted file mode 100644
index 371eb61e..00000000
--- a/apps/server/lib/lexical/server/code_intelligence/completion.ex
+++ /dev/null
@@ -1,369 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion do
- alias Future.Code, as: Code
- alias Lexical.Ast.Analysis
- alias Lexical.Ast.Env
- alias Lexical.Document.Position
- alias Lexical.Project
- alias Lexical.Protocol.Types.Completion
- alias Lexical.Protocol.Types.InsertTextFormat
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.Builder
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
- alias Lexical.Server.Configuration
- alias Lexical.Server.Project.Intelligence
- alias Mix.Tasks.Namespace
-
- require InsertTextFormat
- require Logger
-
- @lexical_deps Enum.map([:lexical | Mix.Project.deps_apps()], &Atom.to_string/1)
-
- @lexical_dep_modules Enum.map(@lexical_deps, &Macro.camelize/1)
-
- def trigger_characters do
- [".", "@", "&", "%", "^", ":", "!", "-", "~"]
- end
-
- @spec complete(Project.t(), Analysis.t(), Position.t(), Completion.Context.t()) ::
- Completion.List.t()
- def complete(
- %Project{} = project,
- %Analysis{} = analysis,
- %Position{} = position,
- %Completion.Context{} = context
- ) do
- case Env.new(project, analysis, position) do
- {:ok, env} ->
- completions = completions(project, env, context)
- log_candidates(completions)
- maybe_to_completion_list(completions)
-
- {:error, _} = error ->
- Logger.error("Failed to build completion env #{inspect(error)}")
- maybe_to_completion_list()
- end
- end
-
- defp log_candidates(candidates) do
- log_iolist =
- Enum.reduce(candidates, ["Emitting Completions: ["], fn %Completion.Item{} = completion,
- acc ->
- name = Map.get(completion, :name) || Map.get(completion, :label)
- kind = completion |> Map.get(:kind, :unknown) |> to_string()
-
- [acc, [kind, ":", name], " "]
- end)
-
- Logger.info([log_iolist, "]"])
- end
-
- defp completions(%Project{} = project, %Env{} = env, %Completion.Context{} = context) do
- prefix_tokens = Env.prefix_tokens(env, 1)
-
- cond do
- prefix_tokens == [] or not should_emit_completions?(env) ->
- []
-
- should_emit_do_end_snippet?(env) ->
- do_end_snippet = "do\n $0\nend"
-
- env
- |> Builder.snippet(
- do_end_snippet,
- label: "do/end block",
- filter_text: "do"
- )
- |> List.wrap()
-
- Env.in_context?(env, :struct_field_key) ->
- project
- |> RemoteControl.Api.complete_struct_fields(env.analysis, env.position)
- |> Enum.map(&Translatable.translate(&1, Builder, env))
-
- true ->
- project
- |> RemoteControl.Api.complete(env)
- |> to_completion_items(project, env, context)
- end
- end
-
- defp should_emit_completions?(%Env{} = env) do
- if inside_comment?(env) or inside_string?(env) do
- false
- else
- always_emit_completions?() or has_meaningful_completions?(env)
- end
- end
-
- defp always_emit_completions? do
- # If VS Code receives an empty completion list, it will never issue
- # a new request, even if `is_incomplete: true` is specified.
- # https://github.com/lexical-lsp/lexical/issues/400
- Configuration.get().client_name == "Visual Studio Code"
- end
-
- defp has_meaningful_completions?(%Env{} = env) do
- case Code.Fragment.cursor_context(env.prefix) do
- :none ->
- false
-
- {:unquoted_atom, name} ->
- length(name) > 1
-
- {:local_or_var, name} ->
- local_length = length(name)
- surround_begin = max(1, env.position.character - local_length)
-
- local_length > 1 or has_surround_context?(env.prefix, 1, surround_begin)
-
- _ ->
- true
- end
- end
-
- defp inside_comment?(env) do
- Env.in_context?(env, :comment)
- end
-
- defp inside_string?(env) do
- Env.in_context?(env, :string)
- end
-
- defp has_surround_context?(fragment, line, column)
- when is_binary(fragment) and line >= 1 and column >= 1 do
- Code.Fragment.surround_context(fragment, {line, column}) != :none
- end
-
- # We emit a do/end snippet if the prefix token is the do operator or 'd', and
- # there is a space before the token preceding it on the same line. This
- # handles situations like `@do|` where a do/end snippet would be invalid.
- defguardp valid_do_prefix(kind, value)
- when (kind === :identifier and value === ~c"d") or
- (kind === :operator and value === :do)
-
- defguardp space_before_preceding_token(do_col, preceding_col)
- when do_col - preceding_col > 1
-
- defp should_emit_do_end_snippet?(%Env{} = env) do
- prefix_tokens = Env.prefix_tokens(env, 2)
-
- valid_prefix? =
- match?(
- [{kind, value, {line, do_col}}, {_, _, {line, preceding_col}}]
- when space_before_preceding_token(do_col, preceding_col) and
- valid_do_prefix(kind, value),
- prefix_tokens
- )
-
- valid_prefix? and Env.empty?(env.suffix)
- end
-
- defp to_completion_items(
- local_completions,
- %Project{} = project,
- %Env{} = env,
- %Completion.Context{} = context
- ) do
- debug_local_completions(local_completions)
-
- for result <- local_completions,
- displayable?(project, result),
- applies_to_context?(project, result, context),
- applies_to_env?(env, result),
- %Completion.Item{} = item <- to_completion_item(result, env) do
- item
- end
- end
-
- defp debug_local_completions(completions) do
- completions_by_type =
- Enum.group_by(completions, fn %candidate_module{} ->
- candidate_module
- |> Atom.to_string()
- |> String.split(".")
- |> List.last()
- |> String.downcase()
- end)
-
- log_iodata =
- Enum.reduce(completions_by_type, ["Local completions are: ["], fn {type, completions},
- acc ->
- names =
- Enum.map_join(completions, ", ", fn candidate ->
- Map.get(candidate, :name) || Map.get(candidate, :detail)
- end)
-
- [acc, [type, ": (", names], ") "]
- end)
-
- Logger.info([log_iodata, "]"])
- end
-
- defp to_completion_item(candidate, env) do
- candidate
- |> Translatable.translate(Builder, env)
- |> List.wrap()
- end
-
- defp displayable?(%Project{} = project, result) do
- suggested_module =
- case result do
- %_{full_name: full_name} when is_binary(full_name) -> full_name
- %_{origin: origin} when is_binary(origin) -> origin
- _ -> ""
- end
-
- cond do
- Namespace.Module.prefixed?(suggested_module) ->
- false
-
- # If we're working on the dependency, we should include it!
- Project.name(project) in @lexical_deps ->
- true
-
- true ->
- Enum.reduce_while(@lexical_dep_modules, true, fn module, _ ->
- if String.starts_with?(suggested_module, module) do
- {:halt, false}
- else
- {:cont, true}
- end
- end)
- end
- end
-
- defp applies_to_env?(%Env{} = env, %struct_module{} = result) do
- cond do
- Env.in_context?(env, :struct_reference) ->
- struct_reference_completion?(result, env)
-
- Env.in_context?(env, :bitstring) ->
- struct_module in [Candidate.BitstringOption, Candidate.Variable]
-
- Env.in_context?(env, :alias) ->
- struct_module in [
- Candidate.Behaviour,
- Candidate.Module,
- Candidate.Protocol,
- Candidate.Struct
- ]
-
- Env.in_context?(env, :use) ->
- # only allow modules that define __using__ in a use statement
- usable?(env, result)
-
- Env.in_context?(env, :impl) ->
- # only allow behaviour modules after @impl
- behaviour?(env, result)
-
- Env.in_context?(env, :spec) or Env.in_context?(env, :type) ->
- typespec_or_type_candidate?(result, env)
-
- true ->
- struct_module != Candidate.Typespec
- end
- end
-
- defp usable?(%Env{} = env, completion) do
- # returns true if the given completion is or is a parent of
- # a module that defines __using__
- case completion do
- %{full_name: full_name} ->
- with_prefix =
- RemoteControl.Api.modules_with_prefix(
- env.project,
- full_name,
- {Kernel, :macro_exported?, [:__using__, 1]}
- )
-
- not Enum.empty?(with_prefix)
-
- _ ->
- false
- end
- end
-
- defp behaviour?(%Env{} = env, completion) do
- # returns true if the given completion is or is a parent of
- # a module that is a behaviour
-
- case completion do
- %{full_name: full_name} ->
- with_prefix =
- RemoteControl.Api.modules_with_prefix(
- env.project,
- full_name,
- {Kernel, :function_exported?, [:behaviour_info, 1]}
- )
-
- not Enum.empty?(with_prefix)
-
- _ ->
- false
- end
- end
-
- defp struct_reference_completion?(%Candidate.Struct{}, _) do
- true
- end
-
- defp struct_reference_completion?(%Candidate.Module{} = module, %Env{} = env) do
- Intelligence.defines_struct?(env.project, module.full_name, to: :great_grandchild)
- end
-
- defp struct_reference_completion?(%Candidate.Macro{name: "__MODULE__"}, _) do
- true
- end
-
- defp struct_reference_completion?(_, _) do
- false
- end
-
- defp typespec_or_type_candidate?(%struct_module{}, _)
- when struct_module in [Candidate.Module, Candidate.Typespec, Candidate.ModuleAttribute] do
- true
- end
-
- defp typespec_or_type_candidate?(%Candidate.Function{} = function, %Env{} = env) do
- case RemoteControl.Api.expand_alias(env.project, [:__MODULE__], env.analysis, env.position) do
- {:ok, expanded} ->
- expanded == function.origin
-
- _error ->
- false
- end
- end
-
- defp typespec_or_type_candidate?(_, _) do
- false
- end
-
- defp applies_to_context?(%Project{} = project, result, %Completion.Context{
- trigger_kind: :trigger_character,
- trigger_character: "%"
- }) do
- case result do
- %Candidate.Module{} = result ->
- Intelligence.defines_struct?(project, result.full_name, from: :child, to: :child)
-
- %Candidate.Struct{} ->
- true
-
- _other ->
- false
- end
- end
-
- defp applies_to_context?(_project, _result, _context) do
- true
- end
-
- defp maybe_to_completion_list(items \\ [])
-
- defp maybe_to_completion_list([]) do
- Completion.List.new(items: [], is_incomplete: true)
- end
-
- defp maybe_to_completion_list(items), do: items
-end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translatable.ex b/apps/server/lib/lexical/server/code_intelligence/completion/translatable.ex
deleted file mode 100644
index 3a53e856..00000000
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translatable.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defprotocol Lexical.Server.CodeIntelligence.Completion.Translatable do
- alias Lexical.Ast.Env
- alias Lexical.Protocol.Types.Completion
- alias Lexical.Server.CodeIntelligence.Completion.Builder
-
- @type t :: any()
-
- @type translated :: [Completion.Item.t()] | Completion.Item.t() | :skip
-
- @fallback_to_any true
- @spec translate(t, Builder.t(), Env.t()) :: translated
- def translate(item, builder, env)
-end
-
-defimpl Lexical.Server.CodeIntelligence.Completion.Translatable, for: Any do
- def translate(_any, _builder, _environment) do
- :skip
- end
-end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/bitstring_option.ex b/apps/server/lib/lexical/server/code_intelligence/completion/translations/bitstring_option.ex
deleted file mode 100644
index f23c5e12..00000000
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/bitstring_option.ex
+++ /dev/null
@@ -1,25 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.BitstringOption do
- alias Lexical.Ast.Env
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
- alias Lexical.Server.CodeIntelligence.Completion.Translations
-
- require Logger
-
- defimpl Translatable, for: Candidate.BitstringOption do
- def translate(option, builder, %Env{} = env) do
- Translations.BitstringOption.translate(option, builder, env)
- end
- end
-
- def translate(%Candidate.BitstringOption{} = option, builder, %Env{} = env) do
- env
- |> builder.plain_text(option.name,
- filter_text: option.name,
- kind: :unit,
- label: option.name
- )
- |> builder.set_sort_scope(SortScope.global())
- end
-end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/function.ex b/apps/server/lib/lexical/server/code_intelligence/completion/translations/function.ex
deleted file mode 100644
index 851894ea..00000000
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/function.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Function do
- alias Lexical.Ast.Env
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
- alias Lexical.Server.CodeIntelligence.Completion.Translations
-
- defimpl Translatable, for: Candidate.Function do
- def translate(function, _builder, %Env{} = env) do
- if Env.in_context?(env, :function_capture) do
- Translations.Callable.capture_completions(function, env)
- else
- Translations.Callable.completion(function, env)
- end
- end
- end
-end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/module_attribute.ex b/apps/server/lib/lexical/server/code_intelligence/completion/translations/module_attribute.ex
deleted file mode 100644
index 5d66783d..00000000
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/module_attribute.ex
+++ /dev/null
@@ -1,178 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleAttribute do
- alias Lexical.Ast
- alias Lexical.Ast.Env
- alias Lexical.Document.Position
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
- alias Lexical.Server.CodeIntelligence.Completion.Translations
-
- defimpl Translatable, for: Candidate.ModuleAttribute do
- def translate(attribute, builder, %Env{} = env) do
- Translations.ModuleAttribute.translate(attribute, builder, env)
- end
- end
-
- def translate(%Candidate.ModuleAttribute{name: "@moduledoc"}, builder, env) do
- doc_snippet = ~s'''
- @moduledoc """
- $0
- """
- '''
-
- case fetch_range(env) do
- {:ok, range} ->
- with_doc =
- builder.text_edit_snippet(env, doc_snippet, range,
- detail: "Document public module",
- kind: :property,
- label: "@moduledoc"
- )
-
- without_doc =
- builder.text_edit(env, "@moduledoc false", range,
- detail: "Mark as private",
- kind: :property,
- label: "@moduledoc false"
- )
-
- [with_doc, without_doc]
-
- :error ->
- :skip
- end
- end
-
- def translate(%Candidate.ModuleAttribute{name: "@doc"}, builder, env) do
- doc_snippet = ~s'''
- @doc """
- $0
- """
- '''
-
- case fetch_range(env) do
- {:ok, range} ->
- with_doc =
- builder.text_edit_snippet(env, doc_snippet, range,
- detail: "Document public function",
- kind: :property,
- label: "@doc"
- )
-
- without_doc =
- builder.text_edit(env, "@doc false", range,
- detail: "Mark as private",
- kind: :property,
- label: "@doc false"
- )
-
- [with_doc, without_doc]
-
- :error ->
- :skip
- end
- end
-
- def translate(%Candidate.ModuleAttribute{name: "@spec"}, builder, env) do
- case fetch_range(env) do
- {:ok, range} ->
- [
- maybe_specialized_spec_snippet(builder, env, range),
- basic_spec_snippet(builder, env, range)
- ]
-
- :error ->
- :skip
- end
- end
-
- def translate(%Candidate.ModuleAttribute{} = attribute, builder, env) do
- case fetch_range(env) do
- {:ok, range} ->
- builder.text_edit(env, attribute.name, range,
- detail: "module attribute",
- kind: :constant,
- label: attribute.name
- )
-
- :error ->
- :skip
- end
- end
-
- defp fetch_range(%Env{} = env) do
- case fetch_at_op_on_same_line(env) do
- {:ok, {:at_op, _, {_line, char}}} ->
- {:ok, {char, env.position.character}}
-
- _ ->
- :error
- end
- end
-
- defp fetch_at_op_on_same_line(%Env{} = env) do
- Enum.reduce_while(Env.prefix_tokens(env), :error, fn
- {:at_op, _, _} = at_op, _acc ->
- {:halt, {:ok, at_op}}
-
- {:eol, _, _}, _acc ->
- {:halt, :error}
-
- _, acc ->
- {:cont, acc}
- end)
- end
-
- defp maybe_specialized_spec_snippet(builder, %Env{} = env, range) do
- with {:ok, %Position{} = position} <- Env.next_significant_position(env),
- {:ok, [{maybe_def, _, [call, _]} | _]} when maybe_def in [:def, :defp] <-
- Ast.path_at(env.analysis, position),
- {function_name, _, args} <- call do
- specialized_spec_snippet(builder, env, range, function_name, args)
- else
- _ -> nil
- end
- end
-
- defp specialized_spec_snippet(builder, env, range, function_name, args) do
- name = to_string(function_name)
-
- args_snippet =
- case args do
- nil ->
- ""
-
- list ->
- Enum.map_join(1..length(list), ", ", &"${#{&1}:term()}")
- end
-
- snippet = ~s"""
- @spec #{name}(#{args_snippet}) :: ${0:term()}
- """
-
- env
- |> builder.text_edit_snippet(snippet, range,
- detail: "Typespec",
- kind: :property,
- label: "@spec #{name}"
- )
- |> builder.set_sort_scope(SortScope.global(false, 0))
- end
-
- defp basic_spec_snippet(builder, env, range) do
- snippet = ~S"""
- @spec ${1:function}(${2:term()}) :: ${3:term()}
- def ${1:function}(${4:args}) do
- $0
- end
- """
-
- env
- |> builder.text_edit_snippet(snippet, range,
- detail: "Typespec",
- kind: :property,
- label: "@spec"
- )
- |> builder.set_sort_scope(SortScope.global(false, 1))
- end
-end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/struct.ex b/apps/server/lib/lexical/server/code_intelligence/completion/translations/struct.ex
deleted file mode 100644
index f166455a..00000000
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/struct.ex
+++ /dev/null
@@ -1,93 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Struct do
- alias Future.Code, as: Code
- alias Lexical.Ast.Env
- alias Lexical.Formats
-
- def completion(%Env{} = _env, _builder, _module_name, _full_name, 0) do
- nil
- end
-
- def completion(%Env{} = env, builder, module_name, full_name, more) when is_integer(more) do
- singular = "${count} more struct"
- plural = "${count} more structs"
-
- builder_opts = [
- kind: :module,
- label: "#{module_name}...(#{Formats.plural(more, singular, plural)})",
- detail: "#{full_name}."
- ]
-
- insert_text = "#{module_name}."
- range = edit_range(env)
-
- builder.text_edit_snippet(env, insert_text, range, builder_opts)
- end
-
- def completion(%Env{} = env, builder, struct_name, full_name) do
- builder_opts = [
- kind: :struct,
- detail: "#{full_name}",
- label: "#{struct_name}"
- ]
-
- range = edit_range(env)
-
- insert_text =
- if add_curlies?(env) do
- struct_name <> "{$1}"
- else
- struct_name
- end
-
- builder.text_edit_snippet(env, insert_text, range, builder_opts)
- end
-
- defp add_curlies?(%Env{} = env) do
- if Env.in_context?(env, :struct_reference) do
- not String.contains?(env.suffix, "{")
- else
- false
- end
- end
-
- defp edit_range(%Env{} = env) do
- prefix_end = env.position.character
-
- edit_begin =
- case Code.Fragment.cursor_context(env.prefix) do
- {:struct, {:dot, {:alias, _typed_module}, _rest}} ->
- prefix_end
-
- {:struct, typed_module_name} ->
- beginning_of_edit(env, typed_module_name)
-
- {:local_or_var, [?_ | _rest] = typed} ->
- beginning_of_edit(env, typed)
- end
-
- {edit_begin, env.position.character}
- end
-
- defp beginning_of_edit(env, typed_module_name) do
- case left_offset_of(typed_module_name, ?.) do
- {:ok, offset} ->
- env.position.character - offset
-
- :error ->
- env.position.character - length(typed_module_name)
- end
- end
-
- defp left_offset_of(string, character) do
- string
- |> Enum.reverse()
- |> Enum.with_index()
- |> Enum.reduce_while(:error, fn
- {^character, index}, _ ->
- {:halt, {:ok, index}}
-
- _, acc ->
- {:cont, acc}
- end)
- end
-end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/typespec.ex b/apps/server/lib/lexical/server/code_intelligence/completion/translations/typespec.ex
deleted file mode 100644
index 0ed98d5a..00000000
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/typespec.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Typespec do
- alias Lexical.Ast.Env
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
- alias Lexical.Server.CodeIntelligence.Completion.Translations.Callable
-
- defimpl Translatable, for: Candidate.Typespec do
- def translate(typespec, _builder, %Env{} = env) do
- Callable.completion(typespec, env)
- end
- end
-end
diff --git a/apps/server/lib/lexical/server/code_intelligence/completion/translations/variable.ex b/apps/server/lib/lexical/server/code_intelligence/completion/translations/variable.ex
deleted file mode 100644
index 811bb14f..00000000
--- a/apps/server/lib/lexical/server/code_intelligence/completion/translations/variable.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.Variable do
- alias Lexical.Ast.Env
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
- alias Lexical.Server.CodeIntelligence.Completion.Translatable
-
- defimpl Translatable, for: Candidate.Variable do
- def translate(variable, builder, %Env{} = env) do
- env
- |> builder.plain_text(variable.name,
- detail: variable.name,
- kind: :variable,
- label: variable.name
- )
- |> builder.set_sort_scope(SortScope.variable())
- end
- end
-end
diff --git a/apps/server/lib/lexical/server/configuration/support.ex b/apps/server/lib/lexical/server/configuration/support.ex
deleted file mode 100644
index fc9fe775..00000000
--- a/apps/server/lib/lexical/server/configuration/support.ex
+++ /dev/null
@@ -1,71 +0,0 @@
-defmodule Lexical.Server.Configuration.Support do
- @moduledoc false
-
- alias Lexical.Protocol.Types.ClientCapabilities
-
- # To track a new client capability, add a new field and the path to the
- # capability in the `Lexical.Protocol.Types.ClientCapabilities` struct
- # to this mapping:
- @client_capability_mapping [
- code_action_dynamic_registration: [
- :text_document,
- :code_action,
- :dynamic_registration
- ],
- hierarchical_symbols: [
- :text_document,
- :document_symbol,
- :hierarchical_document_symbol_support
- ],
- snippet: [
- :text_document,
- :completion,
- :completion_item,
- :snippet_support
- ],
- deprecated: [
- :text_document,
- :completion,
- :completion_item,
- :deprecated_support
- ],
- tags: [
- :text_document,
- :completion,
- :completion_item,
- :tag_support
- ],
- signature_help: [
- :text_document,
- :signature_help
- ],
- work_done_progress: [
- :window,
- :work_done_progress
- ]
- ]
-
- defstruct code_action_dynamic_registration: false,
- hierarchical_symbols: false,
- snippet: false,
- deprecated: false,
- tags: false,
- signature_help: false,
- work_done_progress: false
-
- @type t :: %__MODULE__{}
-
- def new(%ClientCapabilities{} = client_capabilities) do
- defaults =
- for {key, path} <- @client_capability_mapping do
- value = get_in(client_capabilities, path) || false
- {key, value}
- end
-
- struct(__MODULE__, defaults)
- end
-
- def new do
- %__MODULE__{}
- end
-end
diff --git a/apps/server/lib/lexical/server/dialyzer.ex b/apps/server/lib/lexical/server/dialyzer.ex
deleted file mode 100644
index 8723f9b8..00000000
--- a/apps/server/lib/lexical/server/dialyzer.ex
+++ /dev/null
@@ -1,5 +0,0 @@
-defmodule Lexical.Server.Dialyzer do
- def check_support do
- :ok
- end
-end
diff --git a/apps/server/lib/lexical/server/project/diagnostics/state.ex b/apps/server/lib/lexical/server/project/diagnostics/state.ex
deleted file mode 100644
index 4d24450e..00000000
--- a/apps/server/lib/lexical/server/project/diagnostics/state.ex
+++ /dev/null
@@ -1,103 +0,0 @@
-defmodule Lexical.Server.Project.Diagnostics.State do
- defmodule Entry do
- defstruct build_number: 0, diagnostics: []
-
- def new(build_number) when is_integer(build_number) do
- %__MODULE__{build_number: build_number}
- end
-
- def new(build_number, diagnostic) do
- %__MODULE__{build_number: build_number, diagnostics: MapSet.new([diagnostic])}
- end
-
- def add(%__MODULE__{} = entry, build_number, diagnostic) do
- cond do
- build_number < entry.build_number ->
- entry
-
- build_number > entry.build_number ->
- new(build_number, diagnostic)
-
- true ->
- %__MODULE__{entry | diagnostics: MapSet.put(entry.diagnostics, diagnostic)}
- end
- end
-
- def diagnostics(%__MODULE__{} = entry) do
- Enum.to_list(entry.diagnostics)
- end
- end
-
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.Project
-
- defstruct project: nil, entries_by_uri: %{}
-
- require Logger
-
- def new(%Project{} = project) do
- %__MODULE__{project: project}
- end
-
- def get(%__MODULE__{} = state, source_uri) do
- entry = Map.get(state.entries_by_uri, source_uri, Entry.new(0))
- Entry.diagnostics(entry)
- end
-
- def clear(%__MODULE__{} = state, source_uri) do
- new_entries = Map.put(state.entries_by_uri, source_uri, Entry.new(0))
-
- %__MODULE__{state | entries_by_uri: new_entries}
- end
-
- @doc """
- Only clear diagnostics if they've been synced to disk
- It's possible that the diagnostic presented by typing is still correct, and the file
- that exists on the disk is actually an older copy of the file in memory.
- """
- def clear_all_flushed(%__MODULE__{} = state) do
- cleared =
- Map.new(state.entries_by_uri, fn {uri, %Entry{} = entry} ->
- with true <- Document.Store.open?(uri),
- {:ok, %Document{} = document} <- Document.Store.fetch(uri),
- true <- keep_diagnostics?(document) do
- {uri, entry}
- else
- _ ->
- {uri, Entry.new(0)}
- end
- end)
-
- %__MODULE__{state | entries_by_uri: cleared}
- end
-
- def add(%__MODULE__{} = state, build_number, %Diagnostic.Result{} = diagnostic) do
- entries_by_uri =
- Map.update(
- state.entries_by_uri,
- diagnostic.uri,
- Entry.new(build_number, diagnostic),
- fn entry ->
- Entry.add(entry, build_number, diagnostic)
- end
- )
-
- %__MODULE__{state | entries_by_uri: entries_by_uri}
- end
-
- def add(%__MODULE__{} = state, _build_number, other) do
- Logger.error("Invalid diagnostic: #{inspect(other)}")
- state
- end
-
- defp keep_diagnostics?(%Document{} = document) do
- # Keep any diagnostics for script files, which aren't compiled)
- # or dirty files, which have been modified after compilation has occurrend
- document.dirty? or script_file?(document)
- end
-
- defp script_file?(document) do
- Path.extname(document.path) == ".exs"
- end
-end
diff --git a/apps/server/lib/lexical/server/project/progress.ex b/apps/server/lib/lexical/server/project/progress.ex
deleted file mode 100644
index ca05916d..00000000
--- a/apps/server/lib/lexical/server/project/progress.ex
+++ /dev/null
@@ -1,46 +0,0 @@
-defmodule Lexical.Server.Project.Progress do
- alias Lexical.Project
- alias Lexical.Server.Project.Progress.State
-
- import Lexical.RemoteControl.Api.Messages
-
- use GenServer
-
- def start_link(%Project{} = project) do
- GenServer.start_link(__MODULE__, [project], name: name(project))
- end
-
- def child_spec(%Project{} = project) do
- %{
- id: {__MODULE__, Project.name(project)},
- start: {__MODULE__, :start_link, [project]}
- }
- end
-
- # GenServer callbacks
-
- @impl GenServer
- def init([project]) do
- {:ok, State.new(project)}
- end
-
- @impl true
- def handle_info(project_progress(stage: stage) = message, %State{} = state) do
- new_state = apply(State, stage, [state, message])
- {:noreply, new_state}
- end
-
- def handle_info(percent_progress(stage: stage) = message, %State{} = state) do
- new_state = apply(State, stage, [state, message])
-
- {:noreply, new_state}
- end
-
- def name(%Project{} = project) do
- :"#{Project.name(project)}::progress"
- end
-
- def whereis(%Project{} = project) do
- project |> name() |> Process.whereis()
- end
-end
diff --git a/apps/server/lib/lexical/server/project/progress/state.ex b/apps/server/lib/lexical/server/project/progress/state.ex
deleted file mode 100644
index 2e541578..00000000
--- a/apps/server/lib/lexical/server/project/progress/state.ex
+++ /dev/null
@@ -1,106 +0,0 @@
-defmodule Lexical.Server.Project.Progress.State do
- alias Lexical.Project
- alias Lexical.Protocol.Id
- alias Lexical.Protocol.Requests
- alias Lexical.Server.Configuration
- alias Lexical.Server.Project.Progress.Percentage
- alias Lexical.Server.Project.Progress.Value
- alias Lexical.Server.Transport
-
- import Lexical.RemoteControl.Api.Messages
-
- defstruct project: nil, progress_by_label: %{}
-
- def new(%Project{} = project) do
- %__MODULE__{project: project}
- end
-
- def begin(%__MODULE__{} = state, project_progress(label: label)) do
- progress = Value.begin(label)
- progress_by_label = Map.put(state.progress_by_label, label, progress)
-
- write_work_done(progress.token)
- write(progress)
-
- %__MODULE__{state | progress_by_label: progress_by_label}
- end
-
- def begin(%__MODULE__{} = state, percent_progress(label: label, max: max)) do
- progress = Percentage.begin(label, max)
- progress_by_label = Map.put(state.progress_by_label, label, progress)
- write_work_done(progress.token)
- write(progress)
-
- %__MODULE__{state | progress_by_label: progress_by_label}
- end
-
- def report(%__MODULE__{} = state, project_progress(label: label, message: message)) do
- {progress, progress_by_label} =
- Map.get_and_update(state.progress_by_label, label, fn old_value ->
- new_value = Value.report(old_value, message)
- {new_value, new_value}
- end)
-
- write(progress)
- %__MODULE__{state | progress_by_label: progress_by_label}
- end
-
- def report(
- %__MODULE__{} = state,
- percent_progress(label: label, message: message, delta: delta)
- ) do
- {progress, progress_by_label} =
- Map.get_and_update(state.progress_by_label, label, fn old_percentage ->
- new_percentage = Percentage.report(old_percentage, delta, message)
- {new_percentage, new_percentage}
- end)
-
- write(progress)
- %__MODULE__{state | progress_by_label: progress_by_label}
- end
-
- def complete(%__MODULE__{} = state, project_progress(label: label, message: message)) do
- {progress, progress_by_label} =
- Map.get_and_update(state.progress_by_label, label, fn _ -> :pop end)
-
- case progress do
- %Value{} = progress ->
- progress |> Value.complete(message) |> write
-
- _ ->
- :ok
- end
-
- %__MODULE__{state | progress_by_label: progress_by_label}
- end
-
- def complete(%__MODULE__{} = state, percent_progress(label: label, message: message)) do
- {progress, progress_by_label} =
- Map.get_and_update(state.progress_by_label, label, fn _ -> :pop end)
-
- case progress do
- %Percentage{} = progress ->
- progress |> Percentage.complete(message) |> write()
-
- nil ->
- :ok
- end
-
- %__MODULE__{state | progress_by_label: progress_by_label}
- end
-
- defp write_work_done(token) do
- if Configuration.client_supports?(:work_done_progress) do
- progress = Requests.CreateWorkDoneProgress.new(id: Id.next(), token: token)
- Transport.write(progress)
- end
- end
-
- defp write(%progress_module{token: token} = progress) when not is_nil(token) do
- if Configuration.client_supports?(:work_done_progress) do
- progress |> progress_module.to_protocol() |> Transport.write()
- end
- end
-
- defp write(_), do: :ok
-end
diff --git a/apps/server/lib/lexical/server/project/progress/support.ex b/apps/server/lib/lexical/server/project/progress/support.ex
deleted file mode 100644
index cf339b0b..00000000
--- a/apps/server/lib/lexical/server/project/progress/support.ex
+++ /dev/null
@@ -1,42 +0,0 @@
-defmodule Lexical.Server.Project.Progress.Support do
- alias Lexical.Project
- alias Lexical.Server.Project.Progress
-
- import Lexical.RemoteControl.Api.Messages
-
- defmacro __using__(_) do
- quote do
- import unquote(__MODULE__), only: [with_progress: 3]
- end
- end
-
- def with_progress(project, label, func) when is_function(func, 0) do
- dest = Progress.name(project)
-
- try do
- send(dest, project_progress(label: label, stage: :begin))
- func.()
- after
- send(dest, project_progress(label: label, stage: :complete))
- end
- end
-
- def with_percentage_progress(%Project{} = project, label, max, func)
- when is_function(func, 1) do
- dest = Progress.name(project)
-
- report_progress = fn delta, message ->
- message =
- percent_progress(label: label, max: max, message: message, delta: delta, stage: :report)
-
- send(dest, message)
- end
-
- try do
- send(dest, percent_progress(label: label, max: max, stage: :begin))
- func.(report_progress)
- after
- send(dest, percent_progress(label: label, stage: :complete))
- end
- end
-end
diff --git a/apps/server/lib/lexical/server/project/supervisor.ex b/apps/server/lib/lexical/server/project/supervisor.ex
deleted file mode 100644
index 58adf99b..00000000
--- a/apps/server/lib/lexical/server/project/supervisor.ex
+++ /dev/null
@@ -1,53 +0,0 @@
-defmodule Lexical.Server.Project.Supervisor do
- alias Lexical.Project
- alias Lexical.RemoteControl.ProjectNodeSupervisor
- alias Lexical.Server.Project.Diagnostics
- alias Lexical.Server.Project.Intelligence
- alias Lexical.Server.Project.Node
- alias Lexical.Server.Project.Progress
- alias Lexical.Server.Project.SearchListener
-
- use Supervisor
-
- def dynamic_supervisor_name do
- Lexical.Server.ProjectSupervisor
- end
-
- def options do
- [name: dynamic_supervisor_name(), strategy: :one_for_one]
- end
-
- def start_link(%Project{} = project) do
- Supervisor.start_link(__MODULE__, project, name: supervisor_name(project))
- end
-
- def init(%Project{} = project) do
- children = [
- {Progress, project},
- {ProjectNodeSupervisor, project},
- {Node, project},
- {Diagnostics, project},
- {Intelligence, project},
- {SearchListener, project}
- ]
-
- Supervisor.init(children, strategy: :one_for_one)
- end
-
- def start(%Project{} = project) do
- DynamicSupervisor.start_child(dynamic_supervisor_name(), {__MODULE__, project})
- end
-
- def stop(%Project{} = project) do
- pid =
- project
- |> supervisor_name()
- |> Process.whereis()
-
- DynamicSupervisor.terminate_child(dynamic_supervisor_name(), pid)
- end
-
- defp supervisor_name(%Project{} = project) do
- :"#{Project.name(project)}::supervisor"
- end
-end
diff --git a/apps/server/lib/lexical/server/provider/handlers/code_action.ex b/apps/server/lib/lexical/server/provider/handlers/code_action.ex
deleted file mode 100644
index 14ff286a..00000000
--- a/apps/server/lib/lexical/server/provider/handlers/code_action.ex
+++ /dev/null
@@ -1,42 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.CodeAction do
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types
- alias Lexical.Protocol.Types.Workspace
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeAction
- alias Lexical.Server.Configuration
-
- require Logger
-
- def handle(%Requests.CodeAction{} = request, %Configuration{} = config) do
- diagnostics = Enum.map(request.context.diagnostics, &to_code_action_diagnostic/1)
-
- code_actions =
- RemoteControl.Api.code_actions(
- config.project,
- request.document,
- request.range,
- diagnostics,
- request.context.only || :all,
- request.context.trigger_kind
- )
-
- results = Enum.map(code_actions, &to_result/1)
- reply = Responses.CodeAction.new(request.id, results)
-
- {:reply, reply}
- end
-
- defp to_code_action_diagnostic(%Types.Diagnostic{} = diagnostic) do
- CodeAction.Diagnostic.new(diagnostic.range, diagnostic.message, diagnostic.source)
- end
-
- defp to_result(%CodeAction{} = action) do
- Types.CodeAction.new(
- title: action.title,
- kind: action.kind,
- edit: Workspace.Edit.new(changes: %{action.uri => action.changes})
- )
- end
-end
diff --git a/apps/server/lib/lexical/server/provider/handlers/code_lens.ex b/apps/server/lib/lexical/server/provider/handlers/code_lens.ex
deleted file mode 100644
index 62004865..00000000
--- a/apps/server/lib/lexical/server/provider/handlers/code_lens.ex
+++ /dev/null
@@ -1,58 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.CodeLens do
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Document.Range
- alias Lexical.Project
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types.CodeLens
- alias Lexical.RemoteControl
- alias Lexical.Server.Configuration
- alias Lexical.Server.Provider.Handlers
-
- import Document.Line
- require Logger
-
- def handle(%Requests.CodeLens{} = request, %Configuration{} = config) do
- lenses =
- case reindex_lens(config.project, request.document) do
- nil -> []
- lens -> List.wrap(lens)
- end
-
- response = Responses.CodeLens.new(request.id, lenses)
- {:reply, response}
- end
-
- defp reindex_lens(%Project{} = project, %Document{} = document) do
- if show_reindex_lens?(project, document) do
- range = def_project_range(document)
- command = Handlers.Commands.reindex_command(project)
-
- CodeLens.new(command: command, range: range)
- end
- end
-
- @project_regex ~r/def\s+project\s/
- defp def_project_range(%Document{} = document) do
- # returns the line in mix.exs where `def project` occurs
- Enum.reduce_while(document.lines, nil, fn
- line(text: line_text, line_number: line_number), _ ->
- if String.match?(line_text, @project_regex) do
- start_pos = Position.new(document, line_number, 1)
- end_pos = Position.new(document, line_number, String.length(line_text))
- range = Range.new(start_pos, end_pos)
- {:halt, range}
- else
- {:cont, nil}
- end
- end)
- end
-
- defp show_reindex_lens?(%Project{} = project, %Document{} = document) do
- document_path = Path.expand(document.path)
-
- document_path == Project.mix_exs_path(project) and
- not RemoteControl.Api.index_running?(project)
- end
-end
diff --git a/apps/server/lib/lexical/server/provider/handlers/completion.ex b/apps/server/lib/lexical/server/provider/handlers/completion.ex
deleted file mode 100644
index e08115bf..00000000
--- a/apps/server/lib/lexical/server/provider/handlers/completion.ex
+++ /dev/null
@@ -1,37 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.Completion do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types.Completion
- alias Lexical.Server.CodeIntelligence
- alias Lexical.Server.Configuration
-
- require Logger
-
- def handle(%Requests.Completion{} = request, %Configuration{} = config) do
- completions =
- CodeIntelligence.Completion.complete(
- config.project,
- document_analysis(request.document, request.position),
- request.position,
- request.context || Completion.Context.new(trigger_kind: :invoked)
- )
-
- response = Responses.Completion.new(request.id, completions)
- {:reply, response}
- end
-
- defp document_analysis(%Document{} = document, %Position{} = position) do
- case Document.Store.fetch(document.uri, :analysis) do
- {:ok, %Document{}, %Ast.Analysis{} = analysis} ->
- Ast.reanalyze_to(analysis, position)
-
- _ ->
- document
- |> Ast.analyze()
- |> Ast.reanalyze_to(position)
- end
- end
-end
diff --git a/apps/server/lib/lexical/server/provider/handlers/find_references.ex b/apps/server/lib/lexical/server/provider/handlers/find_references.ex
deleted file mode 100644
index a9a788df..00000000
--- a/apps/server/lib/lexical/server/provider/handlers/find_references.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.FindReferences do
- alias Lexical.Ast
- alias Lexical.Document
- alias Lexical.Protocol.Requests.FindReferences
- alias Lexical.Protocol.Responses
- alias Lexical.RemoteControl.Api
- alias Lexical.Server.Configuration
-
- require Logger
-
- def handle(%FindReferences{} = request, %Configuration{} = config) do
- include_declaration? = !!request.context.include_declaration
-
- locations =
- case Document.Store.fetch(request.document.uri, :analysis) do
- {:ok, _document, %Ast.Analysis{} = analysis} ->
- Api.references(config.project, analysis, request.position, include_declaration?)
-
- _ ->
- nil
- end
-
- response = Responses.FindReferences.new(request.id, locations)
- {:reply, response}
- end
-end
diff --git a/apps/server/lib/lexical/server/provider/handlers/formatting.ex b/apps/server/lib/lexical/server/provider/handlers/formatting.ex
deleted file mode 100644
index b250f74e..00000000
--- a/apps/server/lib/lexical/server/provider/handlers/formatting.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.Formatting do
- alias Lexical.Document.Changes
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Responses
- alias Lexical.RemoteControl
- alias Lexical.Server.Configuration
-
- require Logger
-
- def handle(%Requests.Formatting{} = request, %Configuration{} = config) do
- document = request.document
-
- case RemoteControl.Api.format(config.project, document) do
- {:ok, %Changes{} = document_edits} ->
- response = Responses.Formatting.new(request.id, document_edits)
- Logger.info("Response #{inspect(response)}")
- {:reply, response}
-
- {:error, reason} ->
- Logger.error("Formatter failed #{inspect(reason)}")
- {:reply, Responses.Formatting.new(request.id, nil)}
- end
- end
-end
diff --git a/apps/server/lib/lexical/server/provider/handlers/go_to_definition.ex b/apps/server/lib/lexical/server/provider/handlers/go_to_definition.ex
deleted file mode 100644
index 74b8459f..00000000
--- a/apps/server/lib/lexical/server/provider/handlers/go_to_definition.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.GoToDefinition do
- alias Lexical.Protocol.Requests.GoToDefinition
- alias Lexical.Protocol.Responses
- alias Lexical.RemoteControl
- alias Lexical.Server.Configuration
-
- require Logger
-
- def handle(%GoToDefinition{} = request, %Configuration{} = config) do
- case RemoteControl.Api.definition(config.project, request.document, request.position) do
- {:ok, native_location} ->
- {:reply, Responses.GoToDefinition.new(request.id, native_location)}
-
- {:error, reason} ->
- Logger.error("GoToDefinition failed: #{inspect(reason)}")
- {:reply, Responses.GoToDefinition.new(request.id, nil)}
- end
- end
-end
diff --git a/apps/server/lib/lexical/server/provider/handlers/hover.ex b/apps/server/lib/lexical/server/provider/handlers/hover.ex
deleted file mode 100644
index 731261ef..00000000
--- a/apps/server/lib/lexical/server/provider/handlers/hover.ex
+++ /dev/null
@@ -1,237 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.Hover do
- alias Lexical.Ast
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.Document.Position
- alias Lexical.Project
- alias Lexical.Protocol.Requests
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types.Hover
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.CodeIntelligence.Docs
- alias Lexical.Server.Configuration
- alias Lexical.Server.Provider.Markdown
-
- require Logger
-
- def handle(%Requests.Hover{} = request, %Configuration{} = config) do
- maybe_hover =
- with {:ok, _document, %Ast.Analysis{} = analysis} <-
- Document.Store.fetch(request.document.uri, :analysis),
- {:ok, entity, range} <- resolve_entity(config.project, analysis, request.position),
- {:ok, markdown} <- hover_content(entity, config.project) do
- content = Markdown.to_content(markdown)
- %Hover{contents: content, range: range}
- else
- error ->
- Logger.warning("Could not resolve hover request, got: #{inspect(error)}")
- nil
- end
-
- {:reply, Responses.Hover.new(request.id, maybe_hover)}
- end
-
- defp resolve_entity(%Project{} = project, %Analysis{} = analysis, %Position{} = position) do
- RemoteControl.Api.resolve_entity(project, analysis, position)
- end
-
- defp hover_content({kind, module}, %Project{} = project) when kind in [:module, :struct] do
- case RemoteControl.Api.docs(project, module, exclude_hidden: false) do
- {:ok, %Docs{} = module_docs} ->
- header = module_header(kind, module_docs)
- types = module_header_types(kind, module_docs)
-
- additional_sections = [
- module_doc(module_docs.doc),
- module_footer(kind, module_docs)
- ]
-
- if Enum.all?([types | additional_sections], &empty?/1) do
- {:error, :no_doc}
- else
- header_block = "#{header}\n\n#{types}" |> String.trim() |> Markdown.code_block()
- {:ok, Markdown.join_sections([header_block | additional_sections])}
- end
-
- _ ->
- {:error, :no_doc}
- end
- end
-
- defp hover_content({:call, module, fun, arity}, %Project{} = project) do
- with {:ok, %Docs{} = module_docs} <- RemoteControl.Api.docs(project, module),
- {:ok, entries} <- Map.fetch(module_docs.functions_and_macros, fun) do
- sections =
- entries
- |> Enum.sort_by(& &1.arity)
- |> Enum.filter(&(&1.arity >= arity))
- |> Enum.map(&entry_content/1)
-
- {:ok, Markdown.join_sections(sections, Markdown.separator())}
- end
- end
-
- defp hover_content({:type, module, type, arity}, %Project{} = project) do
- with {:ok, %Docs{} = module_docs} <- RemoteControl.Api.docs(project, module),
- {:ok, entries} <- Map.fetch(module_docs.types, type) do
- case Enum.find(entries, &(&1.arity == arity)) do
- %Docs.Entry{} = entry ->
- {:ok, entry_content(entry)}
-
- _ ->
- {:error, :no_type}
- end
- end
- end
-
- defp hover_content(type, _) do
- {:error, {:unsupported, type}}
- end
-
- defp module_header(:module, %Docs{module: module}) do
- Ast.Module.name(module)
- end
-
- defp module_header(:struct, %Docs{module: module}) do
- "%#{Ast.Module.name(module)}{}"
- end
-
- defp module_header_types(:module, %Docs{}), do: ""
-
- defp module_header_types(:struct, %Docs{} = docs) do
- docs.types
- |> Map.get(:t, [])
- |> sort_entries()
- |> Enum.flat_map(& &1.defs)
- |> Enum.join("\n\n")
- end
-
- defp module_doc(s) when is_binary(s), do: s
- defp module_doc(_), do: nil
-
- defp module_footer(:module, docs) do
- callbacks = format_callbacks(docs.callbacks)
-
- unless empty?(callbacks) do
- Markdown.section(callbacks, header: "Callbacks")
- end
- end
-
- defp module_footer(:struct, _docs), do: nil
-
- defp entry_content(%Docs.Entry{kind: fn_or_macro} = entry)
- when fn_or_macro in [:function, :macro] do
- call_header = call_header(entry)
- specs = Enum.map_join(entry.defs, "\n", &("@spec " <> &1))
-
- header =
- [call_header, specs]
- |> Markdown.join_sections()
- |> String.trim()
- |> Markdown.code_block()
-
- Markdown.join_sections([header, entry_doc_content(entry.doc)])
- end
-
- defp entry_content(%Docs.Entry{kind: :type} = entry) do
- header =
- Markdown.code_block("""
- #{call_header(entry)}
-
- #{type_defs(entry)}\
- """)
-
- Markdown.join_sections([header, entry_doc_content(entry.doc)])
- end
-
- @one_line_header_cutoff 50
-
- defp call_header(%Docs.Entry{kind: :type} = entry) do
- module_name = Ast.Module.name(entry.module)
-
- one_line_header = "#{module_name}.#{entry.name}/#{entry.arity}"
-
- two_line_header =
- "#{last_module_name(module_name)}.#{entry.name}/#{entry.arity}\n#{module_name}"
-
- if String.length(one_line_header) >= @one_line_header_cutoff do
- two_line_header
- else
- one_line_header
- end
- end
-
- defp call_header(%Docs.Entry{kind: maybe_macro} = entry) do
- [signature | _] = entry.signature
- module_name = Ast.Module.name(entry.module)
-
- macro_prefix =
- if maybe_macro == :macro do
- "(macro) "
- else
- ""
- end
-
- one_line_header = "#{macro_prefix}#{module_name}.#{signature}"
-
- two_line_header =
- "#{macro_prefix}#{last_module_name(module_name)}.#{signature}\n#{module_name}"
-
- if String.length(one_line_header) >= @one_line_header_cutoff do
- two_line_header
- else
- one_line_header
- end
- end
-
- defp last_module_name(module_name) do
- module_name
- |> String.split(".")
- |> List.last()
- end
-
- defp type_defs(%Docs.Entry{metadata: %{opaque: true}} = entry) do
- Enum.map_join(entry.defs, "\n", fn def ->
- def
- |> String.split("::", parts: 2)
- |> List.first()
- |> String.trim()
- end)
- end
-
- defp type_defs(%Docs.Entry{} = entry) do
- Enum.join(entry.defs, "\n")
- end
-
- defp format_callbacks(callbacks) do
- callbacks
- |> Map.values()
- |> List.flatten()
- |> sort_entries()
- |> Enum.map_join("\n", fn %Docs.Entry{} = entry ->
- header =
- entry.defs
- |> Enum.map_join("\n", &("@callback " <> &1))
- |> Markdown.code_block()
-
- if is_binary(entry.doc) do
- """
- #{header}
- #{entry_doc_content(entry.doc)}
- """
- else
- header
- end
- end)
- end
-
- defp entry_doc_content(s) when is_binary(s), do: String.trim(s)
- defp entry_doc_content(_), do: nil
-
- defp sort_entries(entries) do
- Enum.sort_by(entries, &{&1.name, &1.arity})
- end
-
- defp empty?(empty) when empty in [nil, "", []], do: true
- defp empty?(_), do: false
-end
diff --git a/apps/server/lib/lexical/server/provider/handlers/workspace_symbol.ex b/apps/server/lib/lexical/server/provider/handlers/workspace_symbol.ex
deleted file mode 100644
index 3579eb95..00000000
--- a/apps/server/lib/lexical/server/provider/handlers/workspace_symbol.ex
+++ /dev/null
@@ -1,57 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.WorkspaceSymbol do
- alias Lexical.Protocol.Requests.WorkspaceSymbol
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types.Location
- alias Lexical.Protocol.Types.Symbol.Kind, as: SymbolKind
- alias Lexical.Protocol.Types.Workspace.Symbol
- alias Lexical.RemoteControl.Api
- alias Lexical.RemoteControl.CodeIntelligence.Symbols
- alias Lexical.Server.Configuration
-
- require SymbolKind
-
- require Logger
-
- def handle(%WorkspaceSymbol{} = request, %Configuration{} = config) do
- symbols =
- if String.length(request.query) > 1 do
- config.project
- |> Api.workspace_symbols(request.query)
- |> tap(fn symbols -> Logger.info("syms #{inspect(Enum.take(symbols, 5))}") end)
- |> Enum.map(&to_response/1)
- else
- []
- end
-
- response = Responses.WorkspaceSymbol.new(request.id, symbols)
- {:reply, response}
- end
-
- def to_response(%Symbols.Workspace{} = root) do
- Symbol.new(
- kind: to_kind(root.type),
- location: to_location(root.link),
- name: root.name,
- container_name: root.container_name
- )
- end
-
- defp to_location(%Symbols.Workspace.Link{} = link) do
- Location.new(uri: link.uri, range: link.detail_range)
- end
-
- defp to_kind(:struct), do: :struct
- defp to_kind(:module), do: :module
- defp to_kind({:protocol, _}), do: :module
- defp to_kind({:lx_protocol, _}), do: :module
- defp to_kind(:variable), do: :variable
- defp to_kind({:function, _}), do: :function
- defp to_kind(:module_attribute), do: :constant
- defp to_kind(:ex_unit_test), do: :method
- defp to_kind(:ex_unit_describe), do: :method
- defp to_kind(:ex_unit_setup), do: :method
- defp to_kind(:ex_unit_setup_all), do: :method
- defp to_kind(:type), do: :type_parameter
- defp to_kind(:spec), do: :interface
- defp to_kind(:file), do: :file
-end
diff --git a/apps/server/lib/lexical/server/state.ex b/apps/server/lib/lexical/server/state.ex
deleted file mode 100644
index 932708f4..00000000
--- a/apps/server/lib/lexical/server/state.ex
+++ /dev/null
@@ -1,318 +0,0 @@
-defmodule Lexical.Server.State do
- alias Lexical.Document
- alias Lexical.Protocol.Id
- alias Lexical.Protocol.Notifications
- alias Lexical.Protocol.Notifications.DidChange
- alias Lexical.Protocol.Notifications.DidChangeConfiguration
- alias Lexical.Protocol.Notifications.DidClose
- alias Lexical.Protocol.Notifications.DidOpen
- alias Lexical.Protocol.Notifications.DidSave
- alias Lexical.Protocol.Notifications.Exit
- alias Lexical.Protocol.Notifications.Initialized
- alias Lexical.Protocol.Requests.Initialize
- alias Lexical.Protocol.Requests.RegisterCapability
- alias Lexical.Protocol.Requests.Shutdown
- alias Lexical.Protocol.Responses
- alias Lexical.Protocol.Types
- alias Lexical.Protocol.Types.CodeAction
- alias Lexical.Protocol.Types.CodeLens
- alias Lexical.Protocol.Types.Completion
- alias Lexical.Protocol.Types.DidChangeWatchedFiles
- alias Lexical.Protocol.Types.ExecuteCommand
- alias Lexical.Protocol.Types.FileEvent
- alias Lexical.Protocol.Types.FileSystemWatcher
- alias Lexical.Protocol.Types.Registration
- alias Lexical.Protocol.Types.TextDocument
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Api
- alias Lexical.Server.CodeIntelligence
- alias Lexical.Server.Configuration
- alias Lexical.Server.Project
- alias Lexical.Server.Provider.Handlers
- alias Lexical.Server.Transport
-
- require CodeAction.Kind
- require Logger
-
- import Api.Messages
-
- defstruct configuration: nil,
- initialized?: false,
- shutdown_received?: false,
- in_flight_requests: %{}
-
- @supported_code_actions [
- :quick_fix,
- :refactor,
- :refactor_extract,
- :refactor_inline,
- :refactor_rewrite,
- :source,
- :source_fix_all,
- :source_organize_imports
- ]
-
- def new do
- %__MODULE__{}
- end
-
- def initialize(%__MODULE__{initialized?: false} = state, %Initialize{
- lsp: %Initialize.LSP{} = event
- }) do
- client_name =
- case event.client_info do
- %{name: name} -> name
- _ -> nil
- end
-
- config = Configuration.new(event.root_uri, event.capabilities, client_name)
- new_state = %__MODULE__{state | configuration: config, initialized?: true}
- Logger.info("Starting project at uri #{config.project.root_uri}")
-
- event.id
- |> initialize_result()
- |> Transport.write()
-
- Transport.write(registrations())
-
- Project.Supervisor.start(config.project)
- {:ok, new_state}
- end
-
- def initialize(%__MODULE__{initialized?: true}, %Initialize{}) do
- {:error, :already_initialized}
- end
-
- def in_flight?(%__MODULE__{} = state, request_id) do
- Map.has_key?(state.in_flight_requests, request_id)
- end
-
- def add_request(%__MODULE__{} = state, request, callback) do
- Transport.write(request)
-
- in_flight_requests = Map.put(state.in_flight_requests, request.id, {request, callback})
-
- %__MODULE__{state | in_flight_requests: in_flight_requests}
- end
-
- def finish_request(%__MODULE__{} = state, response) do
- %{"id" => response_id} = response
-
- case Map.pop(state.in_flight_requests, response_id) do
- {{%request_module{} = request, callback}, in_flight_requests} ->
- case request_module.parse_response(response) do
- {:ok, response} ->
- callback.(request, {:ok, response.result})
-
- error ->
- Logger.info("failed to parse response for #{request_module}, #{inspect(error)}")
- callback.(request, error)
- end
-
- %__MODULE__{state | in_flight_requests: in_flight_requests}
-
- _ ->
- state
- end
- end
-
- def default_configuration(%__MODULE__{configuration: config}) do
- Configuration.default(config)
- end
-
- def apply(%__MODULE__{initialized?: false}, request) do
- Logger.error("Received #{request.method} before server was initialized")
- {:error, :not_initialized}
- end
-
- def apply(%__MODULE__{shutdown_received?: true} = state, %Exit{}) do
- Logger.warning("Received an Exit notification. Halting the server in 150ms")
- :timer.apply_after(50, System, :halt, [0])
- {:ok, state}
- end
-
- def apply(%__MODULE__{shutdown_received?: true}, request) do
- Logger.error("Received #{request.method} after shutdown. Ignoring")
- {:error, :shutting_down}
- end
-
- def apply(%__MODULE__{} = state, %DidChangeConfiguration{} = event) do
- case Configuration.on_change(state.configuration, event) do
- {:ok, config} ->
- {:ok, %__MODULE__{state | configuration: config}}
-
- {:ok, config, response} ->
- Transport.write(response)
- {:ok, %__MODULE__{state | configuration: config}}
- end
-
- {:ok, state}
- end
-
- def apply(%__MODULE__{} = state, %DidChange{lsp: event}) do
- uri = event.text_document.uri
- version = event.text_document.version
- project = state.configuration.project
-
- case Document.Store.get_and_update(
- uri,
- &Document.apply_content_changes(&1, version, event.content_changes)
- ) do
- {:ok, updated_source} ->
- updated_message =
- file_changed(
- uri: updated_source.uri,
- open?: true,
- from_version: version,
- to_version: updated_source.version
- )
-
- Api.broadcast(project, updated_message)
- Api.compile_document(state.configuration.project, updated_source)
- {:ok, state}
-
- error ->
- error
- end
- end
-
- def apply(%__MODULE__{} = state, %DidOpen{} = did_open) do
- %TextDocument.Item{
- text: text,
- uri: uri,
- version: version,
- language_id: language_id
- } = did_open.lsp.text_document
-
- case Document.Store.open(uri, text, version, language_id) do
- :ok ->
- Logger.info("opened #{uri}")
- {:ok, state}
-
- error ->
- Logger.error("Could not open #{uri} #{inspect(error)}")
- error
- end
- end
-
- def apply(%__MODULE__{} = state, %DidClose{lsp: event}) do
- uri = event.text_document.uri
-
- case Document.Store.close(uri) do
- :ok ->
- {:ok, state}
-
- error ->
- Logger.warning(
- "Received textDocument/didClose for a file that wasn't open. URI was #{uri}"
- )
-
- error
- end
- end
-
- def apply(%__MODULE__{} = state, %DidSave{lsp: event}) do
- uri = event.text_document.uri
-
- case Document.Store.save(uri) do
- :ok ->
- Api.schedule_compile(state.configuration.project, false)
- {:ok, state}
-
- error ->
- Logger.error("Save failed for uri #{uri} error was #{inspect(error)}")
- error
- end
- end
-
- def apply(%__MODULE__{} = state, %Initialized{}) do
- Logger.info("Lexical Initialized")
- {:ok, %__MODULE__{state | initialized?: true}}
- end
-
- def apply(%__MODULE__{} = state, %Shutdown{} = shutdown) do
- Transport.write(Responses.Shutdown.new(id: shutdown.id))
- Logger.error("Shutting down")
-
- {:ok, %__MODULE__{state | shutdown_received?: true}}
- end
-
- def apply(%__MODULE__{} = state, %Notifications.DidChangeWatchedFiles{lsp: event}) do
- project = state.configuration.project
-
- Enum.each(event.changes, fn %FileEvent{} = change ->
- event = filesystem_event(project: Project, uri: change.uri, event_type: change.type)
- RemoteControl.Api.broadcast(project, event)
- end)
-
- {:ok, state}
- end
-
- def apply(%__MODULE__{} = state, msg) do
- Logger.error("Ignoring unhandled message: #{inspect(msg)}")
- {:ok, state}
- end
-
- defp registrations do
- RegisterCapability.new(id: Id.next(), registrations: [file_watcher_registration()])
- end
-
- @did_changed_watched_files_id "-42"
- @watched_extensions ~w(ex exs)
- defp file_watcher_registration do
- extension_glob = "{" <> Enum.join(@watched_extensions, ",") <> "}"
-
- watchers = [
- FileSystemWatcher.new(glob_pattern: "**/mix.lock"),
- FileSystemWatcher.new(glob_pattern: "**/*.#{extension_glob}")
- ]
-
- Registration.new(
- id: @did_changed_watched_files_id,
- method: "workspace/didChangeWatchedFiles",
- register_options: DidChangeWatchedFiles.Registration.Options.new(watchers: watchers)
- )
- end
-
- def initialize_result(event_id) do
- sync_options =
- TextDocument.Sync.Options.new(open_close: true, change: :incremental, save: true)
-
- code_action_options =
- CodeAction.Options.new(code_action_kinds: @supported_code_actions, resolve_provider: false)
-
- code_lens_options = CodeLens.Options.new(resolve_provider: false)
-
- command_options = ExecuteCommand.Registration.Options.new(commands: Handlers.Commands.names())
-
- completion_options =
- Completion.Options.new(trigger_characters: CodeIntelligence.Completion.trigger_characters())
-
- server_capabilities =
- Types.ServerCapabilities.new(
- code_action_provider: code_action_options,
- code_lens_provider: code_lens_options,
- completion_provider: completion_options,
- definition_provider: true,
- document_formatting_provider: true,
- document_symbol_provider: true,
- execute_command_provider: command_options,
- hover_provider: true,
- references_provider: true,
- text_document_sync: sync_options,
- workspace_symbol_provider: true
- )
-
- result =
- Types.Initialize.Result.new(
- capabilities: server_capabilities,
- server_info:
- Types.Initialize.Result.ServerInfo.new(
- name: "Lexical",
- version: "0.0.1"
- )
- )
-
- Responses.InitializeResult.new(event_id, result)
- end
-end
diff --git a/apps/server/lib/lexical/server/transport.ex b/apps/server/lib/lexical/server/transport.ex
deleted file mode 100644
index a9067283..00000000
--- a/apps/server/lib/lexical/server/transport.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defmodule Lexical.Server.Transport do
- @moduledoc """
- A behaviour for a LSP transport
- """
- @callback write(Jason.Encoder.t()) :: Jason.Encoder.t()
-
- alias Lexical.Server.Transport.StdIO
-
- @implementation Application.compile_env(:server, :transport, StdIO)
-
- defdelegate write(message), to: @implementation
-end
diff --git a/apps/server/mix.exs b/apps/server/mix.exs
deleted file mode 100644
index eff0a4d6..00000000
--- a/apps/server/mix.exs
+++ /dev/null
@@ -1,57 +0,0 @@
-defmodule Lexical.Server.MixProject do
- use Mix.Project
- Code.require_file("../../mix_includes.exs")
-
- def project do
- [
- app: :server,
- version: "0.7.2",
- elixir: "~> 1.15",
- start_permanent: Mix.env() == :prod,
- deps: deps(),
- dialyzer: Mix.Dialyzer.config(add_apps: [:jason, :proto]),
- aliases: aliases(),
- elixirc_paths: elixirc_paths(Mix.env())
- ]
- end
-
- def application do
- [
- extra_applications: [:logger, :runtime_tools, :kernel, :erts],
- mod: {Lexical.Server.Application, []}
- ]
- end
-
- def aliases do
- [
- compile: "compile --docs --debug-info",
- docs: "docs --html",
- test: "test --no-start"
- ]
- end
-
- defp elixirc_paths(:test) do
- ["lib", "test/support"]
- end
-
- defp elixirc_paths(_) do
- ["lib"]
- end
-
- defp deps do
- [
- {:common, path: "../common", env: Mix.env()},
- Mix.Credo.dependency(),
- Mix.Dialyzer.dependency(),
- {:elixir_sense,
- github: "elixir-lsp/elixir_sense", ref: "73ce7e0d239342fb9527d7ba567203e77dbb9b25"},
- {:jason, "~> 1.4"},
- {:logger_file_backend, "~> 0.0", only: [:dev, :prod]},
- {:patch, "~> 0.15", runtime: false, only: [:dev, :test]},
- {:path_glob, "~> 0.2"},
- {:protocol, path: "../protocol", env: Mix.env()},
- {:remote_control, path: "../remote_control", env: Mix.env()},
- {:sourceror, "~> 1.9"}
- ]
- end
-end
diff --git a/apps/server/test/lexical/convertibles/lexical.plugin.diagnostic.result_test.exs b/apps/server/test/lexical/convertibles/lexical.plugin.diagnostic.result_test.exs
deleted file mode 100644
index 4d235aec..00000000
--- a/apps/server/test/lexical/convertibles/lexical.plugin.diagnostic.result_test.exs
+++ /dev/null
@@ -1,106 +0,0 @@
-defmodule Lexical.Convertibles.Lexical.Plugin.V1.Diagnostic.ResultTest do
- alias Lexical.Document
- alias Lexical.Plugin.V1.Diagnostic
- use Lexical.Test.Protocol.ConvertibleSupport
-
- import Lexical.Test.CodeSigil
-
- defp plugin_diagnostic(file_path, position) do
- file_path
- |> Document.Path.ensure_uri()
- |> Diagnostic.Result.new(position, "Broken!", :error, "Elixir")
- end
-
- def open_file_contents do
- ~q[
- defmodule UnderTest do
- def fun_one do
- end
-
- def fun_two do
- "🎸hello"
- end
- end
- ]t
- end
-
- describe "to_lsp/2" do
- setup [:with_an_open_file]
-
- test "it should translate a diagnostic with a line as a position", %{uri: uri} do
- assert {:ok, %Types.Diagnostic{} = converted} = to_lsp(plugin_diagnostic(uri, 1), uri)
-
- assert converted.message == "Broken!"
- assert converted.severity == :error
- assert converted.source == "Elixir"
- assert converted.range == range(:lsp, position(:lsp, 0, 0), position(:lsp, 1, 0))
- end
-
- test "it should translate a diagnostic with a line and a column", %{uri: uri} do
- assert {:ok, %Types.Diagnostic{} = converted} = to_lsp(plugin_diagnostic(uri, {1, 1}), uri)
-
- assert converted.message == "Broken!"
- assert converted.range == range(:lsp, position(:lsp, 0, 0), position(:lsp, 1, 0))
- end
-
- test "it should translate a diagnostic with a four-elements tuple position", %{uri: uri} do
- assert {:ok, %Types.Diagnostic{} = converted} =
- to_lsp(plugin_diagnostic(uri, {2, 5, 2, 8}), uri)
-
- assert converted.message == "Broken!"
- assert converted.range == range(:lsp, position(:lsp, 1, 4), position(:lsp, 1, 7))
-
- assert {:ok, %Types.Diagnostic{} = converted} =
- to_lsp(plugin_diagnostic(uri, {1, 0, 3, 0}), uri)
-
- assert converted.message == "Broken!"
- assert converted.range == range(:lsp, position(:lsp, 0, 0), position(:lsp, 2, 0))
- end
-
- test "it should translate a diagnostic line that is out of bounds (elixir can do this)", %{
- uri: uri
- } do
- assert {:ok, %Types.Diagnostic{} = converted} = to_lsp(plugin_diagnostic(uri, 9), uri)
-
- assert converted.message == "Broken!"
- assert converted.range == range(:lsp, position(:lsp, 7, 0), position(:lsp, 8, 0))
- end
-
- test "it can translate a diagnostic of a file that isn't open", %{uri: uri} do
- assert {:ok, %Types.Diagnostic{}} = to_lsp(plugin_diagnostic(__ENV__.file, 2), uri)
- end
-
- test "it can translate a diagnostic that starts after an emoji", %{uri: uri} do
- assert {:ok, %Types.Diagnostic{} = converted} = to_lsp(plugin_diagnostic(uri, {6, 10}), uri)
-
- assert converted.range == range(:lsp, position(:lsp, 5, 10), position(:lsp, 6, 0))
- end
-
- test "it converts lexical positions", %{uri: uri, document: document} do
- assert {:ok, %Types.Diagnostic{} = converted} =
- to_lsp(plugin_diagnostic(uri, Document.Position.new(document, 1, 1)), uri)
-
- assert converted.range == %Types.Range{
- start: %Types.Position{line: 0, character: 0},
- end: %Types.Position{line: 1, character: 0}
- }
- end
-
- test "it converts lexical ranges", %{uri: uri, document: document} do
- lexical_range =
- Document.Range.new(
- Document.Position.new(document, 2, 5),
- Document.Position.new(document, 2, 8)
- )
-
- assert {:ok, %Types.Diagnostic{} = converted} =
- to_lsp(plugin_diagnostic(uri, lexical_range), uri)
-
- assert %Types.Range{start: start_pos, end: end_pos} = converted.range
- assert start_pos.line == 1
- assert start_pos.character == 4
- assert end_pos.line == 1
- assert end_pos.character == 7
- end
- end
-end
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/module_attribute_test.exs b/apps/server/test/lexical/server/code_intelligence/completion/translations/module_attribute_test.exs
deleted file mode 100644
index 3ae1a9b2..00000000
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/module_attribute_test.exs
+++ /dev/null
@@ -1,283 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.ModuleAttributeTest do
- use Lexical.Test.Server.CompletionCase
-
- describe "module attributes" do
- test "@moduledoc completions", %{project: project} do
- source = ~q[
- defmodule Docs do
- @modu|
- end
- ]
-
- assert [snippet_completion, empty_completion] = complete(project, source)
-
- assert snippet_completion.detail
- assert snippet_completion.label == "@moduledoc"
-
- # note: indentation should be correctly adjusted by editor
- assert apply_completion(snippet_completion) == ~q[
- defmodule Docs do
- @moduledoc """
- $0
- """
- end
- ]
-
- assert empty_completion.detail
- assert empty_completion.label == "@moduledoc false"
-
- assert apply_completion(empty_completion) == ~q[
- defmodule Docs do
- @moduledoc false
- end
- ]
- end
-
- test "@doc completions", %{project: project} do
- source = ~q[
- defmodule MyModule do
- @d|
- def other_thing do
- end
- end
- ]
-
- assert {:ok, [snippet_completion, empty_completion]} =
- project
- |> complete(source)
- |> fetch_completion(kind: :property)
-
- assert snippet_completion.detail
- assert snippet_completion.label == "@doc"
- assert snippet_completion.kind == :property
-
- # note: indentation should be correctly adjusted by editor
- assert apply_completion(snippet_completion) == ~q[
- defmodule MyModule do
- @doc """
- $0
- """
- def other_thing do
- end
- end
- ]
-
- assert empty_completion.detail
- assert empty_completion.label == "@doc false"
- assert empty_completion.kind == :property
-
- assert apply_completion(empty_completion) == ~q[
- defmodule MyModule do
- @doc false
- def other_thing do
- end
- end
- ]
- end
-
- # This is a limitation of ElixirSense, which does not return @doc as
- # a suggestion when the prefix is `@do`. It does for both `@d` and `@doc`.
- @tag :skip
- test "@doc completion with do prefix", %{project: project} do
- source = ~q[
- defmodule MyModule do
- @do|
- def other_thing do
- end
- end
- ]
-
- assert {:ok, [_snippet_completion, empty_completion]} =
- project
- |> complete(source)
- |> fetch_completion(kind: :property)
-
- assert empty_completion.detail
- assert empty_completion.label == "@doc"
- assert empty_completion.kind == :property
-
- assert apply_completion(empty_completion) == ~q[
- defmodule MyModule do
- @doc false
- def other_thing do
- end
- end
- ]
- end
-
- test "local attribute completion with prefix", %{project: project} do
- source = ~q[
- defmodule Attr do
- @my_attribute :foo
- @my_|
- end
- ]
-
- assert [completion] = complete(project, source)
- assert completion.label == "@my_attribute"
-
- assert apply_completion(completion) == ~q[
- defmodule Attr do
- @my_attribute :foo
- @my_attribute
- end
- ]
- end
-
- test "local attribute completion immediately after @", %{project: project} do
- source = ~q[
- defmodule Attr do
- @my_attribute :foo
- @|
- end
- ]
-
- assert {:ok, completion} =
- project
- |> complete(source)
- |> fetch_completion("@my_attribute")
-
- assert completion.label == "@my_attribute"
-
- assert apply_completion(completion) == ~q[
- defmodule Attr do
- @my_attribute :foo
- @my_attribute
- end
- ]
- end
- end
-
- describe "@spec completion" do
- test "with no function following", %{project: project} do
- source = ~q[
- defmodule MyModule do
- @spe|
- end
- ]
-
- assert {:ok, completion} =
- project
- |> complete(source)
- |> fetch_completion("@spec")
-
- assert apply_completion(completion) == ~q[
- defmodule MyModule do
- @spec ${1:function}(${2:term()}) :: ${3:term()}
- def ${1:function}(${4:args}) do
- $0
- end
- end
- ]
- end
-
- test "with a function with args after it", %{project: project} do
- source = ~q[
- defmodule MyModule do
- @spe|
- def my_function(arg1, arg2, arg3) do
- :ok
- end
- end
- ]
-
- assert {:ok, [spec_my_function, spec]} =
- project
- |> complete(source)
- |> fetch_completion(kind: :property)
-
- assert spec_my_function.label == "@spec my_function"
-
- assert apply_completion(spec_my_function) == ~q[
- defmodule MyModule do
- @spec my_function(${1:term()}, ${2:term()}, ${3:term()}) :: ${0:term()}
- def my_function(arg1, arg2, arg3) do
- :ok
- end
- end
- ]
-
- assert spec.label == "@spec"
-
- assert apply_completion(spec) == ~q[
- defmodule MyModule do
- @spec ${1:function}(${2:term()}) :: ${3:term()}
- def ${1:function}(${4:args}) do
- $0
- end
- def my_function(arg1, arg2, arg3) do
- :ok
- end
- end
- ]
- end
-
- test "with a function without args after it", %{project: project} do
- source = ~q[
- defmodule MyModule do
- @spe|
- def my_function do
- :ok
- end
- end
- ]
-
- assert {:ok, [spec_my_function, spec]} =
- project
- |> complete(source)
- |> fetch_completion(kind: :property)
-
- assert spec_my_function.label == "@spec my_function"
-
- assert apply_completion(spec_my_function) == ~q[
- defmodule MyModule do
- @spec my_function() :: ${0:term()}
- def my_function do
- :ok
- end
- end
- ]
-
- assert spec.label == "@spec"
-
- assert apply_completion(spec) == ~q[
- defmodule MyModule do
- @spec ${1:function}(${2:term()}) :: ${3:term()}
- def ${1:function}(${4:args}) do
- $0
- end
- def my_function do
- :ok
- end
- end
- ]
- end
-
- test "with a private function after it", %{project: project} do
- source = ~q[
- defmodule MyModule do
- @spe|
- defp my_function(arg1, arg2, arg3) do
- :ok
- end
- end
- ]
-
- assert {:ok, [spec_my_function, _spec]} =
- project
- |> complete(source)
- |> fetch_completion(kind: :property)
-
- assert spec_my_function.label == "@spec my_function"
-
- assert apply_completion(spec_my_function) == ~q[
- defmodule MyModule do
- @spec my_function(${1:term()}, ${2:term()}, ${3:term()}) :: ${0:term()}
- defp my_function(arg1, arg2, arg3) do
- :ok
- end
- end
- ]
- end
- end
-end
diff --git a/apps/server/test/lexical/server/code_intelligence/completion/translations/variable_test.exs b/apps/server/test/lexical/server/code_intelligence/completion/translations/variable_test.exs
deleted file mode 100644
index ed0b431e..00000000
--- a/apps/server/test/lexical/server/code_intelligence/completion/translations/variable_test.exs
+++ /dev/null
@@ -1,45 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.Completion.Translations.VariableTest do
- use Lexical.Test.Server.CompletionCase
-
- test "variables are completed", %{project: project} do
- source = ~q[
- def my_function do
- stinky = :smelly
- st|
- end
- ]
-
- assert {:ok, completion} =
- project
- |> complete(source)
- |> fetch_completion(kind: :variable)
-
- assert completion.label == "stinky"
- assert completion.detail == "stinky"
-
- assert apply_completion(completion) == ~q[
- def my_function do
- stinky = :smelly
- stinky
- end
- ]
- end
-
- test "all variables are returned", %{project: project} do
- source = ~q[
- def my_function do
- var_1 = 3
- var_2 = 5
- va|
- end
- ]
-
- assert {:ok, [c1, c2]} =
- project
- |> complete(source)
- |> fetch_completion(kind: :variable)
-
- assert c1.label == "var_1"
- assert c2.label == "var_2"
- end
-end
diff --git a/apps/server/test/lexical/server/code_intelligence/completion_test.exs b/apps/server/test/lexical/server/code_intelligence/completion_test.exs
deleted file mode 100644
index 3209f232..00000000
--- a/apps/server/test/lexical/server/code_intelligence/completion_test.exs
+++ /dev/null
@@ -1,281 +0,0 @@
-defmodule Lexical.Server.CodeIntelligence.CompletionTest do
- alias Lexical.Protocol.Types.Completion
- alias Lexical.Protocol.Types.Completion.Item, as: CompletionItem
- alias Lexical.RemoteControl.Completion.Candidate
- alias Lexical.Server.CodeIntelligence.Completion.SortScope
-
- use Lexical.Test.Server.CompletionCase
- use Patch
-
- describe "excluding modules from lexical dependencies" do
- test "lexical modules are removed", %{project: project} do
- assert [] = complete(project, "Lexica|l")
- end
-
- test "Lexical submodules are removed", %{project: project} do
- assert [] = complete(project, "Lexical.RemoteContro|l")
- end
-
- test "Lexical functions are removed", %{project: project} do
- assert [] = complete(project, "Lexical.RemoteControl.|")
- end
-
- test "Dependency modules are removed", %{project: project} do
- assert [] = complete(project, "ElixirSense|")
- end
-
- test "Dependency functions are removed", %{project: project} do
- assert [] = complete(project, "Jason.encod|")
- end
-
- test "Dependency protocols are removed", %{project: project} do
- assert [] = complete(project, "Jason.Encode|")
- end
-
- test "Dependency structs are removed", %{project: project} do
- assert [] = complete(project, "Jason.Fragment|")
- end
-
- test "Dependency exceptions are removed", %{project: project} do
- assert [] = complete(project, "Jason.DecodeErro|")
- end
- end
-
- test "ensure completion works for project", %{project: project} do
- refute [] == complete(project, "Project.|")
- end
-
- describe "single character atom completions" do
- test "complete elixir modules", %{project: project} do
- assert [_ | _] = completions = complete(project, "E|")
-
- for completion <- completions do
- assert completion.kind == :module
- end
- end
-
- test "ignore erlang modules", %{project: project} do
- assert [] == complete(project, ":e|")
- end
- end
-
- describe "ignoring things" do
- test "returns an incomplete completion list when the context is empty", %{project: project} do
- assert %Completion.List{is_incomplete: true, items: []} =
- complete(project, " ", as_list: false)
- end
-
- test "returns no completions in a comment at the beginning of a line", %{project: project} do
- assert [] == complete(project, "# IO.in|")
- end
-
- test "returns no completions in a comment at the end of a line", %{project: project} do
- assert [] == complete(project, "IO.inspe # IO.in|")
- end
-
- test "returns no completions in double quoted strings", %{project: project} do
- assert [] = complete(project, ~S/"IO.in|"/)
- end
-
- test "returns no completions inside heredocs", %{project: project} do
- assert [] = complete(project, ~S/
- """
- This is my heredoc
- It does not IO.in|
- """
- /)
- end
-
- test "returns no completions inside ~s", %{project: project} do
- assert [] = complete(project, ~S/~s[ IO.in|]/)
- end
-
- test "returns no completions inside ~S", %{project: project} do
- assert [] = complete(project, ~S/ ~S[ IO.in|] /)
- end
-
- test "only modules that are behaviuors are completed in an @impl", %{project: project} do
- assert [behaviour] = complete(project, "@impl U|")
- assert behaviour.label == "Unary"
- assert behaviour.kind == :module
- end
- end
-
- describe "do/end" do
- test "returns do/end when the last token is 'do'", %{project: project} do
- assert [completion] = complete(project, "for a <- something do|")
- assert completion.label == "do/end block"
- end
-
- test "returns do/end when the last token is 'd'", %{project: project} do
- assert [completion] = complete(project, "for a <- something d|")
- assert completion.label == "do/end block"
- end
- end
-
- describe "sorting dunder function/macro completions" do
- test "dunder functions are sorted last in their sort scope", %{project: project} do
- {:ok, completion} =
- project
- |> complete("Enum.|")
- |> fetch_completion("__info__")
-
- %CompletionItem{
- sort_text: sort_text
- } = completion
-
- assert sort_text =~ SortScope.remote(false, 9)
- end
-
- test "dunder macros are sorted last in their scope", %{project: project} do
- {:ok, completion} =
- project
- |> complete("Project.__dunder_macro__|")
- |> fetch_completion("__dunder_macro__")
-
- %CompletionItem{
- sort_text: sort_text
- } = completion
-
- assert sort_text =~ SortScope.remote(false, 9)
- end
-
- test "typespecs with no origin are completed", %{project: project} do
- candidate = %Candidate.Typespec{
- argument_names: [],
- metadata: %{builtin: true},
- arity: 0,
- name: "any",
- origin: nil
- }
-
- patch(Lexical.RemoteControl.Api, :complete, [candidate])
-
- [completion] = complete(project, " @type a|")
- assert completion.label == "any()"
- end
-
- test "typespecs with no full_name are completed", %{project: project} do
- candidate = %Candidate.Struct{full_name: nil, metadata: %{}, name: "Struct"}
- patch(Lexical.RemoteControl.Api, :complete, [candidate])
-
- [completion] = complete(project, " %Stru|")
- assert completion.label == "Struct"
- end
- end
-
- def with_all_completion_candidates(_) do
- name = "Foo"
- full_name = "A.B.Foo"
-
- all_completions = [
- %Candidate.Behaviour{name: "#{name}-behaviour", full_name: full_name},
- %Candidate.BitstringOption{name: "#{name}-bitstring", type: "integer"},
- %Candidate.Callback{
- name: "#{name}-callback",
- origin: full_name,
- argument_names: [],
- metadata: %{},
- arity: 0
- },
- %Candidate.Exception{name: "#{name}-exception", full_name: full_name},
- %Candidate.Function{
- name: "my_func",
- origin: full_name,
- argument_names: [],
- metadata: %{},
- arity: 0
- },
- %Candidate.Macro{
- name: "my_macro",
- origin: full_name,
- argument_names: [],
- metadata: %{},
- arity: 0
- },
- %Candidate.MixTask{name: "#{name}-mix-task", full_name: full_name},
- %Candidate.Module{name: "#{name}-module", full_name: full_name},
- %Candidate.Module{name: "#{name}-submodule", full_name: "#{full_name}.Bar"},
- %Candidate.ModuleAttribute{name: "#{name}-module-attribute"},
- %Candidate.Protocol{name: "#{name}-protocol", full_name: full_name},
- %Candidate.Struct{name: "#{name}-struct", full_name: full_name},
- %Candidate.StructField{name: "#{name}-struct-field", origin: full_name},
- %Candidate.Typespec{
- name: "#{name}-typespec",
- origin: full_name,
- argument_names: ["value"],
- arity: 1,
- metadata: %{}
- },
- %Candidate.Variable{name: "#{name}-variable"}
- ]
-
- patch(Lexical.RemoteControl.Api, :complete, all_completions)
- :ok
- end
-
- describe "context aware inclusions and exclusions" do
- setup [:with_all_completion_candidates]
-
- test "only modules and module-like completions are returned in an alias", %{project: project} do
- completions = complete(project, "alias Foo.")
-
- for completion <- complete(project, "alias Foo.") do
- assert %_{kind: :module} = completion
- end
-
- assert {:ok, _} = fetch_completion(completions, label: "Foo-behaviour")
- assert {:ok, _} = fetch_completion(completions, label: "Foo-module")
- assert {:ok, _} = fetch_completion(completions, label: "Foo-protocol")
- assert {:ok, _} = fetch_completion(completions, label: "Foo-struct")
- end
-
- test "only modules, typespecs and module attributes are returned in types", %{
- project: project
- } do
- completions =
- for completion <- complete(project, "@spec F"), into: MapSet.new() do
- completion.label
- end
-
- assert "Foo-module" in completions
- assert "Foo-module-attribute" in completions
- assert "Foo-submodule" in completions
- assert "Foo-typespec(value)" in completions
- assert Enum.count(completions) == 4
- end
-
- test "modules are sorted before functions", %{project: project} do
- code = ~q[
- def in_function do
- Foo.|
- end
- ]
-
- completions =
- project
- |> complete(code)
- |> Enum.sort_by(& &1.sort_text)
-
- module_index = Enum.find_index(completions, &(&1.label == "Foo-module"))
- behaviour_index = Enum.find_index(completions, &(&1.label == "Foo-behaviour"))
- submodule_index = Enum.find_index(completions, &(&1.label == "Foo-submodule"))
-
- function_index = Enum.find_index(completions, &(&1.label == "my_function()"))
- macro_index = Enum.find_index(completions, &(&1.label == "my_macro()"))
- callback_index = Enum.find_index(completions, &(&1.label == "Foo-callback()"))
-
- assert submodule_index < function_index
- assert submodule_index < macro_index
- assert submodule_index < callback_index
-
- assert module_index < function_index
- assert module_index < macro_index
- assert module_index < callback_index
-
- assert behaviour_index < function_index
- assert behaviour_index < macro_index
- assert behaviour_index < callback_index
- end
- end
-end
diff --git a/apps/server/test/lexical/server/project/diagnostics/state_test.exs b/apps/server/test/lexical/server/project/diagnostics/state_test.exs
deleted file mode 100644
index 172c6bb5..00000000
--- a/apps/server/test/lexical/server/project/diagnostics/state_test.exs
+++ /dev/null
@@ -1,167 +0,0 @@
-defmodule Lexical.Project.Diagnostics.StateTest do
- alias Lexical.Document
- alias Lexical.Document.Edit
- alias Lexical.Plugin.V1.Diagnostic
- alias Lexical.Project
- alias Lexical.Server.Project.Diagnostics.State
-
- import Lexical.Test.Fixtures
-
- use Lexical.Test.CodeMod.Case
-
- setup do
- {:ok, _} = start_supervised(Lexical.Document.Store)
-
- project = project()
- state = State.new(project)
- {:ok, project: project(), state: state}
- end
-
- def existing_file_path do
- Path.join([Project.root_path(project()), "lib", "project.ex"])
- end
-
- def document(contents, file_path \\ existing_file_path()) do
- file_uri = Document.Path.to_uri(file_path)
-
- with :ok <- Document.Store.open(file_uri, contents, 0),
- {:ok, document} <- Document.Store.fetch(file_uri) do
- document
- end
- end
-
- def change_with(document, content) do
- changes = [Edit.new(content)]
-
- {:ok, document} =
- Document.Store.get_and_update(
- document.uri,
- &Document.apply_content_changes(&1, 2, changes)
- )
-
- document
- end
-
- def diagnostic(opts \\ []) do
- file_uri =
- opts
- |> Keyword.get(:file, existing_file_path())
- |> Document.Path.ensure_uri()
-
- position = Keyword.get(opts, :position, 1)
- message = Keyword.get(opts, :message, "This file is broken")
- severity = Keyword.get(opts, :severity, :error)
- Diagnostic.Result.new(file_uri, position, message, severity, "Elixir")
- end
-
- describe "add/3" do
- test "allows you to add a diagnostic to a new uri", %{state: state} do
- diagnostic = diagnostic(message: "this code is bad!")
-
- state = State.add(state, 1, diagnostic)
-
- assert [%Diagnostic.Result{}] = State.get(state, diagnostic.uri)
- end
-
- test "allows you to add multiple diagnostics with the same build number", %{state: state} do
- diag_1 = diagnostic(message: "hey!")
- diag_2 = diagnostic(message: "there")
-
- state =
- state
- |> State.add(1, diag_1)
- |> State.add(1, diag_2)
-
- assert [^diag_1, ^diag_2] = State.get(state, diag_1.uri)
- end
-
- test "diagnostics with older build numbers are overwritten", %{state: state} do
- diag_1 = diagnostic(message: "one")
- diag_2 = diagnostic(message: "two")
- diag_3 = diagnostic(message: "three")
-
- state =
- state
- |> State.add(1, diag_1)
- |> State.add(1, diag_2)
- |> State.add(2, diag_3)
-
- assert [^diag_3] = State.get(state, diag_3.uri)
- end
-
- test "duplicate diagnostics are collapsed", %{state: state} do
- diag_1 = diagnostic(message: "dupe")
- diag_2 = diagnostic(message: "two")
- diag_3 = diagnostic(message: "dupe")
-
- state =
- state
- |> State.add(1, diag_1)
- |> State.add(1, diag_2)
- |> State.add(1, diag_3)
-
- assert [^diag_1, ^diag_2] = State.get(state, diag_1.uri)
- end
- end
-
- test "it allows you to add a global diagnostic", %{state: state} do
- diagnostic = diagnostic(message: "This code is awful")
-
- state = State.add(state, 1, diagnostic)
-
- assert [%Diagnostic.Result{}] = State.get(state, diagnostic.uri)
- end
-
- describe "clear_all_flushed/1" do
- test "it should not clear a dirty open file", %{state: state} do
- document =
- "hello"
- |> document()
- |> change_with("hello2")
-
- state = State.add(state, 1, diagnostic(message: "The code is awful"))
-
- old_diagnostics = State.get(state, document.uri)
- state = State.clear_all_flushed(state)
- assert ^old_diagnostics = State.get(state, document.uri)
- end
-
- test "it should not clear a script file even if it is clean", %{
- state: state,
- project: project
- } do
- script_file_path = Path.join([Project.root_path(project), "test", "*.exs"])
- document = document("assert f() == 0", script_file_path)
-
- state = State.add(state, 1, diagnostic(message: "undefined function f/0"))
-
- old_diagnostics = State.get(state, document.uri)
- state = State.clear_all_flushed(state)
- assert ^old_diagnostics = State.get(state, document.uri)
- end
-
- test "it should clear a file's diagnostics if it is just open", %{state: state} do
- document = document("hello")
-
- state = State.add(state, 1, diagnostic(message: "The code is awful"))
-
- state = State.clear_all_flushed(state)
- diagnostics = State.get(state, document.uri)
-
- assert diagnostics == []
- end
-
- test "it should clear a file's diagnostics if it is closed", %{state: state} do
- document = document("hello")
-
- state = State.add(state, 1, diagnostic(message: "The code is awful"))
-
- :ok = Document.Store.close(document.uri)
-
- state = State.clear_all_flushed(state)
- diagnostics = State.get(state, document.uri)
-
- assert diagnostics == []
- end
- end
-end
diff --git a/apps/server/test/lexical/server/project/node_test.exs b/apps/server/test/lexical/server/project/node_test.exs
deleted file mode 100644
index 88da491c..00000000
--- a/apps/server/test/lexical/server/project/node_test.exs
+++ /dev/null
@@ -1,59 +0,0 @@
-defmodule Lexical.Server.Project.NodeTest do
- alias Lexical.RemoteControl
- alias Lexical.Server
- alias Lexical.Server.Project.Node, as: ProjectNode
-
- import Lexical.Test.Fixtures
- import RemoteControl.Api.Messages
-
- use ExUnit.Case
- use Lexical.Test.EventualAssertions
-
- setup do
- project = project()
-
- {:ok, _} = start_supervised({DynamicSupervisor, Server.Project.Supervisor.options()})
- {:ok, _} = start_supervised({Server.Project.Supervisor, project})
-
- :ok = RemoteControl.Api.register_listener(project, self(), [project_compiled()])
-
- {:ok, project: project}
- end
-
- test "the project should be compiled when the node starts" do
- assert_receive project_compiled(), 750
- end
-
- test "remote control is started when the node starts", %{project: project} do
- apps = RemoteControl.call(project, Application, :started_applications)
- app_names = Enum.map(apps, &elem(&1, 0))
- assert :remote_control in app_names
- end
-
- test "the node is restarted when it goes down", %{project: project} do
- node_name = ProjectNode.node_name(project)
- old_pid = node_pid(project)
-
- :ok = RemoteControl.stop(project)
- assert_eventually Node.ping(node_name) == :pong, 1000
-
- new_pid = node_pid(project)
- assert is_pid(new_pid)
- assert new_pid != old_pid
- end
-
- test "the node restarts when the supervisor pid is killed", %{project: project} do
- node_name = ProjectNode.node_name(project)
- supervisor_pid = RemoteControl.call(project, Process, :whereis, [RemoteControl.Supervisor])
-
- assert is_pid(supervisor_pid)
- Process.exit(supervisor_pid, :kill)
- assert_eventually Node.ping(node_name) == :pong, 750
- end
-
- defp node_pid(project) do
- project
- |> RemoteControl.ProjectNode.name()
- |> Process.whereis()
- end
-end
diff --git a/apps/server/test/lexical/server/project/progress/state_test.exs b/apps/server/test/lexical/server/project/progress/state_test.exs
deleted file mode 100644
index bd5aac10..00000000
--- a/apps/server/test/lexical/server/project/progress/state_test.exs
+++ /dev/null
@@ -1,72 +0,0 @@
-defmodule Lexical.Server.Project.Progress.StateTest do
- alias Lexical.Server.Project.Progress.State
- alias Lexical.Server.Project.Progress.Value
-
- import Lexical.RemoteControl.Api.Messages
- import Lexical.Test.Fixtures
-
- use ExUnit.Case, async: true
-
- setup do
- project = project()
- {:ok, project: project}
- end
-
- def progress(label, message \\ nil) do
- project_progress(label: label, message: message)
- end
-
- test "it should be able to add a begin event and put the new token", %{project: project} do
- label = "mix deps.get"
- state = project |> State.new() |> State.begin(progress(label))
-
- assert %Value{token: token, title: ^label, kind: :begin} = state.progress_by_label[label]
- assert token != nil
- end
-
- test "it should be able to add a report event use the begin event token", %{project: project} do
- label = "mix compile"
- state = project |> State.new() |> State.begin(progress(label))
-
- previous_token = state.progress_by_label[label].token
-
- %{progress_by_label: progress_by_label} =
- State.report(state, progress(label, "lib/my_module.ex"))
-
- assert %Value{token: ^previous_token, message: "lib/my_module.ex", kind: :report} =
- progress_by_label[label]
- end
-
- test "clear the token_by_label after received a complete event", %{project: project} do
- state = project |> State.new() |> State.begin(progress("mix compile"))
-
- %{progress_by_label: progress_by_label} =
- State.complete(state, progress("mix compile", "in 2s"))
-
- assert progress_by_label == %{}
- end
-
- test "set the progress value to nil when there is no begin event", %{
- project: project
- } do
- state = project |> State.new() |> State.report(progress("mix compile"))
- assert state.progress_by_label["mix compile"] == nil
- end
-
- test "set the progress value to nil when a complete event received before the report", %{
- project: project
- } do
- label = "mix compile"
-
- state =
- project
- |> State.new()
- |> State.begin(progress(label))
- |> State.complete(progress(label, "in 2s"))
-
- %{progress_by_label: progress_by_label} =
- State.report(state, progress(label, "lib/my_module.ex"))
-
- assert progress_by_label[label] == nil
- end
-end
diff --git a/apps/server/test/lexical/server/project/progress_test.exs b/apps/server/test/lexical/server/project/progress_test.exs
deleted file mode 100644
index 7a08559e..00000000
--- a/apps/server/test/lexical/server/project/progress_test.exs
+++ /dev/null
@@ -1,158 +0,0 @@
-defmodule Lexical.Server.Project.ProgressTest do
- alias Lexical.Protocol.Notifications
- alias Lexical.Protocol.Requests
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl
- alias Lexical.Server.Configuration
- alias Lexical.Server.Project
- alias Lexical.Server.Transport
- alias Lexical.Test.DispatchFake
-
- import Lexical.Test.Fixtures
- import RemoteControl.Api.Messages
-
- use ExUnit.Case
- use Patch
- use DispatchFake
- use Lexical.Test.EventualAssertions
-
- setup do
- project = project()
- pid = start_supervised!({Project.Progress, project})
- DispatchFake.start()
- RemoteControl.Dispatch.register_listener(pid, project_progress())
- RemoteControl.Dispatch.register_listener(pid, percent_progress())
-
- {:ok, project: project}
- end
-
- def percent_begin(project, label, max) do
- message = percent_progress(stage: :begin, label: label, max: max)
- RemoteControl.Api.broadcast(project, message)
- end
-
- defp percent_report(project, label, delta, message \\ nil) do
- message = percent_progress(stage: :report, label: label, message: message, delta: delta)
- RemoteControl.Api.broadcast(project, message)
- end
-
- defp percent_complete(project, label, message) do
- message = percent_progress(stage: :complete, label: label, message: message)
- RemoteControl.Api.broadcast(project, message)
- end
-
- def progress(stage, label, message \\ "") do
- project_progress(label: label, message: message, stage: stage)
- end
-
- def with_patched_transport(_) do
- test = self()
-
- patch(Transport, :write, fn message ->
- send(test, {:transport, message})
- end)
-
- :ok
- end
-
- def with_work_done_progress_support(_) do
- patch(Configuration, :client_supports?, fn :work_done_progress -> true end)
- :ok
- end
-
- describe "report the progress message" do
- setup [:with_patched_transport]
-
- test "it should be able to send the report progress", %{project: project} do
- patch(Configuration, :client_supports?, fn :work_done_progress -> true end)
-
- begin_message = progress(:begin, "mix compile")
- RemoteControl.Api.broadcast(project, begin_message)
-
- assert_receive {:transport, %Requests.CreateWorkDoneProgress{lsp: %{token: token}}}
- assert_receive {:transport, %Notifications.Progress{}}
-
- report_message = progress(:report, "mix compile", "lib/file.ex")
- RemoteControl.Api.broadcast(project, report_message)
- assert_receive {:transport, %Notifications.Progress{lsp: %{token: ^token, value: value}}}
-
- assert value.kind == "report"
- assert value.message == "lib/file.ex"
- assert value.percentage == nil
- assert value.cancellable == nil
- end
-
- test "it should write nothing when the client does not support work done", %{project: project} do
- patch(Configuration, :client_supports?, fn :work_done_progress -> false end)
-
- begin_message = progress(:begin, "mix compile")
- RemoteControl.Api.broadcast(project, begin_message)
-
- refute_receive {:transport, %Requests.CreateWorkDoneProgress{lsp: %{}}}
- end
- end
-
- describe "reporting a percentage progress" do
- setup [:with_patched_transport, :with_work_done_progress_support]
-
- test "it should be able to increment the percentage", %{project: project} do
- percent_begin(project, "indexing", 400)
-
- assert_receive {:transport, %Requests.CreateWorkDoneProgress{lsp: %{token: token}}}
- assert_receive {:transport, %Notifications.Progress{} = progress}
-
- assert progress.lsp.value.kind == "begin"
- assert progress.lsp.value.title == "indexing"
- assert progress.lsp.value.percentage == 0
-
- percent_report(project, "indexing", 100)
-
- assert_receive {:transport, %Notifications.Progress{lsp: %{token: ^token, value: value}}}
- assert value.kind == "report"
- assert value.percentage == 25
- assert value.message == nil
-
- percent_report(project, "indexing", 260, "Almost done")
-
- assert_receive {:transport, %Notifications.Progress{lsp: %{token: ^token, value: value}}}
- assert value.percentage == 90
- assert value.message == "Almost done"
-
- percent_complete(project, "indexing", "Indexing Complete")
-
- assert_receive {:transport, %Notifications.Progress{lsp: %{token: ^token, value: value}}}
- assert value.kind == "end"
- assert value.message == "Indexing Complete"
- end
-
- test "it caps the percentage at 100", %{project: project} do
- percent_begin(project, "indexing", 100)
- percent_report(project, "indexing", 1000)
- assert_receive {:transport, %Notifications.Progress{lsp: %{value: %{kind: "begin"}}}}
- assert_receive {:transport, %Notifications.Progress{lsp: %{value: value}}}
- assert value.kind == "report"
- assert value.percentage == 100
- end
-
- test "it only allows the percentage to grow", %{project: project} do
- percent_begin(project, "indexing", 100)
- assert_receive {:transport, %Notifications.Progress{lsp: %{value: %{kind: "begin"}}}}
-
- percent_report(project, "indexing", 10)
-
- assert_receive {:transport, %Notifications.Progress{lsp: %{value: value}}}
- assert value.kind == "report"
- assert value.percentage == 10
-
- percent_report(project, "indexing", -10)
- assert_receive {:transport, %Notifications.Progress{lsp: %{value: value}}}
- assert value.kind == "report"
- assert value.percentage == 10
-
- percent_report(project, "indexing", 5)
- assert_receive {:transport, %Notifications.Progress{lsp: %{value: value}}}
- assert value.kind == "report"
- assert value.percentage == 15
- end
- end
-end
diff --git a/apps/server/test/lexical/server/provider/handlers/code_lens_test.exs b/apps/server/test/lexical/server/provider/handlers/code_lens_test.exs
deleted file mode 100644
index c6d1e0e3..00000000
--- a/apps/server/test/lexical/server/provider/handlers/code_lens_test.exs
+++ /dev/null
@@ -1,98 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.CodeLensTest do
- alias Lexical.Document
- alias Lexical.Project
- alias Lexical.Proto.Convert
- alias Lexical.Protocol.Requests.CodeLens
- alias Lexical.Protocol.Types
- alias Lexical.RemoteControl
- alias Lexical.Server
- alias Lexical.Server.Provider.Handlers
-
- import Lexical.Test.Protocol.Fixtures.LspProtocol
- import Lexical.RemoteControl.Api.Messages
- import Lexical.Test.Fixtures
- import Lexical.Test.RangeSupport
-
- use ExUnit.Case, async: false
- use Patch
-
- setup_all do
- start_supervised(Document.Store)
- project = project(:umbrella)
-
- start_supervised!({DynamicSupervisor, Server.Project.Supervisor.options()})
- start_supervised!({Server.Project.Supervisor, project})
-
- RemoteControl.Api.register_listener(project, self(), [project_compiled()])
- RemoteControl.Api.schedule_compile(project, true)
-
- assert_receive project_compiled(), 5000
-
- {:ok, project: project}
- end
-
- defp with_indexing_enabled(_) do
- patch(Lexical.RemoteControl.Api, :index_running?, false)
- :ok
- end
-
- defp with_mix_exs(%{project: project}) do
- path = Project.mix_exs_path(project)
- %{uri: Document.Path.ensure_uri(path)}
- end
-
- def build_request(path) do
- uri = Document.Path.ensure_uri(path)
-
- params = [
- text_document: [uri: uri]
- ]
-
- with {:ok, _} <- Document.Store.open_temporary(uri),
- {:ok, req} <- build(CodeLens, params) do
- Convert.to_native(req)
- end
- end
-
- def handle(request, project) do
- config = Server.Configuration.new(project: project)
- Handlers.CodeLens.handle(request, config)
- end
-
- describe "code lens for mix.exs" do
- setup [:with_mix_exs, :with_indexing_enabled]
-
- test "emits a code lens at the project definition", %{project: project, uri: referenced_uri} do
- mix_exs_path = Document.Path.ensure_path(referenced_uri)
- mix_exs = File.read!(mix_exs_path)
-
- {:ok, request} = build_request(mix_exs_path)
- {:reply, %{result: lenses}} = handle(request, project)
-
- assert [%Types.CodeLens{} = code_lens] = lenses
-
- assert extract(mix_exs, code_lens.range) =~ "def project"
- assert code_lens.command == Handlers.Commands.reindex_command(project)
- end
-
- test "does not emit a code lens for a project file", %{project: project} do
- {:ok, request} =
- project
- |> Project.project_path()
- |> Path.join("apps/first/lib/umbrella/first.ex")
- |> build_request()
-
- assert {:reply, %{result: []}} = handle(request, project)
- end
-
- test "does not emite a code lens for an umbrella app's mix.exs", %{project: project} do
- {:ok, request} =
- project
- |> Project.project_path()
- |> Path.join("apps/first/mix.exs")
- |> build_request()
-
- assert {:reply, %{result: []}} = handle(request, project)
- end
- end
-end
diff --git a/apps/server/test/lexical/server/provider/handlers/find_references_test.exs b/apps/server/test/lexical/server/provider/handlers/find_references_test.exs
deleted file mode 100644
index 284db804..00000000
--- a/apps/server/test/lexical/server/provider/handlers/find_references_test.exs
+++ /dev/null
@@ -1,84 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.FindReferencesTest do
- alias Lexical.Ast.Analysis
- alias Lexical.Document
- alias Lexical.Document.Location
- alias Lexical.Proto.Convert
- alias Lexical.Protocol.Requests.FindReferences
- alias Lexical.Protocol.Responses
- alias Lexical.RemoteControl
- alias Lexical.Server
- alias Lexical.Server.Provider.Handlers
-
- import Lexical.Test.Protocol.Fixtures.LspProtocol
- import Lexical.Test.Fixtures
-
- use ExUnit.Case, async: false
- use Patch
-
- setup_all do
- start_supervised(Server.Application.document_store_child_spec())
- :ok
- end
-
- setup do
- project = project(:navigations)
- path = file_path(project, Path.join("lib", "my_definition.ex"))
- uri = Document.Path.ensure_uri(path)
- {:ok, project: project, uri: uri}
- end
-
- def build_request(path, line, char) do
- uri = Document.Path.ensure_uri(path)
-
- params = [
- text_document: [uri: uri],
- position: [line: line, character: char]
- ]
-
- with {:ok, _} <- Document.Store.open_temporary(uri),
- {:ok, req} <- build(FindReferences, params) do
- Convert.to_native(req)
- end
- end
-
- def handle(request, project) do
- config = Server.Configuration.new(project: project)
- Handlers.FindReferences.handle(request, config)
- end
-
- describe "find references" do
- test "returns locations that the entity returns", %{project: project, uri: uri} do
- patch(RemoteControl.Api, :references, fn ^project,
- %Analysis{document: document},
- _position,
- _ ->
- locations = [
- Location.new(
- Document.Range.new(
- Document.Position.new(document, 1, 5),
- Document.Position.new(document, 1, 10)
- ),
- Document.Path.to_uri("/path/to/file.ex")
- )
- ]
-
- locations
- end)
-
- {:ok, request} = build_request(uri, 5, 6)
-
- assert {:reply, %Responses.FindReferences{} = response} = handle(request, project)
- assert [%Location{} = location] = response.result
- assert location.uri =~ "file.ex"
- end
-
- test "returns nothing if the entity can't resolve it", %{project: project, uri: uri} do
- patch(RemoteControl.Api, :references, nil)
-
- {:ok, request} = build_request(uri, 1, 5)
-
- assert {:reply, %Responses.FindReferences{} = response} = handle(request, project)
- assert response.result == nil
- end
- end
-end
diff --git a/apps/server/test/lexical/server/provider/handlers/go_to_definition_test.exs b/apps/server/test/lexical/server/provider/handlers/go_to_definition_test.exs
deleted file mode 100644
index 37cc97b7..00000000
--- a/apps/server/test/lexical/server/provider/handlers/go_to_definition_test.exs
+++ /dev/null
@@ -1,92 +0,0 @@
-defmodule Lexical.Server.Provider.Handlers.GoToDefinitionTest do
- alias Lexical.Document
- alias Lexical.Document.Location
- alias Lexical.Proto.Convert
- alias Lexical.Protocol.Requests.GoToDefinition
- alias Lexical.RemoteControl
- alias Lexical.Server
- alias Lexical.Server.Provider.Handlers
-
- import Lexical.Test.Protocol.Fixtures.LspProtocol
- import Lexical.RemoteControl.Api.Messages
- import Lexical.Test.Fixtures
-
- use ExUnit.Case, async: false
-
- setup_all do
- project = project(:navigations)
-
- start_supervised!(Server.Application.document_store_child_spec())
- start_supervised!({DynamicSupervisor, Server.Project.Supervisor.options()})
- start_supervised!({Server.Project.Supervisor, project})
-
- RemoteControl.Api.register_listener(project, self(), [
- project_compiled(),
- project_index_ready()
- ])
-
- RemoteControl.Api.schedule_compile(project, true)
- assert_receive project_compiled(), 5000
- assert_receive project_index_ready(), 5000
-
- {:ok, project: project}
- end
-
- defp with_referenced_file(%{project: project}) do
- path = file_path(project, Path.join("lib", "my_definition.ex"))
- %{uri: Document.Path.ensure_uri(path)}
- end
-
- def build_request(path, line, char) do
- uri = Document.Path.ensure_uri(path)
-
- params = [
- text_document: [uri: uri],
- position: [line: line, character: char]
- ]
-
- with {:ok, _} <- Document.Store.open_temporary(uri),
- {:ok, req} <- build(GoToDefinition, params) do
- Convert.to_native(req)
- end
- end
-
- def handle(request, project) do
- config = Server.Configuration.new(project: project)
- Handlers.GoToDefinition.handle(request, config)
- end
-
- describe "go to definition" do
- setup [:with_referenced_file]
-
- test "finds user-defined functions", %{project: project, uri: referenced_uri} do
- uses_file_path = file_path(project, Path.join("lib", "uses.ex"))
- {:ok, request} = build_request(uses_file_path, 4, 17)
-
- {:reply, %{result: %Location{} = location}} = handle(request, project)
- assert Location.uri(location) == referenced_uri
- end
-
- test "finds user-defined modules", %{project: project, uri: referenced_uri} do
- uses_file_path = file_path(project, Path.join("lib", "uses.ex"))
- {:ok, request} = build_request(uses_file_path, 4, 4)
-
- {:reply, %{result: %Location{} = location}} = handle(request, project)
- assert Location.uri(location) == referenced_uri
- end
-
- test "does not find built-in functions", %{project: project} do
- uses_file_path = file_path(project, Path.join("lib", "uses.ex"))
- {:ok, request} = build_request(uses_file_path, 8, 7)
-
- {:reply, %{result: nil}} = handle(request, project)
- end
-
- test "does not find built-in modules", %{project: project} do
- uses_file_path = file_path(project, Path.join("lib", "uses.ex"))
- {:ok, request} = build_request(uses_file_path, 8, 4)
-
- {:reply, %{result: nil}} = handle(request, project)
- end
- end
-end
diff --git a/apps/server/test/support/lexical/test/dispatch_fake.ex b/apps/server/test/support/lexical/test/dispatch_fake.ex
deleted file mode 100644
index 9090e416..00000000
--- a/apps/server/test/support/lexical/test/dispatch_fake.ex
+++ /dev/null
@@ -1,27 +0,0 @@
-defmodule Lexical.Test.DispatchFake do
- alias Lexical.RemoteControl
- alias Lexical.RemoteControl.Dispatch
-
- defmacro __using__(_) do
- quote do
- require unquote(__MODULE__)
- end
- end
-
- # This is a macro because patch requires that you're in a unit test, and have a setup block
- # We need to defer the patch macros until we get inside a unit test context, and the macro
- # does that for us.
- defmacro start do
- quote do
- patch(RemoteControl.Api, :register_listener, fn _project, listener_pid, message_types ->
- Dispatch.register_listener(listener_pid, message_types)
- end)
-
- patch(RemoteControl.Api, :broadcast, fn _project, message ->
- Dispatch.broadcast(message)
- end)
-
- start_supervised!(Dispatch)
- end
- end
-end
diff --git a/apps/server/test/support/transport/no_op.ex b/apps/server/test/support/transport/no_op.ex
deleted file mode 100644
index ffd9e88c..00000000
--- a/apps/server/test/support/transport/no_op.ex
+++ /dev/null
@@ -1,5 +0,0 @@
-defmodule Lexical.Test.Transport.NoOp do
- @behaviour Lexical.Server.Transport
-
- def write(_message), do: :ok
-end
diff --git a/flake.nix b/flake.nix
index fcc6165e..ad0d7d8a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -14,7 +14,7 @@
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
flake = {
lib = {
- mkLexical = {erlang}: erlang.callPackage ./nix/lexical.nix {};
+ mkExpert = {erlang}: erlang.callPackage ./nix/expert.nix {};
};
};
@@ -26,7 +26,7 @@
...
}: let
erlang = pkgs.beam.packages.erlang;
- lexical = self.lib.mkLexical {inherit erlang;};
+ expert = self.lib.mkExpert {inherit erlang;};
in {
formatter = pkgs.alejandra;
@@ -47,10 +47,10 @@
};
packages = {
- inherit lexical;
- default = lexical;
+ inherit expert;
+ default = expert;
- __fodHashGen = lexical.mixFodDeps.overrideAttrs (final: curr: {
+ __fodHashGen = expert.mixFodDeps.overrideAttrs (final: curr: {
outputHash = pkgs.lib.fakeSha256;
});
};
diff --git a/integration/Dockerfile b/integration/Dockerfile
index b13624e6..e9e91258 100644
--- a/integration/Dockerfile
+++ b/integration/Dockerfile
@@ -1,7 +1,7 @@
-# Used to build a clean container for testing the Lexical boot sequence.
+# Used to build a clean container for testing the Expert boot sequence.
#
-# Build: docker build -t lx -f integration/Dockerfile .
-# Run: docker run -it lx
+# Build: docker build -t xp -f integration/Dockerfile .
+# Run: docker run -it xp
ARG SYS_ELIXIR_VERSION=1.15.7
ARG SYS_ERLANG_VERSION=26.2.1
@@ -22,7 +22,7 @@ RUN apt-get install -y \
openssl \
libssl-dev
-WORKDIR /lexical
+WORKDIR /expert
COPY integration/boot/set_up_mise.sh integration/boot/set_up_mise.sh
RUN integration/boot/set_up_mise.sh
@@ -33,7 +33,7 @@ RUN integration/boot/set_up_asdf.sh
COPY mix_*.exs .
COPY apps apps
-WORKDIR /lexical/apps/server
+WORKDIR /expert/apps/expert
RUN mix local.hex --force
RUN mix deps.get
diff --git a/integration/README.md b/integration/README.md
index 1765c918..812a1d1b 100644
--- a/integration/README.md
+++ b/integration/README.md
@@ -24,10 +24,10 @@ $ NO_BUILD=1 ./integration/test.sh
### Debugging
-Run the tests with `LX_DEBUG=1` to see the output from the underlying commands:
+Run the tests with `XP_DEBUG=1` to see the output from the underlying commands:
```sh
-$ LX_DEBUG=1 ./integration/test.sh
+$ XP_DEBUG=1 ./integration/test.sh
...
test_find_asdf_directory...
> No version manager detected
diff --git a/integration/build.sh b/integration/build.sh
index 16a52916..6954dfed 100755
--- a/integration/build.sh
+++ b/integration/build.sh
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
set -eo pipefail
-docker build -t lx -f integration/Dockerfile .
+docker build -t xp -f integration/Dockerfile .
diff --git a/integration/test.sh b/integration/test.sh
index 7a786593..9e11b4ba 100755
--- a/integration/test.sh
+++ b/integration/test.sh
@@ -15,14 +15,14 @@ if [ -z "$NO_BUILD" ]; then
"$script_dir"/build.sh
fi
-start_lexical() {
- local command='LX_HALT_AFTER_BOOT=1 _build/dev/package/lexical/bin/start_lexical.sh; exit $?'
+start_expert() {
+ local command='XP_HALT_AFTER_BOOT=1 _build/dev/package/expert/bin/start_expert.sh; exit $?'
if [[ $1 != "" ]]; then
command="$1 && $command"
fi
- docker run -i lx bash -c "$command" 2>&1
+ docker run -i xp bash -c "$command" 2>&1
return $?
}
@@ -36,10 +36,10 @@ run_test() {
log "$test... "
local output
- output=$(start_lexical "$setup")
+ output=$(start_expert "$setup")
local exit_code=$?
- if [[ -n $LX_DEBUG ]]; then
+ if [[ -n $XP_DEBUG ]]; then
log_info "\n$(prefix_lines "> " "$output")"
fi
diff --git a/mix.exs b/mix.exs
index f8a147e5..304e68cd 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,4 +1,4 @@
-defmodule Lexical.LanguageServer.MixProject do
+defmodule Expert.LanguageServer.MixProject do
use Mix.Project
def project do
@@ -9,7 +9,7 @@ defmodule Lexical.LanguageServer.MixProject do
deps: deps(),
aliases: aliases(),
docs: docs(),
- name: "Lexical",
+ name: "Expert",
consolidate_protocols: Mix.env() != :test
]
end
@@ -33,20 +33,20 @@ defmodule Lexical.LanguageServer.MixProject do
),
filter_modules: fn mod_name, _ ->
case Module.split(mod_name) do
- ["Lexical", "Protocol", "Requests" | _] -> true
- ["Lexical", "Protocol", "Notifications" | _] -> true
- ["Lexical", "Protocol", "Responses" | _] -> true
- ["Lexical", "Protocol" | _] -> false
+ ["Expert", "Protocol", "Requests" | _] -> true
+ ["Expert", "Protocol", "Notifications" | _] -> true
+ ["Expert", "Protocol", "Responses" | _] -> true
+ ["Expert", "Protocol" | _] -> false
_ -> true
end
end,
groups_for_modules: [
- Core: ~r/Lexical.^(RemoteControl|Protocol|Server)/,
- "Remote Control": ~r/Lexical.RemoteControl/,
- "Protocol Requests": ~r/Lexical.Protocol.Requests/,
- "Protocol Notifications": ~r/Lexical.Protocol.Notifications/,
- "Protocol Responses": ~r/Lexical.Protocol.Responses/,
- Server: ~r/Lexical.Server/
+ Core: ~r/Expert.^(RemoteControl|Protocol|Server)/,
+ "Remote Control": ~r/Expert.RemoteControl/,
+ "Protocol Requests": ~r/Expert.Protocol.Requests/,
+ "Protocol Notifications": ~r/Expert.Protocol.Notifications/,
+ "Protocol Responses": ~r/Expert.Protocol.Responses/,
+ Server: ~r/Expert.Server/
]
]
end
diff --git a/nix/expert.nix b/nix/expert.nix
new file mode 100644
index 00000000..7fcf4f18
--- /dev/null
+++ b/nix/expert.nix
@@ -0,0 +1,43 @@
+{
+ mixRelease,
+ fetchMixDeps,
+ elixir,
+ writeScript,
+}:
+mixRelease rec {
+ pname = "expert";
+ version = "development";
+
+ src = ./..;
+
+ mixFodDeps = fetchMixDeps {
+ inherit pname;
+ inherit version;
+
+ src = ./..;
+
+ sha256 = builtins.readFile ./hash;
+ };
+
+ installPhase = ''
+ runHook preInstall
+
+ mix do compile --no-deps-check, package --path "$out"
+
+ runHook postInstall
+ '';
+
+ preFixup = let
+ activate_version_manager = writeScript "activate_version_manager.sh" ''
+ true
+ '';
+ in ''
+ substituteInPlace "$out/bin/start_expert.sh" --replace 'elixir_command=' 'elixir_command="${elixir}/bin/"'
+ rm "$out/bin/activate_version_manager.sh"
+ ln -s ${activate_version_manager} "$out/bin/activate_version_manager.sh"
+
+ mv "$out/bin" "$out/binsh"
+
+ makeWrapper "$out/binsh/start_expert.sh" "$out/bin/expert" --set RELEASE_COOKIE expert
+ '';
+}
diff --git a/nix/lexical.nix b/nix/lexical.nix
deleted file mode 100644
index 4a319112..00000000
--- a/nix/lexical.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- mixRelease,
- fetchMixDeps,
- elixir,
- writeScript,
-}:
-mixRelease rec {
- pname = "lexical";
- version = "development";
-
- src = ./..;
-
- mixFodDeps = fetchMixDeps {
- inherit pname;
- inherit version;
-
- src = ./..;
-
- sha256 = builtins.readFile ./hash;
- };
-
- installPhase = ''
- runHook preInstall
-
- mix do compile --no-deps-check, package --path "$out"
-
- runHook postInstall
- '';
-
- preFixup = let
- activate_version_manager = writeScript "activate_version_manager.sh" ''
- true
- '';
- in ''
- substituteInPlace "$out/bin/start_lexical.sh" --replace 'elixir_command=' 'elixir_command="${elixir}/bin/"'
- rm "$out/bin/activate_version_manager.sh"
- ln -s ${activate_version_manager} "$out/bin/activate_version_manager.sh"
-
- mv "$out/bin" "$out/binsh"
-
- makeWrapper "$out/binsh/start_lexical.sh" "$out/bin/lexical" --set RELEASE_COOKIE lexical
- '';
-}
diff --git a/pages/architecture.md b/pages/architecture.md
index c338597a..76d7820f 100644
--- a/pages/architecture.md
+++ b/pages/architecture.md
@@ -18,16 +18,16 @@ Since the `remote_control` app only depends on `common`, `path_glob` and `elixir
## Language Server
-The language server (the `server` app) is the entry point to Lexical. When started by the `start_lexical.sh` command, it sets up a [transport](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server/transport.ex) that [reads JsonRPC from standard input and writes responses to standard output](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server/transport/std_io.ex).
+The language server (the `server` app) is the entry point to Lexical. When started by the `start_lexical.sh` command, it sets up a [transport](https://github.com/lexical-lsp/lexical/blob/main/apps/expert/lib/lexical/server/transport.ex) that [reads JsonRPC from standard input and writes responses to standard output](https://github.com/lexical-lsp/lexical/blob/main/apps/expert/lib/lexical/server/transport/std_io.ex).
-When a message is received, it is parsed into either a [LSP Request](https://github.com/lexical-lsp/lexical/blob/main/apps/protocol/lib/lexical/protocol/requests.ex) or a [LSP Notification](https://github.com/lexical-lsp/lexical/blob/main/apps/protocol/lib/lexical/protocol/notifications.ex) and then it's handed to the [language server](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server.ex) to process.
+When a message is received, it is parsed into either a [LSP Request](https://github.com/lexical-lsp/lexical/blob/main/apps/protocol/lib/lexical/protocol/requests.ex) or a [LSP Notification](https://github.com/lexical-lsp/lexical/blob/main/apps/protocol/lib/lexical/protocol/notifications.ex) and then it's handed to the [language server](https://github.com/lexical-lsp/lexical/blob/main/apps/expert/lib/lexical/server.ex) to process.
-The only messages the [lexical server process](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server.ex) handles directly are those related to the lifecycle of the language server itself:
+The only messages the [lexical server process](https://github.com/lexical-lsp/lexical/blob/main/apps/expert/lib/lexical/server.ex) handles directly are those related to the lifecycle of the language server itself:
- Synchronizing document states.
- Processing LSP configuration changes.
- Performing initialization and shutdown.
-All other messages are delegated to a _Provider Handler_. This delegation is accomplished by the server process adding the request to the [provider queue](https://github.com/lexical-lsp/lexical/blob/main/apps/server/lib/lexical/server/provider/queue.ex). The provider queue asks the `Lexical.Server.Provider.Handlers.for_request/1` function which handler is configured to handle the request, creates a task for the handler and starts it.
+All other messages are delegated to a _Provider Handler_. This delegation is accomplished by the server process adding the request to the [provider queue](https://github.com/lexical-lsp/lexical/blob/main/apps/expert/lib/lexical/server/provider/queue.ex). The provider queue asks the `Expert.Provider.Handlers.for_request/1` function which handler is configured to handle the request, creates a task for the handler and starts it.
-A _Provider Handler_ is just a module that defines a function of arity 2 that takes the request to handle and a `%Lexical.Server.Configuration{}`. These functions can reply to the request, ignore it, or do some other action.
+A _Provider Handler_ is just a module that defines a function of arity 2 that takes the request to handle and a `%Expert.Configuration{}`. These functions can reply to the request, ignore it, or do some other action.
diff --git a/pages/glossary.md b/pages/glossary.md
index 52893e21..3cf46c93 100644
--- a/pages/glossary.md
+++ b/pages/glossary.md
@@ -67,13 +67,13 @@ The `Lexical.Convertible` protocol helps centralize the necessary conversion log
A behaviour responsible for reading, writing, serializing, and deserializing messages between the LSP client and Lexical language server.
-The behaviour is defined in `Lexical.Server.Transport`, with the implementation for stdio in `Lexical.Server.Transport.StdIO`.
+The behaviour is defined in `Expert.Transport`, with the implementation for stdio in `Expert.Transport.StdIO`.
### The Translatable protocol and Translation modules
The `Lexical.Completion.Translatable` protocol specifies how Elixir language constructs (such as behaviour callbacks) are converted into LSP constructs (such as [completion items](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItem)).
-See `Lexical.Server.CodeIntelligence.Completion.Translations` for various implementations.
+See `Expert.CodeIntelligence.Completion.Translations` for various implementations.
### Code Mods