Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple formatters per language? #6

Closed
wbthomason opened this issue Jun 13, 2018 · 13 comments
Closed

Multiple formatters per language? #6

wbthomason opened this issue Jun 13, 2018 · 13 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@wbthomason
Copy link

The equivalent plugin for Neovim, https://github.com/sbdchd/neoformat, configures multiple formatters per language. The main benefit is that the plugin is still functional with a more general set of formatters, but it also enables easy choice of formatter per project, etc.

This seems like it would be easy to implement as an extension to format-all-formatter-for-mode - you could return a list of the formatters for a major mode, and iterate through them in format-all-buffer until one succeeds. If the user invokes format-all-buffer with an argument naming a specific formatter, then you would either just run that formatter (if it's in the defined list) or first check that it's relevant to the current major mode and then run it.

If you like this feature, I'd be happy to submit a quick PR implementing it as described above!

@lassik
Copy link
Owner

lassik commented Jun 13, 2018

Thanks for chiming in and offering to help :)

It was only a matter of time before we'd have to deal with this issue. I've been putting it off since configuration is such a quagmire (the gory details are in #2). For this issue,

  1. It didn't occur to me that we could simply pick the first installed formatter for a particular language. That would indeed solve the problem without us having to make any configuration options. One problem with this approach is that if the user later installed another formatter for that language, then the formatter used by Emacs could suddenly change to that one without warning.

  2. One approach I thought of is to have a default formatter for each language. And if the user wanted to use another formatter they would explicitly have to invoke it using another command. (E.g. if the default formatter for JavaScript is prettier, then format-all-buffer would use that. To use the formatter called standard instead of prettier, one would have to explicitly use the format-all-buffer-standard command.) This also requires no configuration, but isn't very nice to those who use the non-default formatters. This is similar to your suggestion to use a command argument, except that the command argument could be remembered by Emacs, so your idea is probably better.

  3. We could also have a buffer-local variable determining which formatter to use. It could be specified in source files using -*- or Local Variables:. I personally wouldn't like to proliferate these Emacs-specific settings in text files - I'm in favor of emerging cross-editor standards like .editorconfig. But it may be that we'll end up with a local variable for choosing the formatter anyway - its value would just be read from .editorconfig or a similar settings file in most cases.

What do you think about these approaches?

Eventually I think we should just have to bank on .editorconfig or .unibeautifyrc or some such emerging standard for the configuration (as discussed in #2), and the longer we put off the decision the more painful it will be :p But life is full of tradeoffs like that.

@lassik
Copy link
Owner

lassik commented Jun 13, 2018

By the way, if there's a specific formatter that you're missing, please mention it in #5 :) Even better if you can provide a PR using the current framework. The data model already supports multiple formatters (see prettier and standard, both for JavaScript, in the current source code), it just doesn't make it easy for users to call more than one of them.

@lassik
Copy link
Owner

lassik commented Jun 13, 2018

For reference, here's how Unibeautify picks a formatter in their YAML config file:

TypeScript:
    beautifiers: ["Prettier"]

Honestly, I like the way they do it (and the fact that they've done our work for us :p) and I'd just like to adopt that config file immediately if I was confident that it would eventually win the "config file wars" ;)

@wbthomason
Copy link
Author

Thanks for the responses! I have a few thoughts (responding to your comments from last to first):

  • The Unibeautify format (and project) seems like a good standard. But, as you say, it's unclear that it or any competing standard will win out in the end.
  • I was motivated by the absence of yapf in particular. I'd be happy to make a PR adding that (and others supported by Neoformat for other languages I use), but I made this issue first because I was also interested in the ability to run multiple formatters or select different formatters.
  • I agree with @purcell in Configuration, customization, hooks #2. I agree with you that the ideal is a single configuration file per project, common across editors and formatters alike, but I think the pragmatic choice and best design is to implement Emacs Lisp machinery for setting configuration now and adapt it to whichever format wins the configuration wars later. This makes your package more flexible and practical across a variety of configuration contexts.

In the vein of my last response, I agree with your concern in (1), but I don't think this is a major problem. If the user installs another formatter, so long as there's a way to select the original formatter, there's no unavoidable unexpected behavior.

I like the idea of having a default formatter per language and allowing invocation of the command to select another formatter. The plugin could try the formatters in a list defined for each language in sequence, beginning with the default, until one succeeds, and remember which formatter was the first to run correctly (I'll note that this part could have some unexpected behavior if the reason earlier formatters failed was e.g. syntax errors that a later formatter didn't get caught on, so maybe it's best to not store the first to succeed...). To make life easier for those who prefer a non-default formatter, you could make a customization group with the defaults for each language, and persistently set language defaults that way.

This mechanism is pretty similar to how Neoformat works - not that they're the gold standard, but (I'm coming from Neovim, if that wasn't clear :p), they do provide a very nice turn-key experience for both out of the box formatting of files and customization of the formatting.

tl;dr: I agree with you that the philosophically best thing would be a unified configuration file format, but I think that the best current action is to add Emacs configuration options that can be used to load configuration from a file once a standard format emerges. It's better to have some configurability now and add to the options later (e.g. start with only selecting formatters, then maybe in the future allow settings like tab width, etc.) than it is to wait until the perfect solution is viable to have any configurability, imho.

Of course, this is all just my long-winded opinion! Thanks for the great package either way!

@lassik
Copy link
Owner

lassik commented Jun 14, 2018

I'm happy to hear that the package is also useful to others :) No problem with the lengthy post, I appreciate the detail!

Thought about this some more, I'll cave in and implement the following interim solution if it's ok with you:

  • A buffer-local variable format-all-formatter to specify which formatter to use.
  • Modify the format-all-buffer command such that when it's called interactively with a prefix argument, it will prompt in the minibuffer for which formatter to use (with tab completion from a list of formatters compatible with the current major mode). This will set the value of format-all-formatter for the current buffer so that subsequent calls to format-all-buffer without a prefix argument will also use the new formatter.
  • format-all-formatter can also be set like -*- format-all-formatter: yapf -*- or in a Local Variables: section, since it's just a normal variable.
  • We could also consider adding a domain-specific .editorconfig property. I happen to have written the editorconfig-domain-specific package for Emacs :p

I looked at neoformat and unibeautify, and they both allow specifying more than one formatter per file for one language. If I understood correctly, Neoformat picks the first available formatter from that list. I don't know what unibeautify does. Anwyay, I think this is misguided - formatting should be deterministic, and the best way to ensure that is to stick to one particular formatter per file. In theory, two formatters can produce the same result if they are both configured to do so, but in practice formatting is so complex that I expect this will rarely be the case. In fact, different versions of the same formatter are likely to have subtly different output.

To clarify, picking the default formatter from a list of several ones may be okay design (for the sake of convenience), but it the user explicitly chooses a formatter, in my opinion they should choose only one.

@wbthomason
Copy link
Author

That sounds like a very reasonable interim solution. If I can help with a PR, I'd be happy to! Thank you!

@lassik
Copy link
Owner

lassik commented Jun 19, 2018

Go ahead with the PR :) If you feel like adding some of your favorite formatters too that'd be awesome! I'm holiday right now and will be offline for a week but I'll be sure to take a look at it when I return.

@wbthomason
Copy link
Author

Ok! I am in the middle of a crunch period for work, but I'll do what I can afterward!

@lassik
Copy link
Owner

lassik commented Jun 26, 2018

Re: My previous comment:

I looked at neoformat and unibeautify, and they both allow specifying more than one formatter per file for one language. If I understood correctly, Neoformat picks the first available formatter from that list. I don't know what unibeautify does. Anwyay, I think this is misguided - formatting should be deterministic, and the best way to ensure that is to stick to one particular formatter per file. In theory, two formatters can produce the same result if they are both configured to do so, but in practice formatting is so complex that I expect this will rarely be the case. In fact, different versions of the same formatter are likely to have subtly different output.

To clarify, picking the default formatter from a list of several ones may be okay design (for the sake of convenience), but it the user explicitly chooses a formatter, in my opinion they should choose only one.

Apparently Unibeautify/atom-beautify have an issue discussing this at length: Glavin001/atom-beautify#457

Implementation: Unibeautify/unibeautify#4

I had misunderstood their idea. When they specify multiple formatters for one language, they want to run all of them every time (presumably in the order given). That makes sense. (Ideally there would be one formatter per language that did everything, but there is not - JavaScript is given as an example where some people use a chain of multiple formatters to format different aspects of the code.)

Neoformat is able to run all the given formatters in a chain too. From their README:

Run all enabled formatters (by default Neoformat stops after the first formatter
succeeds)

let g:neoformat_run_all_formatters = 1

I guess we should implement chaining also (but I'd like to leave out the possibility of stopping after the first formatter, per the reasoning in my original comments).

The variable could be named format-all-formatters but that's currently used for the internal data structure that specifies all the supported formatters. I can rename the internal variable to something else.

There seems to be a somewhat widespread convention of calling the tools beautifiers but I think that's a bit subjective - many people who don't like their project's elected coding style would probably consider them uglifiers :p Formatter is a more neutral term (and also easier to spell).

@lassik
Copy link
Owner

lassik commented Jun 26, 2018

I talked to the Unibeautify folks and we are starting to plan Unibeautify integration (#7), which is great, but it makes this multi-formatter issue even more complicated than it already was 😄

We could simply tell people who want to use chained formatters, non-default formatters and/or non-default settings to use Unibeautify and fill in a .unibeautifyrc file with the settings they want. First we'd have to make sure that Unibeautify is very easy to install on the major operating systems, but that'd be a very worthwhile goal anyway 😄

@wbthomason
Copy link
Author

Wow, alright! 😄

Perhaps for now it is best to wait for the Unibeautify changes/integration to solidify, before continuing to implement this feature (if it's even necessary)?

@lassik
Copy link
Owner

lassik commented Jun 26, 2018

I agree. Best case, we don't even need to implement user-selectable formatters and can rely entirely on Unibeautify to do it. What we could do is make Unibeautify the default formatter for the languages that it supports, and use the existing formatter specifications as a fallback in case Unibeautify is not installed. That ought to satisfy Unibeautify users, while also giving at least something useful to people who don't want to adopt it (yet).

Do you happen to have any contact with the Vim Neoformat folks? I wonder what their stance on Unibeautify is. I guess Vim is the closest major editor to Emacs in terms of user base preferences (lots of old-school Unix heads, as opposed to the web-centric Atom and Sublime etc. :p)

@lassik lassik added enhancement New feature or request question Further information is requested labels Jul 10, 2018
lassik added a commit that referenced this issue Feb 19, 2020
* More than one formatter can be defined for one language. (Well, that
  it already possible to define more than one, but only one of them
  would be accessible to users.)

* User-defined command line arguments can optionally be passed to
  external formatters.

Issues #2, #6, #27
lassik added a commit that referenced this issue Feb 19, 2020
* More than one formatter can be defined for one language. (Well, that
  it already possible to define more than one, but only one of them
  would be accessible to users.)

* User-defined command line arguments can optionally be passed to
  external formatters.

Issues #2, #6, #27
lassik added a commit that referenced this issue Feb 19, 2020
* More than one formatter can be defined for one language. (Well, that
  it already possible to define more than one, but only one of them
  would be accessible to users.)

* User-defined command line arguments can optionally be passed to
  external formatters.

Issues #2, #6, #27
@lassik
Copy link
Owner

lassik commented Feb 19, 2020

Multi-formatter support is now implemented in the multi-formatter branch. Discussion continuing in issue #27.

@lassik lassik closed this as completed Feb 19, 2020
lassik added a commit that referenced this issue Nov 21, 2020
* More than one formatter can be defined for one language. (Well, that
  it already possible to define more than one, but only one of them
  would be accessible to users.)

* User-defined command line arguments can optionally be passed to
  external formatters.

Issues #2, #6, #27
dochang added a commit to dochang/dotfiles that referenced this issue Dec 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants