Skip to content

Introduce IndentWidth#7301

Merged
MichaReiser merged 1 commit intomainfrom
indent-width
Sep 13, 2023
Merged

Introduce IndentWidth#7301
MichaReiser merged 1 commit intomainfrom
indent-width

Conversation

@MichaReiser
Copy link
Member

Summary

This PR introduces the new configuration IndentWidth that specifies the visual width of a \t character or the number of spaces of an indent when using IndentStyle::Space.
IndentWidth replaces TabWidth and the count on the IndentStyle::Space(count) setting.

The motivation for this change is to reduce the configuration options and align it with Prettier and editor config that both use a single width for the tab width and number of spaces in an indent.

Unifying the two settings makes sense in my view because both \t and the number of spaces in an indentation are about the width of an indentation and they should use consistent values.
The only downside of the change is that we no longer support the configuration used by black's docstring test where it uses 4-space indentation but a tab width of 8.

Test Plan

cargo test.

@MichaReiser
Copy link
Member Author

Current dependencies on/for this PR:

This comment was auto-generated by Graphite.

@MichaReiser MichaReiser added the formatter Related to the formatter label Sep 12, 2023
Copy link
Member Author

Choose a reason for hiding this comment

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

This test uses an indentation of 4 spaces and tab width of 8. We can't support this anymore but I think that's fine.

Copy link
Member

Choose a reason for hiding this comment

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

It seems reasonable though hopefully we're not overlooking some valid usage. This is all inferred, right, so there's no way to GitHub Code Search to find users that might rely on this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Editorconfig doesn't support different visual width for tabs and indent. And I think that makes sense. You don't need a tab-width setting if you don't use tabs.

This particular test case applies only to unformatted code where black assumes a tab size of 8 for tabs in docstrings before normalizing the docstring indentation. This is only a heuristic and I think it probably works best anyway if the space indentation and tab indentation sizes match, because that's probably how you aligned your docstring before even if you happened to have mixed tabs and spaces.

Copy link
Member Author

Choose a reason for hiding this comment

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

These tests were off, probably because we now use best fitting for strings now. I verified manually that they don't exceed the line width for the configured indent width.

@MichaReiser MichaReiser requested a review from konstin September 12, 2023 11:37
Copy link
Member

Choose a reason for hiding this comment

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

It seems reasonable though hopefully we're not overlooking some valid usage. This is all inferred, right, so there's no way to GitHub Code Search to find users that might rely on this?

Comment on lines +731 to +738
fn count_indentation_like_black(line: &str, tab_width: TabWidth) -> TextSize {
/// with the exception that Ruff uses [`IndentWidth`] for the tab width.
fn count_indentation_like_black(line: &str, indent_width: IndentWidth) -> TextSize {
let mut indentation = TextSize::default();
let indent_width = indent_width.value();
for char in line.chars() {
if char == '\t' {
// Pad to the next multiple of tab_width
indentation += TextSize::from(
tab_width.value() - (indentation.to_u32().rem_euclid(tab_width.value())),
);
indentation +=
Copy link
Member

Choose a reason for hiding this comment

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

Both black and cpython itself have this hardcoded to 8, e.g.

print(inspect.cleandoc('''
def a():
    """doc line 1
    doc line 2
\tdoc line 3
    """
'''))

expands to

def a():
    """doc line 1
    doc line 2
        doc line 3
    """

Given that this is a cpython choice, i don't think we should have an option to overwrite it.

Copy link
Member Author

@MichaReiser MichaReiser Sep 12, 2023

Choose a reason for hiding this comment

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

Thanks for bringing this up. I wasn't aware of this (although this PR doesn't change the configurable nature of tab width).

I think the reason why Black hardcoded this is simply because black doesn't support tab indentation and choosing the default tab size used by cpython seems a good choice.

Picking the configured indent-width still seems reasonable to me, except we foresee issues when using indent-style: tab with any tab-width value other than 8. For example, does it mean that the output of inspect.cleancode has incorrect indentation or is it okay for as long as all code uses tabs?

Copy link
Member

Choose a reason for hiding this comment

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

I've tested the semantics:

source = '''
class A:
\tdef a():
\t\t"""doc line 1
\t\tdoc line 2
\t\t  doc line 3
\t\t"""

def a():
\t"""doc line 1
\tdoc line 2
\t  doc line 3
\t"""

class A2:
\tdef a():
\t\t"""doc line 1
\t\t\tdoc line 2
\t\t  doc line 3
\t\t"""

def a2():
\t"""doc line 1
\t\tdoc line 2
\t  doc line 3
\t"""
'''
exec(source)
docs = [A.a.__doc__, a.__doc__, A2.a.__doc__, a2.__doc__]
for doc in docs:
    print(f"---\n{inspect.cleandoc(doc)}")

prints

---
doc line 1
doc line 2
  doc line 3
---
doc line 1
doc line 2
  doc line 3
---
doc line 1
      doc line 2
doc line 3
---
doc line 1
      doc line 2
doc line 3

I've also checked that

source2 = '''
def f():
 \tprint()
 \tprint()

def f():
              \t          \t     print()
              \t          \t     print()
'''
exec(source2)

passes. Python seems to not care about how you mix tabs and spaces as long as you're consistent.

Inside of docstrings, cleandoc doesn't mind inconsistent indentation and will just pad to multiples of 8:

source2 = '''
class A:
\tdef a():
\t\t"""doc line 1
 \t    \t doc line 2
\t \t  doc line 3
\t\t"""
'''
exec(source2)
print(f"---\n{inspect.cleandoc(A.a.__doc__)}")

My suggestion would be to special case count_indentation_like_black by hard code padding to 8 and converting to spaces in docstrings.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for taking a closer look at this. I updated the PR to hardcode 8 for the normalization.

@konstin
Copy link
Member

konstin commented Sep 12, 2023

I like the general improvement, but unfortunately cpython gives us hardcoded precedence about tabs handling in docstrings

@MichaReiser MichaReiser force-pushed the indent-width branch 2 times, most recently from 866ee1e to dc50532 Compare September 13, 2023 11:56
@MichaReiser MichaReiser requested a review from konstin September 13, 2023 11:57
@MichaReiser MichaReiser merged commit 2d9b398 into main Sep 13, 2023
@MichaReiser MichaReiser deleted the indent-width branch September 13, 2023 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

formatter Related to the formatter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants