@@ -231,6 +231,98 @@ export function verbatimCommandLines(text: string): VerbatimLine[] {
231231 return lines . filter ( ( line , i ) => line . text . trim ( ) . length > 0 || i === 0 ) ;
232232}
233233
234+ export type CollapsedPayload = { placeholder : string ; lines : string [ ] } ;
235+
236+ export type CollapsedSegment = {
237+ // The segment with each qualifying payload (a heredoc body, or a quoted
238+ // string spanning multiple lines) replaced by a short "<label, N lines>"
239+ // placeholder. A segment returned by groupChainSegmentsForDisplay is
240+ // already boundary-resolved, so any newline still inside it comes from one
241+ // of these two sources — never a chain boundary — which is what lets the
242+ // collapsed segment always render as a single line.
243+ display : string ;
244+ // The full text of each collapsed payload, in placeholder order, shown when
245+ // the operator expands via Ctrl+O.
246+ payloads : CollapsedPayload [ ] ;
247+ } ;
248+
249+ // Picks a short, human label for a collapsed quoted payload by looking at the
250+ // flag token immediately before it (`-m`/`--message`/`-F` read as a commit
251+ // message; anything else is generic "text"). Display-only guesswork — never
252+ // used for classification or matching.
253+ function payloadLabel ( segment : string , quoteStart : number ) : string {
254+ let k = quoteStart - 1 ;
255+ while ( k >= 0 && ( segment [ k ] === " " || segment [ k ] === "=" ) ) k -- ;
256+ const end = k + 1 ;
257+ while ( k >= 0 && segment [ k ] !== " " && segment [ k ] !== "=" ) k -- ;
258+ const token = segment . slice ( k + 1 , end ) ;
259+ return token === "-m" || token === "--message" || token === "-F" ? "message" : "text" ;
260+ }
261+
262+ function lineCountSuffix ( count : number ) : string {
263+ return `${ count } line${ count === 1 ? "" : "s" } ` ;
264+ }
265+
266+ // Collapse a heredoc body or a multi-line quoted-string argument within one
267+ // display segment into a placeholder. Never influences classification or
268+ // grant matching — display only, mirroring the header comment for this file.
269+ export function collapseSegmentPayloads ( segment : string ) : CollapsedSegment {
270+ const payloads : CollapsedPayload [ ] = [ ] ;
271+ let display = "" ;
272+ let i = 0 ;
273+ while ( i < segment . length ) {
274+ const ch = segment [ i ] as string ;
275+
276+ if ( ch === "<" && segment [ i + 1 ] === "<" ) {
277+ const marker = parseHeredocMarker ( segment , i ) ;
278+ if ( marker !== null ) {
279+ let j = i ;
280+ while ( j < segment . length && segment [ j ] !== "\n" ) j ++ ;
281+ display += segment . slice ( i , j ) ;
282+ i = j + 1 ;
283+ const bodyLines : string [ ] = [ ] ;
284+ while ( i < segment . length ) {
285+ let lineEnd = segment . indexOf ( "\n" , i ) ;
286+ if ( lineEnd === - 1 ) lineEnd = segment . length ;
287+ const line = segment . slice ( i , lineEnd ) ;
288+ if ( line . trim ( ) === marker ) {
289+ i = lineEnd + 1 ;
290+ break ;
291+ }
292+ bodyLines . push ( line ) ;
293+ i = lineEnd + 1 ;
294+ }
295+ const placeholder = `<heredoc, ${ lineCountSuffix ( bodyLines . length ) } >` ;
296+ display += ` ${ placeholder } ` ;
297+ payloads . push ( { placeholder, lines : bodyLines } ) ;
298+ continue ;
299+ }
300+ }
301+
302+ if ( ch === '"' || ch === "'" || ch === "`" ) {
303+ const quote = ch ;
304+ let j = i + 1 ;
305+ while ( j < segment . length && segment [ j ] !== quote ) j ++ ;
306+ const content = segment . slice ( i + 1 , j ) ;
307+ if ( content . includes ( "\n" ) ) {
308+ const lines = content . split ( "\n" ) ;
309+ const placeholder = `<${ payloadLabel ( segment , i ) } , ${ lineCountSuffix ( lines . length ) } >` ;
310+ display += placeholder ;
311+ payloads . push ( { placeholder, lines } ) ;
312+ i = j < segment . length ? j + 1 : j ;
313+ continue ;
314+ }
315+ display += segment . slice ( i , j < segment . length ? j + 1 : j ) ;
316+ i = j < segment . length ? j + 1 : j ;
317+ continue ;
318+ }
319+
320+ display += ch ;
321+ i ++ ;
322+ }
323+ return { display, payloads } ;
324+ }
325+
234326// Truncate to `max` characters keeping both the head and tail, so a set of
235327// strings that share a long common prefix (e.g. persistent Allow options that
236328// differ only in their trailing grant note) stay visually distinguishable
0 commit comments