diff --git a/crates/biome_grit_formatter/src/lib.rs b/crates/biome_grit_formatter/src/lib.rs index 74e5c55a8935..d370e3672f1d 100644 --- a/crates/biome_grit_formatter/src/lib.rs +++ b/crates/biome_grit_formatter/src/lib.rs @@ -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; @@ -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 { + 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 { + biome_formatter::format_sub_tree(root, GritFormatLanguage::new(options)) +} + pub(crate) trait FormatNodeRule where N: AstNode, diff --git a/crates/biome_service/src/file_handlers/grit.rs b/crates/biome_service/src/file_handlers/grit.rs index c1f0133c1165..c54628b97f55 100644 --- a/crates/biome_service/src/file_handlers/grit.rs +++ b/crates/biome_service/src/file_handlers/grit.rs @@ -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)] @@ -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 }, } @@ -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 { + let options = settings.format_options::(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 { + let options = settings.format_options::(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)