Skip to content

Commit fdb49e8

Browse files
committed
make formatter more sane with directories
remove last references to cwd make formatter_for_file return directory with formatter.exs remove error prone code finding formatter.exs directory
1 parent 6b394be commit fdb49e8

File tree

6 files changed

+49
-59
lines changed

6 files changed

+49
-59
lines changed

apps/language_server/lib/language_server/mix_tasks/format.ex

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ defmodule Mix.Tasks.ElixirLSFormat do
333333

334334
formatter_opts_and_subs = load_plugins(formatter_opts_and_subs)
335335

336-
find_formatter_and_opts_for_file(Path.expand(file, cwd), formatter_opts_and_subs)
336+
find_formatter_and_opts_for_file(Path.expand(file, cwd), formatter_opts_and_subs, cwd)
337337
end
338338

339339
@doc """
@@ -530,12 +530,12 @@ defmodule Mix.Tasks.ElixirLSFormat do
530530
if file == :stdin do
531531
stdin_filename = Path.expand(Keyword.get(opts, :stdin_filename, "stdin.exs"), cwd)
532532

533-
{formatter, _opts} =
534-
find_formatter_and_opts_for_file(stdin_filename, {formatter_opts, subs})
533+
{formatter, _opts, _dir} =
534+
find_formatter_and_opts_for_file(stdin_filename, {formatter_opts, subs}, cwd)
535535

536536
{file, formatter}
537537
else
538-
{formatter, _opts} = find_formatter_and_opts_for_file(file, {formatter_opts, subs})
538+
{formatter, _opts, _dir} = find_formatter_and_opts_for_file(file, {formatter_opts, subs}, cwd)
539539
{file, formatter}
540540
end
541541
end
@@ -601,19 +601,22 @@ defmodule Mix.Tasks.ElixirLSFormat do
601601
if plugins != [], do: plugins, else: nil
602602
end
603603

604-
defp find_formatter_and_opts_for_file(file, formatter_opts_and_subs) do
605-
formatter_opts = recur_formatter_opts_for_file(file, formatter_opts_and_subs)
606-
{find_formatter_for_file(file, formatter_opts), formatter_opts}
604+
defp find_formatter_and_opts_for_file(file, formatter_opts_and_subs, cwd) do
605+
{formatter_opts, dir} = recur_formatter_opts_for_file(file, formatter_opts_and_subs) |> dbg
606+
{find_formatter_for_file(file, formatter_opts), formatter_opts, dir || cwd}
607607
end
608608

609609
defp recur_formatter_opts_for_file(file, {formatter_opts, subs}) do
610-
Enum.find_value(subs, formatter_opts, fn {sub, formatter_opts_and_subs} ->
610+
Enum.find_value(subs, {formatter_opts, nil}, fn {sub, formatter_opts_and_subs} ->
611611
size = byte_size(sub)
612612

613613
case file do
614614
<<prefix::binary-size(size), dir_separator, _::binary>>
615615
when prefix == sub and dir_separator in [?\\, ?/] ->
616-
recur_formatter_opts_for_file(file, formatter_opts_and_subs)
616+
case recur_formatter_opts_for_file(file, formatter_opts_and_subs) do
617+
{nested_formatter_opts, nil} -> {nested_formatter_opts, sub}
618+
{nested_formatter_opts, nested_sub} -> {nested_formatter_opts, nested_sub}
619+
end
617620

618621
_ ->
619622
nil

apps/language_server/lib/language_server/providers/execute_command/apply_spec.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ApplySpec do
5757
try do
5858
target_line_length =
5959
case SourceFile.formatter_for(uri, state.project_dir) do
60-
{:ok, {_, opts}} -> Keyword.get(opts, :line_length, @default_target_line_length)
61-
:error -> @default_target_line_length
60+
{:ok, {_, opts, _formatter_exs_dir}} -> Keyword.get(opts, :line_length, @default_target_line_length)
61+
{:error, _} -> @default_target_line_length
6262
end
6363

6464
target_line_length = target_line_length - String.length(indentation)

apps/language_server/lib/language_server/providers/formatting.ex

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,29 @@ defmodule ElixirLS.LanguageServer.Providers.Formatting do
22
import ElixirLS.LanguageServer.Protocol, only: [range: 4]
33
alias ElixirLS.LanguageServer.Protocol.TextEdit
44
alias ElixirLS.LanguageServer.SourceFile
5+
alias ElixirLS.LanguageServer.JsonRpc
56

67
def format(%SourceFile{} = source_file, uri = "file:" <> _, project_dir)
78
when is_binary(project_dir) do
8-
if can_format?(uri, project_dir) do
9+
file_path = SourceFile.Path.absolute_from_uri(uri)
10+
if SourceFile.Path.path_in_dir?(file_path, project_dir) do
911
case SourceFile.formatter_for(uri, project_dir) do
10-
{:ok, {formatter, opts}} ->
11-
if should_format?(uri, project_dir, opts[:inputs]) do
12+
{:ok, {formatter, opts, formatter_exs_dir}} ->
13+
if should_format?(uri, formatter_exs_dir, opts[:inputs]) do
1214
do_format(source_file, formatter, opts)
1315
else
16+
JsonRpc.show_message(:info, "formatter.exs not found for #{file_path}")
1417
{:ok, []}
1518
end
1619

17-
:error ->
18-
{:error, :internal_error, "Unable to fetch formatter options", true}
20+
{:error, message} ->
21+
JsonRpc.show_message(:error, "Unable to fetch formatter options for #{file_path}")
22+
{:error, :internal_error, message, true}
1923
end
2024
else
21-
msg =
22-
"Cannot format file from current directory " <>
23-
"(Currently in #{Path.relative_to(File.cwd!(), project_dir)})"
25+
JsonRpc.show_message(:warning, "Cannot format file #{file_path} outside of project dir #{project_dir}")
2426

25-
{:error, :internal_error, msg, true}
27+
{:ok, []}
2628
end
2729
end
2830

@@ -53,46 +55,16 @@ defmodule ElixirLS.LanguageServer.Providers.Formatting do
5355
IO.iodata_to_binary([Code.format_string!(text, opts), ?\n])
5456
end
5557

56-
# If in an umbrella project, the cwd might be set to a sub-app if it's being compiled. This is
57-
# fine if the file we're trying to format is in that app. Otherwise, we return an error.
58-
defp can_format?(file_uri = "file:" <> _, project_dir) do
58+
defp should_format?(file_uri, formatter_exs_dir, inputs) when is_list(inputs) do
5959
file_path = SourceFile.Path.absolute_from_uri(file_uri)
6060

61-
String.starts_with?(file_path, Path.absname(project_dir)) or
62-
String.starts_with?(file_path, File.cwd!())
63-
end
64-
65-
defp can_format?(_uri, _project_dir), do: false
66-
67-
defp should_format?(file_uri, project_dir, inputs) when is_list(inputs) do
68-
file_path = SourceFile.Path.absolute_from_uri(file_uri)
69-
formatter_dir = find_formatter_dir(project_dir, Path.dirname(file_path))
70-
7161
Enum.any?(inputs, fn input_glob ->
72-
glob = Path.join(formatter_dir, input_glob)
62+
glob = Path.join(formatter_exs_dir, input_glob)
7363
PathGlobVendored.match?(file_path, glob, match_dot: true)
7464
end)
7565
end
7666

77-
defp should_format?(_file_uri, _project_dir, _inputs), do: true
78-
79-
# Finds the deepest directory that contains file_path, that also contains a
80-
# .formatter.exs. It's possible, though unlikely, that the .formatter.exs we
81-
# find is not actually linked to the project_dir via the :subdirectories
82-
# option in the top-level .formatter.exs. Currently, that edge case is
83-
# glossed over.
84-
defp find_formatter_dir(project_dir, dir) do
85-
cond do
86-
dir == project_dir ->
87-
project_dir
88-
89-
Path.join(dir, ".formatter.exs") |> File.exists?() ->
90-
dir
91-
92-
true ->
93-
find_formatter_dir(project_dir, Path.dirname(dir))
94-
end
95-
end
67+
defp should_format?(_file_uri, _formatter_exs_dir, _inputs), do: true
9668

9769
defp myers_diff_to_text_edits(myers_diff) do
9870
myers_diff_to_text_edits(myers_diff, {0, 0}, [])

apps/language_server/lib/language_server/server.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -973,8 +973,8 @@ defmodule ElixirLS.LanguageServer.Server do
973973

974974
locals_without_parens =
975975
case SourceFile.formatter_for(uri, state.project_dir) do
976-
{:ok, {_, opts}} -> Keyword.get(opts, :locals_without_parens, [])
977-
:error -> []
976+
{:ok, {_, opts, _formatter_exs_dir}} -> Keyword.get(opts, :locals_without_parens, [])
977+
{:error, _} -> []
978978
end
979979
|> MapSet.new()
980980

apps/language_server/lib/language_server/source_file.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,14 @@ defmodule ElixirLS.LanguageServer.SourceFile do
229229
"""
230230
end
231231

232-
@spec formatter_for(String.t(), String.t() | nil) :: {:ok, {function | nil, keyword()}} | :error
232+
@spec formatter_for(String.t(), String.t() | nil) :: {:ok, {function | nil, keyword(), String.t}} | :error
233233
def formatter_for(uri = "file:" <> _, project_dir) do
234234
path = __MODULE__.Path.from_uri(uri)
235235

236236
try do
237237
true = Code.ensure_loaded?(Mix.Tasks.ElixirLSFormat)
238238

239-
if project_dir && Version.match?(System.version(), ">= 1.15.0") do
239+
if project_dir do
240240
{:ok, Mix.Tasks.ElixirLSFormat.formatter_for_file(path, root: project_dir)}
241241
else
242242
{:ok, Mix.Tasks.ElixirLSFormat.formatter_for_file(path)}
@@ -248,11 +248,11 @@ defmodule ElixirLS.LanguageServer.SourceFile do
248248

249249
Logger.warning("Unable to get formatter options for #{path}: #{message}")
250250

251-
:error
251+
{:error, message}
252252
end
253253
end
254254

255-
def formatter_for(_, _), do: :error
255+
def formatter_for(_, _), do: {:error, :project_dir_not_set}
256256

257257
defp format_code(code, opts) do
258258
try do

apps/language_server/lib/language_server/source_file/path.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,19 @@ defmodule ElixirLS.LanguageServer.SourceFile.Path do
116116
defp os_type do
117117
:os.type()
118118
end
119+
120+
def path_in_dir?(file, dir) do
121+
case String.starts_with?(file, dir) do
122+
true ->
123+
# Get the grapheme after the directory in the file path
124+
next_char_index = String.length(dir)
125+
next_char = String.slice(file, next_char_index, 1)
126+
127+
# If the next character is either "" (end of string) or a "/", it's a valid match
128+
next_char in ["", "/"]
129+
130+
false ->
131+
false
132+
end
133+
end
119134
end

0 commit comments

Comments
 (0)