diff --git a/apps/remote_control/lib/lexical/remote_control/build.ex b/apps/remote_control/lib/lexical/remote_control/build.ex index f398f56da..6872fbf16 100644 --- a/apps/remote_control/lib/lexical/remote_control/build.ex +++ b/apps/remote_control/lib/lexical/remote_control/build.ex @@ -2,6 +2,7 @@ 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 @@ -26,7 +27,8 @@ defmodule Lexical.RemoteControl.Build do end def compile_document(%Project{} = project, %Document{} = document) do - unless Path.absname(document.path) == "mix.exs" do + with false <- Path.absname(document.path) == "mix.exs", + false <- HEEx.recognizes?(document.language_id) do RemoteControl.call(project, GenServer, :cast, [__MODULE__, {:compile_file, document}]) end @@ -35,7 +37,8 @@ defmodule Lexical.RemoteControl.Build do # this is for testing def force_compile_document(%Project{} = project, %Document{} = document) do - unless Path.absname(document.path) == "mix.exs" do + with false <- Path.absname(document.path) == "mix.exs", + false <- HEEx.recognizes?(document.language_id) do RemoteControl.call(project, GenServer, :call, [ __MODULE__, {:force_compile_file, document} diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex index 21a3bd05f..243ef2249 100644 --- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex +++ b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex @@ -10,14 +10,14 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.EEx do @behaviour Compiler - def recognizes?(%Document{} = document) do - Path.extname(document.path) == ".eex" - end + @impl true + def recognizes?(%Document{language_id: "eex"}), do: true + def recognizes?(_), do: false - def enabled? do - true - end + @impl true + def enabled?, do: true + @impl true def compile(%Document{} = document) do with {:ok, quoted} <- eex_to_quoted(document), :ok <- eval_quoted(document, quoted) do diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/elixir.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/elixir.ex index 43927ff48..36a62d074 100644 --- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/elixir.ex +++ b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/elixir.ex @@ -9,16 +9,15 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.Elixir do alias Lexical.RemoteControl.Build.Document.Compilers @behaviour Build.Document.Compiler - @valid_extensions ~w(.ex .exs) - def recognizes?(%Document{} = doc) do - Path.extname(doc.path) in @valid_extensions - end + @impl true + def recognizes?(%Document{language_id: "elixir"}), do: true + def recognizes?(_), do: false - def enabled? do - true - end + @impl true + def enabled?, do: true + @impl true def compile(%Document{} = document) do case to_quoted(document) do {:ok, quoted} -> diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex index f41c33494..69a0338b2 100644 --- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex +++ b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex @@ -10,9 +10,9 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do @behaviour Compiler - def recognizes?(%Document{} = document) do - Path.extname(document.path) == ".heex" - end + def recognizes?(%Document{language_id: "phoenix-heex"}), do: true + def recognizes?(%Document{language_id: "heex"}), do: true + def recognizes?(_), do: false def enabled? do true diff --git a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_remote_function.ex b/apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_remote_function.ex index 3ae80997c..3c5db2326 100644 --- a/apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_remote_function.ex +++ b/apps/remote_control/lib/lexical/remote_control/code_action/handlers/replace_remote_function.ex @@ -93,7 +93,7 @@ defmodule Lexical.RemoteControl.CodeAction.Handlers.ReplaceRemoteFunction do end end - @function_re ~r/(warning: )?([^\/]+)\/(.*) is undefined or private. Did you mean:.*/ + @function_re ~r/(warning: |function )?([^\/]+)\/(.*) is undefined or private. Did you mean:.*/ defp extract_function(message) do result = with [[_, _, module_and_function, arity]] <- Regex.scan(@function_re, message), diff --git a/apps/server/lib/lexical/server/state.ex b/apps/server/lib/lexical/server/state.ex index 54785f26e..d0e9c9fa3 100644 --- a/apps/server/lib/lexical/server/state.ex +++ b/apps/server/lib/lexical/server/state.ex @@ -170,17 +170,21 @@ defmodule Lexical.Server.State do end end - def apply(%__MODULE__{} = state, %DidOpen{lsp: event}) do - %TextDocument.Item{text: text, uri: uri, version: version} = - text_document = event.text_document - - case Document.Store.open(uri, text, version) do + def apply(%__MODULE__{} = state, %DidOpen{} = did_open) do + %TextDocument.Item{ + text: text, + uri: uri, + version: version, + language_id: language_id + } = did_open.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 #{text_document.uri} #{inspect(error)}") + Logger.error("Could not open #{uri} #{inspect(error)}") error end end diff --git a/projects/lexical_shared/lib/lexical/document.ex b/projects/lexical_shared/lib/lexical/document.ex index cb42cab88..c3afc64ab 100644 --- a/projects/lexical_shared/lib/lexical/document.ex +++ b/projects/lexical_shared/lib/lexical/document.ex @@ -16,16 +16,19 @@ defmodule Lexical.Document do import Lexical.Document.Line + require Logger + alias __MODULE__.Path, as: DocumentPath @derive {Inspect, only: [:path, :version, :dirty?, :lines]} - defstruct [:uri, :path, :version, dirty?: false, lines: nil] + 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(), @@ -41,14 +44,18 @@ defmodule Lexical.Document do as a binary and the vewrsion. """ @spec new(Lexical.path() | Lexical.uri(), String.t(), version()) :: t - def new(maybe_uri, text, version) do + def new(maybe_uri, text, version, language_id \\ nil) do uri = DocumentPath.ensure_uri(maybe_uri) + path = DocumentPath.from_uri(uri) + + language_id = language_id || language_id_from_path(path) %__MODULE__{ uri: uri, version: version, lines: Lines.new(text), - path: DocumentPath.from_uri(uri) + path: path, + language_id: language_id } end @@ -219,6 +226,28 @@ defmodule Lexical.Document do |> 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" + + ".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 diff --git a/projects/lexical_shared/lib/lexical/document/store.ex b/projects/lexical_shared/lib/lexical/document/store.ex index cf5bf392f..f438859e9 100644 --- a/projects/lexical_shared/lib/lexical/document/store.ex +++ b/projects/lexical_shared/lib/lexical/document/store.ex @@ -91,24 +91,25 @@ defmodule Lexical.Document.Store do end end - @spec open(t, Lexical.uri(), String.t(), pos_integer()) :: {:ok, t} | {:error, :already_open} - def open(%__MODULE__{temporary_open_refs: refs} = store, uri, text, version) + @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) + open(store, uri, text, version, language_id) end - def open(%__MODULE__{} = store, uri, text, version) do + def open(%__MODULE__{} = store, uri, text, version, language_id) do case store.open do %{^uri => _} -> {:error, :already_open} _ -> - document = Document.new(uri, text, version) + document = Document.new(uri, text, version, language_id) store = put_open_doc(store, document) {:ok, store} end @@ -256,9 +257,10 @@ defmodule Lexical.Document.Store do GenServer.call(name(), {:open?, uri}) end - @spec open(Lexical.uri(), String.t(), pos_integer()) :: :ok | {:error, :already_open} - def open(uri, text, version) do - GenServer.call(name(), {:open, uri, text, version}) + @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()) :: @@ -308,9 +310,9 @@ defmodule Lexical.Document.Store do {:reply, reply, new_state} end - def handle_call({:open, uri, text, version}, _from, %State{} = state) do + def handle_call({:open, uri, text, version, language_id}, _from, %State{} = state) do {reply, new_state} = - case State.open(state, uri, text, version) do + case State.open(state, uri, text, version, language_id) do {:ok, _} = success -> success error -> {error, state} end