@@ -68,6 +68,26 @@ impl LanguageServer for Backend {
6868 })),
6969 document_symbol_provider: Some(OneOf::Left(true)),
7070 code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
71+ color_provider: Some(ColorProviderCapability::Simple(true)),
72+ folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
73+ semantic_tokens_provider: Some(
74+ SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions {
75+ legend: SemanticTokensLegend {
76+ token_types: vec![
77+ SemanticTokenType::KEYWORD,
78+ SemanticTokenType::VARIABLE,
79+ SemanticTokenType::FUNCTION,
80+ SemanticTokenType::STRING,
81+ SemanticTokenType::COMMENT,
82+ SemanticTokenType::PROPERTY,
83+ ],
84+ token_modifiers: vec![],
85+ },
86+ full: Some(SemanticTokensFullOptions::Bool(true)),
87+ range: None,
88+ ..Default::default()
89+ }),
90+ ),
7191 ..Default::default()
7292 },
7393 ..Default::default()
@@ -220,6 +240,77 @@ impl LanguageServer for Backend {
220240 Some(actions)
221241 })
222242 }
243+
244+ async fn document_color(
245+ &self,
246+ params: DocumentColorParams,
247+ ) -> Result<Vec<ColorInformation>> {
248+ let uri = ¶ms.text_document.uri;
249+ let docs = self.documents.read().await;
250+ let text = match docs.get(uri) {
251+ Some(t) => t.clone(),
252+ None => return Ok(vec![]),
253+ };
254+ drop(docs);
255+ Ok(find_colors(&text))
256+ }
257+
258+ async fn color_presentation(
259+ &self,
260+ params: ColorPresentationParams,
261+ ) -> Result<Vec<ColorPresentation>> {
262+ let c = params.color;
263+ let r = (c.red * 255.0) as u8;
264+ let g = (c.green * 255.0) as u8;
265+ let b = (c.blue * 255.0) as u8;
266+ let hex = if c.alpha < 1.0 {
267+ let a = (c.alpha * 255.0) as u8;
268+ format!("#{:02x}{:02x}{:02x}{:02x}", r, g, b, a)
269+ } else {
270+ format!("#{:02x}{:02x}{:02x}", r, g, b)
271+ };
272+ Ok(vec![ColorPresentation {
273+ label: hex.clone(),
274+ text_edit: Some(TextEdit {
275+ range: params.range,
276+ new_text: hex,
277+ }),
278+ additional_text_edits: None,
279+ }])
280+ }
281+
282+ async fn folding_range(
283+ &self,
284+ params: FoldingRangeParams,
285+ ) -> Result<Option<Vec<FoldingRange>>> {
286+ let uri = ¶ms.text_document.uri;
287+ let docs = self.documents.read().await;
288+ let text = match docs.get(uri) {
289+ Some(t) => t.clone(),
290+ None => return Ok(None),
291+ };
292+ drop(docs);
293+ let ranges = folding_ranges(&text);
294+ Ok(if ranges.is_empty() { None } else { Some(ranges) })
295+ }
296+
297+ async fn semantic_tokens_full(
298+ &self,
299+ params: SemanticTokensParams,
300+ ) -> Result<Option<SemanticTokensResult>> {
301+ let uri = ¶ms.text_document.uri;
302+ let docs = self.documents.read().await;
303+ let text = match docs.get(uri) {
304+ Some(t) => t.clone(),
305+ None => return Ok(None),
306+ };
307+ drop(docs);
308+ let tokens = semantic_tokens(&text);
309+ Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
310+ result_id: None,
311+ data: tokens,
312+ })))
313+ }
223314}
224315
225316// ---------------------------------------------------------------------------
@@ -254,7 +345,7 @@ fn completions(text: &str, position: Position) -> Vec<CompletionItem> {
254345 // State prefix (hover:, active:, focus:) or media prefix (dark:, print:)
255346 if let Some(colon) = current_word.find(':') {
256347 let prefix = ¤t_word[..colon];
257− if matches!(prefix, "hover" | "active" | "focus" | "dark" | "print") {
348+ if matches!(prefix, "hover" | "active" | "focus" | "focus-visible" | "focus-within" | "disabled" | "checked" | "placeholder" | "first" | "last" | "odd" | "even" | "dark" | "print") {
258349 return state_attr_completions(prefix, edit_range);
259350 }
260351 }
@@ -390,6 +481,7 @@ fn element_completions(range: Range) -> Vec<CompletionItem> {
390481 ("@figcaption", "Caption for @figure"),
391482 ("@progress", "Progress bar (value, max attributes)"),
392483 ("@meter", "Meter/gauge element (value, min, max)"),
484+ ("@fragment", "Group children without a wrapper element"),
393485 ]
394486 .iter()
395487 .map(|(name, detail)| item(name, CompletionItemKind::KEYWORD, detail, name, range))
@@ -418,6 +510,8 @@ fn directive_completions(range: Range) -> Vec<CompletionItem> {
418510 ("@default", "Default case (inside @match)", "@default"),
419511 ("@warn", "Emit a compile-time warning", "@warn "),
420512 ("@debug", "Print debug message during compilation", "@debug "),
513+ ("@lang", "Set document language (html lang attribute)", "@lang "),
514+ ("@favicon", "Set favicon (inlined as base64 data URI)", "@favicon "),
421515 ]
422516 .iter()
423517 .map(|(name, detail, insert)| {
@@ -587,10 +681,48 @@ fn attr_completions(range: Range) -> Vec<CompletionItem> {
587681 ("rowspan", "Table cell row span", true),
588682 ("scope", "Table header scope (col/row/colgroup/rowgroup)", true),
589683 ("inline", "Inline SVG images into output", false),
684+ // Hidden
685+ ("hidden", "Hide element (display:none)", false),
686+ // Overflow directional
687+ ("overflow-x", "Horizontal overflow (hidden/scroll/auto)", true),
688+ ("overflow-y", "Vertical overflow (hidden/scroll/auto)", true),
689+ // Inset
690+ ("inset", "Shorthand for top/right/bottom/left", true),
691+ // Modern form theming
692+ ("accent-color", "Accent color for form controls", true),
693+ ("caret-color", "Text cursor color", true),
694+ // List styling
695+ ("list-style", "List style type (disc/circle/square/none)", true),
696+ // Table styling
697+ ("border-collapse", "Border collapse mode (collapse/separate)", true),
698+ ("border-spacing", "Spacing between table cell borders", true),
699+ // Text decoration
700+ ("text-decoration", "Text decoration (underline/overline/line-through)", true),
701+ ("text-decoration-color", "Text decoration color", true),
702+ ("text-decoration-thickness", "Text decoration thickness", true),
703+ ("text-decoration-style", "Text decoration style (solid/dashed/dotted/wavy)", true),
704+ // Grid/flex placement
705+ ("place-items", "Shorthand for align-items + justify-items", true),
706+ ("place-self", "Shorthand for align-self + justify-self", true),
707+ // Scroll behavior
708+ ("scroll-behavior", "Scroll behavior (smooth/auto)", true),
709+ // Resize
710+ ("resize", "Resize behavior (none/both/horizontal/vertical)", true),
590711 // State prefixes
591712 ("hover:", "Style on hover", false),
592713 ("active:", "Style on active/click", false),
593714 ("focus:", "Style on focus", false),
715+ // New pseudo-state prefixes
716+ ("focus-visible:", "Style on keyboard focus", false),
717+ ("focus-within:", "Style when child has focus", false),
718+ ("disabled:", "Style when disabled", false),
719+ ("checked:", "Style when checked", false),
720+ ("placeholder:", "Style placeholder text", false),
721+ // Child selectors
722+ ("first:", "Style first child", false),
723+ ("last:", "Style last child", false),
724+ ("odd:", "Style odd children (1st, 3rd, ...)", false),
725+ ("even:", "Style even children (2nd, 4th, ...)", false),
594726 // Responsive prefixes
595727 ("sm:", "Style at 640px+ (small)", false),
596728 ("md:", "Style at 768px+ (medium)", false),
@@ -619,6 +751,8 @@ fn state_attr_completions(prefix: &str, range: Range) -> Vec<CompletionItem> {
619751 ("border", "Border (width [color])", true),
620752 ("border-top", "Top border (width [color])", true),
621753 ("border-bottom", "Bottom border (width [color])", true),
754+ ("border-left", "Left border (width [color])", true),
755+ ("border-right", "Right border (width [color])", true),
622756 ("rounded", "Border radius", true),
623757 ("bold", "Bold text", false),
624758 ("italic", "Italic text", false),
@@ -634,6 +768,18 @@ fn state_attr_completions(prefix: &str, range: Range) -> Vec<CompletionItem> {
634768 ("visibility", "Visibility", true),
635769 ("pointer-events", "Pointer events", true),
636770 ("user-select", "User selection", true),
771+ // Layout attrs for pseudo-states
772+ ("width", "Width", true),
773+ ("height", "Height", true),
774+ ("padding", "Inner padding", true),
775+ ("padding-x", "Horizontal padding", true),
776+ ("padding-y", "Vertical padding", true),
777+ ("margin", "Outer margin", true),
778+ ("margin-x", "Horizontal margin", true),
779+ ("margin-y", "Vertical margin", true),
780+ ("outline", "Outline", true),
781+ ("text-decoration", "Text decoration", true),
782+ ("text-decoration-color", "Text decoration color", true),
637783 ]
638784 .iter()
639785 .map(|(name, detail, takes_value)| {
@@ -833,6 +979,24 @@ fn hover_builtin(word: &str) -> Option<String> {
833979 (Some("active"), rest)
834980 } else if let Some(rest) = word.strip_prefix("focus:") {
835981 (Some("focus"), rest)
982+ } else if let Some(rest) = word.strip_prefix("focus-visible:") {
983+ (Some("focus-visible"), rest)
984+ } else if let Some(rest) = word.strip_prefix("focus-within:") {
985+ (Some("focus-within"), rest)
986+ } else if let Some(rest) = word.strip_prefix("disabled:") {
987+ (Some("disabled"), rest)
988+ } else if let Some(rest) = word.strip_prefix("checked:") {
989+ (Some("checked"), rest)
990+ } else if let Some(rest) = word.strip_prefix("placeholder:") {
991+ (Some("placeholder"), rest)
992+ } else if let Some(rest) = word.strip_prefix("first:") {
993+ (Some("first"), rest)
994+ } else if let Some(rest) = word.strip_prefix("last:") {
995+ (Some("last"), rest)
996+ } else if let Some(rest) = word.strip_prefix("odd:") {
997+ (Some("odd"), rest)
998+ } else if let Some(rest) = word.strip_prefix("even:") {
999+ (Some("even"), rest)
8361000 } else {
8371001 (None, word)
8381002 };
@@ -901,12 +1065,15 @@ fn hover_builtin(word: &str) -> Option<String> {
9011065 "@figcaption" => "**@figcaption** \u{2014} Figure caption\n\nRenders as `<figcaption>`. Caption text inside `@figure`.",
9021066 "@progress" => "**@progress** \u{2014} Progress bar\n\nRenders as `<progress>`.\n\nUsage: `@progress [value 70, max 100]`",
9031067 "@meter" => "**@meter** \u{2014} Meter\n\nRenders as `<meter>`. Gauge for scalar measurement.\n\nUsage: `@meter [value 0.7, min 0, max 1, low 0.3, high 0.8]`",
1068+ "@fragment" => "**@fragment** \u{2014} Fragment\n\nGroups children without emitting a wrapper element. Renders children directly in the parent.",
9041069 // Directives
9051070 "@match" => "**@match** \u{2014} Pattern matching\n\nMatch a value against cases.\n\n```\n@match $theme\n @case dark\n @el [background #333]\n @case light\n @el [background white]\n @default\n @el [background gray]\n```",
9061071 "@case" => "**@case** \u{2014} Match case\n\nA case inside `@match`. Matches when the value equals the case value.",
9071072 "@default" => "**@default** \u{2014} Default case\n\nFallback case inside `@match` when no other case matches.",
9081073 "@warn" => "**@warn** \u{2014} Compile warning\n\nEmit a custom warning during compilation.\n\nUsage: `@warn This value is deprecated`",
9091074 "@debug" => "**@debug** \u{2014} Debug message\n\nPrint a debug message to stderr during compilation.\n\nUsage: `@debug Theme is $theme`",
1075+ "@lang" => "**@lang** \u{2014} Document language\n\nSets the `lang` attribute on the `<html>` element.\n\nUsage: `@lang en`",
1076+ "@favicon" => "**@favicon** \u{2014} Favicon\n\nInlines a favicon as a base64 data URI in the `<head>`.\n\nUsage: `@favicon favicon.png`",
9101077 // Attributes
9111078 "spacing" | "gap" => "**spacing** `<value>`\n\nGap between children. Supports CSS units (px, rem, em, %).\nMaps to CSS `gap`.",
9121079 "padding" => "**padding** `<value>` | `<y> <x>` | `<t> <h> <b>` | `<t> <r> <b> <l>`\n\nInner padding. Supports CSS units. Accepts 1\u{2013}4 values.",
@@ -1037,6 +1204,23 @@ fn hover_builtin(word: &str) -> Option<String> {
10371204 "rowspan" => "**rowspan** `<value>` \u{2014} Number of rows a cell spans.",
10381205 "scope" => "**scope** `<value>` \u{2014} Header scope (`col`, `row`, `colgroup`, `rowgroup`).",
10391206 "inline" => "**inline** \u{2014} Inline SVG image content into the HTML output.",
1207+ "hidden" => "**hidden** \u{2014} Hide element (`display: none`).",
1208+ "overflow-x" => "**overflow-x** `<value>` \u{2014} Horizontal overflow (`hidden`, `scroll`, `auto`, `visible`).",
1209+ "overflow-y" => "**overflow-y** `<value>` \u{2014} Vertical overflow (`hidden`, `scroll`, `auto`, `visible`).",
1210+ "inset" => "**inset** `<value>` \u{2014} Shorthand for `top`, `right`, `bottom`, `left`. Maps to CSS `inset`.",
1211+ "accent-color" => "**accent-color** `<color>` \u{2014} Accent color for form controls (checkboxes, radios, range).",
1212+ "caret-color" => "**caret-color** `<color>` \u{2014} Color of the text input cursor.",
1213+ "list-style" => "**list-style** `<value>` \u{2014} List style type (`disc`, `circle`, `square`, `decimal`, `none`).",
1214+ "border-collapse" => "**border-collapse** `<value>` \u{2014} Table border model (`collapse`, `separate`).",
1215+ "border-spacing" => "**border-spacing** `<value>` \u{2014} Spacing between table cell borders (when `border-collapse: separate`).",
1216+ "text-decoration" => "**text-decoration** `<value>` \u{2014} Text decoration (`underline`, `overline`, `line-through`, `none`).",
1217+ "text-decoration-color" => "**text-decoration-color** `<color>` \u{2014} Color of text decoration.",
1218+ "text-decoration-thickness" => "**text-decoration-thickness** `<value>` \u{2014} Thickness of text decoration.",
1219+ "text-decoration-style" => "**text-decoration-style** `<value>` \u{2014} Style of text decoration (`solid`, `dashed`, `dotted`, `wavy`, `double`).",
1220+ "place-items" => "**place-items** `<value>` \u{2014} Shorthand for `align-items` and `justify-items`.",
1221+ "place-self" => "**place-self** `<value>` \u{2014} Shorthand for `align-self` and `justify-self`.",
1222+ "scroll-behavior" => "**scroll-behavior** `<value>` \u{2014} Scroll behavior (`smooth`, `auto`).",
1223+ "resize" => "**resize** `<value>` \u{2014} Resize behavior (`none`, `both`, `horizontal`, `vertical`).",
10401224 _ => return None,
10411225 };
10421226
@@ -1530,6 +1714,242 @@ fn extract_between<'a>(msg: &'a str, prefix: &str, suffix: &str) -> Option<&'a s
15301714 }
15311715}
15321716
1717+// ---------------------------------------------------------------------------
1718+// Color provider
1719+// ---------------------------------------------------------------------------
1720+
1721+fn find_colors(text: &str) -> Vec<ColorInformation> {
1722+ let mut colors = Vec::new();
1723+ for (line_idx, line) in text.lines().enumerate() {
1724+ let mut start = 0;
1725+ while let Some(pos) = line[start..].find('#') {
1726+ let abs_pos = start + pos;
1727+ let hex_start = abs_pos + 1;
1728+ let hex_end = line[hex_start..]
1729+ .find(|c: char| !c.is_ascii_hexdigit())
1730+ .map(|p| hex_start + p)
1731+ .unwrap_or(line.len());
1732+ let hex = &line[hex_start..hex_end];
1733+ let len = hex.len();
1734+ if len == 3 || len == 6 || len == 8 {
1735+ if let Some((r, g, b, a)) = parse_hex_color(hex) {
1736+ colors.push(ColorInformation {
1737+ range: Range::new(
1738+ Position::new(line_idx as u32, abs_pos as u32),
1739+ Position::new(line_idx as u32, hex_end as u32),
1740+ ),
1741+ color: Color {
1742+ red: r as f32 / 255.0,
1743+ green: g as f32 / 255.0,
1744+ blue: b as f32 / 255.0,
1745+ alpha: a as f32 / 255.0,
1746+ },
1747+ });
1748+ }
1749+ }
1750+ start = hex_end;
1751+ }
1752+ }
1753+ colors
1754+}
1755+
1756+fn parse_hex_color(hex: &str) -> Option<(u8, u8, u8, u8)> {
1757+ match hex.len() {
1758+ 3 => {
1759+ let r = u8::from_str_radix(&hex[0..1], 16).ok()? * 17;
1760+ let g = u8::from_str_radix(&hex[1..2], 16).ok()? * 17;
1761+ let b = u8::from_str_radix(&hex[2..3], 16).ok()? * 17;
1762+ Some((r, g, b, 255))
1763+ }
1764+ 6 => {
1765+ let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
1766+ let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
1767+ let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
1768+ Some((r, g, b, 255))
1769+ }
1770+ 8 => {
1771+ let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
1772+ let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
1773+ let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
1774+ let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
1775+ Some((r, g, b, a))
1776+ }
1777+ _ => None,
1778+ }
1779+}
1780+
1781+// ---------------------------------------------------------------------------
1782+// Folding ranges
1783+// ---------------------------------------------------------------------------
1784+
1785+fn folding_ranges(text: &str) -> Vec<FoldingRange> {
1786+ let mut ranges = Vec::new();
1787+ let lines: Vec<&str> = text.lines().collect();
1788+ let mut i = 0;
1789+ while i < lines.len() {
1790+ let trimmed = lines[i].trim();
1791+ // Fold blocks that start with @fn, @if, @else, @each, @match, @style, @head, @keyframes, @define
1792+ if trimmed.starts_with("@fn ")
1793+ || trimmed.starts_with("@if ")
1794+ || trimmed == "@else"
1795+ || trimmed.starts_with("@else if ")
1796+ || trimmed.starts_with("@each ")
1797+ || trimmed.starts_with("@match ")
1798+ || trimmed == "@style"
1799+ || trimmed == "@head"
1800+ || trimmed.starts_with("@keyframes ")
1801+ {
1802+ let start_indent = lines[i].len() - lines[i].trim_start().len();
1803+ let start_line = i;
1804+ let mut end_line = i;
1805+ let mut j = i + 1;
1806+ while j < lines.len() {
1807+ let l = lines[j];
1808+ if l.trim().is_empty() {
1809+ j += 1;
1810+ continue;
1811+ }
1812+ let indent = l.len() - l.trim_start().len();
1813+ if indent <= start_indent {
1814+ break;
1815+ }
1816+ end_line = j;
1817+ j += 1;
1818+ }
1819+ if end_line > start_line {
1820+ ranges.push(FoldingRange {
1821+ start_line: start_line as u32,
1822+ start_character: None,
1823+ end_line: end_line as u32,
1824+ end_character: None,
1825+ kind: Some(FoldingRangeKind::Region),
1826+ collapsed_text: None,
1827+ });
1828+ }
1829+ }
1830+ // Fold comment blocks (lines starting with --)
1831+ if trimmed.starts_with("--") {
1832+ let start_line = i;
1833+ let mut end_line = i;
1834+ let mut j = i + 1;
1835+ while j < lines.len() && lines[j].trim().starts_with("--") {
1836+ end_line = j;
1837+ j += 1;
1838+ }
1839+ if end_line > start_line {
1840+ ranges.push(FoldingRange {
1841+ start_line: start_line as u32,
1842+ start_character: None,
1843+ end_line: end_line as u32,
1844+ end_character: None,
1845+ kind: Some(FoldingRangeKind::Comment),
1846+ collapsed_text: None,
1847+ });
1848+ }
1849+ }
1850+ i += 1;
1851+ }
1852+ ranges
1853+}
1854+
1855+// ---------------------------------------------------------------------------
1856+// Semantic tokens
1857+// ---------------------------------------------------------------------------
1858+
1859+fn semantic_tokens(text: &str) -> Vec<SemanticToken> {
1860+ let mut tokens = Vec::new();
1861+ let mut prev_line: u32 = 0;
1862+ let mut prev_start: u32 = 0;
1863+
1864+ for (line_idx, line) in text.lines().enumerate() {
1865+ let trimmed = line.trim();
1866+ let line_num = line_idx as u32;
1867+
1868+ // Detect comments
1869+ if trimmed.starts_with("--") {
1870+ let col = (line.len() - trimmed.len()) as u32;
1871+ push_token(&mut tokens, &mut prev_line, &mut prev_start, line_num, col, trimmed.len() as u32, 4);
1872+ continue;
1873+ }
1874+
1875+ // Scan for @keywords
1876+ let bytes = line.as_bytes();
1877+ let mut i = 0;
1878+ while i < bytes.len() {
1879+ if bytes[i] == b'@' {
1880+ let start = i;
1881+ i += 1;
1882+ while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'-' || bytes[i] == b'_') {
1883+ i += 1;
1884+ }
1885+ let word = &line[start..i];
1886+ let token_type = match word {
1887+ "@page" | "@let" | "@define" | "@fn" | "@if" | "@else" | "@each"
1888+ | "@include" | "@import" | "@meta" | "@head" | "@style" | "@keyframes"
1889+ | "@match" | "@case" | "@default" | "@slot" | "@children"
1890+ | "@warn" | "@debug" | "@lang" | "@favicon" | "@fragment" => 0, // keyword
1891+ _ => {
1892+ // Check if it's a user function call (starts with @ but not a builtin element)
1893+ if is_builtin_element(word) { 0 } else { 2 } // function
1894+ }
1895+ };
1896+ push_token(&mut tokens, &mut prev_line, &mut prev_start, line_num, start as u32, (i - start) as u32, token_type);
1897+ } else if bytes[i] == b'$' {
1898+ let start = i;
1899+ i += 1;
1900+ while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'-' || bytes[i] == b'_') {
1901+ i += 1;
1902+ }
1903+ if i > start + 1 {
1904+ push_token(&mut tokens, &mut prev_line, &mut prev_start, line_num, start as u32, (i - start) as u32, 1); // variable
1905+ }
1906+ } else {
1907+ i += 1;
1908+ }
1909+ }
1910+ }
1911+ tokens
1912+}
1913+
1914+fn is_builtin_element(word: &str) -> bool {
1915+ matches!(word,
1916+ "@row" | "@column" | "@col" | "@el" | "@text" | "@paragraph" | "@p"
1917+ | "@image" | "@img" | "@link" | "@input" | "@button" | "@btn"
1918+ | "@select" | "@textarea" | "@option" | "@opt" | "@label" | "@raw"
1919+ | "@nav" | "@header" | "@footer" | "@main" | "@section" | "@article" | "@aside"
1920+ | "@list" | "@item" | "@li" | "@table" | "@thead" | "@tbody" | "@tr" | "@td" | "@th"
1921+ | "@video" | "@audio" | "@form" | "@details" | "@summary"
1922+ | "@blockquote" | "@cite" | "@code" | "@pre" | "@hr" | "@divider"
1923+ | "@figure" | "@figcaption" | "@progress" | "@meter" | "@fragment"
1924+ )
1925+}
1926+
1927+fn push_token(
1928+ tokens: &mut Vec<SemanticToken>,
1929+ prev_line: &mut u32,
1930+ prev_start: &mut u32,
1931+ line: u32,
1932+ start: u32,
1933+ length: u32,
1934+ token_type: u32,
1935+) {
1936+ let delta_line = line - *prev_line;
1937+ let delta_start = if delta_line == 0 {
1938+ start - *prev_start
1939+ } else {
1940+ start
1941+ };
1942+ tokens.push(SemanticToken {
1943+ delta_line,
1944+ delta_start,
1945+ length,
1946+ token_type,
1947+ token_modifiers_bitset: 0,
1948+ });
1949+ *prev_line = line;
1950+ *prev_start = start;
1951+}
1952+
15331953// ---------------------------------------------------------------------------
15341954// Entry point
15351955// ---------------------------------------------------------------------------