@@ -131,6 +131,17 @@ func HighlightLines(ranges [][2]int) Option {
131131 }
132132}
133133
134+ // WithLinePrompts adds a non-selectable prompt before each line in the given ranges.
135+ //
136+ // A range is the beginning and ending of a range as 1-based line numbers, inclusive.
137+ func WithLinePrompts (prompt string , ranges [][2 ]int ) Option {
138+ return func (f * Formatter ) {
139+ f .linePrompt = prompt
140+ f .linePromptRanges = ranges
141+ sort .Sort (f .linePromptRanges )
142+ }
143+ }
144+
134145// BaseLineNumber sets the initial number to start line numbering at. Defaults to 1.
135146func BaseLineNumber (n int ) Option {
136147 return func (f * Formatter ) {
@@ -219,15 +230,30 @@ type Formatter struct {
219230 lineNumbersInTable bool
220231 linkableLineNumbers bool
221232 lineNumbersIDPrefix string
222- highlightRanges highlightRanges
233+ highlightRanges lineRanges
234+ linePrompt string
235+ linePromptRanges lineRanges
223236 baseLineNumber int
224237}
225238
226- type highlightRanges [][2 ]int
239+ type lineRanges [][2 ]int
240+
241+ func (r lineRanges ) Len () int { return len (r ) }
242+ func (r lineRanges ) Swap (i , j int ) { r [i ], r [j ] = r [j ], r [i ] }
243+ func (r lineRanges ) Less (i , j int ) bool { return r [i ][0 ] < r [j ][0 ] }
227244
228- func (h highlightRanges ) Len () int { return len (h ) }
229- func (h highlightRanges ) Swap (i , j int ) { h [i ], h [j ] = h [j ], h [i ] }
230- func (h highlightRanges ) Less (i , j int ) bool { return h [i ][0 ] < h [j ][0 ] }
245+ func (r lineRanges ) match (rangeIndex , line int ) (bool , int ) {
246+ for rangeIndex < len (r ) && line > r [rangeIndex ][1 ] {
247+ rangeIndex ++
248+ }
249+ if rangeIndex < len (r ) {
250+ lineRange := r [rangeIndex ]
251+ if line >= lineRange [0 ] && line <= lineRange [1 ] {
252+ return true , rangeIndex
253+ }
254+ }
255+ return false , rangeIndex
256+ }
231257
232258func (f * Formatter ) Format (w io.Writer , style * chroma.Style , iterator iter.Seq [chroma.Token ]) (err error ) {
233259 return f .writeHTML (w , style , slices .Collect (iterator ))
@@ -266,10 +292,8 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
266292 fmt .Fprintf (w , "%s" , f .preWrapper .Start (false , f .styleAttrWithMode (css , chroma .PreWrapper , style )))
267293 for index := range lines {
268294 line := f .baseLineNumber + index
269- highlight , next := f .shouldHighlight (highlightIndex , line )
270- if next {
271- highlightIndex ++
272- }
295+ highlight , nextHighlightIndex := f .highlightRanges .match (highlightIndex , line )
296+ highlightIndex = nextHighlightIndex
273297 if highlight {
274298 fmt .Fprintf (w , "<span%s>" , f .styleAttr (css , chroma .LineHighlight ))
275299 }
@@ -288,13 +312,14 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
288312 fmt .Fprintf (w , "%s" , f .preWrapper .Start (true , f .styleAttrWithMode (css , chroma .PreWrapper , style )))
289313
290314 highlightIndex = 0
315+ linePromptIndex := 0
291316 for index , tokens := range lines {
292317 // 1-based line number.
293318 line := f .baseLineNumber + index
294- highlight , next := f .shouldHighlight (highlightIndex , line )
295- if next {
296- highlightIndex ++
297- }
319+ highlight , nextHighlightIndex := f .highlightRanges . match (highlightIndex , line )
320+ highlightIndex = nextHighlightIndex
321+ linePrompt , nextLinePromptIndex := f . linePromptRanges . match ( linePromptIndex , line )
322+ linePromptIndex = nextLinePromptIndex
298323
299324 if ! f .preventSurroundingPre && ! f .inlineCode {
300325 // Start of Line
@@ -318,6 +343,9 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
318343 }
319344
320345 fmt .Fprintf (w , `<span%s>` , f .styleAttr (css , chroma .CodeLine ))
346+ if linePrompt {
347+ fmt .Fprintf (w , `<span%s>%s</span>` , f .styleAttr (css , chroma .GenericPrompt ), html .EscapeString (f .linePrompt ))
348+ }
321349 }
322350
323351 for _ , token := range tokens {
@@ -369,21 +397,6 @@ func (f *Formatter) lineID(line int) string {
369397 return fmt .Sprintf ("%s%d" , f .lineNumbersIDPrefix , line )
370398}
371399
372- func (f * Formatter ) shouldHighlight (highlightIndex , line int ) (bool , bool ) {
373- next := false
374- for highlightIndex < len (f .highlightRanges ) && line > f .highlightRanges [highlightIndex ][1 ] {
375- highlightIndex ++
376- next = true
377- }
378- if highlightIndex < len (f .highlightRanges ) {
379- hrange := f .highlightRanges [highlightIndex ]
380- if line >= hrange [0 ] && line <= hrange [1 ] {
381- return true , next
382- }
383- }
384- return false , next
385- }
386-
387400func (f * Formatter ) class (t chroma.TokenType ) string {
388401 for t != 0 {
389402 if cls , ok := chroma .StandardTypes [t ]; ok {
@@ -584,10 +597,14 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
584597 classes [chroma .PreWrapper ] += `white-space: pre-wrap; word-break: break-word;`
585598 }
586599 lineNumbersStyle := `white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;`
600+ linePromptStyle := `white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em;`
587601 // All rules begin with default rules followed by user provided rules
588602 classes [chroma .Line ] = `display: flex;` + classes [chroma .Line ]
589603 classes [chroma .LineNumbers ] = lineNumbersStyle + classes [chroma .LineNumbers ]
590604 classes [chroma .LineNumbersTable ] = lineNumbersStyle + classes [chroma .LineNumbersTable ]
605+ if len (f .linePromptRanges ) > 0 {
606+ classes [chroma .GenericPrompt ] = linePromptStyle + classes [chroma .GenericPrompt ]
607+ }
591608 classes [chroma .LineTable ] = "border-spacing: 0; padding: 0; margin: 0; border: 0;" + classes [chroma .LineTable ]
592609 classes [chroma .LineTableTD ] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes [chroma .LineTableTD ]
593610 classes [chroma .LineLink ] = "outline: none; text-decoration: none; color: inherit" + classes [chroma .LineLink ]
0 commit comments