From a6ec0c30e2f342d507f3033a7ddffeda71c6277f Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:08:22 +0200 Subject: [PATCH 01/14] do not error when no mixfile --- apps/language_server/lib/language_server/build.ex | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/language_server/lib/language_server/build.ex b/apps/language_server/lib/language_server/build.ex index ee26a6a6c..05e6d6e32 100644 --- a/apps/language_server/lib/language_server/build.ex +++ b/apps/language_server/lib/language_server/build.ex @@ -37,6 +37,9 @@ defmodule ElixirLS.LanguageServer.Build do {:error, mixfile_diagnostics} -> Server.build_finished(parent, {:error, mixfile_diagnostics}) + + :no_mixfile -> + Server.build_finished(parent, {:ok, []}) end end) @@ -165,10 +168,10 @@ defmodule ElixirLS.LanguageServer.Build do "No mixfile found in project. " <> "To use a subdirectory, set `elixirLS.projectDir` in your settings" - JsonRpc.log_message(:error, msg <> ". Looked for mixfile at #{inspect(mixfile)}") - JsonRpc.show_message(:error, msg) + JsonRpc.log_message(:info, msg <> ". Looked for mixfile at #{inspect(mixfile)}") + JsonRpc.show_message(:info, msg) - {:error, [mixfile_diagnostic({Path.absname(mixfile), nil, msg}, :error)]} + :no_mixfile end end From b28762cb7cc7bb0b7ffa37e0a1006df5bcff755c Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:08:53 +0200 Subject: [PATCH 02/14] add tests --- apps/language_server/test/server_test.exs | 80 ++++++++++++++--------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 2d11899d6..3e34d13f6 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -1046,36 +1046,6 @@ defmodule ElixirLS.LanguageServer.ServerTest do end) end - @tag :fixture - test "reports errors if no mixfile", %{server: server} do - in_fixture(__DIR__, "no_mixfile", fn -> - mixfile_uri = SourceFile.path_to_uri("mix.exs") - - initialize(server) - - assert_receive notification("textDocument/publishDiagnostics", %{ - "uri" => ^mixfile_uri, - "diagnostics" => [ - %{ - "message" => "No mixfile found" <> _, - "severity" => 1 - } - ] - }), - 5000 - - assert_receive notification("window/logMessage", %{ - "message" => "No mixfile found in project." <> _ - }) - - assert_receive notification("window/showMessage", %{ - "message" => "No mixfile found in project." <> _ - }) - - wait_until_compiled(server) - end) - end - @tag :fixture test "finds references in non-umbrella project", %{server: server} do in_fixture(__DIR__, "references", fn -> @@ -1422,6 +1392,56 @@ defmodule ElixirLS.LanguageServer.ServerTest do end) end + describe "no mix project" do + @tag :fixture + test "dir with no mix.exs", %{server: server} do + in_fixture(__DIR__, "no_mixfile", fn -> + mixfile_uri = SourceFile.path_to_uri("mix.exs") + + initialize(server) + + assert_receive notification("window/logMessage", %{ + "message" => "No mixfile found in project." <> _ + }), 1000 + + assert_receive notification("window/showMessage", %{ + "message" => "No mixfile found in project." <> _ + }) + + assert_receive notification("window/logMessage", %{ + "message" => "Compile took" <> _ + }) + + wait_until_compiled(server) + end) + end + + @tag :fixture + test "single file", %{server: server} do + in_fixture(__DIR__, "no_mixfile", fn -> + mixfile_uri = SourceFile.path_to_uri("mix.exs") + + IO.inspect root_uri() + + initialize(server, root_uri() <> "/a.exs") + + assert_receive notification("window/logMessage", %{ + "message" => "No mixfile found in project." <> _ + }), 1000 + + assert_receive notification("window/showMessage", %{ + "message" => "No mixfile found in project." <> _ + }) + + assert_receive notification("window/logMessage", %{ + "message" => "Compile took" <> _ + }) + + wait_until_compiled(server) + end) + end + end + defp with_new_server(func) do server = start_supervised!({Server, nil}) packet_capture = start_supervised!({PacketCapture, self()}) From 8efb8fff39cb8c23df52b20a0e420146585f79c5 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:09:18 +0200 Subject: [PATCH 03/14] do not warn when no mixfile --- apps/language_server/lib/language_server/server.ex | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 95d082f68..2578a7677 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -1163,11 +1163,6 @@ defmodule ElixirLS.LanguageServer.Server do end defp create_gitignore(state) do - JsonRpc.log_message( - :warning, - "Cannot create .elixir_ls/.gitignore, cause: project_dir not set" - ) - state end From 85dd47a78da5d7c65f7dca44c5f22bd9ee96dfe5 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:10:07 +0200 Subject: [PATCH 04/14] assert project dir is set when building --- apps/language_server/lib/language_server/server.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 2578a7677..1b6e3bacd 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -894,12 +894,12 @@ defmodule ElixirLS.LanguageServer.Server do # Build - defp trigger_build(state) do + defp trigger_build(state = %__MODULE__{project_dir: project_dir}) when is_binary(project_dir) do if build_enabled?(state) and not state.build_running? do fetch_deps? = Map.get(state.settings || %{}, "fetchDeps", true) {_pid, build_ref} = - Build.build(self(), state.project_dir, + Build.build(self(), project_dir, fetch_deps?: fetch_deps?, load_all_modules?: state.load_all_modules? ) From 9c87248c0dfbd862457d8c8ecea09561640355ef Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:18:28 +0200 Subject: [PATCH 05/14] pattern match module type --- .../lib/language_server/server.ex | 138 +++++++++--------- 1 file changed, 70 insertions(+), 68 deletions(-) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 1b6e3bacd..314a50602 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -108,7 +108,7 @@ defmodule ElixirLS.LanguageServer.Server do end @impl GenServer - def handle_call({:request_finished, id, result}, _from, state) do + def handle_call({:request_finished, id, result}, _from, state = %__MODULE__{}) do case result do {:error, type, msg} -> JsonRpc.respond_with_error(id, type, msg) {:ok, result} -> JsonRpc.respond(id, result) @@ -119,7 +119,7 @@ defmodule ElixirLS.LanguageServer.Server do end @impl GenServer - def handle_call({:suggest_contracts, uri = "file:" <> _}, from, state) do + def handle_call({:suggest_contracts, uri = "file:" <> _}, from, state = %__MODULE__{}) do case state do %{analysis_ready?: true, source_files: %{^uri => %{dirty?: false}}} -> abs_path = @@ -140,42 +140,42 @@ defmodule ElixirLS.LanguageServer.Server do end end - def handle_call({:suggest_contracts, _uri}, _from, state) do + def handle_call({:suggest_contracts, _uri}, _from, state = %__MODULE__{}) do {:reply, [], state} end @impl GenServer - def handle_cast({:build_finished, {status, diagnostics}}, state) + def handle_cast({:build_finished, {status, diagnostics}}, state = %__MODULE__{}) when status in [:ok, :noop, :error] and is_list(diagnostics) do {:noreply, handle_build_result(status, diagnostics, state)} end @impl GenServer - def handle_cast({:dialyzer_finished, diagnostics, build_ref}, state) do + def handle_cast({:dialyzer_finished, diagnostics, build_ref}, state = %__MODULE__{}) do {:noreply, handle_dialyzer_result(diagnostics, build_ref, state)} end @impl GenServer - def handle_cast({:receive_packet, request(id, _, _) = packet}, state) do + def handle_cast({:receive_packet, request(id, _, _) = packet}, state = %__MODULE__{}) do {:noreply, handle_request_packet(id, packet, state)} end @impl GenServer - def handle_cast({:receive_packet, request(id, method)}, state) do + def handle_cast({:receive_packet, request(id, method)}, state = %__MODULE__{}) do {:noreply, handle_request_packet(id, request(id, method, nil), state)} end @impl GenServer def handle_cast( {:receive_packet, notification(_) = packet}, - state = %{received_shutdown?: false, server_instance_id: server_instance_id} + state = %__MODULE__{received_shutdown?: false, server_instance_id: server_instance_id} ) when is_initialized(server_instance_id) do {:noreply, handle_notification(packet, state)} end @impl GenServer - def handle_cast({:receive_packet, notification(_) = packet}, state) do + def handle_cast({:receive_packet, notification(_) = packet}, state = %__MODULE__{}) do case packet do notification("exit") -> {:noreply, handle_notification(packet, state)} @@ -186,12 +186,12 @@ defmodule ElixirLS.LanguageServer.Server do end @impl GenServer - def handle_cast(:rebuild, state) do + def handle_cast(:rebuild, state = %__MODULE__{}) do {:noreply, trigger_build(state)} end @impl GenServer - def handle_info(:default_config, state) do + def handle_info(:default_config, state = %__MODULE__{}) do state = case state do %{settings: nil} -> @@ -211,7 +211,7 @@ defmodule ElixirLS.LanguageServer.Server do end @impl GenServer - def handle_info({:DOWN, ref, _, _pid, reason}, %{build_ref: ref, build_running?: true} = state) do + def handle_info({:DOWN, ref, _, _pid, reason}, %__MODULE__{build_ref: ref, build_running?: true} = state) do state = %{state | build_running?: false} state = @@ -229,7 +229,7 @@ defmodule ElixirLS.LanguageServer.Server do end @impl GenServer - def handle_info({:DOWN, _ref, :process, pid, reason}, %{requests: requests} = state) do + def handle_info({:DOWN, _ref, :process, pid, reason}, %__MODULE__{requests: requests} = state) do state = case Enum.find(requests, &match?({_, ^pid}, &1)) do {id, _} -> @@ -246,7 +246,7 @@ defmodule ElixirLS.LanguageServer.Server do ## Helpers - defp handle_notification(notification("initialized"), state) do + defp handle_notification(notification("initialized"), state = %__MODULE__{}) do # If we don't receive workspace/didChangeConfiguration for 5 seconds, use default settings Process.send_after(self(), :default_config, 5000) @@ -273,7 +273,7 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp handle_notification(cancel_request(id), %{requests: requests} = state) do + defp handle_notification(cancel_request(id), %__MODULE__{requests: requests} = state) do case requests do %{^id => pid} -> Process.exit(pid, :cancelled) @@ -293,7 +293,7 @@ defmodule ElixirLS.LanguageServer.Server do # We don't start performing builds until we receive settings from the client in case they've set # the `projectDir` or `mixEnv` settings. If the settings don't match the format expected, leave # settings unchanged or set default settings if this is the first request. - defp handle_notification(did_change_configuration(changed_settings), state) do + defp handle_notification(did_change_configuration(changed_settings), state = %__MODULE__{}) do prev_settings = state.settings || %{} new_settings = @@ -308,7 +308,7 @@ defmodule ElixirLS.LanguageServer.Server do set_settings(state, new_settings) end - defp handle_notification(notification("exit"), state) do + defp handle_notification(notification("exit"), state = %__MODULE__{}) do code = if state.received_shutdown?, do: 0, else: 1 unless Application.get_env(:language_server, :test_mode) do @@ -320,7 +320,7 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp handle_notification(did_open(uri, _language_id, version, text), state) do + defp handle_notification(did_open(uri, _language_id, version, text), state = %__MODULE__{}) do if Map.has_key?(state.source_files, uri) do # An open notification must not be sent more than once without a corresponding # close notification send before @@ -343,7 +343,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp handle_notification(did_close(uri), state) do + defp handle_notification(did_close(uri), state = %__MODULE__{}) do if not Map.has_key?(state.source_files, uri) do # A close notification requires a previous open notification to be sent JsonRpc.log_message( @@ -363,7 +363,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp handle_notification(did_change(uri, version, content_changes), state) do + defp handle_notification(did_change(uri, version, content_changes), state = %__MODULE__{}) do if not Map.has_key?(state.source_files, uri) do # The source file was not marked as open either due to a bug in the # client or a restart of the server. So just ignore the message and do @@ -382,7 +382,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp handle_notification(did_save(uri), state) do + defp handle_notification(did_save(uri), state = %__MODULE__{}) do if not Map.has_key?(state.source_files, uri) do JsonRpc.log_message( :warning, @@ -397,7 +397,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp handle_notification(did_change_watched_files(changes), state) do + defp handle_notification(did_change_watched_files(changes), state = %__MODULE__{}) do changes = Enum.filter(changes, &match?(%{"uri" => "file:" <> _}, &1)) needs_build = @@ -449,17 +449,17 @@ defmodule ElixirLS.LanguageServer.Server do if needs_build, do: trigger_build(state), else: state end - defp handle_notification(%{"method" => "$/" <> _}, state) do + defp handle_notification(%{"method" => "$/" <> _}, state = %__MODULE__{}) do # not supported "$/" notifications may be safely ignored state end - defp handle_notification(packet, state) do + defp handle_notification(packet, state = %__MODULE__{}) do JsonRpc.log_message(:warning, "Received unmatched notification: #{inspect(packet)}") state end - defp handle_request_packet(id, packet, state = %{server_instance_id: server_instance_id}) + defp handle_request_packet(id, packet, state = %__MODULE__{server_instance_id: server_instance_id}) when not is_initialized(server_instance_id) do case packet do initialize_req(_id, _root_uri, _client_capabilities) -> @@ -473,7 +473,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp handle_request_packet(id, packet, state = %{received_shutdown?: false}) do + defp handle_request_packet(id, packet, state = %__MODULE__{received_shutdown?: false}) do case handle_request(packet, state) do {:ok, result, state} -> JsonRpc.respond(id, result) @@ -493,14 +493,14 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp handle_request_packet(id, _packet, state) do + defp handle_request_packet(id, _packet, state = %__MODULE__{}) do JsonRpc.respond_with_error(id, :invalid_request) state end defp handle_request( initialize_req(_id, root_uri, client_capabilities), - state = %{server_instance_id: server_instance_id} + state = %__MODULE__{server_instance_id: server_instance_id} ) when not is_initialized(server_instance_id) do show_version_warnings() @@ -544,11 +544,11 @@ defmodule ElixirLS.LanguageServer.Server do }, state} end - defp handle_request(request(_id, "shutdown", _params), state) do + defp handle_request(request(_id, "shutdown", _params), state = %__MODULE__{}) do {:ok, nil, %{state | received_shutdown?: true}} end - defp handle_request(definition_req(_id, uri, line, character), state) do + defp handle_request(definition_req(_id, uri, line, character), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> @@ -558,7 +558,7 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(implementation_req(_id, uri, line, character), state) do + defp handle_request(implementation_req(_id, uri, line, character), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> @@ -568,7 +568,7 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(references_req(_id, uri, line, character, include_declaration), state) do + defp handle_request(references_req(_id, uri, line, character, include_declaration), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> @@ -585,7 +585,7 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(hover_req(_id, uri, line, character), state) do + defp handle_request(hover_req(_id, uri, line, character), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> @@ -595,7 +595,7 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(document_symbol_req(_id, uri), state) do + defp handle_request(document_symbol_req(_id, uri), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> @@ -616,7 +616,7 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(workspace_symbol_req(_id, query), state) do + defp handle_request(workspace_symbol_req(_id, query), state = %__MODULE__{}) do fun = fn -> WorkspaceSymbols.symbols(query) end @@ -624,7 +624,7 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(completion_req(_id, uri, line, character), state) do + defp handle_request(completion_req(_id, uri, line, character), state = %__MODULE__{}) do source_file = get_source_file(state, uri) snippets_supported = @@ -681,19 +681,19 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(formatting_req(_id, uri, _options), state) do + defp handle_request(formatting_req(_id, uri, _options), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> Formatting.format(source_file, uri, state.project_dir) end {:async, fun, state} end - defp handle_request(signature_help_req(_id, uri, line, character), state) do + defp handle_request(signature_help_req(_id, uri, line, character), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> SignatureHelp.signature(source_file, line, character) end {:async, fun, state} end - defp handle_request(on_type_formatting_req(_id, uri, line, character, ch, options), state) do + defp handle_request(on_type_formatting_req(_id, uri, line, character, ch, options), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> @@ -703,7 +703,7 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(code_lens_req(_id, uri), state) do + defp handle_request(code_lens_req(_id, uri), state = %__MODULE__{}) do source_file = get_source_file(state, uri) fun = fn -> @@ -725,7 +725,7 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(execute_command_req(_id, command, args) = req, state) do + defp handle_request(execute_command_req(_id, command, args) = req, state = %__MODULE__{}) do {:async, fn -> case ExecuteCommand.execute(command, args, state) do @@ -739,7 +739,7 @@ defmodule ElixirLS.LanguageServer.Server do end, state} end - defp handle_request(folding_range_req(_id, uri), state) do + defp handle_request(folding_range_req(_id, uri), state = %__MODULE__{}) do case get_source_file(state, uri) do nil -> {:error, :server_error, "Missing source file", state} @@ -751,7 +751,7 @@ defmodule ElixirLS.LanguageServer.Server do end # TODO remove in ElixirLS 0.8 - defp handle_request(macro_expansion(_id, whole_buffer, selected_macro, macro_line), state) do + defp handle_request(macro_expansion(_id, whole_buffer, selected_macro, macro_line), state = %__MODULE__{}) do IO.warn( "Custom `elixirDocument/macroExpansion` request is deprecated. Switch to command `executeMacro` via `workspace/executeCommand`" ) @@ -760,12 +760,12 @@ defmodule ElixirLS.LanguageServer.Server do {:ok, x, state} end - defp handle_request(%{"method" => "$/" <> _}, state) do + defp handle_request(%{"method" => "$/" <> _}, state = %__MODULE__{}) do # "$/" requests that the server doesn't support must return method_not_found {:error, :method_not_found, nil, state} end - defp handle_request(req, state) do + defp handle_request(req, state = %__MODULE__{}) do JsonRpc.log_message(:warning, "Unmatched request: #{inspect(req)}") {:error, :invalid_request, nil, state} end @@ -818,7 +818,7 @@ defmodule ElixirLS.LanguageServer.Server do } end - defp get_spec_code_lenses(state, uri, source_file) do + defp get_spec_code_lenses(state = %__MODULE__{}, uri, source_file) do if dialyzer_enabled?(state) and !!state.settings["suggestSpecs"] do CodeLens.spec_code_lens(state.server_instance_id, uri, source_file.text) else @@ -826,7 +826,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp get_test_code_lenses(state, uri, source_file) do + defp get_test_code_lenses(state = %__MODULE__{}, uri, source_file) do get_test_code_lenses( state, uri, @@ -836,9 +836,9 @@ defmodule ElixirLS.LanguageServer.Server do ) end - defp get_test_code_lenses(_state, _uri, _source_file, false, _), do: {:ok, []} + defp get_test_code_lenses(%__MODULE__{}, _uri, _source_file, false, _), do: {:ok, []} - defp get_test_code_lenses(state, uri, source_file, true = _enabled, true = _umbrella) do + defp get_test_code_lenses(state = %__MODULE__{}, uri, source_file, true = _enabled, true = _umbrella) do file_path = SourceFile.path_from_uri(uri) Mix.Project.apps_paths() @@ -856,7 +856,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp get_test_code_lenses(state, uri, source_file, true = _enabled, false = _umbrella) do + defp get_test_code_lenses(state = %__MODULE__{}, uri, source_file, true = _enabled, false = _umbrella) do try do file_path = SourceFile.path_from_uri(uri) @@ -870,7 +870,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp is_test_file?(file_path, state, app, app_path) do + defp is_test_file?(file_path, state = %__MODULE__{}, app, app_path) do app_name = Atom.to_string(app) test_paths = @@ -917,7 +917,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp dialyze(state) do + defp dialyze(state = %__MODULE__{}) do warn_opts = (state.settings["dialyzerWarnOpts"] || []) |> Enum.map(&String.to_atom/1) @@ -927,11 +927,13 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp dialyzer_default_format(state) do + defp dialyzer_default_format(state = %__MODULE__{}) do state.settings["dialyzerFormat"] || "dialyxir_long" end - defp handle_build_result(status, diagnostics, state) do + # TODO no mixfile + + defp handle_build_result(status, diagnostics, state = %__MODULE__{}) do old_diagnostics = state.build_diagnostics ++ state.dialyzer_diagnostics state = put_in(state.build_diagnostics, diagnostics) @@ -956,7 +958,7 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp handle_dialyzer_result(diagnostics, build_ref, state) do + defp handle_dialyzer_result(diagnostics, build_ref, state = %__MODULE__{}) do old_diagnostics = state.build_diagnostics ++ state.dialyzer_diagnostics state = put_in(state.dialyzer_diagnostics, diagnostics) @@ -997,11 +999,11 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp build_enabled?(state) do + defp build_enabled?(state = %__MODULE__{}) do is_binary(state.project_dir) end - defp dialyzer_enabled?(state) do + defp dialyzer_enabled?(state = %__MODULE__{}) do Dialyzer.check_support() == :ok and build_enabled?(state) and state.dialyzer_sup != nil end @@ -1039,7 +1041,7 @@ defmodule ElixirLS.LanguageServer.Server do :ok end - defp set_settings(state, settings) do + defp set_settings(state = %__MODULE__{}, settings) do enable_dialyzer = Dialyzer.check_support() == :ok && Map.get(settings, "dialyzerEnabled", true) @@ -1058,7 +1060,7 @@ defmodule ElixirLS.LanguageServer.Server do trigger_build(%{state | settings: settings}) end - defp set_dialyzer_enabled(state, enable_dialyzer) do + defp set_dialyzer_enabled(state = %__MODULE__{}, enable_dialyzer) do cond do enable_dialyzer and state.dialyzer_sup == nil and is_binary(state.project_dir) -> {:ok, pid} = Dialyzer.Supervisor.start_link(state.project_dir) @@ -1073,7 +1075,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp set_mix_env(state, env) do + defp set_mix_env(state = %__MODULE__{}, env) do prev_env = state.settings["mixEnv"] if is_nil(prev_env) or env == prev_env do @@ -1085,13 +1087,13 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp maybe_set_mix_target(state, nil), do: state + defp maybe_set_mix_target(state = %__MODULE__{}, nil), do: state - defp maybe_set_mix_target(state, target) do + defp maybe_set_mix_target(state = %__MODULE__{}, target) do set_mix_target(state, target) end - defp set_mix_target(state, target) do + defp set_mix_target(state = %__MODULE__{}, target) do target = target || "host" prev_target = state.settings["mixTarget"] @@ -1105,7 +1107,7 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp set_project_dir(%{project_dir: prev_project_dir, root_uri: root_uri} = state, project_dir) + defp set_project_dir(%__MODULE__{project_dir: prev_project_dir, root_uri: root_uri} = state, project_dir) when is_binary(root_uri) do root_dir = root_uri |> SourceFile.abs_path_from_uri() @@ -1138,11 +1140,11 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp set_project_dir(state, _) do + defp set_project_dir(state = %__MODULE__{}, _) do state end - defp create_gitignore(%{project_dir: project_dir} = state) when is_binary(project_dir) do + defp create_gitignore(%__MODULE__{project_dir: project_dir} = state) when is_binary(project_dir) do with gitignore_path <- Path.join([project_dir, ".elixir_ls", ".gitignore"]), false <- File.exists?(gitignore_path), :ok <- gitignore_path |> Path.dirname() |> File.mkdir_p(), @@ -1162,11 +1164,11 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp create_gitignore(state) do + defp create_gitignore(state = %__MODULE__{}) do state end - def get_source_file(state, uri) do + def get_source_file(state = %__MODULE__{}, uri) do case state.source_files[uri] do nil -> raise InvalidParamError, uri From 2beec396ec6612148e6440c8288ddad43cc5ebc8 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:44:05 +0200 Subject: [PATCH 06/14] use path api --- apps/language_server/lib/language_server/server.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 314a50602..28e3e3a67 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -849,7 +849,7 @@ defmodule ElixirLS.LanguageServer.Server do {app, app_path} -> if is_test_file?(file_path, state, app, app_path) do - CodeLens.test_code_lens(uri, source_file.text, "#{state.project_dir}/#{app_path}") + CodeLens.test_code_lens(uri, source_file.text, Path.join(project_dir, app_path)) else {:ok, []} end From dc67a83f9351466a645190259e64fd0d01fcb7bd Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:44:42 +0200 Subject: [PATCH 07/14] do nat try to return test lenses when project_dir is nil --- apps/language_server/lib/language_server/server.ex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 28e3e3a67..685d07cf1 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -836,9 +836,7 @@ defmodule ElixirLS.LanguageServer.Server do ) end - defp get_test_code_lenses(%__MODULE__{}, _uri, _source_file, false, _), do: {:ok, []} - - defp get_test_code_lenses(state = %__MODULE__{}, uri, source_file, true = _enabled, true = _umbrella) do + defp get_test_code_lenses(state = %__MODULE__{project_dir: project_dir}, uri, source_file, true = _enabled, true = _umbrella) when is_binary(project_dir) do file_path = SourceFile.path_from_uri(uri) Mix.Project.apps_paths() @@ -856,12 +854,12 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp get_test_code_lenses(state = %__MODULE__{}, uri, source_file, true = _enabled, false = _umbrella) do + defp get_test_code_lenses(state = %__MODULE__{project_dir: project_dir}, uri, source_file, true = _enabled, false = _umbrella) when is_binary(project_dir) do try do file_path = SourceFile.path_from_uri(uri) if is_test_file?(file_path) do - CodeLens.test_code_lens(uri, source_file.text, state.project_dir) + CodeLens.test_code_lens(uri, source_file.text, project_dir) else {:ok, []} end @@ -870,12 +868,14 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp is_test_file?(file_path, state = %__MODULE__{}, app, app_path) do + defp get_test_code_lenses(%__MODULE__{}, _uri, _source_file, _, _), do: {:ok, []} + + defp is_test_file?(file_path, state = %__MODULE__{project_dir: project_dir}, app, app_path) when is_binary(project_dir) do app_name = Atom.to_string(app) test_paths = (get_in(state.settings, ["testPaths", app_name]) || ["test"]) - |> Enum.map(fn path -> Path.join([state.project_dir, app_path, path]) end) + |> Enum.map(fn path -> Path.join([project_dir, app_path, path]) end) test_pattern = get_in(state.settings, ["testPattern", app_name]) || "*_test.exs" From 15bc1eccb7bdbbbe4c7e06fbaf61c06e85f968cf Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:44:53 +0200 Subject: [PATCH 08/14] add assertion --- apps/language_server/lib/language_server/build.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/language_server/lib/language_server/build.ex b/apps/language_server/lib/language_server/build.ex index 05e6d6e32..a25419200 100644 --- a/apps/language_server/lib/language_server/build.ex +++ b/apps/language_server/lib/language_server/build.ex @@ -1,7 +1,7 @@ defmodule ElixirLS.LanguageServer.Build do alias ElixirLS.LanguageServer.{Server, JsonRpc, SourceFile, Diagnostics} - def build(parent, root_path, opts) do + def build(parent, root_path, opts) when is_binary(root_path) do if Path.absname(File.cwd!()) != Path.absname(root_path) do IO.puts("Skipping build because cwd changed from #{root_path} to #{File.cwd!()}") {nil, nil} From cc1205f2505b0783fa0a147d729ae6da4aece9f3 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 12:45:15 +0200 Subject: [PATCH 09/14] format with default options when project_dir is nil --- .../language_server/providers/formatting.ex | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/language_server/lib/language_server/providers/formatting.ex b/apps/language_server/lib/language_server/providers/formatting.ex index b7b76fc42..be0a41023 100644 --- a/apps/language_server/lib/language_server/providers/formatting.ex +++ b/apps/language_server/lib/language_server/providers/formatting.ex @@ -2,19 +2,12 @@ defmodule ElixirLS.LanguageServer.Providers.Formatting do import ElixirLS.LanguageServer.Protocol, only: [range: 4] alias ElixirLS.LanguageServer.SourceFile - def format(%SourceFile{} = source_file, uri, project_dir) do + def format(%SourceFile{} = source_file, uri, project_dir) when is_binary(project_dir) do if can_format?(uri, project_dir) do case SourceFile.formatter_opts(uri) do {:ok, opts} -> if should_format?(uri, project_dir, opts[:inputs]) do - formatted = IO.iodata_to_binary([Code.format_string!(source_file.text, opts), ?\n]) - - response = - source_file.text - |> String.myers_difference(formatted) - |> myers_diff_to_text_edits() - - {:ok, response} + do_format(source_file, opts) else {:ok, []} end @@ -29,6 +22,21 @@ defmodule ElixirLS.LanguageServer.Providers.Formatting do {:error, :internal_error, msg} end + end + + def format(%SourceFile{} = source_file, _uri, nil) do + do_format(source_file) + end + + defp do_format(%SourceFile{text: text}, opts \\ []) do + formatted = IO.iodata_to_binary([Code.format_string!(text, opts), ?\n]) + + response = + text + |> String.myers_difference(formatted) + |> myers_diff_to_text_edits() + + {:ok, response} rescue _e in [TokenMissingError, SyntaxError] -> {:error, :internal_error, "Unable to format due to syntax error"} From 6949dcc24209bf9a0c1c1072218a9fe2458bdabb Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 15:02:45 +0200 Subject: [PATCH 10/14] undo wrong assertion --- apps/language_server/lib/language_server/server.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 685d07cf1..4186710ae 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -854,7 +854,7 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp get_test_code_lenses(state = %__MODULE__{project_dir: project_dir}, uri, source_file, true = _enabled, false = _umbrella) when is_binary(project_dir) do + defp get_test_code_lenses(%__MODULE__{project_dir: project_dir}, uri, source_file, true = _enabled, false = _umbrella) when is_binary(project_dir) do try do file_path = SourceFile.path_from_uri(uri) From 2ca1ece7ce5624ff1551012c3c6310837e6ba0d6 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 15:03:41 +0200 Subject: [PATCH 11/14] fix logic error - do not set needs_build? when build not enabled --- .../lib/language_server/server.ex | 41 ++++++++++--------- apps/language_server/test/server_test.exs | 29 +++++++------ 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 4186710ae..3cee14346 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -894,26 +894,29 @@ defmodule ElixirLS.LanguageServer.Server do # Build - defp trigger_build(state = %__MODULE__{project_dir: project_dir}) when is_binary(project_dir) do - if build_enabled?(state) and not state.build_running? do - fetch_deps? = Map.get(state.settings || %{}, "fetchDeps", true) - - {_pid, build_ref} = - Build.build(self(), project_dir, - fetch_deps?: fetch_deps?, - load_all_modules?: state.load_all_modules? - ) - - %__MODULE__{ + defp trigger_build(state = %__MODULE__{project_dir: project_dir}) do + cond do + not build_enabled?(state) -> state - | build_ref: build_ref, - needs_build?: false, - build_running?: true, - analysis_ready?: false, - load_all_modules?: false - } - else - %__MODULE__{state | needs_build?: true, analysis_ready?: false} + not state.build_running? -> + fetch_deps? = Map.get(state.settings || %{}, "fetchDeps", true) + + {_pid, build_ref} = + Build.build(self(), project_dir, + fetch_deps?: fetch_deps?, + load_all_modules?: state.load_all_modules? + ) + + %__MODULE__{ + state + | build_ref: build_ref, + needs_build?: false, + build_running?: true, + analysis_ready?: false, + load_all_modules?: false + } + true -> + %__MODULE__{state | needs_build?: true, analysis_ready?: false} end end diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 3e34d13f6..6b5ece17b 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -18,7 +18,10 @@ defmodule ElixirLS.LanguageServer.ServerTest do end defp fake_initialize(server) do - :sys.replace_state(server, fn state -> %{state | server_instance_id: "123"} end) + :sys.replace_state(server, fn state -> %{state | + server_instance_id: "123", + project_dir: "/fake_dir" + } end) end defp root_uri do @@ -396,7 +399,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do state = :sys.get_state(server) assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) - assert state.needs_build? + assert state.needs_build? || state.build_running? end test "textDocument/didSave not open", %{server: server} do @@ -441,7 +444,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? end test "watched file updated outside", %{server: server} do @@ -450,7 +453,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? end test "watched file deleted outside", %{server: server} do @@ -459,7 +462,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? end test "watched open file created in editor", %{server: server} do @@ -469,7 +472,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) end @@ -494,7 +497,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) end @@ -506,7 +509,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? end @tag :fixture @@ -539,7 +542,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) end) end @@ -571,7 +574,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? assert %SourceFile{dirty?: true} = Server.get_source_file(state, uri) end) end @@ -601,7 +604,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? assert %SourceFile{dirty?: true} = Server.get_source_file(state, uri) assert_receive %{ @@ -637,7 +640,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do state = :sys.get_state(server) assert %SourceFile{dirty?: true} = Server.get_source_file(state, uri) - assert state.needs_build? + assert state.needs_build? || state.build_running? end test "watched open file deleted outside", %{server: server} do @@ -647,7 +650,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) state = :sys.get_state(server) - assert state.needs_build? + assert state.needs_build? || state.build_running? end test "gracefully skip not supported URI scheme", %{server: server} do From 9ca78165660e5a740bd376b422fa11b96b1b641c Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 15:21:31 +0200 Subject: [PATCH 12/14] show no mixfile message only once instead of on every trigger_build --- .../lib/language_server/build.ex | 3 +-- .../lib/language_server/server.ex | 15 ++++++++++++--- apps/language_server/test/server_test.exs | 18 +++++++----------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/language_server/lib/language_server/build.ex b/apps/language_server/lib/language_server/build.ex index a25419200..a1dfd1a00 100644 --- a/apps/language_server/lib/language_server/build.ex +++ b/apps/language_server/lib/language_server/build.ex @@ -39,7 +39,7 @@ defmodule ElixirLS.LanguageServer.Build do Server.build_finished(parent, {:error, mixfile_diagnostics}) :no_mixfile -> - Server.build_finished(parent, {:ok, []}) + Server.build_finished(parent, {:no_mixfile, []}) end end) @@ -169,7 +169,6 @@ defmodule ElixirLS.LanguageServer.Build do "To use a subdirectory, set `elixirLS.projectDir` in your settings" JsonRpc.log_message(:info, msg <> ". Looked for mixfile at #{inspect(mixfile)}") - JsonRpc.show_message(:info, msg) :no_mixfile end diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 3cee14346..65690b8d6 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -57,7 +57,8 @@ defmodule ElixirLS.LanguageServer.Server do # Tracks source files that are currently open in the editor source_files: %{}, awaiting_contracts: [], - supports_dynamic: false + supports_dynamic: false, + no_mixfile_warned?: false ] defmodule InvalidParamError do @@ -146,7 +147,7 @@ defmodule ElixirLS.LanguageServer.Server do @impl GenServer def handle_cast({:build_finished, {status, diagnostics}}, state = %__MODULE__{}) - when status in [:ok, :noop, :error] and is_list(diagnostics) do + when status in [:ok, :noop, :error, :no_mixfile] and is_list(diagnostics) do {:noreply, handle_build_result(status, diagnostics, state)} end @@ -934,7 +935,15 @@ defmodule ElixirLS.LanguageServer.Server do state.settings["dialyzerFormat"] || "dialyxir_long" end - # TODO no mixfile + defp handle_build_result(:no_mixfile, _, state = %__MODULE__{}) do + unless state.no_mixfile_warned? do + msg = + "No mixfile found in project. " <> + "To use a subdirectory, set `elixirLS.projectDir` in your settings" + JsonRpc.show_message(:info, msg) + end + %__MODULE__{state | no_mixfile_warned?: true} + end defp handle_build_result(status, diagnostics, state = %__MODULE__{}) do old_diagnostics = state.build_diagnostics ++ state.dialyzer_diagnostics diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 6b5ece17b..7a1c06d1b 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -1424,22 +1424,18 @@ defmodule ElixirLS.LanguageServer.ServerTest do in_fixture(__DIR__, "no_mixfile", fn -> mixfile_uri = SourceFile.path_to_uri("mix.exs") - IO.inspect root_uri() + Server.receive_packet(server, initialize_req(1, nil, %{})) + Server.receive_packet(server, notification("initialized")) - initialize(server, root_uri() <> "/a.exs") + Server.receive_packet( + server, + did_change_configuration(%{"elixirLS" => %{"dialyzerEnabled" => false}}) + ) - assert_receive notification("window/logMessage", %{ + refute_receive notification("window/logMessage", %{ "message" => "No mixfile found in project." <> _ }), 1000 - assert_receive notification("window/showMessage", %{ - "message" => "No mixfile found in project." <> _ - }) - - assert_receive notification("window/logMessage", %{ - "message" => "Compile took" <> _ - }) - wait_until_compiled(server) end) end From ea598a3acb27f55937c837e6e87d6016572a9032 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 15:21:53 +0200 Subject: [PATCH 13/14] run formatter --- .../lib/language_server/server.ex | 61 +++++++++++++++---- apps/language_server/test/server_test.exs | 29 ++++----- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/apps/language_server/lib/language_server/server.ex b/apps/language_server/lib/language_server/server.ex index 65690b8d6..e0648f78e 100644 --- a/apps/language_server/lib/language_server/server.ex +++ b/apps/language_server/lib/language_server/server.ex @@ -212,7 +212,10 @@ defmodule ElixirLS.LanguageServer.Server do end @impl GenServer - def handle_info({:DOWN, ref, _, _pid, reason}, %__MODULE__{build_ref: ref, build_running?: true} = state) do + def handle_info( + {:DOWN, ref, _, _pid, reason}, + %__MODULE__{build_ref: ref, build_running?: true} = state + ) do state = %{state | build_running?: false} state = @@ -460,7 +463,11 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp handle_request_packet(id, packet, state = %__MODULE__{server_instance_id: server_instance_id}) + defp handle_request_packet( + id, + packet, + state = %__MODULE__{server_instance_id: server_instance_id} + ) when not is_initialized(server_instance_id) do case packet do initialize_req(_id, _root_uri, _client_capabilities) -> @@ -569,7 +576,10 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(references_req(_id, uri, line, character, include_declaration), state = %__MODULE__{}) do + defp handle_request( + references_req(_id, uri, line, character, include_declaration), + state = %__MODULE__{} + ) do source_file = get_source_file(state, uri) fun = fn -> @@ -694,7 +704,10 @@ defmodule ElixirLS.LanguageServer.Server do {:async, fun, state} end - defp handle_request(on_type_formatting_req(_id, uri, line, character, ch, options), state = %__MODULE__{}) do + defp handle_request( + on_type_formatting_req(_id, uri, line, character, ch, options), + state = %__MODULE__{} + ) do source_file = get_source_file(state, uri) fun = fn -> @@ -752,7 +765,10 @@ defmodule ElixirLS.LanguageServer.Server do end # TODO remove in ElixirLS 0.8 - defp handle_request(macro_expansion(_id, whole_buffer, selected_macro, macro_line), state = %__MODULE__{}) do + defp handle_request( + macro_expansion(_id, whole_buffer, selected_macro, macro_line), + state = %__MODULE__{} + ) do IO.warn( "Custom `elixirDocument/macroExpansion` request is deprecated. Switch to command `executeMacro` via `workspace/executeCommand`" ) @@ -837,7 +853,14 @@ defmodule ElixirLS.LanguageServer.Server do ) end - defp get_test_code_lenses(state = %__MODULE__{project_dir: project_dir}, uri, source_file, true = _enabled, true = _umbrella) when is_binary(project_dir) do + defp get_test_code_lenses( + state = %__MODULE__{project_dir: project_dir}, + uri, + source_file, + true = _enabled, + true = _umbrella + ) + when is_binary(project_dir) do file_path = SourceFile.path_from_uri(uri) Mix.Project.apps_paths() @@ -855,7 +878,14 @@ defmodule ElixirLS.LanguageServer.Server do end end - defp get_test_code_lenses(%__MODULE__{project_dir: project_dir}, uri, source_file, true = _enabled, false = _umbrella) when is_binary(project_dir) do + defp get_test_code_lenses( + %__MODULE__{project_dir: project_dir}, + uri, + source_file, + true = _enabled, + false = _umbrella + ) + when is_binary(project_dir) do try do file_path = SourceFile.path_from_uri(uri) @@ -871,7 +901,8 @@ defmodule ElixirLS.LanguageServer.Server do defp get_test_code_lenses(%__MODULE__{}, _uri, _source_file, _, _), do: {:ok, []} - defp is_test_file?(file_path, state = %__MODULE__{project_dir: project_dir}, app, app_path) when is_binary(project_dir) do + defp is_test_file?(file_path, state = %__MODULE__{project_dir: project_dir}, app, app_path) + when is_binary(project_dir) do app_name = Atom.to_string(app) test_paths = @@ -899,7 +930,8 @@ defmodule ElixirLS.LanguageServer.Server do cond do not build_enabled?(state) -> state - not state.build_running? -> + + not state.build_running? -> fetch_deps? = Map.get(state.settings || %{}, "fetchDeps", true) {_pid, build_ref} = @@ -916,6 +948,7 @@ defmodule ElixirLS.LanguageServer.Server do analysis_ready?: false, load_all_modules?: false } + true -> %__MODULE__{state | needs_build?: true, analysis_ready?: false} end @@ -940,8 +973,10 @@ defmodule ElixirLS.LanguageServer.Server do msg = "No mixfile found in project. " <> "To use a subdirectory, set `elixirLS.projectDir` in your settings" + JsonRpc.show_message(:info, msg) end + %__MODULE__{state | no_mixfile_warned?: true} end @@ -1119,7 +1154,10 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp set_project_dir(%__MODULE__{project_dir: prev_project_dir, root_uri: root_uri} = state, project_dir) + defp set_project_dir( + %__MODULE__{project_dir: prev_project_dir, root_uri: root_uri} = state, + project_dir + ) when is_binary(root_uri) do root_dir = root_uri |> SourceFile.abs_path_from_uri() @@ -1156,7 +1194,8 @@ defmodule ElixirLS.LanguageServer.Server do state end - defp create_gitignore(%__MODULE__{project_dir: project_dir} = state) when is_binary(project_dir) do + defp create_gitignore(%__MODULE__{project_dir: project_dir} = state) + when is_binary(project_dir) do with gitignore_path <- Path.join([project_dir, ".elixir_ls", ".gitignore"]), false <- File.exists?(gitignore_path), :ok <- gitignore_path |> Path.dirname() |> File.mkdir_p(), diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 7a1c06d1b..417ce3751 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -18,10 +18,9 @@ defmodule ElixirLS.LanguageServer.ServerTest do end defp fake_initialize(server) do - :sys.replace_state(server, fn state -> %{state | - server_instance_id: "123", - project_dir: "/fake_dir" - } end) + :sys.replace_state(server, fn state -> + %{state | server_instance_id: "123", project_dir: "/fake_dir"} + end) end defp root_uri do @@ -1399,21 +1398,20 @@ defmodule ElixirLS.LanguageServer.ServerTest do @tag :fixture test "dir with no mix.exs", %{server: server} do in_fixture(__DIR__, "no_mixfile", fn -> - mixfile_uri = SourceFile.path_to_uri("mix.exs") - initialize(server) assert_receive notification("window/logMessage", %{ - "message" => "No mixfile found in project." <> _ - }), 1000 + "message" => "No mixfile found in project." <> _ + }), + 1000 assert_receive notification("window/showMessage", %{ - "message" => "No mixfile found in project." <> _ - }) + "message" => "No mixfile found in project." <> _ + }) assert_receive notification("window/logMessage", %{ - "message" => "Compile took" <> _ - }) + "message" => "Compile took" <> _ + }) wait_until_compiled(server) end) @@ -1422,8 +1420,6 @@ defmodule ElixirLS.LanguageServer.ServerTest do @tag :fixture test "single file", %{server: server} do in_fixture(__DIR__, "no_mixfile", fn -> - mixfile_uri = SourceFile.path_to_uri("mix.exs") - Server.receive_packet(server, initialize_req(1, nil, %{})) Server.receive_packet(server, notification("initialized")) @@ -1433,8 +1429,9 @@ defmodule ElixirLS.LanguageServer.ServerTest do ) refute_receive notification("window/logMessage", %{ - "message" => "No mixfile found in project." <> _ - }), 1000 + "message" => "No mixfile found in project." <> _ + }), + 1000 wait_until_compiled(server) end) From c84e455f23710ffa1bd9452ac49809502d000c2e Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 27 Jun 2021 15:54:49 +0200 Subject: [PATCH 14/14] add tests --- .../test/providers/formatting_test.exs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/apps/language_server/test/providers/formatting_test.exs b/apps/language_server/test/providers/formatting_test.exs index 9a3b3003b..db6086fa5 100644 --- a/apps/language_server/test/providers/formatting_test.exs +++ b/apps/language_server/test/providers/formatting_test.exs @@ -5,6 +5,106 @@ defmodule ElixirLS.LanguageServer.Providers.FormattingTest do alias ElixirLS.LanguageServer.SourceFile alias ElixirLS.LanguageServer.Test.FixtureHelpers + @tag :fixture + test "no mixfile" do + in_fixture(Path.join(__DIR__, ".."), "no_mixfile", fn -> + path = "lib/file.ex" + uri = SourceFile.path_to_uri(path) + + text = """ + defmodule MyModule do + require Logger + + def dummy_function() do + Logger.info "dummy" + end + end + """ + + source_file = %SourceFile{ + text: text, + version: 1, + dirty?: true + } + + project_dir = maybe_convert_path_separators(FixtureHelpers.get_path("no_mixfile")) + + assert {:ok, changes} = Formatting.format(source_file, uri, project_dir) + + assert changes == [ + %{ + "newText" => ")", + "range" => %{ + "end" => %{"character" => 23, "line" => 4}, + "start" => %{"character" => 23, "line" => 4} + } + }, + %{ + "newText" => "(", + "range" => %{ + "end" => %{"character" => 16, "line" => 4}, + "start" => %{"character" => 15, "line" => 4} + } + } + ] + + assert Enum.all?(changes, fn change -> + assert_position_type(change["range"]["end"]) and + assert_position_type(change["range"]["start"]) + end) + end) + end + + @tag :fixture + test "no project dir" do + in_fixture(Path.join(__DIR__, ".."), "no_mixfile", fn -> + path = "lib/file.ex" + uri = SourceFile.path_to_uri(path) + + text = """ + defmodule MyModule do + require Logger + + def dummy_function() do + Logger.info "dummy" + end + end + """ + + source_file = %SourceFile{ + text: text, + version: 1, + dirty?: true + } + + project_dir = nil + + assert {:ok, changes} = Formatting.format(source_file, uri, project_dir) + + assert changes == [ + %{ + "newText" => ")", + "range" => %{ + "end" => %{"character" => 23, "line" => 4}, + "start" => %{"character" => 23, "line" => 4} + } + }, + %{ + "newText" => "(", + "range" => %{ + "end" => %{"character" => 16, "line" => 4}, + "start" => %{"character" => 15, "line" => 4} + } + } + ] + + assert Enum.all?(changes, fn change -> + assert_position_type(change["range"]["end"]) and + assert_position_type(change["range"]["start"]) + end) + end) + end + @tag :fixture test "Formats a file with LF line endings" do in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->