Skip to content

Commit

Permalink
feat(grit): add range formatting (#4525)
Browse files Browse the repository at this point in the history
Co-authored-by: Arend van Beelen jr. <[email protected]>
  • Loading branch information
ematipico and arendjr authored Nov 13, 2024
1 parent 9611497 commit 6d75a4c
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 7 deletions.
37 changes: 35 additions & 2 deletions crates/biome_grit_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ use biome_formatter::{
comments::Comments,
prelude::*,
trivia::{format_dangling_comments, format_leading_comments, format_trailing_comments},
write, CstFormatContext, Format, FormatLanguage, FormatResult, Formatted,
write, CstFormatContext, Format, FormatLanguage, FormatResult, Formatted, Printed,
};
use biome_grit_syntax::{GritLanguage, GritSyntaxNode};
use comments::GritCommentStyle;

pub(crate) use crate::context::GritFormatContext;

use biome_rowan::AstNode;
use biome_rowan::{AstNode, TextRange};
use context::GritFormatOptions;
use cst::FormatGritSyntaxNode;

Expand All @@ -33,6 +33,39 @@ pub fn format_node(
biome_formatter::format_node(root, GritFormatLanguage::new(options))
}

/// Formats a range within a file, supported by Biome
///
/// This runs a simple heuristic to determine the initial indentation
/// level of the node based on the provided [GritFormatOptions], which
/// must match the current indentation of the file. Additionally,
/// because the reformatting happens only locally the resulting code
/// will be indented with the same level as the original selection,
/// even if it's a mismatch from the rest of the block the selection is in
///
/// It returns a [Printed] result with a range corresponding to the
/// range of the input that was effectively overwritten by the formatter
pub fn format_range(
options: GritFormatOptions,
root: &GritSyntaxNode,
range: TextRange,
) -> FormatResult<Printed> {
biome_formatter::format_range(root, range, GritFormatLanguage::new(options))
}

/// Formats a single node within a file, supported by Biome.
///
/// This runs a simple heuristic to determine the initial indentation
/// level of the node based on the provided [GritFormatOptions], which
/// must match the current indentation of the file. Additionally,
/// because the reformatting happens only locally the resulting code
/// will be indented with the same level as the original selection,
/// even if it's a mismatch from the rest of the block the selection is in
///
/// Returns the [Printed] code.
pub fn format_sub_tree(options: GritFormatOptions, root: &GritSyntaxNode) -> FormatResult<Printed> {
biome_formatter::format_sub_tree(root, GritFormatLanguage::new(options))
}

pub(crate) trait FormatNodeRule<N>
where
N: AstNode<Language = GritLanguage>,
Expand Down
63 changes: 58 additions & 5 deletions crates/biome_service/src/file_handlers/grit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ use crate::{
};
use biome_analyze::{AnalyzerConfiguration, AnalyzerOptions};
use biome_diagnostics::{Diagnostic, Severity};
use biome_formatter::{IndentStyle, IndentWidth, LineEnding, LineWidth, Printed};
use biome_formatter::{FormatError, IndentStyle, IndentWidth, LineEnding, LineWidth, Printed};
use biome_fs::BiomePath;
use biome_grit_formatter::{context::GritFormatOptions, format_node};
use biome_grit_formatter::{context::GritFormatOptions, format_node, format_sub_tree};
use biome_grit_parser::parse_grit_with_cache;
use biome_grit_syntax::{GritLanguage, GritRoot, GritSyntaxNode};
use biome_parser::AnyParse;
use biome_rowan::NodeCache;
use biome_rowan::{NodeCache, TextRange, TextSize, TokenAtOffset};
use tracing::debug_span;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -129,8 +129,8 @@ impl ExtensionHandler for GritFileHandler {
},
formatter: FormatterCapabilities {
format: Some(format),
format_range: None,
format_on_type: None,
format_range: Some(format_range),
format_on_type: Some(format_on_type),
},
search: SearchCapabilities { search: None },
}
Expand Down Expand Up @@ -196,6 +196,59 @@ fn format(
}
}

#[tracing::instrument(level = "debug", skip_all)]
fn format_range(
biome_path: &BiomePath,
document_file_source: &DocumentFileSource,
parse: AnyParse,
settings: WorkspaceSettingsHandle,
range: TextRange,
) -> Result<Printed, WorkspaceError> {
let options = settings.format_options::<GritLanguage>(biome_path, document_file_source);

let tree = parse.syntax();
let printed = biome_grit_formatter::format_range(options, &tree, range)?;
Ok(printed)
}

#[tracing::instrument(level = "debug", skip_all)]
fn format_on_type(
biome_path: &BiomePath,
document_file_source: &DocumentFileSource,
parse: AnyParse,
settings: WorkspaceSettingsHandle,
offset: TextSize,
) -> Result<Printed, WorkspaceError> {
let options = settings.format_options::<GritLanguage>(biome_path, document_file_source);

let tree = parse.syntax();

let range = tree.text_range();
if offset < range.start() || offset > range.end() {
return Err(WorkspaceError::FormatError(FormatError::RangeError {
input: TextRange::at(offset, TextSize::from(0)),
tree: range,
}));
}

let token = match tree.token_at_offset(offset) {
// File is empty, do nothing
TokenAtOffset::None => panic!("empty file"),
TokenAtOffset::Single(token) => token,
// The cursor should be right after the closing character that was just typed,
// select the previous token as the correct one
TokenAtOffset::Between(token, _) => token,
};

let root_node = match token.parent() {
Some(node) => node,
None => panic!("found a token with no parent"),
};

let printed = format_sub_tree(options, &root_node)?;
Ok(printed)
}

#[tracing::instrument(level = "debug", skip(params))]
fn lint(params: LintParams) -> LintResults {
let _ = debug_span!("Linting Grit file", path =? params.path, language =? params.language)
Expand Down

0 comments on commit 6d75a4c

Please sign in to comment.