Skip to content

[RFC] Indent with tabs#920

Closed
rhengles wants to merge 1 commit intoprettier:masterfrom
arijs:indent-tabs-refactor
Closed

[RFC] Indent with tabs#920
rhengles wants to merge 1 commit intoprettier:masterfrom
arijs:indent-tabs-refactor

Conversation

@rhengles
Copy link
Copy Markdown
Contributor

@rhengles rhengles commented Mar 6, 2017

I know this isn't going to be merged any time soon, but as said by @jlongster on #908, support for tabs may be considered.

Then, I'd like you to consider this implementation.

Comment thread src/doc-printer.js Outdated
// while loop which is much faster. The while loop below adds new
// cmds to the array instead of recursively calling `print`.
let cmds = [[0, MODE_BREAK, doc]];
let cmds = [[newIndent(), MODE_BREAK, doc, 0]];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this part - , 0 - is a leftover from old code, and should be removed.

@jlongster
Copy link
Copy Markdown
Member

The approach that I think is a lot better is to do the conversion in the lower-level printer in doc-printer.js. It will be even easier, and you just need to change one place. Also, there are a few places we manually indent a specific number of spaces (like indent(1, ...) or 3). We'll output mostly tabs, but in a few places print spaces to lines things up perfectly.

In the low-level printer, it'll just replace the configured number of spaces with a tab when printing out the current indent level.

@rhengles
Copy link
Copy Markdown
Contributor Author

rhengles commented Mar 7, 2017

@jlongster Yes, there is a provision for "manually indent a specific number of spaces" - it's called "indentCustom". Don't you think it's much more clean calling simply "indentLevel()" rather than "indent(options.tabWidth)" every time? 99% of the time, you want to indent by "tabWidth". And when you must use a custom value, it's much more explicit calling "indentCustom()".

Also, there is no need to "change one place" - once this lands, it won't be changed anymore.

But there's another point, and I think it's even more important - you're missing the point of tabs if you're thinking about mixing them with spaces! Please don't do that! People want tabs so they can configure their editor to use a custom tab width! When someone uses a different tab width, it will not be "lined perfectly!"

Also, I bet tools like ESLint will complain when you mix tabs and spaces! Nobody does that!

Also, take in consideration that over 500 people downloaded prettier-with-tabs on npmjs.org alone in the last month, and not a single one of them raised a issue on github, so I guess they're satisfied 😃

Also, you can see below that all tests pass - so it keeps the correct behaviour when using spaces.

Also, I can't help but feel it is a 'hacky' way to convert spaces into tabs by using math. Why not let the commands specify exactly what the code should be indented by, rather than converting it afterwards?

With this solution, you've got absolute control on the place where the indentation is actually printed - here:

https://github.com/prettier/prettier/pull/920/files#diff-a16a7bda0720c6059b3e0883718faa28R280

@jlongster
Copy link
Copy Markdown
Member

Also, I bet tools like ESLint will complain when you mix tabs and spaces! Nobody does that!

Last I asked many people said that yes, they indent with additional spaces sometimes to line things up. That's one thing I don't like about tabs: for certain scenarios it requires a specific spacing to line up with something above it because additional syntax moved it the line above. People definitely do it.

But yes, I do think it would be a lot less work to just do it in the low-level printer. It requires a lot less code changes, and is more straight-forward: it always just indents n spaces. Converting to tabs is super easy. It's just spaces / N, where N is the number of spaces per tab. We can round it down instead of adding more spaces if that's the desired behavior, but note that in a few cases things aren't going to line up.

@rhengles
Copy link
Copy Markdown
Contributor Author

rhengles commented Mar 7, 2017

@jlongster Is it not a lot less work calling simply "indentLevel()" rather than "indent(options.tabWidth)" 33 times? Why do you want to repeat "options.tabWidth" so much? Like I said, this will only change once, it's straightforward, simpler, more readable and specially, gives full control!

You didn't answer to the fact that the purpose of tabs are to allow each user to have their own "tabWidth" in their editor - so you're defeating the purpose of tabs, it will be broken when a user changes their editor tab width!

Like @JedWatson said:

[tabs are] a well established part of our project standards across hundreds of repos, including that we have programmers who prefer 2 or 4 spaces and like being able to choose

@jlongster
Copy link
Copy Markdown
Member

@rhengles It's less work in the lower-level printer, and we should keep that as simple as possible. It's super straight-forward: just indent, that's it. It doesn't bother me to repeat options.tabWidth, it's basically just a constant. You need to introduce multiple commands and add stuff to the lower-level printer which I don't love.

Let me give you an example of why people mix spaces and tabs. Take this code:

type foo =
  | {
      prop1: string,
      prop3: string,
      prop3: string,
    }
  | { prop2: string }

We indent the closing brace of the first object very carefully. It's specifically one indent level + 2 spaces. We need to do this because the opening one is not indented again, it's just bumped over because of syntax. Let's say you were using tabs, indented the closing brace twice, and used 4 spaces for tabs. This is what it would look like:

type foo =
    | {
          prop1: string,
          prop3: string,
          prop3: string,
        }
    | { prop2: string }

Which is wrong. If you all are OK with that, than sure, that's fine I guess. Just weird that some edge cases are going to look wrong!

@jlongster
Copy link
Copy Markdown
Member

Of course most indentation is still done with tabs and the programmer can choose whatever. But in a few rare edge cases we can use spaces to align things like that.

@lydell
Copy link
Copy Markdown
Member

lydell commented Mar 8, 2017

One thing that I commonly here from tabs people is:

Tabs for indentation, spaces for alignment.

That's called "smart tabs".

I see nothing wrong with prettier outputting tabs for indentation, and sometimes 2 spaces or so on top of that for alignment.

@rhengles
Copy link
Copy Markdown
Contributor Author

rhengles commented Mar 8, 2017

Then I guess it would be necessary an option:

--indent [ spaces | tabs | smart-tabs ]

because honestly, an option that mixes tabs and spaces is useless to me.

But is that feature being actively requested by tab users or only being mentioned by space users?

I didn't introduce multiple commands, I introduce one single command and rename another one. If that's the issue, we can rename them with better names.

Maybe indentLevel to indent and indentCustom to align?

Please also imagine for a moment that somewhere, you may "manually indent something a specific number of spaces" that is longer than tabWidth. Now it would output two tabs or more. It is absolutely essential to have control on src/printer.js of how many tabs it would be output when custom aligning something!

@rhengles rhengles force-pushed the indent-tabs-refactor branch 2 times, most recently from 97823bc to 65e6fc6 Compare March 8, 2017 14:36
@rhengles
Copy link
Copy Markdown
Contributor Author

rhengles commented Mar 8, 2017

@jlongster There, with the rename should be much better now.

Note that this would make it trivial to also support smart-tabs, if someone ever requests that.

Haven't I said already that nobody complained about my handling of tabs on prettier-with-tabs? The reason I mention this is because you must take into account all those users who already have a solution to use prettier with tabs and are satisfied with it!

@rhengles rhengles force-pushed the indent-tabs-refactor branch from 65e6fc6 to 660e771 Compare March 9, 2017 01:56
@rhengles
Copy link
Copy Markdown
Contributor Author

rhengles commented Mar 9, 2017

@jlongster Please take another look, with the renaming i'm sure you will love it ❤️

Ah, just to mention it - I tried the project mentioned by @Alhadis on #34, and the only issue it gives with the formatting is because of code indented inside of ternaries, where the code is indented two tabs from one line to the next, but this is expected.

Would you be more willing to accept it if I also add support for smart-tabs?

@rhengles rhengles changed the title [In Discussion] Indent with tabs [RFC] Indent with tabs Mar 9, 2017
@rhengles rhengles force-pushed the indent-tabs-refactor branch 3 times, most recently from 0d5eba6 to c68bb11 Compare March 9, 2017 18:08
@rhengles rhengles force-pushed the indent-tabs-refactor branch from c68bb11 to 3b293b5 Compare March 11, 2017 10:24
@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Mar 15, 2017

I've been chatting in person with @jlongster at React Conf and we came up with a solution that we are both happy with:

This will have minimal impact on the printer code and provide reasonable support for tabs, even for the ternary case where we indent it by a fixed amount of spaces.

Do you think it's reasonable and are you interested in implementing it? If yes, I'd love to review and merge it!

@rhengles
Copy link
Copy Markdown
Contributor Author

Thank you, @vjeux .

I do not think this solution is sufficient. As a tab user, it doesn't fill my needs, and I will have to continue using prettier-with-tabs.

I realize that you guys have reached a final decision. Is there one main reason you can't change indent() and add align() ? I'm really sorry for this, but I can't understand your motivations.

@rhengles rhengles closed this Mar 15, 2017
@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Mar 16, 2017

Arg, I can't re-open the pull request as you deleted your branch. I need to think more about it.

Question to you, if you have nested ternaries and other indentation, you're going to have <tab><space><tab><space>. Isn't it an issue in practice?

@rhengles
Copy link
Copy Markdown
Contributor Author

Actually I didn't delete it, I rebased it. Github should allow it to be reopened...
Please wait, I will create another one for us to discuss it.

@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Mar 16, 2017

image

@vjeux
Copy link
Copy Markdown
Contributor

vjeux commented Mar 16, 2017

It turns out my comment got scrambled by github.

Question to you, if you have nested ternaries and other indentation, you're going to have <tab><space><tab><space>. Isn't it an issue in practice?

@rhengles rhengles mentioned this pull request Mar 16, 2017
@rhengles
Copy link
Copy Markdown
Contributor Author

@vjeux I answered it in #1026

@sylvainpolletvillard
Copy link
Copy Markdown

sylvainpolletvillard commented Mar 19, 2017

you're missing the point of tabs if you're thinking about mixing them with spaces! Please don't do that! People want tabs so they can configure their editor to use a custom tab width!

Huh ? Yes, people do that. An indentation width dependent on the reader's preferences is a major argument in favor of tabs, but loses all its interest if there is only one width where everything is aligned correctly. Smart tabs gives you the best of both worlds. "Use tabs to indent, spaces to align" is a well-known maxim and smart-tabs is already supported in many linters.

I'm a strong supporter of tabs for prettier but only if it is smart tabs, otherwise it would break alignement and we'd better rename this project "uglier".

In the doc-printer, replace --tab-width consecutive spaces with a tab

That would break alignment in many cases, such as:

const x = 1,
      y = 2; // 5 spaces is required to keep things aligned

@rhengles
Copy link
Copy Markdown
Contributor Author

@sylvainpolletvillard

(1): Space alignment only work when they're at the end of the indentation, which means there cannot be tabs after "alignment spaces".

(2): In the entirety of Prettier today, there's only two places that use fixed alignment. They're UnionTypeAnnotation (example) and ConditionalExpression (ternaries). In both of them, there might be expressions inside which have another level of indentation. So would you convert all these sub-expressions indentation into spaces? Would you print tabs after the spaces?

(3): The example you provided is not implemented in Prettier (see #850). But how would you convert the following code to tabs?

const x = 1,
      y = 2, // **6 spaces are required to keep things aligned
      myObject = {
        prop1: {
          x: 1
        },
        prop2: {
          y: 2
        }
      };

@sylvainpolletvillard
Copy link
Copy Markdown

Space alignment only work when they're at the end of the indentation, which means there cannot be tabs after "alignment spaces".

If you put tabs behind spaces in sub-expressions of a space-aligned block, it will keep things aligned but only in the context of this specific block, which may or may not be what you are expecting.

For example, the code you gave in (3) looks okay as it is, but if you had another block just after, the indentation starts to be confusing: in the example below, it is not obvious that both props have the same indentation level.

const x = 1,
      y = 2, 
      myObject = {
        prop: {
          x: 1
        }
      };
const myOtherObj = {
  prop: {
    x: 2
  }
}

So in general, whether it be tabs or spaces, I would recommend for optimal readability to avoid space-aligning a block with sub-expressions.

Yet Prettier cannot refactor code, and you need an answer for (3). I guess tabs after spaces would be globally less surprising, especially for large tab-width (8 spaces for most user agents on the web). But I have no strong opinion about this, since I always respect the rule mentioned above.

@lock lock Bot added the locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting. label Jan 21, 2019
@lock lock Bot locked as resolved and limited conversation to collaborators Jan 21, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

locked-due-to-inactivity Please open a new issue and fill out the template instead of commenting.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants