Elixir formatter in neovim (using coc.nvim) sometimes adds parenthesis around macros

Hi all,

my editor (neovim + elixir-ls via coc.nvim) does format on save.

Randomly, it adds parenthesis around macros, so I have a diff looking like:

- get "/", PageController, :home
+ get("/", PageController, :home)

Is anyone experiencing something similar?

Thank you

I’d guess this is related to this elixir-ls issue: Formatter doesn't respect formatter.exs while your code is still compiling · Issue #526 · elixir-lsp/elixir-ls · GitHub

If you wait for the underlying compilation to finish, the formatter options are respected again, which makes it seem random.

1 Like

Ah!

Thank you

Sorry to revive this, I’m experiencing this all the time in NeoVim (LazyVim). I’ve already updated all plugins to their latest versions, and using Eliixir 1.15. It doesn’t add parenthesis in VS Code. Not sure what else to try…

Not sure if that helps but you could just specify these exceptions in your own .formatter.exs in the root of your project. Here’s an example from one of mine:

[
  import_deps: [:ecto, :phoenix],
  inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
  locals_without_parens: [from: 2],
  line_length: 100,
  subdirectories: ["priv/*/migrations"]
]

In this case, I don’t want Ecto.Query.from be given parentheses.

Oh, here’s a more detailed example:

[
  inputs: ["mix.exs", ".formatter.exs", "{config,lib,test,priv}/**/*.{ex,exs}"],
  locals_without_parens: [
    # Plug
    plug: 1,
    plug: 2,

    # Phoenix
    pipe_through: 1,
    render: :*,
    action_fallback: 1,
    get: :*,
    post: :*,
    put: :*,
    delete: :*,

    # Ecto
    add: 2,
    add: 3,
    field: 2,
    belongs_to: 2,
    from: 2,
    embeds_many: 2
  ]
]
1 Like

More info here: mix format — Mix v1.15.7

Thanks - I’ve tried that and even [:*], and it doesn’t seem to have any effect…

Well admittedly I don’t use coc.nvim so not sure I can help further. I use other NeoVim plugins.

I don’t think mine uses coc either - here’s the list of the ones in use, I believe: https://github.com/LazyVim/LazyVim/blob/68ff818a5bb7549f90b05e412b76fe448f605ffb/lua/lazyvim/plugins/extras/lang/elixir.lua (also nvim-lint is enabled by default, while none-ls is not).

So it does seem to use elixir_ls under the hood, but for some reason it only behaves correctly in VS Code wrt formatting (other functionality like linting works well).

Even though I use NeoVim for a year or so now, I am very slowly learning it (not enough time or energy to become a pro there at the moment). So not sure I can help – maybe try disabling credo?

Thanks for the suggestion - I tried both with and without credo, I think it was always like that for me. It’s strange that there aren’t many reports of this, and the ones from the GH issue you linked seemed to have been resolved by 1.15. I’m just at a loss how to even troubleshoot this further, but will probably file a new issue on GH as next step.

Have you tried the usual desperate measures like removing the deps and _build directories and fetching deps + compiling from scratch? Removing the .elixir_ls directory as well before opening an editor?

That didn’t seem to help at first, but it did re-create .elixir_ls dir in the parent (!) folder when I re-opened the child application folder using cd <app> && nvim . (seemingly without restoring nvim session)

And, it looks like I’ve overlooked the obvious - my .formatter.exs was created automatically by Phoenix at the root of the application directory, but there wasn’t one in the parent folder (where .elixir_ls is). I’ve just added this very simple one there:

[
  inputs: ["*.{heex,ex,exs}"]
]

and suddenly it no longer adds parenthesis! That said, now the one in the application folder doesn’t seem to apply. So It seems elixir_ls really “wants” to live in the parent folder when used via this Neovim setup. This is not the same behavior as in VS Code, where .elixir_ls is stored in the app folder if it’s opened directly. I might need to dig a bit deeper into that.

Thanks for helping me nail down this issue!

1 Like

No worries, I was just flailing about and trying to go through the usual checklist. Glad that helped somewhat.

As far as I remember, Elixir itself wants stuff to be in the root of the project – and if you have an umbrella, that opens the door for such problems that you witnessed. But I’ll let you determine this for yourself, just citing my semi-reliable memories.

FWIW, you can try have multiple copies of the same .formatter.exs in the root dir and the subdirs?

1 Like

Alas, when I make a verbatim copy of that, Neovim Elixir plugin just complains since it cannot find Ecto and other Elixir dependencies in the parent folder, and all of the paths in .formatter.exs are relative to that folder so they no longer seem to work (unless maybe I prepend **/ to all, which I can try).

For posterity, I did find what seems to be the relevant Neovim issue, although the solution hasn’t unfortunately resolved this yet - will keep digging: bug: The workspace is automatically replaced with the parent directory · Issue #1912 · LazyVim/LazyVim · GitHub

1 Like

You defintely can and should use nested .formatter.exs. I also have an umbrella app with several apps within.

Here’s my root .formatter.exs:

[
  inputs: ["mix.exs", "config/*.exs"],
  subdirectories: ["apps/*"]
]

And in the app that uses Ecto ./apps/analytics/.formatter.exs:

  import_deps: [:ecto, :ecto_sql],
  inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
  subdirectories: ["priv/*/migrations"]
]

And in the phoenix app ./apps/analytics_web/.formatter.exs:

[
  import_deps: [:phoenix],
  plugins: [Phoenix.LiveView.HTMLFormatter],
  inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"]
]

You can always generate a new umbrella Phoenix app that will give a boilerplate on how those files are generated.

This issue is related to LazyVim changing Neovim’s cwd which would cause some weird interactions with neotree, for instance. It would change the file tree from neotree to only display ./apps/analytics_web in certain scenarios.

Since you’re using LazyVim, which uses nvim-lspconfig, the actual “directory” for ElixirLs is defined here:

I changed that recently to give priority to a .git folder in the root dir, so that you only have one ElixirLS in the entire umbrella app.

2 Likes

Thanks @georgeguimaraes - yes, and someone else responded in LazyVim discussions re root_dir in ElixirLS config as well.

I wonder if it’s possible to have an umbrella .formatter.exs without an umbrella app - would that look something like this?

[
  subdirectories: ["*"]
]

I tried that, and when I save a child router.ex for example, elixirls flashes an error saying something like Unable to find formatter for /Users/<user>/code/phoenix-apps/app/lib/app_web/router.ex: "** (Mix.Error) Unknown dependency :ecto_sql given to :import_deps in the formatter configuration. Make sure the dependency is listed in your mix.exs for environment :test and you have run \"mix deps.get\"\n (mix 1.15.7) lib/mix.ex:577: Mix.raise/2\n (language_server 0.17.7) lib/language_server/mix_tasks/format.ex:489: anonymous fn/3 in Mix.Tasks.ElixirLSFormat.eval_deps_opts/2\n (elixir 1.15.7) lib/enum.ex:2510: Enum.\"-reduce/3-lists^foldl/2-0-\"/3\n (language_server 0.17.7) lib/language_server/mix_tasks/format.ex:488: Mix.Tasks.ElixirLSFormat.eval_deps_opts/2\n (language_server 0.17.7) lib/language_server/mix_tasks/format.ex:437: anonymous fn/8 in Mix.Tasks.ElixirLSFormat.eval_deps_and_subdirectories/8\n (language_server 0.17.7) lib/language_server/mix_tasks/format.ex:425: Mix.Tasks.ElixirLSFormat.eval_deps_and_subdirectories/8\n (language_server 0.17.7) lib/language_server/mix_tasks/format.ex:512: anonymous fn/6 in Mix.Tasks.ElixirLSFormat.eval_subs_opts/7\n (elixir 1.15.7) lib/enum.ex:1297: anonymous fn/3 in Enum.flat_map_reduce/3\n" (this is despite ecto_sql being present in mix.exs and installed, for the child app)

@dimitarvp I would love to hear about your nvim setup.

It’s LunarVim + a few customizations in the init.lua file. Nothing special but works very well.

Can post it if you want it but have in mind that we have other forum users who are much better versed in NeoVim than me.