@@ -17,6 +17,8 @@ use tower_lsp::lsp_types::{
1717 WorkDoneProgressOptions ,
1818} ;
1919
20+ use crate :: state:: ConditionalBlock ;
21+
2022/// Convert usize to u32 for LSP types, saturating at `u32::MAX`.
2123fn to_lsp_u32 ( val : usize ) -> u32 {
2224 val. try_into ( ) . unwrap_or ( u32:: MAX )
@@ -37,8 +39,9 @@ pub const TOKEN_TYPES: &[SemanticTokenType] = &[
3739
3840/// Semantic token modifiers
3941pub const TOKEN_MODIFIERS : & [ SemanticTokenModifier ] = & [
40- SemanticTokenModifier :: DECLARATION , // anchor definitions
41- SemanticTokenModifier :: DEFINITION , // section with ID
42+ SemanticTokenModifier :: DECLARATION , // 0 - anchor definitions
43+ SemanticTokenModifier :: DEFINITION , // 1 - section with ID
44+ SemanticTokenModifier :: new ( "disabled" ) , // 2 - inactive conditional content
4245] ;
4346
4447/// Create the semantic tokens legend for capability registration
@@ -70,12 +73,17 @@ struct RawToken {
7073 token_modifiers : u32 ,
7174}
7275
73- /// Compute semantic tokens for a document
76+ /// Compute semantic tokens for a document, including conditional directive awareness.
7477#[ must_use]
75- pub fn compute_semantic_tokens ( doc : & Document ) -> SemanticTokens {
78+ pub fn compute_semantic_tokens (
79+ doc : & Document ,
80+ conditionals : & [ ConditionalBlock ] ,
81+ text : & str ,
82+ ) -> SemanticTokens {
7683 let mut tokens: Vec < RawToken > = Vec :: new ( ) ;
7784
7885 collect_tokens_from_blocks ( & doc. blocks , & mut tokens) ;
86+ collect_conditional_tokens ( conditionals, text, & mut tokens) ;
7987
8088 // Sort by position for delta encoding
8189 tokens. sort_by ( |a, b| a. line . cmp ( & b. line ) . then ( a. start_char . cmp ( & b. start_char ) ) ) ;
@@ -89,6 +97,63 @@ pub fn compute_semantic_tokens(doc: &Document) -> SemanticTokens {
8997 }
9098}
9199
100+ /// Emit semantic tokens for conditional directives and inactive content.
101+ fn collect_conditional_tokens (
102+ conditionals : & [ ConditionalBlock ] ,
103+ text : & str ,
104+ tokens : & mut Vec < RawToken > ,
105+ ) {
106+ let lines: Vec < & str > = text. lines ( ) . collect ( ) ;
107+
108+ for cond in conditionals {
109+ // Directive line as KEYWORD
110+ if let Some ( line_text) = lines. get ( cond. start_line )
111+ && !line_text. is_empty ( )
112+ {
113+ tokens. push ( RawToken {
114+ line : to_lsp_u32 ( cond. start_line ) ,
115+ start_char : 0 ,
116+ length : to_lsp_u32 ( line_text. len ( ) ) ,
117+ token_type : 6 , // KEYWORD
118+ token_modifiers : 0 ,
119+ } ) ;
120+ }
121+
122+ // Endif line as KEYWORD
123+ if let Some ( endif_line) = cond. end_line
124+ && let Some ( line_text) = lines. get ( endif_line)
125+ && !line_text. is_empty ( )
126+ {
127+ tokens. push ( RawToken {
128+ line : to_lsp_u32 ( endif_line) ,
129+ start_char : 0 ,
130+ length : to_lsp_u32 ( line_text. len ( ) ) ,
131+ token_type : 6 , // KEYWORD
132+ token_modifiers : 0 ,
133+ } ) ;
134+ }
135+
136+ // For inactive blocks, emit COMMENT + disabled modifier for content lines
137+ if !cond. is_active
138+ && let Some ( end_line) = cond. end_line
139+ {
140+ for line_idx in ( cond. start_line + 1 ) ..end_line {
141+ if let Some ( line_text) = lines. get ( line_idx)
142+ && !line_text. is_empty ( )
143+ {
144+ tokens. push ( RawToken {
145+ line : to_lsp_u32 ( line_idx) ,
146+ start_char : 0 ,
147+ length : to_lsp_u32 ( line_text. len ( ) ) ,
148+ token_type : 5 , // COMMENT
149+ token_modifiers : 4 , // bit 2 = disabled
150+ } ) ;
151+ }
152+ }
153+ }
154+ }
155+ }
156+
92157fn collect_tokens_from_blocks ( blocks : & [ Block ] , tokens : & mut Vec < RawToken > ) {
93158 for block in blocks {
94159 collect_tokens_from_block ( block, tokens) ;
@@ -370,7 +435,7 @@ Some content.
370435 let options = Options :: default ( ) ;
371436 let doc = acdc_parser:: parse ( content, & options) ?;
372437
373- let tokens = compute_semantic_tokens ( & doc) ;
438+ let tokens = compute_semantic_tokens ( & doc, & [ ] , content ) ;
374439 // Should have at least tokens for section titles
375440 assert ! ( !tokens. data. is_empty( ) ) ;
376441 Ok ( ( ) )
@@ -388,7 +453,7 @@ See <<target>> for more.
388453 let options = Options :: default ( ) ;
389454 let doc = acdc_parser:: parse ( content, & options) ?;
390455
391- let tokens = compute_semantic_tokens ( & doc) ;
456+ let tokens = compute_semantic_tokens ( & doc, & [ ] , content ) ;
392457 // Should have tokens for section, anchor, and xref
393458 assert ! ( tokens. data. len( ) >= 2 ) ;
394459 Ok ( ( ) )
@@ -400,5 +465,121 @@ See <<target>> for more.
400465 assert ! ( legend. token_types. contains( & SemanticTokenType :: NAMESPACE ) ) ;
401466 assert ! ( legend. token_types. contains( & SemanticTokenType :: FUNCTION ) ) ;
402467 assert ! ( legend. token_types. contains( & SemanticTokenType :: COMMENT ) ) ;
468+ assert_eq ! ( legend. token_modifiers. len( ) , 3 ) ;
469+ assert_eq ! (
470+ legend. token_modifiers. get( 2 ) ,
471+ Some ( & SemanticTokenModifier :: new( "disabled" ) )
472+ ) ;
473+ }
474+
475+ #[ test]
476+ fn test_conditional_inactive_tokens ( ) -> Result < ( ) , acdc_parser:: Error > {
477+ use crate :: state:: { ConditionalBlock , ConditionalDirectiveKind } ;
478+
479+ let content = "ifdef::missing[]\n inactive content\n endif::[]" ;
480+ let options = Options :: default ( ) ;
481+ let doc = acdc_parser:: parse ( content, & options) ?;
482+
483+ let conditionals = vec ! [ ConditionalBlock {
484+ kind: ConditionalDirectiveKind :: Ifdef ,
485+ attributes: vec![ "missing" . to_string( ) ] ,
486+ operation: None ,
487+ is_active: false ,
488+ start_line: 0 ,
489+ end_line: Some ( 2 ) ,
490+ } ] ;
491+
492+ let tokens = compute_semantic_tokens ( & doc, & conditionals, content) ;
493+ // Should have tokens: ifdef line (KEYWORD), inactive content (COMMENT+disabled), endif (KEYWORD)
494+ assert ! ( tokens. data. len( ) >= 3 ) ;
495+
496+ // Reconstruct absolute positions from delta encoding
497+ let mut abs_tokens = Vec :: new ( ) ;
498+ let mut prev_line = 0u32 ;
499+ let mut prev_start = 0u32 ;
500+ for t in & tokens. data {
501+ let line = prev_line + t. delta_line ;
502+ let start = if t. delta_line == 0 {
503+ prev_start + t. delta_start
504+ } else {
505+ t. delta_start
506+ } ;
507+ abs_tokens. push ( (
508+ line,
509+ start,
510+ t. length ,
511+ t. token_type ,
512+ t. token_modifiers_bitset ,
513+ ) ) ;
514+ prev_line = line;
515+ prev_start = start;
516+ }
517+
518+ // Line 0: ifdef directive → KEYWORD (type 6)
519+ assert ! (
520+ abs_tokens. iter( ) . any( |t| t. 0 == 0 && t. 3 == 6 ) ,
521+ "expected KEYWORD token on line 0"
522+ ) ;
523+ // Line 1: inactive content → COMMENT (type 5) + disabled (bit 2 = 4)
524+ assert ! (
525+ abs_tokens. iter( ) . any( |t| t. 0 == 1 && t. 3 == 5 && t. 4 == 4 ) ,
526+ "expected COMMENT+disabled token on line 1"
527+ ) ;
528+ // Line 2: endif → KEYWORD (type 6)
529+ assert ! (
530+ abs_tokens. iter( ) . any( |t| t. 0 == 2 && t. 3 == 6 ) ,
531+ "expected KEYWORD token on line 2"
532+ ) ;
533+
534+ Ok ( ( ) )
535+ }
536+
537+ #[ test]
538+ fn test_conditional_active_no_disabled_tokens ( ) -> Result < ( ) , acdc_parser:: Error > {
539+ use crate :: state:: { ConditionalBlock , ConditionalDirectiveKind } ;
540+
541+ let content = "ifdef::present[]\n active content\n endif::[]" ;
542+ let options = Options :: default ( ) ;
543+ let doc = acdc_parser:: parse ( content, & options) ?;
544+
545+ let conditionals = vec ! [ ConditionalBlock {
546+ kind: ConditionalDirectiveKind :: Ifdef ,
547+ attributes: vec![ "present" . to_string( ) ] ,
548+ operation: None ,
549+ is_active: true ,
550+ start_line: 0 ,
551+ end_line: Some ( 2 ) ,
552+ } ] ;
553+
554+ let tokens = compute_semantic_tokens ( & doc, & conditionals, content) ;
555+ // Active block: should have KEYWORD tokens for directives but NO disabled tokens
556+ let mut abs_tokens = Vec :: new ( ) ;
557+ let mut prev_line = 0u32 ;
558+ let mut prev_start = 0u32 ;
559+ for t in & tokens. data {
560+ let line = prev_line + t. delta_line ;
561+ let start = if t. delta_line == 0 {
562+ prev_start + t. delta_start
563+ } else {
564+ t. delta_start
565+ } ;
566+ abs_tokens. push ( (
567+ line,
568+ start,
569+ t. length ,
570+ t. token_type ,
571+ t. token_modifiers_bitset ,
572+ ) ) ;
573+ prev_line = line;
574+ prev_start = start;
575+ }
576+
577+ // No tokens should have the disabled modifier (bit 2 = 4)
578+ assert ! (
579+ !abs_tokens. iter( ) . any( |t| t. 4 & 4 != 0 ) ,
580+ "active block should have no disabled tokens"
581+ ) ;
582+
583+ Ok ( ( ) )
403584 }
404585}
0 commit comments