33// that can be found in the LICENSE file.
44module parser
55
6+ import v.ast
67import v.token
78import v.errors
89import os
@@ -223,13 +224,26 @@ pub fn (mut p Parser) compile_template_file(template_file string, fn_name string
223224 lstartlength := lines.len * 30
224225 tmpl_str_start := "\t sb_${fn_name }.write_string('"
225226 mut source := strings.new_builder (1000 )
227+
228+ // Reset line mapping for this template compilation
229+ p.template_line_map = []
230+
226231 source.writeln ('
227232import strings
228233// === veb html template for file: ${template_file } ===
229234fn veb_tmpl_${fn_name }() string {
230235 mut sb_${fn_name } := strings.new_builder(${lstartlength })\n
231236
232237' )
238+ // Header adds 8 lines (0: empty, 1: import, 2: comment, 3: fn, 4: builder, 5: empty from \n escape, 6: empty from literal, 7: empty from writeln)
239+ // Pre-fill the line map with placeholder entries for header lines
240+ for _ in 0 .. 8 {
241+ p.template_line_map << ast.TemplateLineInfo{
242+ tmpl_path: template_file
243+ tmpl_line: 0
244+ }
245+ }
246+
233247 source.write_string (tmpl_str_start)
234248
235249 mut state := State.simple
@@ -325,35 +339,91 @@ fn veb_tmpl_${fn_name}() string {
325339 }
326340 if line.contains ('@if ' ) {
327341 source.writeln (tmpl_str_end)
342+ // tmpl_str_end contains '\n', so writeln creates 2 lines: ')' and empty
343+ p.template_line_map << ast.TemplateLineInfo{
344+ tmpl_path: template_file
345+ tmpl_line: tline_number
346+ }
347+ p.template_line_map << ast.TemplateLineInfo{
348+ tmpl_path: template_file
349+ tmpl_line: tline_number
350+ }
328351 pos := line.index ('@if' ) or { continue }
329352 source.writeln ('if ' + line[pos + 4 ..] + '{' )
353+ p.template_line_map << ast.TemplateLineInfo{
354+ tmpl_path: template_file
355+ tmpl_line: tline_number
356+ }
330357 source.write_string (tmpl_str_start)
331358 continue
332359 }
333360 if line.contains ('@end' ) {
334361 source.writeln (tmpl_str_end)
362+ // tmpl_str_end contains '\n', so writeln creates 2 lines: ')' and empty
363+ p.template_line_map << ast.TemplateLineInfo{
364+ tmpl_path: template_file
365+ tmpl_line: tline_number
366+ }
367+ p.template_line_map << ast.TemplateLineInfo{
368+ tmpl_path: template_file
369+ tmpl_line: tline_number
370+ }
335371 source.writeln ('}' )
372+ p.template_line_map << ast.TemplateLineInfo{
373+ tmpl_path: template_file
374+ tmpl_line: tline_number
375+ }
336376 source.write_string (tmpl_str_start)
337377 continue
338378 }
339379 if line.contains ('@else' ) {
340380 source.writeln (tmpl_str_end)
381+ // tmpl_str_end contains '\n', so writeln creates 2 lines: ')' and empty
382+ p.template_line_map << ast.TemplateLineInfo{
383+ tmpl_path: template_file
384+ tmpl_line: tline_number
385+ }
386+ p.template_line_map << ast.TemplateLineInfo{
387+ tmpl_path: template_file
388+ tmpl_line: tline_number
389+ }
341390 pos := line.index ('@else' ) or { continue }
342391 source.writeln ('}' + line[pos + 1 ..] + '{' )
392+ p.template_line_map << ast.TemplateLineInfo{
393+ tmpl_path: template_file
394+ tmpl_line: tline_number
395+ }
343396 // source.writeln(' } else { ')
344397 source.write_string (tmpl_str_start)
345398 continue
346399 }
347400 if line.contains ('@for' ) {
348401 source.writeln (tmpl_str_end)
402+ // tmpl_str_end contains '\n', so writeln creates 2 lines: ')' and empty
403+ p.template_line_map << ast.TemplateLineInfo{
404+ tmpl_path: template_file
405+ tmpl_line: tline_number
406+ }
407+ p.template_line_map << ast.TemplateLineInfo{
408+ tmpl_path: template_file
409+ tmpl_line: tline_number
410+ }
349411 pos := line.index ('@for' ) or { continue }
350412 source.writeln ('for ' + line[pos + 4 ..] + '{' )
413+ p.template_line_map << ast.TemplateLineInfo{
414+ tmpl_path: template_file
415+ tmpl_line: tline_number
416+ }
351417 source.write_string (tmpl_str_start)
352418 continue
353419 }
354420 if state == .simple {
355421 // by default, just copy 1:1
356422 source.writeln (insert_template_code (fn_name, tmpl_str_start, line))
423+ p.template_line_map << ast.TemplateLineInfo{
424+ tmpl_path: template_file
425+ tmpl_line: tline_number
426+ }
357427 continue
358428 }
359429 // in_write = false
@@ -365,13 +435,21 @@ fn veb_tmpl_${fn_name}() string {
365435 source.write_string ('<script src="' )
366436 source.write_string (line[pos + 5 ..line.len - 1 ])
367437 source.writeln ('"></script>' )
438+ p.template_line_map << ast.TemplateLineInfo{
439+ tmpl_path: template_file
440+ tmpl_line: tline_number
441+ }
368442 continue
369443 }
370444 if line.contains ('@css ' ) {
371445 pos := line.index ('@css' ) or { continue }
372446 source.write_string ('<link href="' )
373447 source.write_string (line[pos + 6 ..line.len - 1 ])
374448 source.writeln ('" rel="stylesheet" type="text/css">' )
449+ p.template_line_map << ast.TemplateLineInfo{
450+ tmpl_path: template_file
451+ tmpl_line: tline_number
452+ }
375453 continue
376454 }
377455 }
@@ -383,6 +461,10 @@ fn veb_tmpl_${fn_name}() string {
383461 // `span.header {` => `<span class='header'>`
384462 class := line.find_between ('span.' , '{' ).trim_space ()
385463 source.writeln ('<span class="${class }">' )
464+ p.template_line_map << ast.TemplateLineInfo{
465+ tmpl_path: template_file
466+ tmpl_line: tline_number
467+ }
386468 in_span = true
387469 continue
388470 } else if line_t.starts_with ('.' ) && line.ends_with ('{' ) {
@@ -391,11 +473,19 @@ fn veb_tmpl_${fn_name}() string {
391473 trimmed := line.trim_space ()
392474 source.write_string (strings.repeat (`\t ` , line.len - trimmed.len)) // add the necessary indent to keep <div><div><div> code clean
393475 source.writeln ('<div class="${class }">' )
476+ p.template_line_map << ast.TemplateLineInfo{
477+ tmpl_path: template_file
478+ tmpl_line: tline_number
479+ }
394480 continue
395481 } else if line_t.starts_with ('#' ) && line.ends_with ('{' ) {
396482 // `#header {` => `<div id='header'>`
397483 class := line.find_between ('#' , '{' ).trim_space ()
398484 source.writeln ('<div id="${class }">' )
485+ p.template_line_map << ast.TemplateLineInfo{
486+ tmpl_path: template_file
487+ tmpl_line: tline_number
488+ }
399489 continue
400490 } else if line_t == '}' {
401491 source.write_string (strings.repeat (`\t ` , line.len - line_t.len)) // add the necessary indent to keep <div><div><div> code clean
@@ -405,12 +495,20 @@ fn veb_tmpl_${fn_name}() string {
405495 } else {
406496 source.writeln ('</div>' )
407497 }
498+ p.template_line_map << ast.TemplateLineInfo{
499+ tmpl_path: template_file
500+ tmpl_line: tline_number
501+ }
408502 continue
409503 }
410504 }
411505 .js {
412506 // if line.contains('//V_TEMPLATE') {
413507 source.writeln (insert_template_code (fn_name, tmpl_str_start, line))
508+ p.template_line_map << ast.TemplateLineInfo{
509+ tmpl_path: template_file
510+ tmpl_line: tline_number
511+ }
414512 // } else {
415513 // replace `$` to `\$` at first to escape JavaScript template literal syntax
416514 // source.writeln(line.replace(r'$', r'\$').replace(r'$$', r'@').replace(r'.$',
@@ -422,6 +520,10 @@ fn veb_tmpl_${fn_name}() string {
422520 // disable template variable declaration in inline stylesheet
423521 // because of some CSS rules prefixed with `@`.
424522 source.writeln (line.replace (r '.$' , r '.@' ).replace (r "'" , r "\'" ))
523+ p.template_line_map << ast.TemplateLineInfo{
524+ tmpl_path: template_file
525+ tmpl_line: tline_number
526+ }
425527 continue
426528 }
427529 else {}
@@ -459,10 +561,18 @@ fn veb_tmpl_${fn_name}() string {
459561 }
460562 // println(source.str())
461563 source.writeln (insert_template_code (fn_name, tmpl_str_start, line_))
564+ p.template_line_map << ast.TemplateLineInfo{
565+ tmpl_path: template_file
566+ tmpl_line: tline_number
567+ }
462568 // exit(0)
463569 } else {
464570 // by default, just copy 1:1
465571 source.writeln (insert_template_code (fn_name, tmpl_str_start, line))
572+ p.template_line_map << ast.TemplateLineInfo{
573+ tmpl_path: template_file
574+ tmpl_line: tline_number
575+ }
466576 }
467577 }
468578
0 commit comments