@@ -23,6 +23,17 @@ pub fn cmd_render_stream(rx: Receiver<ReplyStreamEvent>, abort: SharedAbortSigna
23
23
dump ( markdown_render. render ( & output) , 1 ) ;
24
24
} else {
25
25
buffer = format ! ( "{buffer}{text}" ) ;
26
+ if !( markdown_render. is_code_block ( )
27
+ || buffer. len ( ) < 60
28
+ || buffer. starts_with ( '#' )
29
+ || buffer. starts_with ( '>' )
30
+ || buffer. starts_with ( '|' ) )
31
+ {
32
+ if let Some ( ( output, remain) ) = split_line ( & buffer) {
33
+ dump ( markdown_render. render_line_stateless ( & output) , 0 ) ;
34
+ buffer = remain
35
+ }
36
+ }
26
37
}
27
38
}
28
39
ReplyStreamEvent :: Done => {
@@ -35,3 +46,215 @@ pub fn cmd_render_stream(rx: Receiver<ReplyStreamEvent>, abort: SharedAbortSigna
35
46
}
36
47
Ok ( ( ) )
37
48
}
49
+
50
+ fn split_line ( line : & str ) -> Option < ( String , String ) > {
51
+ let mut balance: Vec < Kind > = Vec :: new ( ) ;
52
+ let chars: Vec < char > = line. chars ( ) . collect ( ) ;
53
+ let mut index = 0 ;
54
+ let len = chars. len ( ) ;
55
+ while index < len - 1 {
56
+ let ch = chars[ index] ;
57
+ if balance. is_empty ( )
58
+ && ( ( matches ! ( ch, ',' | '.' | ';' ) && chars[ index + 1 ] . is_whitespace ( ) )
59
+ || matches ! ( ch, ',' | '。' | ';' ) )
60
+ {
61
+ let ( output, remain) = chars. split_at ( index + 1 ) ;
62
+ return Some ( ( output. iter ( ) . collect ( ) , remain. iter ( ) . collect ( ) ) ) ;
63
+ }
64
+ if index + 2 < len && do_balance ( & mut balance, & chars[ index..=index + 2 ] ) {
65
+ index += 3 ;
66
+ continue ;
67
+ }
68
+ if do_balance ( & mut balance, & chars[ index..=index + 1 ] ) {
69
+ index += 2 ;
70
+ continue ;
71
+ }
72
+ do_balance ( & mut balance, & chars[ index..index + 1 ] ) ;
73
+ index += 1
74
+ }
75
+
76
+ None
77
+ }
78
+
79
+ #[ derive( Debug , Clone , Copy , Eq , PartialEq ) ]
80
+ enum Kind {
81
+ ParentheseStart ,
82
+ ParentheseEnd ,
83
+ BracketStart ,
84
+ BracketEnd ,
85
+ Asterisk ,
86
+ Asterisk2 ,
87
+ SingleQuota ,
88
+ DoubleQuota ,
89
+ Tilde ,
90
+ Tilde2 ,
91
+ Backtick ,
92
+ Backtick3 ,
93
+ }
94
+
95
+ impl Kind {
96
+ fn from_chars ( chars : & [ char ] ) -> Option < Self > {
97
+ let kind = match chars. len ( ) {
98
+ 1 => match chars[ 0 ] {
99
+ '(' => Kind :: ParentheseStart ,
100
+ ')' => Kind :: ParentheseEnd ,
101
+ '[' => Kind :: BracketStart ,
102
+ ']' => Kind :: BracketEnd ,
103
+ '*' => Kind :: Asterisk ,
104
+ '\'' => Kind :: SingleQuota ,
105
+ '"' => Kind :: DoubleQuota ,
106
+ '~' => Kind :: Tilde ,
107
+ '`' => Kind :: Backtick ,
108
+ _ => return None ,
109
+ } ,
110
+ 2 if chars[ 0 ] == chars[ 1 ] => match chars[ 0 ] {
111
+ '*' => Kind :: Asterisk2 ,
112
+ '~' => Kind :: Tilde2 ,
113
+ _ => return None ,
114
+ } ,
115
+ 3 => {
116
+ if chars == [ '`' , '`' , '`' ] {
117
+ Kind :: Backtick3
118
+ } else {
119
+ return None ;
120
+ }
121
+ }
122
+ _ => return None ,
123
+ } ;
124
+ Some ( kind)
125
+ }
126
+ }
127
+
128
+ fn do_balance ( balance : & mut Vec < Kind > , chars : & [ char ] ) -> bool {
129
+ if let Some ( kind) = Kind :: from_chars ( chars) {
130
+ let last = balance. last ( ) ;
131
+ match ( kind, last) {
132
+ ( Kind :: ParentheseStart | Kind :: BracketStart , _) => {
133
+ balance. push ( kind) ;
134
+ true
135
+ }
136
+ ( Kind :: ParentheseEnd , Some ( & Kind :: ParentheseStart ) ) => {
137
+ balance. pop ( ) ;
138
+ true
139
+ }
140
+ ( Kind :: BracketEnd , Some ( & Kind :: BracketStart ) ) => {
141
+ balance. pop ( ) ;
142
+ true
143
+ }
144
+ ( Kind :: Asterisk , Some ( & Kind :: Asterisk ) )
145
+ | ( Kind :: Asterisk2 , Some ( & Kind :: Asterisk2 ) )
146
+ | ( Kind :: SingleQuota , Some ( & Kind :: SingleQuota ) )
147
+ | ( Kind :: DoubleQuota , Some ( & Kind :: DoubleQuota ) )
148
+ | ( Kind :: Tilde , Some ( & Kind :: Tilde ) )
149
+ | ( Kind :: Tilde2 , Some ( & Kind :: Tilde2 ) )
150
+ | ( Kind :: Backtick , Some ( & Kind :: Backtick ) )
151
+ | ( Kind :: Backtick3 , Some ( & Kind :: Backtick3 ) ) => {
152
+ balance. pop ( ) ;
153
+ true
154
+ }
155
+ ( Kind :: Asterisk , _)
156
+ | ( Kind :: Asterisk2 , _)
157
+ | ( Kind :: SingleQuota , _)
158
+ | ( Kind :: DoubleQuota , _)
159
+ | ( Kind :: Tilde , _)
160
+ | ( Kind :: Tilde2 , _)
161
+ | ( Kind :: Backtick , _)
162
+ | ( Kind :: Backtick3 , _) => {
163
+ balance. push ( kind) ;
164
+ true
165
+ }
166
+ _ => false ,
167
+ }
168
+ } else {
169
+ false
170
+ }
171
+ }
172
+
173
+ #[ cfg( test) ]
174
+ mod tests {
175
+ use super :: * ;
176
+
177
+ macro_rules! assert_split_line {
178
+ ( $a: literal, $b: literal, true ) => {
179
+ assert_eq!(
180
+ split_line( & format!( "{}{}" , $a, $b) ) ,
181
+ Some ( ( $a. into( ) , $b. into( ) ) )
182
+ ) ;
183
+ } ;
184
+ ( $a: literal, $b: literal, false ) => {
185
+ assert_eq!( split_line( & format!( "{}{}" , $a, $b) ) , None ) ;
186
+ } ;
187
+ }
188
+
189
+ #[ test]
190
+ fn test_split_line ( ) {
191
+ assert_split_line ! (
192
+ "Lorem ipsum dolor sit amet," ,
193
+ " consectetur adipiscing elit." ,
194
+ true
195
+ ) ;
196
+ assert_split_line ! (
197
+ "Lorem ipsum dolor sit amet." ,
198
+ " consectetur adipiscing elit." ,
199
+ true
200
+ ) ;
201
+ assert_split_line ! ( "黃更室幼許刀知," , "波食小午足田世根候法。" , true ) ;
202
+ assert_split_line ! ( "黃更室幼許刀知。" , "波食小午足田世根候法。" , true ) ;
203
+ assert_split_line ! ( "黃更室幼許刀知;" , "波食小午足田世根候法。" , true ) ;
204
+ assert_split_line ! (
205
+ "Lorem ipsum (dolor sit amet)." ,
206
+ " consectetur adipiscing elit." ,
207
+ true
208
+ ) ;
209
+ assert_split_line ! (
210
+ "Lorem ipsum dolor sit `amet," ,
211
+ " consectetur` adipiscing elit." ,
212
+ false
213
+ ) ;
214
+ assert_split_line ! (
215
+ "Lorem ipsum dolor sit ```amet," ,
216
+ " consectetur``` adipiscing elit." ,
217
+ false
218
+ ) ;
219
+ assert_split_line ! (
220
+ "Lorem ipsum dolor sit *amet," ,
221
+ " consectetur* adipiscing elit." ,
222
+ false
223
+ ) ;
224
+ assert_split_line ! (
225
+ "Lorem ipsum dolor sit **amet," ,
226
+ " consectetur** adipiscing elit." ,
227
+ false
228
+ ) ;
229
+ assert_split_line ! (
230
+ "Lorem ipsum dolor sit ~amet," ,
231
+ " consectetur~ adipiscing elit." ,
232
+ false
233
+ ) ;
234
+ assert_split_line ! (
235
+ "Lorem ipsum dolor sit ~~amet," ,
236
+ " consectetur~~ adipiscing elit." ,
237
+ false
238
+ ) ;
239
+ assert_split_line ! (
240
+ "Lorem ipsum dolor sit ``amet," ,
241
+ " consectetur`` adipiscing elit." ,
242
+ true
243
+ ) ;
244
+ assert_split_line ! (
245
+ "Lorem ipsum dolor sit \" amet," ,
246
+ " consectetur\" adipiscing elit." ,
247
+ false
248
+ ) ;
249
+ assert_split_line ! (
250
+ "Lorem ipsum dolor sit 'amet," ,
251
+ " consectetur' adipiscing elit." ,
252
+ false
253
+ ) ;
254
+ assert_split_line ! (
255
+ "Lorem ipsum dolor sit amet." ,
256
+ "consectetur adipiscing elit." ,
257
+ false
258
+ ) ;
259
+ }
260
+ }
0 commit comments