@@ -83,6 +83,13 @@ impl<'db> DocumentationCommentParser<'db> {
8383 item_id : DocumentableItemId < ' db > ,
8484 documentation_comment : String ,
8585 ) -> Vec < DocumentationCommentToken < ' db > > {
86+ // Build a map of line indices to their leading indentation (number of spaces)
87+ // before markdown parsing removes them.
88+ let line_indents: Vec < usize > = documentation_comment
89+ . lines ( )
90+ . map ( |line| line. len ( ) - line. trim_start ( ) . len ( ) )
91+ . collect ( ) ;
92+
8693 let mut tokens = Vec :: new ( ) ;
8794 let mut current_link: Option < CommentLinkToken < ' db > > = None ;
8895 let mut is_indented_code_block = false ;
@@ -139,7 +146,88 @@ impl<'db> DocumentationCommentParser<'db> {
139146 if is_indented_code_block {
140147 format ! ( " {text}" )
141148 } else {
142- text. to_string ( )
149+ // Process text line by line to restore indentation
150+ let text_str = text. as_ref ( ) ;
151+ let lines: Vec < & str > = text_str. split_inclusive ( '\n' ) . collect ( ) ;
152+
153+ let mut result = String :: new ( ) ;
154+ for ( line_idx, line) in lines. iter ( ) . enumerate ( ) {
155+ let trimmed_line = line. trim ( ) ;
156+
157+ // Check if this is the start of a new line in the original text
158+ let is_new_line = line_idx == 0
159+ && ( tokens. is_empty ( )
160+ || tokens
161+ . last ( )
162+ . and_then ( |last| {
163+ if let DocumentationCommentToken :: Content (
164+ content,
165+ ) = last
166+ {
167+ Some ( content. ends_with ( '\n' ) )
168+ } else {
169+ None
170+ }
171+ } )
172+ . unwrap_or ( true ) ) ;
173+
174+ // For each non-empty line, try to find matching line in
175+ // original text
176+ if !trimmed_line. is_empty ( ) && ( is_new_line || line_idx > 0 ) {
177+ // Find the line in original text that matches this content
178+ // Try exact match first, then partial match
179+ let mut found_line_num = None ;
180+ for ( i, orig_line) in
181+ documentation_comment. lines ( ) . enumerate ( )
182+ {
183+ let trimmed_orig = orig_line. trim ( ) ;
184+ // Exact match (most reliable)
185+ if trimmed_orig == trimmed_line {
186+ found_line_num = Some ( i) ;
187+ break ;
188+ }
189+ }
190+
191+ // If no exact match, try partial match
192+ if found_line_num. is_none ( ) {
193+ for ( i, orig_line) in
194+ documentation_comment. lines ( ) . enumerate ( )
195+ {
196+ let trimmed_orig = orig_line. trim ( ) ;
197+ // Check if one is a prefix of the other (for cases
198+ // where markdown splits text)
199+ if ( trimmed_line. len ( ) >= 5
200+ && trimmed_orig. starts_with (
201+ & trimmed_line[ ..trimmed_line
202+ . len ( )
203+ . min ( trimmed_orig. len ( ) ) ] ,
204+ ) )
205+ || ( trimmed_orig. len ( ) >= 5
206+ && trimmed_line. starts_with (
207+ & trimmed_orig[ ..trimmed_orig
208+ . len ( )
209+ . min ( trimmed_line. len ( ) ) ] ,
210+ ) )
211+ {
212+ found_line_num = Some ( i) ;
213+ break ;
214+ }
215+ }
216+ }
217+
218+ if let Some ( line_num) = found_line_num
219+ && line_num < line_indents. len ( )
220+ {
221+ let indent = line_indents[ line_num] ;
222+ if indent > 0 {
223+ result. push_str ( & " " . repeat ( indent) ) ;
224+ }
225+ }
226+ }
227+ result. push_str ( line) ;
228+ }
229+
230+ if result. is_empty ( ) { text. to_string ( ) } else { result }
143231 }
144232 } ;
145233 tokens. push ( DocumentationCommentToken :: Content ( text) ) ;
0 commit comments