Skip to content

Commit 2f02be4

Browse files
committed
Add support for reading air.toml in the CLI and LSP
1 parent f41a27e commit 2f02be4

38 files changed

+1616
-168
lines changed

Cargo.lock

Lines changed: 76 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ line_ending = { path = "./crates/line_ending" }
3232
lsp = { path = "./crates/lsp" }
3333
lsp_test = { path = "./crates/lsp_test" }
3434
tests_macros = { path = "./crates/tests_macros" }
35+
workspace = { path = "./crates/workspace" }
3536

3637
anyhow = "1.0.89"
3738
assert_matches = "1.5.0"
@@ -59,14 +60,17 @@ line-index = "0.1.2"
5960
memchr = "2.7.4"
6061
path-absolutize = "3.1.1"
6162
proc-macro2 = "1.0.86"
62-
serde = { version = "1.0.215", features = ["derive"] }
63+
rustc-hash = "2.1.0"
64+
serde = "1.0.215"
6365
serde_json = "1.0.132"
6466
struct-field-names-as-array = "0.3.0"
6567
strum = "0.26"
68+
tempfile = "3.9.0"
6669
time = "0.3.37"
6770
thiserror = "2.0.5"
6871
tokio = { version = "1.41.1" }
6972
tokio-util = "0.7.12"
73+
toml = "0.8.19"
7074
# For https://github.com/ebkalderon/tower-lsp/pull/428
7175
tower-lsp = { git = "https://github.com/lionel-/tower-lsp", branch = "bugfix/patches" }
7276
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
@@ -124,7 +128,6 @@ unnecessary_join = "warn"
124128
unnested_or_patterns = "warn"
125129
unreadable_literal = "warn"
126130
verbose_bit_mask = "warn"
127-
zero_sized_map_values = "warn"
128131

129132
# restriction
130133
cfg_not_test = "warn"

crates/air/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ lsp = { workspace = true }
2828
thiserror = { workspace = true }
2929
tokio = "1.41.1"
3030
tracing = { workspace = true }
31+
workspace = { workspace = true }
3132

3233
[dev-dependencies]
33-
tempfile = "3.9.0"
34+
tempfile = { workspace = true }
3435

3536
[lints]
3637
workspace = true

crates/air/src/commands/format.rs

Lines changed: 19 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,32 @@ use std::path::PathBuf;
88
use air_r_formatter::context::RFormatOptions;
99
use air_r_parser::RParserOptions;
1010
use fs::relativize_path;
11-
use ignore::DirEntry;
1211
use itertools::Either;
1312
use itertools::Itertools;
14-
use line_ending::LineEnding;
1513
use thiserror::Error;
14+
use workspace::resolve::discover_r_file_paths;
15+
use workspace::resolve::SettingsResolver;
16+
use workspace::settings::FormatSettings;
17+
use workspace::settings::Settings;
1618

1719
use crate::args::FormatCommand;
1820
use crate::ExitStatus;
1921

2022
pub(crate) fn format(command: FormatCommand) -> anyhow::Result<ExitStatus> {
2123
let mode = FormatMode::from_command(&command);
22-
let paths = resolve_paths(&command.paths);
24+
25+
let paths = discover_r_file_paths(&command.paths);
26+
27+
let mut resolver = SettingsResolver::new(Settings::default());
28+
resolver.load_from_paths(&command.paths)?;
2329

2430
let (actions, errors): (Vec<_>, Vec<_>) = paths
2531
.into_iter()
2632
.map(|path| match path {
27-
Ok(path) => format_file(path, mode),
33+
Ok(path) => {
34+
let settings = resolver.resolve_or_fallback(&path);
35+
format_file(path, mode, &settings.format)
36+
}
2837
Err(err) => Err(err.into()),
2938
})
3039
.partition_map(|result| match result {
@@ -99,62 +108,6 @@ fn write_changed(actions: &[FormatFileAction], f: &mut impl Write) -> io::Result
99108
Ok(())
100109
}
101110

102-
fn resolve_paths(paths: &[PathBuf]) -> Vec<Result<PathBuf, ignore::Error>> {
103-
let paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).collect();
104-
105-
let (first_path, paths) = paths
106-
.split_first()
107-
.expect("Clap should ensure at least 1 path is supplied.");
108-
109-
// TODO: Parallel directory visitor
110-
let mut builder = ignore::WalkBuilder::new(first_path);
111-
112-
for path in paths {
113-
builder.add(path);
114-
}
115-
116-
let mut out = Vec::new();
117-
118-
for path in builder.build() {
119-
match path {
120-
Ok(entry) => {
121-
if let Some(path) = is_valid_path(entry) {
122-
out.push(Ok(path));
123-
}
124-
}
125-
Err(err) => {
126-
out.push(Err(err));
127-
}
128-
}
129-
}
130-
131-
out
132-
}
133-
134-
// Decide whether or not to accept an `entry` based on include/exclude rules.
135-
fn is_valid_path(entry: DirEntry) -> Option<PathBuf> {
136-
// Ignore directories
137-
if entry.file_type().map_or(true, |ft| ft.is_dir()) {
138-
return None;
139-
}
140-
141-
// Accept all files that are passed-in directly, even non-R files
142-
if entry.depth() == 0 {
143-
let path = entry.into_path();
144-
return Some(path);
145-
}
146-
147-
// Otherwise check if we should accept this entry
148-
// TODO: Many other checks based on user exclude/includes
149-
let path = entry.into_path();
150-
151-
if !fs::has_r_extension(&path) {
152-
return None;
153-
}
154-
155-
Some(path)
156-
}
157-
158111
pub(crate) enum FormatFileAction {
159112
Formatted(PathBuf),
160113
Unchanged,
@@ -166,18 +119,15 @@ impl FormatFileAction {
166119
}
167120
}
168121

169-
// TODO: Take workspace `FormatOptions` that get resolved to `RFormatOptions`
170-
// for the formatter here. Respect user specified `LineEnding` option too, and
171-
// only use inferred endings when `FormatOptions::LineEnding::Auto` is used.
172-
fn format_file(path: PathBuf, mode: FormatMode) -> Result<FormatFileAction, FormatCommandError> {
122+
fn format_file(
123+
path: PathBuf,
124+
mode: FormatMode,
125+
settings: &FormatSettings,
126+
) -> Result<FormatFileAction, FormatCommandError> {
173127
let source = std::fs::read_to_string(&path)
174128
.map_err(|err| FormatCommandError::Read(path.clone(), err))?;
175129

176-
let line_ending = match line_ending::infer(&source) {
177-
LineEnding::Lf => biome_formatter::LineEnding::Lf,
178-
LineEnding::Crlf => biome_formatter::LineEnding::Crlf,
179-
};
180-
let options = RFormatOptions::default().with_line_ending(line_ending);
130+
let options = settings.to_format_options(&source);
181131

182132
let formatted = match format_source(source.as_str(), options) {
183133
Ok(formatted) => formatted,

crates/air_r_formatter/src/context.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use biome_formatter::TransformSourceMap;
1717
use crate::comments::FormatRLeadingComment;
1818
use crate::comments::RCommentStyle;
1919
use crate::comments::RComments;
20+
use crate::options::MagicLineBreak;
2021

2122
pub struct RFormatContext {
2223
options: RFormatOptions,
@@ -77,6 +78,10 @@ pub struct RFormatOptions {
7778

7879
/// The max width of a line. Defaults to 80.
7980
line_width: LineWidth,
81+
82+
// TODO: Actually use this internally!
83+
/// The behavior of magic line breaks.
84+
magic_line_break: MagicLineBreak,
8085
}
8186

8287
impl RFormatOptions {
@@ -106,6 +111,11 @@ impl RFormatOptions {
106111
self
107112
}
108113

114+
pub fn with_magic_line_break(mut self, magic_line_break: MagicLineBreak) -> Self {
115+
self.magic_line_break = magic_line_break;
116+
self
117+
}
118+
109119
pub fn set_indent_style(&mut self, indent_style: IndentStyle) {
110120
self.indent_style = indent_style;
111121
}
@@ -121,6 +131,10 @@ impl RFormatOptions {
121131
pub fn set_line_width(&mut self, line_width: LineWidth) {
122132
self.line_width = line_width;
123133
}
134+
135+
pub fn set_magic_line_break(&mut self, magic_line_break: MagicLineBreak) {
136+
self.magic_line_break = magic_line_break;
137+
}
124138
}
125139

126140
impl FormatOptions for RFormatOptions {

crates/air_r_formatter/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::cst::FormatRSyntaxNode;
2121
pub mod comments;
2222
pub mod context;
2323
mod cst;
24+
pub mod options;
2425
mod prelude;
2526
mod r;
2627
pub(crate) mod separated;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod magic_line_break;
2+
3+
pub use magic_line_break::*;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::fmt::Display;
2+
use std::str::FromStr;
3+
4+
#[derive(Debug, Default, Clone, Copy, Eq, Hash, PartialEq)]
5+
pub enum MagicLineBreak {
6+
/// Respect
7+
#[default]
8+
Respect,
9+
/// Ignore
10+
Ignore,
11+
}
12+
13+
impl MagicLineBreak {
14+
/// Returns `true` if magic line breaks should be respected.
15+
pub const fn is_respect(&self) -> bool {
16+
matches!(self, MagicLineBreak::Respect)
17+
}
18+
19+
/// Returns `true` if magic line breaks should be ignored.
20+
pub const fn is_ignore(&self) -> bool {
21+
matches!(self, MagicLineBreak::Ignore)
22+
}
23+
}
24+
25+
impl FromStr for MagicLineBreak {
26+
type Err = &'static str;
27+
28+
fn from_str(s: &str) -> Result<Self, Self::Err> {
29+
match s {
30+
"respect" => Ok(Self::Respect),
31+
"ignore" => Ok(Self::Ignore),
32+
_ => Err("Unsupported value for this option"),
33+
}
34+
}
35+
}
36+
37+
impl Display for MagicLineBreak {
38+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39+
match self {
40+
MagicLineBreak::Respect => std::write!(f, "Respect"),
41+
MagicLineBreak::Ignore => std::write!(f, "Ignore"),
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)