@@ -428,4 +428,250 @@ describe("peekstack.ui.stack_view", function()
428428
429429 vim .fn .delete (tmpfile )
430430 end )
431+
432+ it (" applies treesitter highlights to preview lines when available" , function ()
433+ local tmpfile = vim .fn .tempname () .. " .lua"
434+ vim .fn .writefile ({ " local value = 42" }, tmpfile )
435+
436+ local original_get_parser = vim .treesitter .get_parser
437+ local original_query_get = vim .treesitter .query .get
438+ local ok , err = pcall (function ()
439+ vim .api .nvim_set_hl (0 , " @keyword.peekstack_test_ts" , { link = " Keyword" })
440+
441+ local fake_node = {
442+ range = function ()
443+ return 0 , 0 , 0 , 5
444+ end ,
445+ }
446+ local fake_root = {
447+ range = function ()
448+ return 0 , 0 , 0 , 20
449+ end ,
450+ }
451+ local fake_tree = {
452+ root = function ()
453+ return fake_root
454+ end ,
455+ lang = function ()
456+ return " peekstack_test_ts"
457+ end ,
458+ }
459+ local fake_query = {
460+ captures = { " keyword" },
461+ }
462+ fake_query .iter_captures = function (_self , _root , _bufnr , _start_row , _end_row )
463+ local emitted = false
464+ return function ()
465+ if emitted then
466+ return nil
467+ end
468+ emitted = true
469+ return 1 , fake_node
470+ end
471+ end
472+
473+ vim .treesitter .get_parser = function (_bufnr )
474+ return {
475+ parse = function () end ,
476+ trees = function ()
477+ return { fake_tree }
478+ end ,
479+ }
480+ end
481+ vim .treesitter .query .get = function (_lang , _query_name )
482+ return fake_query
483+ end
484+
485+ local loc = helpers .make_location ({
486+ uri = vim .uri_from_fname (tmpfile ),
487+ range = { start = { line = 0 , character = 0 }, [" end" ] = { line = 0 , character = 0 } },
488+ })
489+ local model = stack .push (loc )
490+ assert .is_not_nil (model )
491+
492+ stack_view .open ()
493+ local state = stack_view ._get_state ()
494+ stack_view ._render (state )
495+
496+ local lines = vim .api .nvim_buf_get_lines (state .bufnr , 0 , - 1 , false )
497+ local preview_line_nr = nil
498+ for idx , line in ipairs (lines ) do
499+ if line :find (" local value = 42" , 1 , true ) then
500+ preview_line_nr = idx
501+ break
502+ end
503+ end
504+ assert .is_not_nil (preview_line_nr )
505+
506+ local ns = vim .api .nvim_create_namespace (" PeekstackStackView" )
507+ local extmarks = vim .api .nvim_buf_get_extmarks (state .bufnr , ns , 0 , - 1 , { details = true })
508+ local found = false
509+ for _ , mark in ipairs (extmarks ) do
510+ local row = mark [2 ]
511+ local details = mark [4 ] or {}
512+ if row == preview_line_nr - 1 and details .hl_group == " @keyword.peekstack_test_ts" then
513+ found = true
514+ break
515+ end
516+ end
517+ assert .is_true (found , " treesitter highlight should be applied to preview line" )
518+ end )
519+
520+ vim .treesitter .get_parser = original_get_parser
521+ vim .treesitter .query .get = original_query_get
522+ vim .fn .delete (tmpfile )
523+ if not ok then
524+ error (err )
525+ end
526+ end )
527+
528+ it (" falls back to default preview highlight when treesitter parser fails" , function ()
529+ local tmpfile = vim .fn .tempname () .. " .lua"
530+ vim .fn .writefile ({ " local x = 42" }, tmpfile )
531+
532+ local original_get_parser = vim .treesitter .get_parser
533+ local ok , err = pcall (function ()
534+ vim .treesitter .get_parser = function (_bufnr )
535+ error (" parser unavailable" )
536+ end
537+
538+ local loc = helpers .make_location ({
539+ uri = vim .uri_from_fname (tmpfile ),
540+ range = { start = { line = 0 , character = 0 }, [" end" ] = { line = 0 , character = 0 } },
541+ })
542+ local model = stack .push (loc )
543+ assert .is_not_nil (model )
544+
545+ stack_view .open ()
546+ local state = stack_view ._get_state ()
547+ assert .has_no .errors (function ()
548+ stack_view ._render (state )
549+ end )
550+
551+ local lines = vim .api .nvim_buf_get_lines (state .bufnr , 0 , - 1 , false )
552+ local preview_line_nr = nil
553+ for idx , line in ipairs (lines ) do
554+ if line :find (" local x = 42" , 1 , true ) then
555+ preview_line_nr = idx
556+ break
557+ end
558+ end
559+ assert .is_not_nil (preview_line_nr )
560+
561+ local ns = vim .api .nvim_create_namespace (" PeekstackStackView" )
562+ local extmarks = vim .api .nvim_buf_get_extmarks (state .bufnr , ns , 0 , - 1 , { details = true })
563+ local found = false
564+ for _ , mark in ipairs (extmarks ) do
565+ local row = mark [2 ]
566+ local details = mark [4 ] or {}
567+ if row == preview_line_nr - 1 and details .hl_group == " PeekstackStackViewPreview" then
568+ found = true
569+ break
570+ end
571+ end
572+ assert .is_true (found , " default preview highlight should remain when treesitter fails" )
573+ end )
574+
575+ vim .treesitter .get_parser = original_get_parser
576+ vim .fn .delete (tmpfile )
577+ if not ok then
578+ error (err )
579+ end
580+ end )
581+
582+ it (" clamps treesitter highlight range on truncated preview lines" , function ()
583+ local tmpfile = vim .fn .tempname () .. " .lua"
584+ vim .fn .writefile ({ string.rep (" a" , 400 ) }, tmpfile )
585+
586+ local original_get_parser = vim .treesitter .get_parser
587+ local original_query_get = vim .treesitter .query .get
588+ local ok , err = pcall (function ()
589+ vim .api .nvim_set_hl (0 , " @string.peekstack_test_trunc" , { link = " String" })
590+
591+ local fake_node = {
592+ range = function ()
593+ return 0 , 0 , 0 , 400
594+ end ,
595+ }
596+ local fake_root = {
597+ range = function ()
598+ return 0 , 0 , 0 , 400
599+ end ,
600+ }
601+ local fake_tree = {
602+ root = function ()
603+ return fake_root
604+ end ,
605+ lang = function ()
606+ return " peekstack_test_trunc"
607+ end ,
608+ }
609+ local fake_query = {
610+ captures = { " string" },
611+ }
612+ fake_query .iter_captures = function (_self , _root , _bufnr , _start_row , _end_row )
613+ local emitted = false
614+ return function ()
615+ if emitted then
616+ return nil
617+ end
618+ emitted = true
619+ return 1 , fake_node
620+ end
621+ end
622+
623+ vim .treesitter .get_parser = function (_bufnr )
624+ return {
625+ parse = function () end ,
626+ trees = function ()
627+ return { fake_tree }
628+ end ,
629+ }
630+ end
631+ vim .treesitter .query .get = function (_lang , _query_name )
632+ return fake_query
633+ end
634+
635+ local loc = helpers .make_location ({
636+ uri = vim .uri_from_fname (tmpfile ),
637+ range = { start = { line = 0 , character = 0 }, [" end" ] = { line = 0 , character = 0 } },
638+ })
639+ local model = stack .push (loc )
640+ assert .is_not_nil (model )
641+
642+ stack_view .open ()
643+ local state = stack_view ._get_state ()
644+ assert .has_no .errors (function ()
645+ stack_view ._render (state )
646+ end )
647+
648+ local lines = vim .api .nvim_buf_get_lines (state .bufnr , 0 , - 1 , false )
649+ local preview_line_nr = nil
650+ for idx , line in ipairs (lines ) do
651+ if line :find (" ^ a" , 1 , false ) then
652+ preview_line_nr = idx
653+ break
654+ end
655+ end
656+ assert .is_not_nil (preview_line_nr )
657+ local preview_len = # lines [preview_line_nr ]
658+
659+ local ns = vim .api .nvim_create_namespace (" PeekstackStackView" )
660+ local extmarks = vim .api .nvim_buf_get_extmarks (state .bufnr , ns , 0 , - 1 , { details = true })
661+ for _ , mark in ipairs (extmarks ) do
662+ local row = mark [2 ]
663+ local details = mark [4 ] or {}
664+ if row == preview_line_nr - 1 and details .hl_group == " @string.peekstack_test_trunc" then
665+ assert .is_true ((details .end_col or 0 ) <= preview_len )
666+ end
667+ end
668+ end )
669+
670+ vim .treesitter .get_parser = original_get_parser
671+ vim .treesitter .query .get = original_query_get
672+ vim .fn .delete (tmpfile )
673+ if not ok then
674+ error (err )
675+ end
676+ end )
431677end )
0 commit comments