Skip to content

Commit 39d4b2f

Browse files
doorganmhanberg
andauthored
refactor: isolate Engine from Expert (#23)
* refactor: integrate GenLSP * fixup!: fix tests * fixup!: use persistent_term instead of adding .lsp to Project * fixup!: fix dialyzer issues * fixup!: Don't use parenthesis on 0-arity function * fixup!: cleanup credo changes * fixup!: Remove outdated comments * fixup!: use info severity for normal shutdown log * fixup!: cleanup credo changes * refactor: isolate Engine from Expert * refactor: rename project node modules and simplify EngineApi/EngineNode code * chore: Remove build/release tasks * chore: fix unordered aliases --------- Co-authored-by: Mitchell Hanberg <mitch@mitchellhanberg.com>
1 parent 62e5515 commit 39d4b2f

File tree

238 files changed

+1223
-847
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

238 files changed

+1223
-847
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,7 @@ expert_debug
2929
priv/plts
3030

3131
apps/forge/src/future_elixir_parser.erl
32+
33+
.DS_Store
34+
3235
.notes/

apps/engine/.formatter.exs

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,11 @@ current_directory = Path.dirname(__ENV__.file)
33

44
import_deps = [:forge]
55

6-
impossible_to_format = [
7-
Path.join([
8-
current_directory,
9-
"test",
10-
"fixtures",
11-
"compilation_errors",
12-
"lib",
13-
"compilation_errors.ex"
14-
]),
15-
Path.join([current_directory, "test", "fixtures", "parse_errors", "lib", "parse_errors.ex"])
16-
]
17-
186
locals_without_parens = [with_progress: 2, with_progress: 3, defkey: 2, defkey: 3, with_wal: 2]
197

208
[
219
locals_without_parens: locals_without_parens,
2210
export: [locals_without_parens: locals_without_parens],
2311
import_deps: import_deps,
24-
inputs:
25-
Enum.flat_map(
26-
[
27-
Path.join(current_directory, "*.exs"),
28-
Path.join(current_directory, "{lib,test}/**/*.{ex,exs}")
29-
],
30-
&Path.wildcard(&1, match_dot: true)
31-
) -- impossible_to_format
12+
inputs: ["*.exs", "{lib,test}/**/*.{ex,exs}"]
3213
]

apps/engine/lib/engine/engine.ex

Lines changed: 9 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@ defmodule Engine do
88
alias Engine.Api.Proxy
99
alias Engine.CodeAction
1010
alias Engine.CodeIntelligence
11-
alias Engine.ProjectNode
1211
alias Forge.Project
1312

14-
alias Mix.Tasks.Namespace
15-
1613
require Logger
1714

1815
@excluded_apps [:patch, :nimble_parsec]
@@ -67,21 +64,19 @@ defmodule Engine do
6764

6865
def list_apps do
6966
for {app, _, _} <- :application.loaded_applications(),
70-
not Namespace.Module.prefixed?(app),
67+
not Forge.Namespace.Module.prefixed?(app),
7168
do: app
7269
end
7370

74-
def start_link(%Project{} = project) do
75-
:ok = ensure_epmd_started()
76-
start_net_kernel(project)
77-
78-
apps_to_start = [:elixir | @allowed_apps] ++ [:runtime_tools]
79-
node = Project.node_name(project)
71+
def ensure_apps_started do
72+
apps_to_start = [:elixir, :runtime_tools | @allowed_apps]
8073

81-
with {:ok, node_pid} <- ProjectNode.start(project, glob_paths()),
82-
:ok <- ensure_apps_started(node, apps_to_start) do
83-
{:ok, node, node_pid}
84-
end
74+
Enum.reduce_while(apps_to_start, :ok, fn app_name, _ ->
75+
case :application.ensure_all_started(app_name) do
76+
{:ok, _} -> {:cont, :ok}
77+
error -> {:halt, error}
78+
end
79+
end)
8580
end
8681

8782
def deps_paths do
@@ -116,140 +111,4 @@ defmodule Engine do
116111
def set_project(%Project{} = project) do
117112
:persistent_term.put({__MODULE__, :project}, project)
118113
end
119-
120-
defdelegate stop(project), to: ProjectNode
121-
122-
def call(%Project{} = project, m, f, a \\ []) do
123-
project
124-
|> Project.node_name()
125-
|> :erpc.call(m, f, a)
126-
end
127-
128-
def manager_node_name(%Project{} = project) do
129-
:"manager-#{Project.name(project)}-#{Project.entropy(project)}@127.0.0.1"
130-
end
131-
132-
defp start_net_kernel(%Project{} = project) do
133-
manager = manager_node_name(project)
134-
:net_kernel.start(manager, %{name_domain: :longnames})
135-
end
136-
137-
defp ensure_apps_started(node, app_names) do
138-
Enum.reduce_while(app_names, :ok, fn app_name, _ ->
139-
case :rpc.call(node, :application, :ensure_all_started, [app_name]) do
140-
{:ok, _} -> {:cont, :ok}
141-
error -> {:halt, error}
142-
end
143-
end)
144-
end
145-
146-
defp glob_paths do
147-
for entry <- :code.get_path(),
148-
entry_string = List.to_string(entry),
149-
entry_string != ".",
150-
Enum.any?(app_globs(), &PathGlob.match?(entry_string, &1, match_dot: true)) do
151-
entry
152-
end
153-
end
154-
155-
def elixir_executable(%Project{} = project) do
156-
root_path = Project.root_path(project)
157-
158-
{path_result, env} =
159-
with nil <- version_manager_path_and_env("asdf", root_path),
160-
nil <- version_manager_path_and_env("mise", root_path),
161-
nil <- version_manager_path_and_env("rtx", root_path) do
162-
{File.cd!(root_path, fn -> System.find_executable("elixir") end), System.get_env()}
163-
end
164-
165-
case path_result do
166-
nil ->
167-
{:error, :no_elixir}
168-
169-
executable when is_binary(executable) ->
170-
{:ok, executable, env}
171-
end
172-
end
173-
174-
defp app_globs do
175-
app_globs = Enum.map(@allowed_apps, fn app_name -> "/**/#{app_name}*/ebin" end)
176-
["/**/priv" | app_globs]
177-
end
178-
179-
defp ensure_epmd_started do
180-
case System.cmd("epmd", ~w(-daemon)) do
181-
{"", 0} ->
182-
:ok
183-
184-
_ ->
185-
{:error, :epmd_failed}
186-
end
187-
end
188-
189-
defp version_manager_path_and_env(manager, root_path) do
190-
with true <- is_binary(System.find_executable(manager)),
191-
env = reset_env(manager, root_path),
192-
{path, 0} <- System.cmd(manager, ~w(which elixir), cd: root_path, env: env) do
193-
{String.trim(path), env}
194-
else
195-
_ ->
196-
nil
197-
end
198-
end
199-
200-
# We launch expert by asking the version managers to provide an environment,
201-
# which contains path munging. This initial environment is present in the running
202-
# VM, and needs to be undone so we can find the correct elixir executable in the project.
203-
defp reset_env("asdf", _root_path) do
204-
orig_path = System.get_env("PATH_SAVE", System.get_env("PATH"))
205-
206-
Enum.map(System.get_env(), fn
207-
{"ASDF_ELIXIR_VERSION", _} -> {"ASDF_ELIXIR_VERSION", nil}
208-
{"ASDF_ERLANG_VERSION", _} -> {"ASDF_ERLANG_VERSION", nil}
209-
{"PATH", _} -> {"PATH", orig_path}
210-
other -> other
211-
end)
212-
end
213-
214-
defp reset_env("rtx", root_path) do
215-
{env, _} = System.cmd("rtx", ~w(env -s bash), cd: root_path)
216-
217-
env
218-
|> String.trim()
219-
|> String.split("\n")
220-
|> Enum.map(fn
221-
"export " <> key_and_value ->
222-
[key, value] =
223-
key_and_value
224-
|> String.split("=", parts: 2)
225-
|> Enum.map(&String.trim/1)
226-
227-
{key, value}
228-
229-
_ ->
230-
nil
231-
end)
232-
|> Enum.reject(&is_nil/1)
233-
end
234-
235-
defp reset_env("mise", root_path) do
236-
{env, _} = System.cmd("mise", ~w(env -s bash), cd: root_path)
237-
238-
env
239-
|> String.trim()
240-
|> String.split("\n")
241-
|> Enum.map(fn
242-
"export " <> key_and_value ->
243-
[key, value] =
244-
key_and_value
245-
|> String.split("=", parts: 2)
246-
|> Enum.map(&String.trim/1)
247-
248-
{key, value}
249-
250-
_ ->
251-
nil
252-
end)
253-
|> Enum.reject(&is_nil/1)
254-
end
255114
end

apps/engine/lib/engine/engine/api/proxy.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ defmodule Engine.Api.Proxy do
3333
alias Forge.Document
3434
alias Forge.Document.Changes
3535

36-
alias Engine.Api.Messages
3736
alias Engine.Api.Proxy.BufferingState
3837
alias Engine.Api.Proxy.DrainingState
3938
alias Engine.Api.Proxy.ProxyingState
4039
alias Engine.Api.Proxy.Records
4140
alias Engine.CodeMod
4241
alias Engine.Commands
42+
alias Forge.EngineApi.Messages
4343

4444
import Messages
4545
import Record

apps/engine/lib/engine/engine/api/proxy/buffering_state.ex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
defmodule Engine.Api.Proxy.BufferingState do
22
alias Forge.Document
33

4-
alias Engine.Api
54
alias Engine.Build
65
alias Engine.Commands
76

8-
import Api.Messages
9-
import Api.Proxy.Records
7+
import Forge.EngineApi.Messages
8+
import Engine.Api.Proxy.Records
109

1110
defstruct initiator_pid: nil, buffer: []
1211

apps/engine/lib/engine/engine/build.ex

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ defmodule Engine.Build do
44

55
alias Engine.Build.Document.Compilers.HEEx
66
alias Engine.Build.State
7-
alias Forge.VM.Versions
87

98
require Logger
109
use GenServer
@@ -13,15 +12,6 @@ defmodule Engine.Build do
1312

1413
# Public interface
1514

16-
def path(%Project{} = project) do
17-
%{elixir: elixir, erlang: erlang} = Versions.current()
18-
erlang_major = erlang |> String.split(".") |> List.first()
19-
elixir_version = Version.parse!(elixir)
20-
elixir_major = "#{elixir_version.major}.#{elixir_version.minor}"
21-
build_root = Project.build_path(project)
22-
Path.join([build_root, "erl-#{erlang_major}", "elixir-#{elixir_major}"])
23-
end
24-
2515
def schedule_compile(%Project{} = _project, force? \\ false) do
2616
GenServer.cast(__MODULE__, {:compile, force?})
2717
end

apps/engine/lib/engine/engine/build/state.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
defmodule Engine.Build.State do
22
alias Elixir.Features
3-
alias Engine.Api.Messages
43
alias Engine.Build
54
alias Engine.Plugin
65
alias Forge.Document
6+
alias Forge.EngineApi.Messages
77
alias Forge.Project
88
alias Forge.VM.Versions
99

@@ -60,7 +60,7 @@ defmodule Engine.Build.State do
6060
# If the project directory isn't there, for some reason the main build fails, so we create it here
6161
# to ensure that the build will succeed.
6262
project = state.project
63-
build_path = Engine.Build.path(project)
63+
build_path = Project.versioned_build_path(project)
6464

6565
unless Versions.compatible?(build_path) do
6666
Logger.info("Build path #{build_path} was compiled on a previous erlang version. Deleting")

apps/engine/lib/engine/engine/code_action.ex

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,9 @@
11
defmodule Engine.CodeAction do
2-
alias Engine.CodeAction.Diagnostic
32
alias Engine.CodeAction.Handlers
3+
alias Forge.CodeAction.Diagnostic
44
alias Forge.Document
5-
alias Forge.Document.Changes
65
alias Forge.Document.Range
76

8-
require Logger
9-
10-
defstruct [:title, :kind, :changes, :uri]
11-
12-
@type code_action_kind :: GenLSP.Enumerations.CodeActionKind.t()
13-
14-
@type trigger_kind :: GenLSP.Enumerations.CodeActionTriggerKind.t()
15-
16-
@type t :: %__MODULE__{
17-
title: String.t(),
18-
kind: code_action_kind,
19-
changes: Changes.t(),
20-
uri: Forge.uri()
21-
}
22-
237
@handlers [
248
Handlers.ReplaceRemoteFunction,
259
Handlers.ReplaceWithUnderscore,
@@ -29,18 +13,13 @@ defmodule Engine.CodeAction do
2913
Handlers.Refactorex
3014
]
3115

32-
@spec new(Forge.uri(), String.t(), code_action_kind(), Changes.t()) :: t()
33-
def new(uri, title, kind, changes) do
34-
%__MODULE__{uri: uri, title: title, changes: changes, kind: kind}
35-
end
36-
3716
@spec for_range(
3817
Document.t(),
3918
Range.t(),
4019
[Diagnostic.t()],
41-
[code_action_kind] | :all,
42-
trigger_kind
43-
) :: [t()]
20+
[Forge.CodeAction.code_action_kind()] | :all,
21+
Forge.CodeAction.trigger_kind()
22+
) :: [Forge.CodeAction.t()]
4423
def for_range(%Document{} = doc, %Range{} = range, diagnostics, kinds, trigger_kind) do
4524
Enum.flat_map(@handlers, fn handler ->
4625
if handle_kinds?(handler, kinds) and handle_trigger_kind?(handler, trigger_kind) do

apps/engine/lib/engine/engine/code_action/handler.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
defmodule Engine.CodeAction.Handler do
2-
alias Engine.CodeAction
3-
alias Engine.CodeAction.Diagnostic
2+
alias Forge.CodeAction
3+
alias Forge.CodeAction.Diagnostic
44
alias Forge.Document
55
alias Forge.Document.Range
66

apps/engine/lib/engine/engine/code_action/handlers/add_alias.ex

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ defmodule Engine.CodeAction.Handlers.AddAlias do
55
alias Engine.CodeMod
66
alias Engine.Modules
77
alias Engine.Search.Fuzzy
8-
alias Engine.Search.Indexer.Entry
98
alias Forge.Ast
109
alias Forge.Ast.Analysis
1110
alias Forge.Ast.Analysis.Alias
@@ -14,8 +13,8 @@ defmodule Engine.CodeAction.Handlers.AddAlias do
1413
alias Forge.Document.Position
1514
alias Forge.Document.Range
1615
alias Forge.Formats
16+
alias Forge.Search.Indexer.Entry
1717
alias GenLSP.Enumerations.CodeActionKind
18-
alias Mix.Tasks.Namespace
1918
alias Sourceror.Zipper
2019

2120
@behaviour CodeAction.Handler
@@ -66,7 +65,7 @@ defmodule Engine.CodeAction.Handlers.AddAlias do
6665

6766
changes = Changes.new(analysis.document, replace_current_alias ++ alias_edits)
6867

69-
CodeAction.new(
68+
Forge.CodeAction.new(
7069
analysis.document.uri,
7170
"alias #{Formats.module(potential_alias_module)}",
7271
CodeActionKind.quick_fix(),
@@ -189,7 +188,7 @@ defmodule Engine.CodeAction.Handlers.AddAlias do
189188

190189
for {mod, _, _} <- all_modules(),
191190
elixir_module?(mod),
192-
not Namespace.Module.prefixed?(mod) do
191+
not Forge.Namespace.Module.prefixed?(mod) do
193192
module_name = List.to_atom(mod)
194193

195194
%Entry{

0 commit comments

Comments
 (0)