@@ -33,26 +33,17 @@ func parseHighlightSyntax(syntax string) []LineRange {
33
33
}
34
34
35
35
syntax = strings .Trim (syntax , "{}" )
36
-
37
- var ranges []LineRange
38
36
parts := strings .Split (syntax , "," )
37
+ var ranges []LineRange
39
38
40
39
for _ , part := range parts {
41
40
part = strings .TrimSpace (part )
42
41
if strings .Contains (part , "-" ) {
43
- // Range like "1-3"
44
- rangeParts := strings .Split (part , "-" )
45
- if len (rangeParts ) == 2 {
46
- start , err1 := strconv .Atoi (strings .TrimSpace (rangeParts [0 ]))
47
- end , err2 := strconv .Atoi (strings .TrimSpace (rangeParts [1 ]))
48
- if err1 == nil && err2 == nil && start <= end {
49
- ranges = append (ranges , LineRange {Start : start , End : end })
50
- }
42
+ if r := parseRange (part ); r != nil {
43
+ ranges = append (ranges , * r )
51
44
}
52
45
} else {
53
- // Single line like "5"
54
- line , err := strconv .Atoi (part )
55
- if err == nil {
46
+ if line , err := strconv .Atoi (part ); err == nil {
56
47
ranges = append (ranges , LineRange {Start : line , End : line })
57
48
}
58
49
}
@@ -61,7 +52,26 @@ func parseHighlightSyntax(syntax string) []LineRange {
61
52
return ranges
62
53
}
63
54
55
+ func parseRange (part string ) * LineRange {
56
+ rangeParts := strings .Split (part , "-" )
57
+ if len (rangeParts ) != 2 {
58
+ return nil
59
+ }
60
+
61
+ start , err1 := strconv .Atoi (strings .TrimSpace (rangeParts [0 ]))
62
+ end , err2 := strconv .Atoi (strings .TrimSpace (rangeParts [1 ]))
63
+
64
+ if err1 == nil && err2 == nil && start <= end {
65
+ return & LineRange {Start : start , End : end }
66
+ }
67
+ return nil
68
+ }
69
+
64
70
func shouldHighlightLine (lineNum int , ranges []LineRange ) bool {
71
+ if len (ranges ) == 0 {
72
+ return true // highlight all lines if no ranges specified
73
+ }
74
+
65
75
for _ , r := range ranges {
66
76
if lineNum >= r .Start && lineNum <= r .End {
67
77
return true
@@ -70,102 +80,185 @@ func shouldHighlightLine(lineNum int, ranges []LineRange) bool {
70
80
return false
71
81
}
72
82
73
- func renderCustomCodeBlock (content string , info CodeHighlightInfo , themeName string ) string {
74
- lexer := lexers .Get (info .Language )
75
- if lexer == nil {
76
- lexer = lexers .Fallback
83
+ func formatLineNumber (lineNum , width int ) string {
84
+ style := lipgloss .NewStyle ().
85
+ Foreground (lipgloss .Color ("240" )).
86
+ PaddingRight (1 )
87
+
88
+ lineNumStr := strconv .Itoa (lineNum )
89
+ paddedLineNum := fmt .Sprintf ("%*s" , width - 1 , lineNumStr )
90
+ return style .Render (paddedLineNum )
91
+ }
92
+
93
+ func getLineNumberWidth (startLine , totalLines int ) int {
94
+ if totalLines == 0 {
95
+ return 0
77
96
}
78
- lexer = chroma .Coalesce (lexer )
97
+ maxLineNum := startLine + totalLines - 1
98
+ return len (strconv .Itoa (maxLineNum )) + 2
99
+ }
79
100
80
- style := config .GetChromaStyle (themeName )
81
- return renderWithStyle (content , info , lexer , style )
101
+ func renderPlainCode (lines []string , info CodeHighlightInfo ) string {
102
+ var result strings.Builder
103
+ lineNumberWidth := 0
104
+
105
+ if info .ShowLineNumbers {
106
+ lineNumberWidth = getLineNumberWidth (info .StartLine , len (lines ))
107
+ }
108
+
109
+ for i , line := range lines {
110
+ displayLineNum := info .StartLine + i
111
+
112
+ if info .ShowLineNumbers {
113
+ result .WriteString (formatLineNumber (displayLineNum , lineNumberWidth ))
114
+ }
115
+
116
+ result .WriteString (line )
117
+
118
+ if i < len (lines )- 1 {
119
+ result .WriteString ("\n " )
120
+ }
121
+ }
122
+
123
+ return result .String ()
82
124
}
83
125
84
- func renderWithStyle (content string , info CodeHighlightInfo , lexer chroma.Lexer , style * chroma.Style ) string {
126
+ func renderHighlightedCode (content string , lines [] string , info CodeHighlightInfo , lexer chroma.Lexer , style * chroma.Style ) string {
85
127
formatter := formatters .Get ("terminal256" )
86
128
if formatter == nil {
87
- formatter = formatters . Fallback
129
+ return renderPlainCode ( lines , info )
88
130
}
89
131
90
- lines := strings .Split (content , "\n " )
132
+ iterator , err := lexer .Tokenise (nil , content )
133
+ if err != nil {
134
+ return renderPlainCode (lines , info )
135
+ }
136
+
137
+ var formattedBuf strings.Builder
138
+ if err := formatter .Format (& formattedBuf , style , iterator ); err != nil {
139
+ return renderPlainCode (lines , info )
140
+ }
91
141
92
- highlightStyle := lipgloss . NewStyle ( )
142
+ formattedLines := strings . Split ( formattedBuf . String (), " \n " )
93
143
144
+ // Ensure we have the same number of lines
145
+ for len (formattedLines ) < len (lines ) {
146
+ formattedLines = append (formattedLines , "" )
147
+ }
148
+ if len (formattedLines ) > len (lines ) {
149
+ formattedLines = formattedLines [:len (lines )]
150
+ }
151
+
152
+ var result strings.Builder
94
153
lineNumberWidth := 0
154
+
95
155
if info .ShowLineNumbers {
96
- maxLineNum := info .StartLine + len (lines ) - 1
97
- lineNumberWidth = len (strconv .Itoa (maxLineNum )) + 2
156
+ lineNumberWidth = getLineNumberWidth (info .StartLine , len (lines ))
98
157
}
99
158
100
- lineNumberStyle := lipgloss . NewStyle ().
101
- Foreground ( lipgloss . Color ( "240" )).
102
- PaddingRight ( 1 )
159
+ for i , line := range lines {
160
+ displayLineNum := info . StartLine + i
161
+ relativeLineNum := i + 1
103
162
104
- var result strings.Builder
105
- for lineNum , line := range lines {
106
- displayLineNum := info .StartLine + lineNum
107
- relativeLineNum := lineNum + 1
108
-
109
- // Add line number if requested
110
163
if info .ShowLineNumbers {
111
- lineNumStr := strconv .Itoa (displayLineNum )
112
- paddedLineNum := fmt .Sprintf ("%*s" , lineNumberWidth - 1 , lineNumStr )
113
- result .WriteString (lineNumberStyle .Render (paddedLineNum ))
164
+ result .WriteString (formatLineNumber (displayLineNum , lineNumberWidth ))
114
165
}
115
166
116
- shouldHighlight := len (info .Ranges ) == 0 || shouldHighlightLine (relativeLineNum , info .Ranges )
117
-
118
- if ! shouldHighlight {
119
- result .WriteString (line )
120
- if lineNum < len (lines )- 1 {
121
- result .WriteString ("\n " )
167
+ if shouldHighlightLine (relativeLineNum , info .Ranges ) {
168
+ formattedLine := ""
169
+ if i < len (formattedLines ) {
170
+ formattedLine = strings .TrimRight (formattedLines [i ], " \t \n \r " )
122
171
}
123
- continue
124
- }
125
-
126
- lineIterator , err := lexer .Tokenise (nil , line )
127
- if err != nil {
128
- result .WriteString (highlightStyle .Render (line ))
129
- if lineNum < len (lines )- 1 {
130
- result .WriteString ("\n " )
172
+ if formattedLine == "" {
173
+ formattedLine = line
131
174
}
132
- continue
175
+ result .WriteString (formattedLine )
176
+ } else {
177
+ result .WriteString (line )
133
178
}
134
179
135
- var lineBuf strings.Builder
136
- err = formatter .Format (& lineBuf , style , lineIterator )
137
- if err != nil {
138
- result .WriteString (highlightStyle .Render (line ))
139
- if lineNum < len (lines )- 1 {
140
- result .WriteString ("\n " )
141
- }
142
- continue
180
+ if i < len (lines )- 1 {
181
+ result .WriteString ("\n " )
143
182
}
183
+ }
144
184
145
- syntaxHighlighted := lineBuf .String ()
146
- syntaxHighlighted = strings .TrimRight (syntaxHighlighted , " \t \n \r " )
147
- result .WriteString (highlightStyle .Render (syntaxHighlighted ))
185
+ return result .String ()
186
+ }
148
187
149
- if lineNum < len (lines )- 1 {
150
- result .WriteString ("\n " )
188
+ func renderCustomCodeBlock (content string , info CodeHighlightInfo , themeName string ) string {
189
+ lines := strings .Split (content , "\n " )
190
+
191
+ var renderedContent string
192
+
193
+ if info .Language != "" {
194
+ lexer := lexers .Get (info .Language )
195
+ if lexer == nil {
196
+ lexer = lexers .Fallback
151
197
}
198
+ lexer = chroma .Coalesce (lexer )
199
+ style := config .GetChromaStyle (themeName )
200
+
201
+ renderedContent = renderHighlightedCode (content , lines , info , lexer , style )
202
+ } else {
203
+ renderedContent = renderPlainCode (lines , info )
152
204
}
153
205
206
+ // Apply consistent styling
154
207
codeStyle := lipgloss .NewStyle ().
155
208
Padding (1 , 2 ).
156
209
Width (78 ).
157
210
MarginTop (1 ).
158
211
MarginBottom (1 )
159
212
160
- return codeStyle .Render (result .String ())
213
+ return codeStyle .Render (renderedContent )
214
+ }
215
+
216
+ func parseCodeBlockInfo (match []string ) CodeHighlightInfo {
217
+ if len (match ) < 7 {
218
+ return CodeHighlightInfo {}
219
+ }
220
+
221
+ language := match [1 ]
222
+ highlightSyntax := ""
223
+ if len (match ) > 2 && match [2 ] != "" {
224
+ highlightSyntax = match [2 ]
225
+ }
226
+ numberedFlag := strings .TrimSpace (match [3 ])
227
+ startLineStr := match [5 ]
228
+
229
+ startLine := 1
230
+ if startLineStr != "" {
231
+ if parsed , err := strconv .Atoi (startLineStr ); err == nil && parsed > 0 {
232
+ startLine = parsed
233
+ }
234
+ }
235
+
236
+ return CodeHighlightInfo {
237
+ Language : language ,
238
+ Ranges : parseHighlightSyntax (highlightSyntax ),
239
+ ShowLineNumbers : strings .Contains (numberedFlag , "--numbered" ),
240
+ StartLine : startLine ,
241
+ }
242
+ }
243
+
244
+ func renderMarkdownSection (text , themeName string ) string {
245
+ if strings .TrimSpace (text ) == "" {
246
+ return ""
247
+ }
248
+
249
+ rendered , err := glamour .Render (text , themeName )
250
+ if err != nil {
251
+ return text
252
+ }
253
+ return rendered
161
254
}
162
255
163
256
func processMarkdownWithHighlighting (markdown string , themeName string ) (string , error ) {
164
- re := regexp .MustCompile ("(?s)```([a-zA-Z0-9_+-]*)({[^}]*})?(\\ s+--numbered)?(\\ s+--start-at-line\\ s+(\\ d+))?\\ s*\n (.*?)\n ```" )
257
+ // Regex to match code blocks with optional highlighting syntax
258
+ re := regexp .MustCompile (`(?s)` + "`" + `{3}([a-zA-Z0-9_+-]*)({[^}]*})?(\s+--numbered)?(\s+--start-at-line\s+(\d+))?\s*\n(.*?)\n` + "`" + `{3}` )
165
259
166
260
matches := re .FindAllStringSubmatch (markdown , - 1 )
167
261
if len (matches ) == 0 {
168
- // No code blocks found, render the entire markdown with Glamour
169
262
return glamour .Render (markdown , themeName )
170
263
}
171
264
@@ -174,80 +267,35 @@ func processMarkdownWithHighlighting(markdown string, themeName string) (string,
174
267
indices := re .FindAllStringIndex (markdown , - 1 )
175
268
176
269
for i , match := range matches {
177
- if len (match ) < 7 {
178
- continue
179
- }
180
-
181
270
matchStart := indices [i ][0 ]
182
271
matchEnd := indices [i ][1 ]
183
272
184
- // Add the text before this code block
273
+ // Add text before this code block
185
274
if matchStart > lastIndex {
186
275
beforeText := markdown [lastIndex :matchStart ]
187
- if strings .TrimSpace (beforeText ) != "" {
188
- // Render regular markdown content with Glamour
189
- rendered , err := glamour .Render (beforeText , themeName )
190
- if err != nil {
191
- parts = append (parts , beforeText )
192
- } else {
193
- parts = append (parts , rendered )
194
- }
195
- }
276
+ parts = append (parts , renderMarkdownSection (beforeText , themeName ))
196
277
}
197
278
198
- // Extract code block info
199
- language := match [1 ] // e.g., "typescript"
200
- highlightSyntax := ""
201
- if len (match ) > 2 && match [2 ] != "" {
202
- highlightSyntax = match [2 ] // e.g., "{1-2}"
203
- }
204
- numberedFlag := strings .TrimSpace (match [3 ]) // " --numbered"
205
- startLineStr := match [5 ] // The line number from --start-at-line
206
- content := match [6 ] // The actual code content
207
-
208
- startLine := 1
209
- if startLineStr != "" {
210
- if parsed , err := strconv .Atoi (startLineStr ); err == nil && parsed > 0 {
211
- startLine = parsed
212
- }
213
- }
279
+ // Process the code block
280
+ info := parseCodeBlockInfo (match )
281
+ content := match [6 ]
214
282
215
- info := CodeHighlightInfo {
216
- Language : language ,
217
- Ranges : parseHighlightSyntax (highlightSyntax ),
218
- ShowLineNumbers : strings .Contains (numberedFlag , "--numbered" ),
219
- StartLine : startLine ,
220
- }
221
-
222
- if language != "" || info .ShowLineNumbers {
283
+ if info .Language != "" || info .ShowLineNumbers {
223
284
customRendered := renderCustomCodeBlock (content , info , themeName )
224
285
parts = append (parts , customRendered )
225
286
} else {
226
- // No language specified and no line numbers, use Glamour directly for the entire code block
287
+ // Use Glamour for plain code blocks
227
288
codeBlock := match [0 ]
228
- rendered , err := glamour .Render (codeBlock , themeName )
229
- if err != nil {
230
- parts = append (parts , codeBlock )
231
- } else {
232
- parts = append (parts , rendered )
233
- }
289
+ parts = append (parts , renderMarkdownSection (codeBlock , themeName ))
234
290
}
235
291
236
292
lastIndex = matchEnd
237
293
}
238
294
239
- // Add any remaining text after the last code block
295
+ // Add remaining text after the last code block
240
296
if lastIndex < len (markdown ) {
241
297
remainingText := markdown [lastIndex :]
242
- if strings .TrimSpace (remainingText ) != "" {
243
- // Render remaining markdown content with Glamour
244
- rendered , err := glamour .Render (remainingText , themeName )
245
- if err != nil {
246
- parts = append (parts , remainingText )
247
- } else {
248
- parts = append (parts , rendered )
249
- }
250
- }
298
+ parts = append (parts , renderMarkdownSection (remainingText , themeName ))
251
299
}
252
300
253
301
return strings .Join (parts , "" ), nil
0 commit comments