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

Add tab width option #6848

Merged
merged 1 commit into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions crates/ruff_formatter/src/format_element/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::prelude::tag::GroupMode;
use crate::prelude::*;
use crate::printer::LineEnding;
use crate::source_code::SourceCode;
use crate::{format, write};
use crate::{format, write, TabWidth};
use crate::{
BufferExtensions, Format, FormatContext, FormatElement, FormatOptions, FormatResult, Formatter,
IndentStyle, LineWidth, PrinterOptions,
Expand Down Expand Up @@ -215,13 +215,17 @@ impl FormatOptions for IrFormatOptions {
IndentStyle::Space(2)
}

fn tab_width(&self) -> TabWidth {
TabWidth::default()
}

fn line_width(&self) -> LineWidth {
LineWidth(80)
}

fn as_print_options(&self) -> PrinterOptions {
PrinterOptions {
tab_width: 2,
tab_width: TabWidth::default(),
print_width: self.line_width().into(),
line_ending: LineEnding::LineFeed,
indent_style: IndentStyle::Space(2),
Expand Down
45 changes: 44 additions & 1 deletion crates/ruff_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, Pri
pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS};
pub use group_id::GroupId;
use ruff_text_size::{TextRange, TextSize};
use std::num::ParseIntError;
use std::num::{NonZeroU8, ParseIntError, TryFromIntError};
use std::str::FromStr;

#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
Expand Down Expand Up @@ -108,6 +108,33 @@ impl std::fmt::Display for IndentStyle {
}
}

/// The visual width of a `\t` character.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TabWidth(NonZeroU8);

impl TabWidth {
/// Return the numeric value for this [`LineWidth`]
pub const fn value(&self) -> u32 {
self.0.get() as u32
}
}

impl Default for TabWidth {
fn default() -> Self {
Self(NonZeroU8::new(2).unwrap())
}
}

impl TryFrom<u8> for TabWidth {
type Error = TryFromIntError;

fn try_from(value: u8) -> Result<Self, Self::Error> {
NonZeroU8::try_from(value).map(Self)
}
}

/// Validated value for the `line_width` formatter options
///
/// The allowed range of values is 1..=320
Expand Down Expand Up @@ -213,6 +240,17 @@ pub trait FormatOptions {
/// The indent style.
fn indent_style(&self) -> IndentStyle;

/// The visual width of a tab character.
fn tab_width(&self) -> TabWidth;

/// The visual width of an indent
fn indent_width(&self) -> u32 {
match self.indent_style() {
IndentStyle::Tab => self.tab_width().value(),
IndentStyle::Space(spaces) => u32::from(spaces),
}
}

/// What's the max width of a line. Defaults to 80.
fn line_width(&self) -> LineWidth;

Expand Down Expand Up @@ -264,13 +302,18 @@ impl FormatOptions for SimpleFormatOptions {
self.indent_style
}

fn tab_width(&self) -> TabWidth {
TabWidth::default()
}

fn line_width(&self) -> LineWidth {
self.line_width
}

fn as_print_options(&self) -> PrinterOptions {
PrinterOptions::default()
.with_indent(self.indent_style)
.with_tab_width(self.tab_width())
.with_print_width(self.line_width.into())
}
}
Expand Down
15 changes: 8 additions & 7 deletions crates/ruff_formatter/src/printer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ impl<'a> Printer<'a> {

#[allow(clippy::cast_possible_truncation)]
let char_width = if char == '\t' {
u32::from(self.options.tab_width)
self.options.tab_width.value()
} else {
// SAFETY: A u32 is sufficient to represent the width of a file <= 4GB
char.width().unwrap_or(0) as u32
Expand Down Expand Up @@ -1277,13 +1277,12 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> {

fn fits_text(&mut self, text: &str, args: PrintElementArgs) -> Fits {
let indent = std::mem::take(&mut self.state.pending_indent);
self.state.line_width += u32::from(indent.level())
* u32::from(self.options().indent_width())
+ u32::from(indent.align());
self.state.line_width +=
u32::from(indent.level()) * self.options().indent_width() + u32::from(indent.align());

for c in text.chars() {
let char_width = match c {
'\t' => u32::from(self.options().tab_width),
'\t' => self.options().tab_width.value(),
'\n' => {
if self.must_be_flat {
return Fits::No;
Expand Down Expand Up @@ -1422,7 +1421,9 @@ mod tests {
use crate::prelude::*;
use crate::printer::{LineEnding, PrintWidth, Printer, PrinterOptions};
use crate::source_code::SourceCode;
use crate::{format_args, write, Document, FormatState, IndentStyle, Printed, VecBuffer};
use crate::{
format_args, write, Document, FormatState, IndentStyle, Printed, TabWidth, VecBuffer,
};

fn format(root: &dyn Format<SimpleFormatContext>) -> Printed {
format_with_options(
Expand Down Expand Up @@ -1572,7 +1573,7 @@ two lines`,
fn it_use_the_indent_character_specified_in_the_options() {
let options = PrinterOptions {
indent_style: IndentStyle::Tab,
tab_width: 4,
tab_width: TabWidth::try_from(4).unwrap(),
print_width: PrintWidth::new(19),
..PrinterOptions::default()
};
Expand Down
33 changes: 15 additions & 18 deletions crates/ruff_formatter/src/printer/printer_options/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::{FormatOptions, IndentStyle, LineWidth};
use crate::{FormatOptions, IndentStyle, LineWidth, TabWidth};

/// Options that affect how the [`crate::Printer`] prints the format tokens
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct PrinterOptions {
/// Width of a single tab character (does it equal 2, 4, ... spaces?)
pub tab_width: u8,
pub tab_width: TabWidth,

/// What's the max width of a line. Defaults to 80
pub print_width: PrintWidth,
Expand Down Expand Up @@ -74,23 +74,31 @@ impl PrinterOptions {
self
}

#[must_use]
pub fn with_tab_width(mut self, width: TabWidth) -> Self {
self.tab_width = width;

self
}

pub(crate) fn indent_style(&self) -> IndentStyle {
self.indent_style
}

/// Width of an indent in characters.
pub(super) const fn indent_width(&self) -> u8 {
pub(super) const fn indent_width(&self) -> u32 {
match self.indent_style {
IndentStyle::Tab => self.tab_width,
IndentStyle::Space(count) => count,
IndentStyle::Tab => self.tab_width.value(),
IndentStyle::Space(count) => count as u32,
}
}
}

#[allow(dead_code)]
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub enum LineEnding {
/// Line Feed only (\n), common on Linux and macOS as well as inside git repos
#[default]
LineFeed,

/// Carriage Return + Line Feed characters (\r\n), common on Windows
Expand All @@ -110,14 +118,3 @@ impl LineEnding {
}
}
}

impl Default for PrinterOptions {
fn default() -> Self {
PrinterOptions {
tab_width: 2,
print_width: PrintWidth::default(),
indent_style: IndentStyle::default(),
line_ending: LineEnding::LineFeed,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"tab_width": 8
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@
{
"indent_style": {
"Space": 4
}
},
"tab_width": 8
},
{
"indent_style": {
"Space": 2
}
},
"tab_width": 8
},
{
"indent_style": "Tab"
"indent_style": "Tab",
"tab_width": 8
},
{
"indent_style": "Tab",
"tab_width": 4
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"tab_width": 2
},
{
"tab_width": 4
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Fits with tab width 2
1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234567890"

# Fits with tab width 4
1 + " 0123456789012345678901234567890123456789012345678901234567890123456789012345678"

# Fits with tab width 8
1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234"
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ where
// of 5 characters to avoid it exceeding the line width by 1 reduces the readability.
// * The text is know to never fit: The text can never fit even when parenthesizing if it is longer
// than the configured line width (minus indent).
text_len > 5 && text_len < context.options().line_width().value() as usize
text_len > 5
&& text_len
<= context.options().line_width().value() as usize
- context.options().indent_width() as usize
}

pub(crate) trait NeedsParentheses {
Expand Down
33 changes: 23 additions & 10 deletions crates/ruff_python_formatter/src/expression/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow;

use bitflags::bitflags;

use ruff_formatter::{format_args, write, FormatError};
use ruff_formatter::{format_args, write, FormatError, FormatOptions, TabWidth};
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::{self as ast, ExprConstant, ExprFString, Ranged};
use ruff_python_parser::lexer::{lex_starts_at, LexicalError, LexicalErrorType};
Expand Down Expand Up @@ -682,13 +682,14 @@ fn normalize_string(
/// to the next multiple of 8. This is effectively a port of
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs),
/// which black [calls with the default tab width of 8](https://github.com/psf/black/blob/c36e468794f9256d5e922c399240d49782ba04f1/src/black/strings.py#L61)
fn count_indentation_like_black(line: &str) -> TextSize {
let tab_width: u32 = 8;
fn count_indentation_like_black(line: &str, tab_width: TabWidth) -> TextSize {
let mut indentation = TextSize::default();
for char in line.chars() {
if char == '\t' {
// Pad to the next multiple of tab_width
indentation += TextSize::from(tab_width - (indentation.to_u32().rem_euclid(tab_width)));
indentation += TextSize::from(
tab_width.value() - (indentation.to_u32().rem_euclid(tab_width.value())),
);
} else if char.is_whitespace() {
indentation += char.text_len();
} else {
Expand Down Expand Up @@ -868,7 +869,7 @@ fn format_docstring(string_part: &FormatStringPart, f: &mut PyFormatter) -> Form
.clone()
// We don't want to count whitespace-only lines as miss-indented
.filter(|line| !line.trim().is_empty())
.map(count_indentation_like_black)
.map(|line| count_indentation_like_black(line, f.options().tab_width()))
.min()
.unwrap_or_default();

Expand Down Expand Up @@ -943,7 +944,8 @@ fn format_docstring_line(
// overindented, in which case we strip the additional whitespace (see example in
// [`format_docstring`] doc comment). We then prepend the in-docstring indentation to the
// string.
let indent_len = count_indentation_like_black(trim_end) - stripped_indentation;
let indent_len =
count_indentation_like_black(trim_end, f.options().tab_width()) - stripped_indentation;
let in_docstring_indent = " ".repeat(indent_len.to_usize()) + trim_end.trim_start();
dynamic_text(&in_docstring_indent, Some(offset)).fmt(f)?;
} else {
Expand Down Expand Up @@ -976,12 +978,23 @@ fn format_docstring_line(
#[cfg(test)]
mod tests {
use crate::expression::string::count_indentation_like_black;
use ruff_formatter::TabWidth;

#[test]
fn test_indentation_like_black() {
assert_eq!(count_indentation_like_black("\t \t \t").to_u32(), 24);
assert_eq!(count_indentation_like_black("\t \t").to_u32(), 24);
assert_eq!(count_indentation_like_black("\t\t\t").to_u32(), 24);
assert_eq!(count_indentation_like_black(" ").to_u32(), 4);
let tab_width = TabWidth::try_from(8).unwrap();
assert_eq!(
count_indentation_like_black("\t \t \t", tab_width).to_u32(),
24
);
assert_eq!(
count_indentation_like_black("\t \t", tab_width).to_u32(),
24
);
assert_eq!(
count_indentation_like_black("\t\t\t", tab_width).to_u32(),
24
);
assert_eq!(count_indentation_like_black(" ", tab_width).to_u32(), 4);
}
}
Loading
Loading