JuliaFormatter.jl icon indicating copy to clipboard operation
JuliaFormatter.jl copied to clipboard

Can we indent with tabs rather than spaces?

Open singularitti opened this issue 4 years ago • 11 comments

Some people asked me whether they can indent with tabs instead of spaces. Can JuliaFormatter.jl do that?

singularitti avatar Jun 01 '21 08:06 singularitti

Would that mean using '\t' instead of ' '?

domluna avatar Jun 01 '21 13:06 domluna

To be exact, use one '\t' instead of four ' '.

liurui39660 avatar Jun 01 '21 13:06 liurui39660

what if the number of spaces is different than 4?

domluna avatar Jun 01 '21 13:06 domluna

I don't think it's a problem, the options can be whether to indent with X spaces or 1 tab.

liurui39660 avatar Jun 01 '21 13:06 liurui39660

Maybe instead of indent::Int = 4, we can set indent::Union{Char,String} = ' '^4 by default.

singularitti avatar Jun 01 '21 23:06 singularitti

In general, the right way to indent with tabs is to use them for the "semantic" indent while continuing to use spaces for the "physical" indent. For instance, when indenting with four spaces, JuliaFormatter produces the following code:

@testset "Contrived example" begin
    @test (@test_logs very_very_very_very_very_very_very_very_very_very_long_expression()) ==
          another_very_very_very_very_very_very_very_very_very_very_long_expression() ==
          if contrived_example
              println("42")
              return 1
          else
              return 0
          end
end

The whole @test statement is indented because it lives inside the nested scope of the @testset. In addition, you can see that another_, if, else, and end are all lined up with (@test_logs; the a, i, and two es are directly under the (. They are each indented a total of ten spaces, four to account for @test's indent and an additional six to line up with (@test_logs (since @test␣ is six characters long). The statements inside the if-else-end, being inside an additional nested scope, are indented four additional spaces as expected.

If using tabs to indent, then @test should be indented with a tab, and -- crucially (every formatter, across every language, gets this wrong!) -- everything that is set to line up vertically should be indented with one tab and six spaces (not two tabs!), as using tabs alone for indentation will break the alignment of code. That way, regardless of how wide tabs are displayed on the user's computer, everything is still lined up. Then, inside the if-else-end, the additional indentation should be created with tabs because of the nested scope. Basically, wherever the indentation represents a semantic indent -- an indent that's representing nested scope -- you use tabs. And where indentation is purely physical -- used to line up multiple statements across different lines -- you use spaces. Some examples below, where [] with spaces in between the brackets represents a tab displayed at different user-specified widths.

# tab width = 2
@testset "Contrived example" begin
[]@test (@test_logs very_very_very_very_very_very_very_very_very_very_long_expression()) ==
[]      another_very_very_very_very_very_very_very_very_very_very_long_expression() ==
[]      if contrived_example
[]      []println("42")
[]      []return 1
[]      else
[]      []return 0
[]      end
end

# tab width = 4
@testset "Contrived example" begin
[  ]@test (@test_logs very_very_very_very_very_very_very_very_very_very_long_expression()) ==
[  ]      another_very_very_very_very_very_very_very_very_very_very_long_expression() ==
[  ]      if contrived_example
[  ]      [  ]println("42")
[  ]      [  ]return 1
[  ]      else
[  ]      [  ]return 0
[  ]      end
end

# tab width = 16!?
@testset "Contrived example" begin
[              ]@test (@test_logs very_very_very_very_very_very_very_very_very_very_long_expression()) ==
[              ]      another_very_very_very_very_very_very_very_very_very_very_long_expression() ==
[              ]      if contrived_example
[              ]      [              ]println("42")
[              ]      [              ]return 1
[              ]      else
[              ]      [              ]return 0
[              ]      end
end

Everything stays aligned regardless of the tab size, and you still get the right semantic indent.

rben01 avatar Jun 14 '21 04:06 rben01

@rben01 I think you are mixing the concept of continuation indent and code alignment. I don't know your preference, but for me, I usually turn off all possible code alignment.

My settings for most IDEs are like no code alignment, tab size = 4, indent = 1 tab, continuation indent = 1 tab. So in your example, a in another_... is at the same column of the 2nd t in @test. The if-else-end statement is also indented to the 2nd t.

For the tab size, I think many IDEs have such an option to choose its width.

My idea is: don't decide for users, give them options.

liurui39660 avatar Jun 14 '21 05:06 liurui39660

@liurui39660 Sure, the option is nice. But I don't think that JuliaFormatter currently has that option; it always aligns when there's a continuation (try replacing @test_logs with @test_logs_foo_bar; it'll push the stuff underneath it over). Given that, then, I was just describing the right way to use tabs in this paradigm. Continuation indent is a separate story.

rben01 avatar Jun 14 '21 13:06 rben01

Any progress on this issue? It’s the one thing preventing me from using this package 😅

ProvocaTeach avatar Jul 30 '22 08:07 ProvocaTeach

Can we set the formatter to indent with tabs instead of spaces?

montyvesselinov avatar Sep 23 '22 00:09 montyvesselinov