1- use crate :: css_data:: {
2- AtDirectiveEntry , CssCustomData , MarkupDescriptionOrString , PropertyEntry , Reference , Status ,
3- } ;
4- use biome_css_syntax:: { CssLanguage , CssSyntaxKind } ;
5- use biome_rowan:: { AstNode , SyntaxNode } ;
6- use lsp_types:: { Hover , HoverContents , MarkupContent , MarkupKind , Position , TextDocumentItem } ;
7- use std:: fmt:: Write ;
8-
91use crate :: {
10- converters:: { from_proto:: offset, line_index:: LineIndex , to_proto:: range, PositionEncoding } ,
2+ converters:: {
3+ from_proto:: offset,
4+ line_index:: LineIndex ,
5+ to_proto:: { self , range} ,
6+ PositionEncoding ,
7+ } ,
8+ css_data:: {
9+ AtDirectiveEntry , CssCustomData , MarkupDescriptionOrString , PropertyEntry , Reference ,
10+ Status ,
11+ } ,
1112 service:: LanguageService ,
1213} ;
14+ use biome_css_syntax:: { CssLanguage , CssSyntaxKind } ;
15+ use biome_rowan:: { AstNode , SyntaxNode , TextSize } ;
16+ use lsp_types:: {
17+ Hover , HoverContents , MarkupContent , MarkupKind , Position , Range , TextDocumentItem ,
18+ } ;
19+ use std:: fmt:: Write ;
1320
1421/// Extracts hover information for the given CSS node and position.
1522fn extract_hover_information (
1623 node : & SyntaxNode < CssLanguage > ,
1724 position : Position ,
1825 line_index : & LineIndex ,
1926 encoding : PositionEncoding ,
20- css_data : & Vec < & CssCustomData > ,
27+ css_data : & [ & CssCustomData ] ,
2128) -> Option < Hover > {
2229 let offset = offset ( line_index, position, encoding) . ok ( ) ?;
2330 let token = node. token_at_offset ( offset) . right_biased ( ) ?;
24- let mut selector_node = None ;
25- for ancestor in token. ancestors ( ) {
26- match ancestor. kind ( ) {
27- // These nodes represent the full selector, including combinators
31+ // Since the token is a leaf node, we need to find a more meaningful parent to provide hover informations.
32+ token
33+ . ancestors ( )
34+ . find_map ( |ancestor| match ancestor. kind ( ) {
35+ // Handle CSS selectors, e.g. `.class`, `#id`, `element`, `element.class`, etc.
2836 CssSyntaxKind :: CSS_COMPLEX_SELECTOR | CssSyntaxKind :: CSS_SELECTOR_LIST => {
29- selector_node = Some ( ancestor. clone ( ) ) ;
30- break ; // We've found the full selector
37+ let name = & ancestor. text_trimmed ( ) . to_string ( ) ;
38+ let content = format_selector_entry ( name, Some ( calculate_specificity ( name) ) ) ;
39+ Some ( Hover {
40+ contents : HoverContents :: Markup ( MarkupContent {
41+ kind : MarkupKind :: Markdown ,
42+ value : content,
43+ } ) ,
44+ range : range ( line_index, ancestor. text_trimmed_range ( ) , encoding) . ok ( ) ,
45+ } )
3146 }
32- // Update selector_node if it's not already set
33- CssSyntaxKind :: CSS_COMPOUND_SELECTOR => {
34- if selector_node. is_none ( ) {
35- selector_node = Some ( ancestor. clone ( ) ) ;
36- }
37- }
38- CssSyntaxKind :: CSS_IDENTIFIER => {
39- // Handle identifiers like properties or at-rules
40- if let Some ( hover_content) =
41- get_css_hover_content ( ancestor. kind ( ) , token. text ( ) . trim ( ) , css_data)
42- {
43- return Some ( Hover {
44- contents : HoverContents :: Markup ( MarkupContent {
45- kind : MarkupKind :: Markdown ,
46- value : hover_content,
47- } ) ,
48- range : range ( line_index, ancestor. text_trimmed_range ( ) , encoding) . ok ( ) ,
49- } ) ;
50- }
47+ // Handle CSS properties, e.g. `color`, `font-size`, etc.
48+ CssSyntaxKind :: CSS_GENERIC_PROPERTY => {
49+ // We can assume that the token is the IDENT token with the property name.
50+ let name = token. text_trimmed ( ) . to_string ( ) ;
51+ css_data. iter ( ) . find_map ( |data| {
52+ data. properties
53+ . as_ref ( ) ?
54+ . iter ( )
55+ . find ( |prop| prop. name == name)
56+ . map ( format_property_entry)
57+ . map ( |content| Hover {
58+ contents : HoverContents :: Markup ( MarkupContent {
59+ kind : MarkupKind :: Markdown ,
60+ value : content,
61+ } ) ,
62+ range : range ( line_index, token. text_trimmed_range ( ) , encoding) . ok ( ) ,
63+ } )
64+ } )
5165 }
52- _ => {
53- // Not part of a selector; continue traversing
66+ // Handle CSS at-rules, e.g. `@media`, `@keyframes`, etc.
67+ CssSyntaxKind :: CSS_AT_RULE => {
68+ // We can't assume that the token is the KW token (with the at-rule name) since it could be the AT token.
69+ let at_rule_token = ancestor. first_child ( ) ?. first_token ( ) ?;
70+ css_data. iter ( ) . find_map ( |data| {
71+ data. at_directives
72+ . as_ref ( ) ?
73+ . iter ( )
74+ . find ( |at_rule| {
75+ // CSS Custom Data uses `@` prefix for at-rules, so we need to add it back.
76+ at_rule. name == format ! ( "@{}" , at_rule_token. text_trimmed( ) )
77+ } )
78+ . map ( format_at_rule_entry)
79+ . map ( |content| Hover {
80+ contents : HoverContents :: Markup ( MarkupContent {
81+ kind : MarkupKind :: Markdown ,
82+ value : content,
83+ } ) ,
84+ range : Some ( Range :: new (
85+ to_proto:: position (
86+ line_index,
87+ // We need to include the `@` symbol in the selection range.
88+ at_rule_token. text_trimmed_range ( ) . start ( ) - TextSize :: from ( 1 ) ,
89+ encoding,
90+ )
91+ . unwrap ( ) ,
92+ to_proto:: position (
93+ line_index,
94+ at_rule_token. text_trimmed_range ( ) . end ( ) ,
95+ encoding,
96+ )
97+ . unwrap ( ) ,
98+ ) ) ,
99+ } )
100+ } )
54101 }
55- }
56- }
57-
58- // Use the identified selector node for hover content
59- if let Some ( selector_node) = selector_node {
60- if let Some ( hover_content) = get_css_hover_content (
61- selector_node. kind ( ) ,
62- selector_node. text ( ) . to_string ( ) . trim ( ) ,
63- css_data,
64- ) {
65- return Some ( Hover {
66- contents : HoverContents :: Markup ( MarkupContent {
67- kind : MarkupKind :: Markdown ,
68- value : hover_content,
69- } ) ,
70- range : range ( line_index, selector_node. text_trimmed_range ( ) , encoding) . ok ( ) ,
71- } ) ;
72- }
73- }
74-
75- None
76- }
77-
78- /// Generates hover content for a given CSS entity using the provided CSS custom data.
79- fn get_css_hover_content (
80- kind : CssSyntaxKind ,
81- name : & str ,
82- css_data : & Vec < & CssCustomData > ,
83- ) -> Option < String > {
84- match kind {
85- // Handle CSS properties like "color", "font-size", etc.
86- CssSyntaxKind :: CSS_IDENTIFIER => {
87- for data in css_data {
88- if let Some ( property) = data
89- . properties
90- . as_ref ( )
91- . iter ( )
92- . flat_map ( |props| props. iter ( ) )
93- . find ( |prop| prop. name == name)
94- {
95- return Some ( format_css_property_entry ( property) ) ;
96- }
97- }
98- None
99- }
100- // Handle at-rules like @media, @supports, etc.
101- CssSyntaxKind :: CSS_AT_RULE => {
102- eprintln ! ( "Looking for at-rule: {}" , name) ;
103- for data in css_data {
104- if let Some ( at_directive) = data
105- . at_directives
106- . as_ref ( )
107- . iter ( )
108- . flat_map ( |ats| ats. iter ( ) )
109- . find ( |at| at. name == name)
110- {
111- return Some ( format_css_at_rule_entry ( at_directive) ) ;
112- }
113- }
114- None
115- }
116- // Handle CSS selectors like ".class", "#id", "element", etc.
117- CssSyntaxKind :: CSS_SELECTOR_LIST
118- | CssSyntaxKind :: CSS_COMPLEX_SELECTOR
119- | CssSyntaxKind :: CSS_COMPOUND_SELECTOR => Some ( format_css_selector_entry (
120- name,
121- Some ( calculate_specificity ( name) ) ,
122- ) ) ,
123- _ => None ,
124- }
102+ _ => None ,
103+ } )
125104}
126105
127106/// Formats the CSS property entry into a hover content string.
128- fn format_css_property_entry ( property : & PropertyEntry ) -> String {
107+ fn format_property_entry ( property : & PropertyEntry ) -> String {
129108 let mut content = String :: new ( ) ;
130109 write_status ( & mut content, & property. status ) ;
131110 write_description ( & mut content, & property. description ) ;
@@ -136,7 +115,7 @@ fn format_css_property_entry(property: &PropertyEntry) -> String {
136115}
137116
138117/// Formats the CSS at-rule entry into a hover content string.
139- fn format_css_at_rule_entry ( at_property : & AtDirectiveEntry ) -> String {
118+ fn format_at_rule_entry ( at_property : & AtDirectiveEntry ) -> String {
140119 let mut content = String :: new ( ) ;
141120 write_status ( & mut content, & at_property. status ) ;
142121 write_description ( & mut content, & at_property. description ) ;
@@ -145,7 +124,7 @@ fn format_css_at_rule_entry(at_property: &AtDirectiveEntry) -> String {
145124}
146125
147126/// Formats the CSS selector entry into a hover content string.
148- fn format_css_selector_entry ( name : & str , specificity : Option < ( u32 , u32 , u32 ) > ) -> String {
127+ fn format_selector_entry ( name : & str , specificity : Option < ( u32 , u32 , u32 ) > ) -> String {
149128 let mut content = String :: new ( ) ;
150129 // TODO: this is a placeholder, we should render an HTML preview of the selector
151130 writeln ! ( content, "**{}**\n " , escape_markdown( name) ) . unwrap ( ) ;
0 commit comments