155 lines
4.4 KiB
Rust
155 lines
4.4 KiB
Rust
use crate::buffer::*;
|
|
use crate::util::*;
|
|
|
|
mod buffer;
|
|
mod component;
|
|
mod element;
|
|
mod expr;
|
|
mod util;
|
|
|
|
/// 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.
|
|
///
|
|
/// In a "perfect" world we would have tiny edits to preserve things like cursor states and selections. The API here makes
|
|
/// it possible to migrate to a more precise modification approach in the future without breaking existing code.
|
|
///
|
|
/// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if
|
|
/// multiple edits are applied in a single file without tracking text shifts.
|
|
#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)]
|
|
pub struct FormattedBlock {
|
|
/// The new contents of the block
|
|
pub formatted: String,
|
|
|
|
/// The line number of the first line of the block.
|
|
pub start: usize,
|
|
|
|
/// The end of the block, exclusive.
|
|
pub end: usize,
|
|
}
|
|
|
|
/// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
|
|
///
|
|
/// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
|
|
pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|
let mut formatted_blocks = Vec::new();
|
|
let mut last_bracket_end = 0;
|
|
|
|
use triple_accel::{levenshtein_search, Match};
|
|
|
|
for Match { end, start, k } in levenshtein_search(b"rsx! {", contents.as_bytes()) {
|
|
if k > 1 {
|
|
continue;
|
|
}
|
|
|
|
// ensure the marker is not nested
|
|
if start < last_bracket_end {
|
|
continue;
|
|
}
|
|
|
|
let mut indent_level = {
|
|
// walk backwards from start until we find a new line
|
|
let mut lines = contents[..start].lines().rev();
|
|
match lines.next() {
|
|
Some(line) => {
|
|
if line.starts_with("//") || line.starts_with("///") {
|
|
continue;
|
|
}
|
|
|
|
line.chars().take_while(|c| *c == ' ').count() / 4
|
|
}
|
|
None => 0,
|
|
}
|
|
};
|
|
|
|
let remaining = &contents[end - 1..];
|
|
let bracket_end = find_bracket_end(remaining).unwrap();
|
|
let sub_string = &contents[end..bracket_end + end - 1];
|
|
last_bracket_end = bracket_end + end - 1;
|
|
|
|
let mut new = fmt_block(sub_string, indent_level).unwrap();
|
|
|
|
if new.len() <= 80 && !new.contains('\n') {
|
|
new = format!(" {new} ");
|
|
|
|
// if the new string is not multiline, don't try to adjust the marker ending
|
|
// We want to trim off any indentation that there might be
|
|
indent_level = 0;
|
|
}
|
|
|
|
let end_marker = end + bracket_end - indent_level * 4 - 1;
|
|
|
|
if new == contents[end..end_marker] {
|
|
continue;
|
|
}
|
|
|
|
formatted_blocks.push(FormattedBlock {
|
|
formatted: new,
|
|
start: end,
|
|
end: end_marker,
|
|
});
|
|
}
|
|
|
|
formatted_blocks
|
|
}
|
|
|
|
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
|
let mut buf = Buffer {
|
|
src: block.lines().map(|f| f.to_string()).collect(),
|
|
indent: indent_level,
|
|
..Buffer::default()
|
|
};
|
|
|
|
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
|
|
|
|
// Oneliner optimization
|
|
if buf.is_short_children(&body.roots).is_some() {
|
|
buf.write_ident(&body.roots[0]).unwrap();
|
|
} else {
|
|
buf.write_body_indented(&body.roots).unwrap();
|
|
}
|
|
|
|
// writing idents leaves the final line ended at the end of the last ident
|
|
if buf.buf.contains('\n') {
|
|
buf.new_line().unwrap();
|
|
}
|
|
|
|
buf.consume()
|
|
}
|
|
|
|
pub fn apply_format(input: &str, block: FormattedBlock) -> String {
|
|
let start = block.start;
|
|
let end = block.end;
|
|
|
|
let (left, _) = input.split_at(start);
|
|
let (_, right) = input.split_at(end);
|
|
|
|
dbg!(&block.formatted);
|
|
|
|
format!("{}{}{}", left, block.formatted, right)
|
|
}
|
|
|
|
// Apply all the blocks
|
|
pub fn apply_formats(input: &str, blocks: Vec<FormattedBlock>) -> String {
|
|
let mut out = String::new();
|
|
|
|
let mut last = 0;
|
|
|
|
for FormattedBlock {
|
|
formatted,
|
|
start,
|
|
end,
|
|
} in blocks
|
|
{
|
|
let prefix = &input[last..start];
|
|
out.push_str(prefix);
|
|
out.push_str(&formatted);
|
|
last = end;
|
|
}
|
|
|
|
let suffix = &input[last..];
|
|
out.push_str(suffix);
|
|
|
|
out
|
|
}
|