@@ -13,7 +13,8 @@ impl HybridWrapper {
1313 Self {
1414 code_block_regex : Regex :: new ( r"```[\s\S]*?```" ) . unwrap ( ) ,
1515 link_regex : Regex :: new ( r"https?://[^\s]+|\[([^\]]+)\]\(([^)]+)\)" ) . unwrap ( ) ,
16- inline_code_regex : Regex :: new ( r"`[^`]+`" ) . unwrap ( ) ,
16+ // 支持单反引号和双反引号的行内代码(如 `code` 或 ``code``)
17+ inline_code_regex : Regex :: new ( r"`{1,3}[^`]+?`{1,3}" ) . unwrap ( ) ,
1718 }
1819 }
1920}
@@ -24,38 +25,120 @@ impl WordWrapper for HybridWrapper {
2425 return String :: new ( ) ;
2526 }
2627
27- // 解析文本段
28- let segments = self . parse_segments ( text) ;
28+ // 如果 preserve_paragraphs 为 true,需要先处理段落,然后再处理段
29+ if config. preserve_paragraphs {
30+ let mut result = String :: new ( ) ;
31+ let paragraphs: Vec < & str > = text. split ( "\n \n " ) . collect ( ) ;
32+
33+ for ( i, paragraph) in paragraphs. iter ( ) . enumerate ( ) {
34+ if i > 0 {
35+ result. push_str ( "\n \n " ) ; // 段落之间保留空行
36+ }
37+
38+ // 检查段落内是否有换行符,如果有则保留
39+ if paragraph. contains ( '\n' ) {
40+ let lines: Vec < & str > = paragraph. lines ( ) . collect ( ) ;
41+ for ( j, line) in lines. iter ( ) . enumerate ( ) {
42+ if j > 0 {
43+ result. push ( '\n' ) ;
44+ }
45+ if !line. trim ( ) . is_empty ( ) {
46+ // 对每一行单独处理,但不在段级别使用 preserve_paragraphs
47+ let mut line_config = config. clone ( ) ;
48+ line_config. preserve_paragraphs = false ;
49+ let segments = self . parse_segments ( line. trim ( ) ) ;
50+ let wrapped_line = self . wrap_segments ( & segments, & line_config) ;
51+ result. push_str ( & wrapped_line) ;
52+ } else {
53+ result. push ( '\n' ) ;
54+ }
55+ }
56+ } else {
57+ // 段落内没有换行符,直接处理整个段落
58+ // 但不在段级别使用 preserve_paragraphs,因为段落处理已经在更高层级完成
59+ let mut para_config = config. clone ( ) ;
60+ para_config. preserve_paragraphs = false ;
61+ let segments = self . parse_segments ( paragraph. trim ( ) ) ;
62+ let wrapped_paragraph = self . wrap_segments ( & segments, & para_config) ;
63+ result. push_str ( & wrapped_paragraph) ;
64+ }
65+ }
2966
30- // 处理分段文本
31- self . wrap_segments ( & segments, config)
67+ result
68+ } else {
69+ // 解析文本段
70+ let segments = self . parse_segments ( text) ;
71+
72+ // 处理分段文本
73+ self . wrap_segments ( & segments, config)
74+ }
3275 }
3376
3477 fn wrap_segments ( & self , segments : & [ TextSegment ] , config : & WrapConfig ) -> String {
3578 let mut result = String :: new ( ) ;
3679 let mut current_line = String :: new ( ) ;
3780 let mut current_width = config. indent . width ( ) ;
3881
39- for segment in segments {
40- let processed = self . process_segment ( segment, config) ;
82+ // 检查是否是列表项(第一个段是 PlainText 且以 "- " 开头)
83+ let is_list_item = segments
84+ . first ( )
85+ . and_then ( |s| {
86+ if let TextSegment :: PlainText ( text) = s {
87+ Some ( text. trim_start ( ) . starts_with ( "-" ) )
88+ } else {
89+ None
90+ }
91+ } )
92+ . unwrap_or ( false ) ;
93+
94+ for ( idx, segment) in segments. iter ( ) . enumerate ( ) {
95+ // 对于行内代码,需要特殊处理:作为不可分割的单元
96+ // 行内代码已经包含反引号(从正则匹配中),不需要通过process_segment重复添加
97+ let processed = match segment {
98+ TextSegment :: InlineCode ( code) => {
99+ // 行内代码已经包含反引号,直接使用,作为不可分割的单元
100+ code. clone ( )
101+ }
102+ _ => self . process_segment ( segment, config) ,
103+ } ;
41104
42- if current_width + processed. width ( ) <= config. max_width {
105+ let processed_width = processed. width ( ) ;
106+
107+ // 检查当前行是否能放下这个段
108+ // 对于行内代码,如果当前行已经有内容且放不下,需要整体换行
109+ // 对于普通文本,可以继续处理
110+ // 特殊处理:如果是列表项,且当前行只包含列表标记("-"),不要换行
111+ let is_list_marker_only = is_list_item && idx == 0 && current_line. trim ( ) == "-" ;
112+ let should_wrap =
113+ current_width + processed_width > config. max_width && !is_list_marker_only;
114+
115+ if !should_wrap {
43116 current_line. push_str ( & processed) ;
44- current_width += processed . width ( ) ;
117+ current_width += processed_width ;
45118 } else {
46119 // 当前行放不下,需要换行
47- if !current_line. is_empty ( ) {
120+ // 特殊处理:如果是列表项,且当前行只包含列表标记("-"),不要换行
121+ if !current_line. is_empty ( ) && !is_list_marker_only {
48122 result. push_str ( & current_line) ;
49123 result. push ( '\n' ) ;
50124 }
51125
52126 // 新行处理
53- current_line = config. indent . clone ( ) ;
54- if !current_line. is_empty ( ) {
55- current_line. push_str ( & config. hanging_indent ) ;
127+ if is_list_marker_only {
128+ // 当前行只有 "-",不要换行,继续在当前行处理
129+ if !current_line. ends_with ( ' ' ) {
130+ current_line. push ( ' ' ) ;
131+ }
132+ current_line. push_str ( & processed) ;
133+ current_width = current_line. width ( ) ;
134+ } else {
135+ current_line = config. indent . clone ( ) ;
136+ if !current_line. is_empty ( ) {
137+ current_line. push_str ( & config. hanging_indent ) ;
138+ }
139+ current_line. push_str ( & processed) ;
140+ current_width = current_line. width ( ) ;
56141 }
57- current_line. push_str ( & processed) ;
58- current_width = current_line. width ( ) ;
59142 }
60143 }
61144
@@ -155,7 +238,12 @@ impl HybridWrapper {
155238 match segment {
156239 TextSegment :: PlainText ( text) => {
157240 if config. handle_code_blocks {
158- self . wrap_plain_text ( text, config)
241+ // 在 wrap_segments 中,PlainText 段不应该使用 wrap_with_paragraphs
242+ // 因为段落处理应该在更高层级(wrap_text)进行
243+ // 这里只处理单词级别的换行,不处理段落
244+ let mut no_paragraph_config = config. clone ( ) ;
245+ no_paragraph_config. preserve_paragraphs = false ;
246+ self . wrap_plain_text ( text, & no_paragraph_config)
159247 } else {
160248 text. clone ( )
161249 }
@@ -175,7 +263,9 @@ impl HybridWrapper {
175263 }
176264 }
177265 TextSegment :: InlineCode ( code) => {
178- format ! ( "`{}`" , code)
266+ // InlineCode段已经包含反引号(从正则匹配中),直接返回
267+ // 行内代码应该作为不可分割的单元,不进行额外的包装处理
268+ code. clone ( )
179269 }
180270 }
181271 }
@@ -207,7 +297,8 @@ impl HybridWrapper {
207297 result. push ( '\n' ) ;
208298 }
209299 if !line. trim ( ) . is_empty ( ) {
210- let wrapped_line = self . wrap_without_paragraphs ( line. trim ( ) , config) ;
300+ let trimmed = line. trim ( ) ;
301+ let wrapped_line = self . wrap_without_paragraphs ( trimmed, config) ;
211302 result. push_str ( & wrapped_line) ;
212303 } else {
213304 result. push ( '\n' ) ;
@@ -237,6 +328,9 @@ impl HybridWrapper {
237328 let word_width = word. width ( ) ;
238329 let separator_width = if current_line. is_empty ( ) { 0 } else { 1 } ;
239330
331+ // 特殊处理:如果当前行只包含 "-"(列表项标记),不要换行
332+ let is_list_marker_only = current_line. trim ( ) == "-" ;
333+
240334 if current_width + separator_width + word_width <= config. max_width {
241335 if !current_line. is_empty ( ) {
242336 current_line. push ( ' ' ) ;
@@ -247,10 +341,17 @@ impl HybridWrapper {
247341 // 当前单词放不下,需要换行
248342 if config. break_long_words && word_width > config. max_width {
249343 // 长单词强制换行
250- if !current_line. is_empty ( ) {
344+ // 特殊处理:如果当前行只包含 "-",不要换行,而是继续在当前行处理
345+ if !is_list_marker_only && !current_line. is_empty ( ) {
251346 lines. push ( current_line) ;
252347 current_line = String :: new ( ) ;
253348 current_width = config. indent . width ( ) ;
349+ } else if is_list_marker_only {
350+ // 当前行只有 "-",不要换行,继续在当前行处理长单词
351+ if !current_line. ends_with ( ' ' ) {
352+ current_line. push ( ' ' ) ;
353+ current_width += 1 ;
354+ }
254355 }
255356
256357 let mut remaining = word;
@@ -262,7 +363,7 @@ impl HybridWrapper {
262363 self . break_word_at_width ( remaining, available)
263364 } ;
264365
265- if !current_line. is_empty ( ) {
366+ if !current_line. is_empty ( ) && !current_line . ends_with ( ' ' ) {
266367 current_line. push ( ' ' ) ;
267368 }
268369 current_line. push_str ( part) ;
@@ -279,12 +380,23 @@ impl HybridWrapper {
279380 }
280381 } else {
281382 // 普通换行
282- if !current_line. is_empty ( ) {
283- lines. push ( current_line) ;
383+ // 特殊处理:如果当前行只包含 "-",不要换行,而是继续在当前行处理
384+ if is_list_marker_only {
385+ // 当前行只有 "-",不要换行,继续在当前行处理
386+ if !current_line. ends_with ( ' ' ) {
387+ current_line. push ( ' ' ) ;
388+ }
389+ current_line. push_str ( word) ;
390+ current_width = current_line. width ( ) ;
391+ } else {
392+ // 正常换行
393+ if !current_line. is_empty ( ) {
394+ lines. push ( current_line) ;
395+ }
396+ current_line = config. hanging_indent . clone ( ) ;
397+ current_line. push_str ( word) ;
398+ current_width = current_line. width ( ) ;
284399 }
285- current_line = config. hanging_indent . clone ( ) ;
286- current_line. push_str ( word) ;
287- current_width = current_line. width ( ) ;
288400 }
289401 }
290402 }
0 commit comments