@@ -11,13 +11,15 @@ use itertools::Itertools;
1111use log:: { error, warn} ;
1212use rayon:: iter:: Either :: { Left , Right } ;
1313use rayon:: iter:: { IntoParallelRefIterator , ParallelIterator } ;
14+ use regex:: { Captures , Regex } ;
1415use ruff_db:: diagnostic:: {
1516 Annotation , Diagnostic , DiagnosticId , DisplayDiagnosticConfig , Severity , Span ,
1617} ;
1718use ruff_linter:: message:: { EmitterContext , create_panic_diagnostic, render_diagnostics} ;
1819use ruff_linter:: settings:: types:: OutputFormat ;
1920use ruff_notebook:: NotebookIndex ;
2021use ruff_python_parser:: ParseError ;
22+ use ruff_python_trivia:: textwrap:: { dedent, indent} ;
2123use rustc_hash:: { FxHashMap , FxHashSet } ;
2224use thiserror:: Error ;
2325use tracing:: debug;
@@ -489,8 +491,65 @@ pub(crate) fn format_source(
489491 formatted,
490492 ) ) )
491493 }
492- SourceKind :: Markdown ( source) => {
493- unimplemented ! ( )
494+ SourceKind :: Markdown ( unformatted_document) => {
495+ // adapted from blacken-docs
496+ // https://github.com/adamchainz/blacken-docs/blob/fb107c1dce25f9206e29297aaa1ed7afc2980a5a/src/blacken_docs/__init__.py#L17
497+ let code_block_regex = Regex :: new (
498+ r"(?imsx)
499+ (?<before>
500+ ^(?<indent>\ *)```[^\S\r\n]*
501+ (?:python|py|python3|py3)
502+ (?:\ .*?)?\n
503+ )
504+ (?<code>.*?)
505+ (?<after>
506+ ^\ *```[^\S\r\n]*$
507+ )
508+ " ,
509+ )
510+ . unwrap ( ) ;
511+
512+ let mut changed = false ;
513+ let formatted_document =
514+ code_block_regex. replace_all ( unformatted_document, |capture : & Captures | {
515+ let ( original, [ before, code_indent, unformatted_code, after] ) =
516+ capture. extract ( ) ;
517+
518+ let unformatted_code = dedent ( unformatted_code) ;
519+ let options = settings. to_format_options ( source_type, & unformatted_code, path) ;
520+
521+ let formatted_code = if let Some ( _range) = range {
522+ unimplemented ! ( )
523+ } else {
524+ // Using `Printed::into_code` requires adding `ruff_formatter` as a direct dependency, and I suspect that Rust can optimize the closure away regardless.
525+ #[ expect( clippy:: redundant_closure_for_method_calls) ]
526+ format_module_source ( & unformatted_code, options)
527+ . map ( |formatted| formatted. into_code ( ) )
528+ } ;
529+
530+ // TODO: figure out how to properly raise errors from inside closure
531+ if let Ok ( formatted_code) = formatted_code {
532+ if formatted_code. len ( ) == unformatted_code. len ( )
533+ && formatted_code == * unformatted_code
534+ {
535+ original. to_string ( )
536+ } else {
537+ changed = true ;
538+ let formatted_code = indent ( formatted_code. as_str ( ) , code_indent) ;
539+ format ! ( "{before}{formatted_code}{after}" )
540+ }
541+ } else {
542+ original. to_string ( )
543+ }
544+ } ) ;
545+
546+ if changed {
547+ Ok ( FormattedSource :: Formatted ( SourceKind :: Markdown (
548+ formatted_document. to_string ( ) ,
549+ ) ) )
550+ } else {
551+ Ok ( FormattedSource :: Unchanged )
552+ }
494553 }
495554 }
496555}
0 commit comments