Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ expert_debug
priv/plts

apps/forge/src/future_elixir_parser.erl

.DS_Store

.notes/
21 changes: 1 addition & 20 deletions apps/engine/.formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,11 @@ 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
inputs: ["*.exs", "{lib,test}/**/*.{ex,exs}"]
]
159 changes: 9 additions & 150 deletions apps/engine/lib/engine/engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ defmodule Engine do
alias Engine.Api.Proxy
alias Engine.CodeAction
alias Engine.CodeIntelligence
alias Engine.ProjectNode
alias Forge.Project

alias Mix.Tasks.Namespace

require Logger

@excluded_apps [:patch, :nimble_parsec]
Expand Down Expand Up @@ -67,21 +64,19 @@ defmodule Engine do

def list_apps do
for {app, _, _} <- :application.loaded_applications(),
not Namespace.Module.prefixed?(app),
not Forge.Namespace.Module.prefixed?(app),
do: app
end

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)
def ensure_apps_started do
apps_to_start = [:elixir, :runtime_tools | @allowed_apps]

with {:ok, node_pid} <- ProjectNode.start(project, glob_paths()),
:ok <- ensure_apps_started(node, apps_to_start) do
{:ok, node, node_pid}
end
Enum.reduce_while(apps_to_start, :ok, fn app_name, _ ->
case :application.ensure_all_started(app_name) do
{:ok, _} -> {:cont, :ok}
error -> {:halt, error}
end
end)
end

def deps_paths do
Expand Down Expand Up @@ -116,140 +111,4 @@ defmodule Engine do
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
2 changes: 1 addition & 1 deletion apps/engine/lib/engine/engine/api/proxy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ defmodule Engine.Api.Proxy do
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
alias Forge.EngineApi.Messages

import Messages
import Record
Expand Down
5 changes: 2 additions & 3 deletions apps/engine/lib/engine/engine/api/proxy/buffering_state.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
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
import Forge.EngineApi.Messages
import Engine.Api.Proxy.Records

defstruct initiator_pid: nil, buffer: []

Expand Down
10 changes: 0 additions & 10 deletions apps/engine/lib/engine/engine/build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ defmodule Engine.Build do

alias Engine.Build.Document.Compilers.HEEx
alias Engine.Build.State
alias Forge.VM.Versions

require Logger
use GenServer
Expand All @@ -13,15 +12,6 @@ defmodule Engine.Build do

# Public interface

def path(%Project{} = project) do
%{elixir: elixir, erlang: erlang} = Versions.current()
erlang_major = erlang |> String.split(".") |> List.first()
elixir_version = Version.parse!(elixir)
elixir_major = "#{elixir_version.major}.#{elixir_version.minor}"
build_root = Project.build_path(project)
Path.join([build_root, "erl-#{erlang_major}", "elixir-#{elixir_major}"])
end

def schedule_compile(%Project{} = _project, force? \\ false) do
GenServer.cast(__MODULE__, {:compile, force?})
end
Expand Down
4 changes: 2 additions & 2 deletions apps/engine/lib/engine/engine/build/state.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
defmodule Engine.Build.State do
alias Elixir.Features
alias Engine.Api.Messages
alias Engine.Build
alias Engine.Plugin
alias Forge.Document
alias Forge.EngineApi.Messages
alias Forge.Project
alias Forge.VM.Versions

Expand Down Expand Up @@ -60,7 +60,7 @@ defmodule Engine.Build.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)
build_path = Project.versioned_build_path(project)

unless Versions.compatible?(build_path) do
Logger.info("Build path #{build_path} was compiled on a previous erlang version. Deleting")
Expand Down
29 changes: 4 additions & 25 deletions apps/engine/lib/engine/engine/code_action.ex
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
defmodule Engine.CodeAction do
alias Engine.CodeAction.Diagnostic
alias Engine.CodeAction.Handlers
alias Forge.CodeAction.Diagnostic
alias Forge.Document
alias Forge.Document.Changes
alias Forge.Document.Range

require Logger

defstruct [:title, :kind, :changes, :uri]

@type code_action_kind :: GenLSP.Enumerations.CodeActionKind.t()

@type trigger_kind :: GenLSP.Enumerations.CodeActionTriggerKind.t()

@type t :: %__MODULE__{
title: String.t(),
kind: code_action_kind,
changes: Changes.t(),
uri: Forge.uri()
}

@handlers [
Handlers.ReplaceRemoteFunction,
Handlers.ReplaceWithUnderscore,
Expand All @@ -29,18 +13,13 @@ defmodule Engine.CodeAction do
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()]
[Forge.CodeAction.code_action_kind()] | :all,
Forge.CodeAction.trigger_kind()
) :: [Forge.CodeAction.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
Expand Down
4 changes: 2 additions & 2 deletions apps/engine/lib/engine/engine/code_action/handler.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule Engine.CodeAction.Handler do
alias Engine.CodeAction
alias Engine.CodeAction.Diagnostic
alias Forge.CodeAction
alias Forge.CodeAction.Diagnostic
alias Forge.Document
alias Forge.Document.Range

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule Engine.CodeAction.Handlers.AddAlias do
alias Engine.CodeMod
alias Engine.Modules
alias Engine.Search.Fuzzy
alias Engine.Search.Indexer.Entry
alias Forge.Ast
alias Forge.Ast.Analysis
alias Forge.Ast.Analysis.Alias
Expand All @@ -14,8 +13,8 @@ defmodule Engine.CodeAction.Handlers.AddAlias do
alias Forge.Document.Position
alias Forge.Document.Range
alias Forge.Formats
alias Forge.Search.Indexer.Entry
alias GenLSP.Enumerations.CodeActionKind
alias Mix.Tasks.Namespace
alias Sourceror.Zipper

@behaviour CodeAction.Handler
Expand Down Expand Up @@ -66,7 +65,7 @@ defmodule Engine.CodeAction.Handlers.AddAlias do

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

CodeAction.new(
Forge.CodeAction.new(
analysis.document.uri,
"alias #{Formats.module(potential_alias_module)}",
CodeActionKind.quick_fix(),
Expand Down Expand Up @@ -189,7 +188,7 @@ defmodule Engine.CodeAction.Handlers.AddAlias do

for {mod, _, _} <- all_modules(),
elixir_module?(mod),
not Namespace.Module.prefixed?(mod) do
not Forge.Namespace.Module.prefixed?(mod) do
module_name = List.to_atom(mod)

%Entry{
Expand Down
Loading
Loading