Skip to content

Commit 820e233

Browse files
committed
hl: handle multi page /**/ (now comments that start before current screen are highlighted)
1 parent 0406b5d commit 820e233

File tree

3 files changed

+175
-64
lines changed

3 files changed

+175
-64
lines changed

draw.v

Lines changed: 133 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module main
22

33
import gx
44
import os
5+
import time
56

67
fn (mut ved Ved) draw() {
78
mut view := ved.view
@@ -154,21 +155,29 @@ fn (ved &Ved) split_x(i int) int {
154155

155156
fn (mut ved Ved) draw_split(i int, split_from int) {
156157
view := ved.views[i]
157-
ved.is_ml_comment = false
158+
// Determine initial comment state for the first visible line
159+
// (handle /**/ comments blocks that start before current page)
160+
mut current_is_ml_comment := false
161+
if view.hl_on {
162+
current_is_ml_comment = ved.determine_ml_comment_state(view, view.from)
163+
}
164+
ext := os.file_ext(view.path) // Cache extension
165+
mcomment := get_mcomment_by_ext(ext) // Cache delimiters
166+
158167
split_width := ved.split_width()
159168
split_x := split_width * (i - split_from)
160169
// Vertical split line
161170
ved.gg.draw_line(split_x, ved.cfg.line_height + 1, split_x, ved.win_height, ved.cfg.split_color)
162171
// Lines
163-
mut line_nr := 1 // relative y
172+
mut line_nr_rel := 1 // relative y on screen
164173
for j := view.from; j < view.from + ved.page_height && j < view.lines.len; j++ {
165174
line := view.lines[j]
166175
if line.len > 5000 {
167176
println('line len too big! views[${i}].lines[${j}] (${line.len}) path=${ved.view.path}')
168177
continue
169178
}
170179
x := split_x + view.padding_left
171-
y := line_nr * ved.cfg.line_height
180+
y := line_nr_rel * ved.cfg.line_height
172181
// Error bg
173182
if view.error_y == j {
174183
ved.gg.draw_rect_filled(x + 10, y - 1, split_width - view.padding_left - 10,
@@ -201,7 +210,7 @@ fn (mut ved Ved) draw_split(i int, split_from int) {
201210
}
202211
mut s := line[nr_tabs..] // tabs have been skipped, remove them from the string
203212
if s == '' {
204-
line_nr++
213+
line_nr_rel++
205214
continue
206215
}
207216
// Number of chars to display in this view
@@ -230,16 +239,51 @@ fn (mut ved Ved) draw_split(i int, split_from int) {
230239
s = s[..max]
231240
}
232241
}
242+
233243
if view.hl_on {
234-
// println('line="$s" nrtabs=$nr_tabs line_x=$line_x')
235-
ved.draw_text_line(line_x, y, s, os.file_ext(view.path))
244+
// Handle multi page /**/
245+
start_comment_pos := s.index(mcomment.start1.str() + mcomment.start2.str()) or { -1 }
246+
end_comment_pos := s.index(mcomment.end1.str() + mcomment.end2.str()) or { -1 }
247+
if current_is_ml_comment {
248+
if end_comment_pos != -1 { // Comment ends on this line
249+
// Draw comment part
250+
comment_part := s[..end_comment_pos + 2]
251+
ved.gg.draw_text(line_x, y, comment_part, ved.cfg.comment_cfg)
252+
// Draw rest normally
253+
normal_part := s[end_comment_pos + 2..]
254+
if normal_part.len > 0 {
255+
normal_part_x := line_x + comment_part.len * ved.cfg.char_width // Adjust based on actual rendered width if needed
256+
ved.draw_text_line_standard_syntax(normal_part_x, y, normal_part,
257+
ext)
258+
}
259+
current_is_ml_comment = false // Update state for next line
260+
} else { // Entire line is inside the comment
261+
ved.gg.draw_text(line_x, y, s, ved.cfg.comment_cfg)
262+
// current_is_ml_comment remains true
263+
}
264+
} else { // current_is_ml_comment is false
265+
if start_comment_pos != -1
266+
&& (end_comment_pos == -1 || end_comment_pos < start_comment_pos) { // Comment starts here and continues
267+
// Draw normal part before / *
268+
normal_part := s[..start_comment_pos]
269+
if normal_part.len > 0 {
270+
ved.draw_text_line_standard_syntax(line_x, y, normal_part, ext)
271+
}
272+
// Draw comment part from / * onwards
273+
comment_part := s[start_comment_pos..]
274+
comment_part_x := line_x + normal_part.len * ved.cfg.char_width // Adjust based on actual rendered width if needed
275+
ved.gg.draw_text(comment_part_x, y, comment_part, ved.cfg.comment_cfg)
276+
current_is_ml_comment = true // Update state for next line
277+
} else { // No multiline comment start OR it's a single-line /* ... */
278+
// Use the standard highlighter for the whole line
279+
ved.draw_text_line_standard_syntax(line_x, y, s, ext)
280+
// current_is_ml_comment remains false
281+
}
282+
}
236283
} else {
237284
ved.gg.draw_text(line_x, y, s, ved.cfg.txt_cfg)
238285
}
239-
// if old_len != s.len {
240-
// ved.draw_text_line(line_x, y + 1, '!!!')
241-
//}
242-
line_nr++
286+
line_nr_rel++
243287
}
244288
}
245289

@@ -257,7 +301,10 @@ fn (mut ved Ved) add_chunk(typ ChunkKind, start int, end int) {
257301
ved.chunks << chunk
258302
}
259303

260-
fn (mut ved Ved) draw_text_line(x int, y int, line string, ext string) {
304+
// Handles syntax highlighting for a line assuming it does *not* start within a multi-line comment
305+
// and does *not* start a multi-line comment that continues to the next line.
306+
// It handles single-line comments (//, #), strings, keywords, literals, and single-line /* ... */ comments.
307+
fn (mut ved Ved) draw_text_line_standard_syntax(x int, y int, line string, ext string) {
261308
// mcomment := get_mcomment_by_ext(os.file_ext(ved.view.path))
262309
mcomment := get_mcomment_by_ext(ext)
263310
// Red/green test hack
@@ -274,74 +321,97 @@ fn (mut ved Ved) draw_text_line(x int, y int, line string, ext string) {
274321
ved.chunks = []
275322
cur_syntax := ved.syntaxes[ved.current_syntax_idx] or { Syntax{} }
276323
// TODO use runes for everything to fix keyword + 2+ byte rune words
277-
for i := 0; i < line.len; i++ {
324+
325+
mut i := 0 // Use mut i instead of for loop index to allow manual increment
326+
for i < line.len {
278327
start := i
279328
// Comment // #
280329
if i > 0 && line[i - 1] == `/` && line[i] == `/` {
281330
ved.add_chunk(.a_comment, start - 1, line.len)
331+
i = line.len // End the loop
282332
break
283333
}
284334
if line[i] == `#` {
285335
ved.add_chunk(.a_comment, start, line.len)
336+
i = line.len // End the loop
286337
break
287338
}
288-
// Comment /*
289-
// (unless it's /* line */ which is a single line)
290-
if i > 0 && line[i - 1] == mcomment.start1 && line[i] == mcomment.start2
291-
&& !(line[line.len - 2] == mcomment.end1 && line[line.len - 1] == mcomment.end2) {
292-
// All after /* is a comment
293-
ved.add_chunk(.a_comment, start, line.len)
294-
ved.is_ml_comment = true
295-
break
296-
}
297-
// End of /**/
298-
if i > 0 && line[i - 1] == mcomment.end1 && line[i] == mcomment.end2 {
299-
// All before */ is still a comment
300-
ved.add_chunk(.a_comment, 0, start + 1)
301-
ved.is_ml_comment = false
302-
break
339+
340+
// Single line Comment /* ... */
341+
if i < line.len - 1 && line[i] == mcomment.start1 && line[i + 1] == mcomment.start2 {
342+
end_pos := line.index_after(mcomment.end1.str() + mcomment.end2.str(), i + 2) or { -1 }
343+
if end_pos != -1 {
344+
ved.add_chunk(.a_comment, start, end_pos + 2)
345+
i = end_pos + 2 // Move past the comment
346+
continue
347+
} else {
348+
// This case (start found but no end on this line) should be handled by the new logic in draw_split.
349+
// If we reach here, it means draw_split decided this line doesn't start a *continuing* multiline comment.
350+
// Treat the '/*' as normal text by just advancing `i`.
351+
i += 1
352+
continue
353+
}
303354
}
304-
// String
355+
// String '...'
305356
if line[i] == `'` {
306-
i++
307-
for i < line.len - 1 && line[i] != `'` {
308-
i++
309-
}
310-
if i >= line.len {
311-
i = line.len - 1
357+
mut end := i + 1
358+
for end < line.len && line[end] != `'` {
359+
// Handle escaped quote \'
360+
if line[end] == `\\` && end + 1 < line.len && line[end + 1] == `'` {
361+
end++ // Skip escaped quote
362+
}
363+
end++
312364
}
313-
ved.add_chunk(.a_string, start, i + 1)
365+
if end >= line.len {
366+
end = line.len - 1
367+
} else {
368+
end += 1
369+
} // include closing quote
370+
ved.add_chunk(.a_string, start, end)
371+
i = end // Move past the string
372+
continue
314373
}
374+
// String "..."
315375
if line[i] == `"` {
316-
i++
317-
for i < line.len - 1 && line[i] != `"` {
318-
i++
376+
mut end := i + 1
377+
for end < line.len && line[end] != `"` {
378+
// Handle escaped quote \"
379+
if line[end] == `\\` && end + 1 < line.len && line[end + 1] == `"` {
380+
end++ // Skip escaped quote
381+
}
382+
end++
319383
}
320-
if i >= line.len {
321-
i = line.len - 1
322-
}
323-
ved.add_chunk(.a_string, start, i + 1)
384+
if end >= line.len {
385+
end = line.len - 1
386+
} else {
387+
end += 1
388+
} // include closing quote
389+
ved.add_chunk(.a_string, start, end)
390+
i = end // Move past the string
391+
continue
324392
}
325393
// Key
326-
for i < line.len && is_alpha_underscore(int(line[i])) {
327-
i++
328-
}
329-
word := line[start..i]
330-
// println('word="$word"')
331-
if word in cur_syntax.literals {
332-
// println('$word is key')
333-
ved.add_chunk(.a_lit, start, i)
334-
// println('adding key. len=$ved.chunks.len')
335-
} else if word in cur_syntax.keywords {
336-
// println('$word is key')
337-
ved.add_chunk(.a_key, start, i)
338-
// println('adding key. len=$ved.chunks.len')
394+
if is_alpha_underscore(int(line[i])) {
395+
mut end := i + 1
396+
for end < line.len && is_alpha_underscore(int(line[end])) {
397+
end++
398+
}
399+
word := line[start..end]
400+
if word in cur_syntax.literals {
401+
ved.add_chunk(.a_lit, start, end)
402+
} else if word in cur_syntax.keywords {
403+
ved.add_chunk(.a_key, start, end)
404+
}
405+
// If it's not a keyword or literal, it will be drawn as normal text later.
406+
i = end // Move past the word
407+
continue
339408
}
409+
410+
// If none of the above matched, advance by one character
411+
i++
340412
}
341-
if ved.is_ml_comment {
342-
ved.gg.draw_text(x, y, line, ved.cfg.comment_cfg)
343-
return
344-
}
413+
414+
// --- Keep the original chunk drawing logic ---
345415
if ved.chunks.len == 0 {
346416
// println('no chunks')
347417
ved.gg.draw_text(x, y, line, ved.cfg.txt_cfg)
@@ -352,7 +422,7 @@ fn (mut ved Ved) draw_text_line(x int, y int, line string, ext string) {
352422
// TODO use runes
353423
// runes := msg.runes.slice_fast(chunk.pos, chunk.end)
354424
// txt := join_strings(runes)
355-
for i, chunk in ved.chunks {
425+
for j, chunk in ved.chunks {
356426
// println('chunk #$i start=$chunk.start end=$chunk.end typ=$chunk.typ')
357427
// Initial text chunk (not necessarily initial, but the one right before current chunk,
358428
// since we don't have a seperate chunk for text)
@@ -372,11 +442,12 @@ fn (mut ved Ved) draw_text_line(x int, y int, line string, ext string) {
372442
ved.gg.draw_text(x + chunk.start * ved.cfg.char_width, y, s, cfg)
373443
pos = chunk.end
374444
// Final text chunk
375-
if i == ved.chunks.len - 1 && chunk.end < line.len {
376-
final := line[chunk.end..line.len]
445+
if j == ved.chunks.len - 1 && chunk.end < line.len {
446+
final := line[chunk.end..]
377447
ved.gg.draw_text(x + pos * ved.cfg.char_width, y, final, ved.cfg.txt_cfg)
378448
}
379449
}
450+
// --- End of original chunk drawing logic ---
380451
}
381452

382453
fn (ved &Ved) draw_cursor(cursor_x int, y int) {

hl.v

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// that can be found in the LICENSE file.
44
module main
55

6+
import os
7+
68
struct Mcomment {
79
start1 rune
810
start2 rune
@@ -23,3 +25,42 @@ fn get_mcomment_by_ext(ext string) Mcomment {
2325
}
2426
}
2527
}
28+
29+
// Scans the file content *before* the target line number to determine
30+
// if that target line starts inside an unclosed multiline comment block.
31+
fn (ved &Ved) determine_ml_comment_state(view &View, target_line_nr int) bool {
32+
if target_line_nr <= 0 {
33+
return false // First line cannot start inside a comment
34+
}
35+
mcomment := get_mcomment_by_ext(os.file_ext(view.path))
36+
// State: true = inside comment, false = outside comment
37+
mut is_inside := false
38+
// Scan all lines *before* the target line
39+
for i := 0; i < target_line_nr; i++ {
40+
// Basic bounds check, though shouldn't be needed if target_line_nr is valid
41+
if i >= view.lines.len {
42+
continue
43+
}
44+
line := view.lines[i]
45+
mut k := 0
46+
// Scan through the characters of the line
47+
for k < line.len - 1 {
48+
// Check for start delimiter "/*" only if we are *outside* a comment
49+
if !is_inside && line[k] == mcomment.start1 && line[k + 1] == mcomment.start2 {
50+
is_inside = true // Enter comment state
51+
k += 2 // Skip the delimiter
52+
continue
53+
}
54+
// Check for end delimiter "*/" only if we are *inside* a comment
55+
if is_inside && line[k] == mcomment.end1 && line[k + 1] == mcomment.end2 {
56+
is_inside = false // Exit comment state
57+
k += 2 // Skip the delimiter
58+
continue
59+
}
60+
// No delimiter found at this position, move to the next character
61+
k++
62+
}
63+
}
64+
// Return the final state after checking all preceding lines
65+
return is_inside
66+
}

ved.v

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ mut:
5454
git_diff_minus string
5555
syntaxes []Syntax
5656
current_syntax_idx int
57-
chunks []Chunk
57+
chunks []Chunk // Used temporarily during highlighting
5858
is_building bool
5959
timer Timer
6060
task_start_unix i64
@@ -63,7 +63,6 @@ mut:
6363
file_y_pos map[string]int // to save current line for each file s
6464
refresh bool = true
6565
char_width int
66-
is_ml_comment bool
6766
gg_lines []string
6867
gg_pos int
6968
cfg Config

0 commit comments

Comments
 (0)