Merge pull request #1597 from ColonelThirtyTwo/fmt-indent

Use indentation settings in dx fmt and vscode extension
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2023-12-06 22:06:16 -08:00
commit 8f1791c039
24 files changed
+294 -53

No files matched your search

+5 -2
View File
@@ -124,8 +124,6 @@ jobs:
} }
steps: steps:
- uses: actions/checkout@v4
- name: install stable - name: install stable
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
with: with:
@@ -141,6 +139,11 @@ jobs:
workspaces: core -> ../target workspaces: core -> ../target
save-if: ${{ matrix.features.key == 'all' }} save-if: ${{ matrix.features.key == 'all' }}
- name: Install rustfmt
run: rustup component add rustfmt
- uses: actions/checkout@v4
- name: test - name: test
run: | run: |
${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }} ${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }}
+6 -5
View File
@@ -8,13 +8,14 @@ use std::fmt::{Result, Write};
use dioxus_rsx::IfmtInput; use dioxus_rsx::IfmtInput;
use crate::write_ifmt; use crate::{indent::IndentOptions, write_ifmt};
/// The output buffer that tracks indent and string /// The output buffer that tracks indent and string
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Buffer { pub struct Buffer {
pub buf: String, pub buf: String,
pub indent: usize, pub indent_level: usize,
pub indent: IndentOptions,
} }
impl Buffer { impl Buffer {
@@ -31,16 +32,16 @@ impl Buffer {
} }
pub fn tab(&mut self) -> Result { pub fn tab(&mut self) -> Result {
self.write_tabs(self.indent) self.write_tabs(self.indent_level)
} }
pub fn indented_tab(&mut self) -> Result { pub fn indented_tab(&mut self) -> Result {
self.write_tabs(self.indent + 1) self.write_tabs(self.indent_level + 1)
} }
pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result { pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result {
for _ in 0..num { for _ in 0..num {
write!(self.buf, " ")? write!(self.buf, "{}", self.indent.indent_str())?
} }
Ok(()) Ok(())
} }
+6 -6
View File
@@ -66,7 +66,7 @@ impl Writer<'_> {
// check if we have a lot of attributes // check if we have a lot of attributes
let attr_len = self.is_short_attrs(attributes); let attr_len = self.is_short_attrs(attributes);
let is_short_attr_list = (attr_len + self.out.indent * 4) < 80; let is_short_attr_list = (attr_len + self.out.indent_level * 4) < 80;
let children_len = self.is_short_children(children); let children_len = self.is_short_children(children);
let is_small_children = children_len.is_some(); let is_small_children = children_len.is_some();
@@ -86,7 +86,7 @@ impl Writer<'_> {
// if we have few children and few attributes, make it a one-liner // if we have few children and few attributes, make it a one-liner
if is_short_attr_list && is_small_children { if is_short_attr_list && is_small_children {
if children_len.unwrap() + attr_len + self.out.indent * 4 < 100 { if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 {
opt_level = ShortOptimization::Oneliner; opt_level = ShortOptimization::Oneliner;
} else { } else {
opt_level = ShortOptimization::PropsOnTop; opt_level = ShortOptimization::PropsOnTop;
@@ -185,11 +185,11 @@ impl Writer<'_> {
} }
while let Some(attr) = attr_iter.next() { while let Some(attr) = attr_iter.next() {
self.out.indent += 1; self.out.indent_level += 1;
if !sameline { if !sameline {
self.write_comments(attr.attr.start())?; self.write_comments(attr.attr.start())?;
} }
self.out.indent -= 1; self.out.indent_level -= 1;
if !sameline { if !sameline {
self.out.indented_tabbed_line()?; self.out.indented_tabbed_line()?;
@@ -398,14 +398,14 @@ impl Writer<'_> {
for idx in start.line..end.line { for idx in start.line..end.line {
let line = &self.src[idx]; let line = &self.src[idx];
if line.trim().starts_with("//") { if line.trim().starts_with("//") {
for _ in 0..self.out.indent + 1 { for _ in 0..self.out.indent_level + 1 {
write!(self.out, " ")? write!(self.out, " ")?
} }
writeln!(self.out, "{}", line.trim()).unwrap(); writeln!(self.out, "{}", line.trim()).unwrap();
} }
} }
for _ in 0..self.out.indent { for _ in 0..self.out.indent_level {
write!(self.out, " ")? write!(self.out, " ")?
} }
+3 -3
View File
@@ -29,7 +29,7 @@ impl Writer<'_> {
let first_line = &self.src[start.line - 1]; let first_line = &self.src[start.line - 1];
write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?; write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?;
let prev_block_indent_level = crate::leading_whitespaces(first_line) / 4; let prev_block_indent_level = self.out.indent.count_indents(first_line);
for (id, line) in self.src[start.line..end.line].iter().enumerate() { for (id, line) in self.src[start.line..end.line].iter().enumerate() {
writeln!(self.out)?; writeln!(self.out)?;
@@ -43,9 +43,9 @@ impl Writer<'_> {
}; };
// trim the leading whitespace // trim the leading whitespace
let previous_indent = crate::leading_whitespaces(line) / 4; let previous_indent = self.out.indent.count_indents(line);
let offset = previous_indent.saturating_sub(prev_block_indent_level); let offset = previous_indent.saturating_sub(prev_block_indent_level);
let required_indent = self.out.indent + offset; let required_indent = self.out.indent_level + offset;
self.out.write_tabs(required_indent)?; self.out.write_tabs(required_indent)?;
let line = line.trim_start(); let line = line.trim_start();
+108
View File
@@ -0,0 +1,108 @@
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IndentType {
Spaces,
Tabs,
}
#[derive(Debug, Clone)]
pub struct IndentOptions {
width: usize,
indent_string: String,
}
impl IndentOptions {
pub fn new(typ: IndentType, width: usize) -> Self {
assert_ne!(width, 0, "Cannot have an indent width of 0");
Self {
width,
indent_string: match typ {
IndentType::Tabs => "\t".into(),
IndentType::Spaces => " ".repeat(width),
},
}
}
/// Gets a string containing one indent worth of whitespace
pub fn indent_str(&self) -> &str {
&self.indent_string
}
/// Computes the line length in characters, counting tabs as the indent width.
pub fn line_length(&self, line: &str) -> usize {
line.chars()
.map(|ch| if ch == '\t' { self.width } else { 1 })
.sum()
}
/// Estimates how many times the line has been indented.
pub fn count_indents(&self, mut line: &str) -> usize {
let mut indent = 0;
while !line.is_empty() {
// Try to count tabs
let num_tabs = line.chars().take_while(|ch| *ch == '\t').count();
if num_tabs > 0 {
indent += num_tabs;
line = &line[num_tabs..];
continue;
}
// Try to count spaces
let num_spaces = line.chars().take_while(|ch| *ch == ' ').count();
if num_spaces >= self.width {
// Intentionally floor here to take only the amount of space that matches an indent
let num_space_indents = num_spaces / self.width;
indent += num_space_indents;
line = &line[num_space_indents * self.width..];
continue;
}
// Line starts with either non-indent characters or an unevent amount of spaces,
// so no more indent remains.
break;
}
indent
}
}
impl Default for IndentOptions {
fn default() -> Self {
Self::new(IndentType::Spaces, 4)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn count_indents() {
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents("no indentation here!"),
0
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
1
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
2
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
2
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\tv += 2"),
2
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\t v += 2"),
2
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 2).count_indents(" v += 2"),
2
);
}
}
+12 -15
View File
@@ -16,8 +16,11 @@ mod collect_macros;
mod component; mod component;
mod element; mod element;
mod expr; mod expr;
mod indent;
mod writer; mod writer;
pub use indent::{IndentOptions, IndentType};
/// A modification to the original file to be applied by an IDE /// A modification to the original file to be applied by an IDE
/// ///
/// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes. /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
@@ -47,7 +50,7 @@ pub struct FormattedBlock {
/// back to the file precisely. /// back to the file precisely.
/// ///
/// Nested blocks of RSX will be handled automatically /// Nested blocks of RSX will be handled automatically
pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> { pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
let mut formatted_blocks = Vec::new(); let mut formatted_blocks = Vec::new();
let parsed = syn::parse_file(contents).unwrap(); let parsed = syn::parse_file(contents).unwrap();
@@ -61,6 +64,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
} }
let mut writer = Writer::new(contents); let mut writer = Writer::new(contents);
writer.out.indent = indent;
// Don't parse nested macros // Don't parse nested macros
let mut end_span = LineColumn { column: 0, line: 0 }; let mut end_span = LineColumn { column: 0, line: 0 };
@@ -76,7 +80,10 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
let rsx_start = macro_path.span().start(); let rsx_start = macro_path.span().start();
writer.out.indent = leading_whitespaces(writer.src[rsx_start.line - 1]) / 4; writer.out.indent_level = writer
.out
.indent
.count_indents(writer.src[rsx_start.line - 1]);
write_body(&mut writer, &body); write_body(&mut writer, &body);
@@ -159,12 +166,13 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
buf.consume() buf.consume()
} }
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> { pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap(); let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
let mut buf = Writer::new(block); let mut buf = Writer::new(block);
buf.out.indent = indent_level; buf.out.indent = indent;
buf.out.indent_level = indent_level;
write_body(&mut buf, &body); write_body(&mut buf, &body);
@@ -230,14 +238,3 @@ pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::f
let display = DisplayIfmt(input); let display = DisplayIfmt(input);
write!(writable, "{}", display) write!(writable, "{}", display)
} }
pub fn leading_whitespaces(input: &str) -> usize {
input
.chars()
.map_while(|c| match c {
' ' => Some(1),
'\t' => Some(4),
_ => None,
})
.sum()
}
+2 -2
View File
@@ -96,11 +96,11 @@ impl<'a> Writer<'a> {
// Push out the indent level and write each component, line by line // Push out the indent level and write each component, line by line
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result { pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
self.out.indent += 1; self.out.indent_level += 1;
self.write_body_no_indent(children)?; self.write_body_no_indent(children)?;
self.out.indent -= 1; self.out.indent_level -= 1;
Ok(()) Ok(())
} }
+1 -1
View File
@@ -12,7 +12,7 @@ macro_rules! twoway {
#[test] #[test]
fn $name() { fn $name() {
let src = include_str!(concat!("./samples/", stringify!($name), ".rsx")); let src = include_str!(concat!("./samples/", stringify!($name), ".rsx"));
let formatted = dioxus_autofmt::fmt_file(src); let formatted = dioxus_autofmt::fmt_file(src, Default::default());
let out = dioxus_autofmt::apply_formats(src, formatted); let out = dioxus_autofmt::apply_formats(src, formatted);
// normalize line endings // normalize line endings
let out = out.replace("\r", ""); let out = out.replace("\r", "");
+10 -5
View File
@@ -1,10 +1,12 @@
use dioxus_autofmt::{IndentOptions, IndentType};
macro_rules! twoway { macro_rules! twoway {
($val:literal => $name:ident) => { ($val:literal => $name:ident ($indent:expr)) => {
#[test] #[test]
fn $name() { fn $name() {
let src_right = include_str!(concat!("./wrong/", $val, ".rsx")); let src_right = include_str!(concat!("./wrong/", $val, ".rsx"));
let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx")); let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx"));
let formatted = dioxus_autofmt::fmt_file(src_wrong); let formatted = dioxus_autofmt::fmt_file(src_wrong, $indent);
let out = dioxus_autofmt::apply_formats(src_wrong, formatted); let out = dioxus_autofmt::apply_formats(src_wrong, formatted);
// normalize line endings // normalize line endings
@@ -16,8 +18,11 @@ macro_rules! twoway {
}; };
} }
twoway!("comments" => comments); twoway!("comments-4sp" => comments_4sp (IndentOptions::new(IndentType::Spaces, 4)));
twoway!("comments-tab" => comments_tab (IndentOptions::new(IndentType::Tabs, 4)));
twoway!("multi" => multi); twoway!("multi-4sp" => multi_4sp (IndentOptions::new(IndentType::Spaces, 4)));
twoway!("multi-tab" => multi_tab (IndentOptions::new(IndentType::Tabs, 4)));
twoway!("multiexpr" => multiexpr); twoway!("multiexpr-4sp" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4)));
twoway!("multiexpr-tab" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4)));
@@ -0,0 +1,7 @@
rsx! {
div {
// Comments
class: "asdasd",
"hello world"
}
}
@@ -0,0 +1,5 @@
rsx! {
div {
// Comments
class: "asdasd", "hello world" }
}
@@ -0,0 +1,3 @@
fn app(cx: Scope) -> Element {
cx.render(rsx! { div { "hello world" } })
}
@@ -0,0 +1,5 @@
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {"hello world" }
})
}
@@ -0,0 +1,8 @@
fn ItWroks() {
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
left,
right
}
})
}
@@ -0,0 +1,5 @@
fn ItWroks() {
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
})
}
+62 -6
View File
@@ -1,3 +1,4 @@
use dioxus_autofmt::{IndentOptions, IndentType};
use futures::{stream::FuturesUnordered, StreamExt}; use futures::{stream::FuturesUnordered, StreamExt};
use std::{fs, path::Path, process::exit}; use std::{fs, path::Path, process::exit};
@@ -35,7 +36,8 @@ impl Autoformat {
} }
if let Some(raw) = self.raw { if let Some(raw) = self.raw {
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0) { let indent = indentation_for(".")?;
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
println!("{}", inner); println!("{}", inner);
} else { } else {
// exit process with error // exit process with error
@@ -46,17 +48,21 @@ impl Autoformat {
// Format single file // Format single file
if let Some(file) = self.file { if let Some(file) = self.file {
let file_content = if file == "-" { let file_content;
let indent;
if file == "-" {
indent = indentation_for(".")?;
let mut contents = String::new(); let mut contents = String::new();
std::io::stdin().read_to_string(&mut contents)?; std::io::stdin().read_to_string(&mut contents)?;
Ok(contents) file_content = Ok(contents);
} else { } else {
fs::read_to_string(&file) indent = indentation_for(".")?;
file_content = fs::read_to_string(&file);
}; };
match file_content { match file_content {
Ok(s) => { Ok(s) => {
let edits = dioxus_autofmt::fmt_file(&s); let edits = dioxus_autofmt::fmt_file(&s, indent);
let out = dioxus_autofmt::apply_formats(&s, edits); let out = dioxus_autofmt::apply_formats(&s, edits);
if file == "-" { if file == "-" {
print!("{}", out); print!("{}", out);
@@ -93,6 +99,12 @@ async fn autoformat_project(check: bool) -> Result<()> {
let mut files_to_format = vec![]; let mut files_to_format = vec![];
collect_rs_files(&crate_config.crate_dir, &mut files_to_format); collect_rs_files(&crate_config.crate_dir, &mut files_to_format);
if files_to_format.is_empty() {
return Ok(());
}
let indent = indentation_for(&files_to_format[0])?;
let counts = files_to_format let counts = files_to_format
.into_iter() .into_iter()
.filter(|file| { .filter(|file| {
@@ -104,10 +116,11 @@ async fn autoformat_project(check: bool) -> Result<()> {
}) })
.map(|path| async { .map(|path| async {
let _path = path.clone(); let _path = path.clone();
let _indent = indent.clone();
let res = tokio::spawn(async move { let res = tokio::spawn(async move {
let contents = tokio::fs::read_to_string(&path).await?; let contents = tokio::fs::read_to_string(&path).await?;
let edits = dioxus_autofmt::fmt_file(&contents); let edits = dioxus_autofmt::fmt_file(&contents, _indent.clone());
let len = edits.len(); let len = edits.len();
if !edits.is_empty() { if !edits.is_empty() {
@@ -151,6 +164,49 @@ async fn autoformat_project(check: bool) -> Result<()> {
Ok(()) Ok(())
} }
fn indentation_for(file_or_dir: impl AsRef<Path>) -> Result<IndentOptions> {
let out = std::process::Command::new("cargo")
.args(["fmt", "--", "--print-config", "current"])
.arg(file_or_dir.as_ref())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::inherit())
.output()?;
if !out.status.success() {
return Err(Error::CargoError("cargo fmt failed".into()));
}
let config = String::from_utf8_lossy(&out.stdout);
let hard_tabs = config
.lines()
.find(|line| line.starts_with("hard_tabs "))
.and_then(|line| line.split_once('='))
.map(|(_, value)| value.trim() == "true")
.ok_or_else(|| {
Error::RuntimeError("Could not find hard_tabs option in rustfmt config".into())
})?;
let tab_spaces = config
.lines()
.find(|line| line.starts_with("tab_spaces "))
.and_then(|line| line.split_once('='))
.map(|(_, value)| value.trim().parse::<usize>())
.ok_or_else(|| {
Error::RuntimeError("Could not find tab_spaces option in rustfmt config".into())
})?
.map_err(|_| {
Error::RuntimeError("Could not parse tab_spaces option in rustfmt config".into())
})?;
Ok(IndentOptions::new(
if hard_tabs {
IndentType::Tabs
} else {
IndentType::Spaces
},
tab_spaces,
))
}
fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) { fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {
let Ok(folder) = folder.read_dir() else { let Ok(folder) = folder.read_dir() else {
return; return;
+39 -7
View File
@@ -1,17 +1,39 @@
//! This file exports functions into the vscode extension //! This file exports functions into the vscode extension
use dioxus_autofmt::FormattedBlock; use dioxus_autofmt::{FormattedBlock, IndentOptions, IndentType};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
pub fn format_rsx(raw: String) -> String { pub fn format_rsx(raw: String, use_tabs: bool, indent_size: usize) -> String {
let block = dioxus_autofmt::fmt_block(&raw, 0); let block = dioxus_autofmt::fmt_block(
&raw,
0,
IndentOptions::new(
if use_tabs {
IndentType::Tabs
} else {
IndentType::Spaces
},
indent_size,
),
);
block.unwrap() block.unwrap()
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn format_selection(raw: String) -> String { pub fn format_selection(raw: String, use_tabs: bool, indent_size: usize) -> String {
let block = dioxus_autofmt::fmt_block(&raw, 0); let block = dioxus_autofmt::fmt_block(
&raw,
0,
IndentOptions::new(
if use_tabs {
IndentType::Tabs
} else {
IndentType::Spaces
},
indent_size,
),
);
block.unwrap() block.unwrap()
} }
@@ -35,8 +57,18 @@ impl FormatBlockInstance {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn format_file(contents: String) -> FormatBlockInstance { pub fn format_file(contents: String, use_tabs: bool, indent_size: usize) -> FormatBlockInstance {
let _edits = dioxus_autofmt::fmt_file(&contents); let _edits = dioxus_autofmt::fmt_file(
&contents,
IndentOptions::new(
if use_tabs {
IndentType::Tabs
} else {
IndentType::Spaces
},
indent_size,
),
);
let out = dioxus_autofmt::apply_formats(&contents, _edits.clone()); let out = dioxus_autofmt::apply_formats(&contents, _edits.clone());
FormatBlockInstance { new: out, _edits } FormatBlockInstance { new: out, _edits }
} }
+7 -1
View File
@@ -90,7 +90,13 @@ function fmtDocument(document: vscode.TextDocument) {
if (!editor) return; // Need an editor to apply text edits. if (!editor) return; // Need an editor to apply text edits.
const contents = editor.document.getText(); const contents = editor.document.getText();
const formatted = dioxus.format_file(contents); let tabSize: number;
if (typeof editor.options.tabSize === 'number') {
tabSize = editor.options.tabSize;
} else {
tabSize = 4;
}
const formatted = dioxus.format_file(contents, !editor.options.insertSpaces, tabSize);
// Replace the entire text document // Replace the entire text document
// Yes, this is a bit heavy handed, but the dioxus side doesn't know the line/col scheme that vscode is using // Yes, this is a bit heavy handed, but the dioxus side doesn't know the line/col scheme that vscode is using