From 57b59eeccc50784c580537622f905c893f4735d5 Mon Sep 17 00:00:00 2001 From: Dominik Nakamura Date: Wed, 13 Dec 2023 17:44:51 +0900 Subject: [PATCH] feat(lsp): generate semantic tokens for schema files Provide semantic tokens for schema files to provide better code highlighting. Currently, not all possible tokens are supplied yet. --- crates/stef-build/src/definition.rs | 14 +- crates/stef-go/src/definition.rs | 16 +- crates/stef-lsp/src/handlers.rs | 64 ++--- crates/stef-lsp/src/main.rs | 1 + crates/stef-lsp/src/semantic_tokens.rs | 330 ++++++++++++++++++++++ crates/stef-lsp/src/state.rs | 2 +- crates/stef-parser/src/lib.rs | 104 ++++++- crates/stef-parser/src/parser.rs | 19 +- crates/stef-parser/src/parser/literals.rs | 16 +- vscode-extension/package.json | 42 +++ 10 files changed, 522 insertions(+), 86 deletions(-) create mode 100644 crates/stef-lsp/src/semantic_tokens.rs diff --git a/crates/stef-build/src/definition.rs b/crates/stef-build/src/definition.rs index 7b3e985..9d73bb6 100644 --- a/crates/stef-build/src/definition.rs +++ b/crates/stef-build/src/definition.rs @@ -2,7 +2,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use stef_parser::{ Comment, Const, DataType, Definition, Enum, ExternalType, Fields, Generics, Import, Literal, - Module, NamedField, Schema, Struct, Type, TypeAlias, UnnamedField, Variant, + LiteralValue, Module, NamedField, Schema, Struct, Type, TypeAlias, UnnamedField, Variant, }; use super::{decode, encode, size}; @@ -387,11 +387,11 @@ fn compile_const_data_type(ty: &Type<'_>) -> TokenStream { } fn compile_literal(literal: &Literal) -> TokenStream { - match literal { - Literal::Bool(b) => quote! { #b }, - Literal::Int(i) => proc_macro2::Literal::i128_unsuffixed(*i).into_token_stream(), - Literal::Float(f) => proc_macro2::Literal::f64_unsuffixed(*f).into_token_stream(), - Literal::String(s) => proc_macro2::Literal::string(s).into_token_stream(), - Literal::Bytes(b) => proc_macro2::Literal::byte_string(b).into_token_stream(), + match &literal.value { + LiteralValue::Bool(b) => quote! { #b }, + LiteralValue::Int(i) => proc_macro2::Literal::i128_unsuffixed(*i).into_token_stream(), + LiteralValue::Float(f) => proc_macro2::Literal::f64_unsuffixed(*f).into_token_stream(), + LiteralValue::String(s) => proc_macro2::Literal::string(s).into_token_stream(), + LiteralValue::Bytes(b) => proc_macro2::Literal::byte_string(b).into_token_stream(), } } diff --git a/crates/stef-go/src/definition.rs b/crates/stef-go/src/definition.rs index 804bb55..dc060e3 100644 --- a/crates/stef-go/src/definition.rs +++ b/crates/stef-go/src/definition.rs @@ -1,8 +1,8 @@ use std::fmt::{self, Display, Write}; use stef_parser::{ - Comment, Const, DataType, Definition, Enum, ExternalType, Fields, Generics, Literal, Name, - Schema, Struct, Type, TypeAlias, Variant, + Comment, Const, DataType, Definition, Enum, ExternalType, Fields, Generics, Literal, + LiteralValue, Name, Schema, Struct, Type, TypeAlias, Variant, }; use crate::{decode, encode, size, Opts, Output}; @@ -597,12 +597,12 @@ struct RenderLiteral<'a>(&'a Literal); impl Display for RenderLiteral<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - Literal::Bool(b) => write!(f, "{b}"), - Literal::Int(i) => write!(f, "{i}"), - Literal::Float(f2) => write!(f, "{f2}"), - Literal::String(s) => write!(f, "{s:?}"), - Literal::Bytes(b) => { + match &self.0.value { + LiteralValue::Bool(b) => write!(f, "{b}"), + LiteralValue::Int(i) => write!(f, "{i}"), + LiteralValue::Float(f2) => write!(f, "{f2}"), + LiteralValue::String(s) => write!(f, "{s:?}"), + LiteralValue::Bytes(b) => { if b.is_empty() { return Ok(()); } diff --git a/crates/stef-lsp/src/handlers.rs b/crates/stef-lsp/src/handlers.rs index 6bea210..2ff56e6 100644 --- a/crates/stef-lsp/src/handlers.rs +++ b/crates/stef-lsp/src/handlers.rs @@ -6,14 +6,14 @@ use log::{as_debug, as_display, debug, error, warn}; use lsp_types::{ DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, InitializeParams, InitializeResult, InitializedParams, - PositionEncodingKind, Registration, SemanticTokenModifier, SemanticTokenType, - SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, SemanticTokensParams, - SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities, ServerInfo, - TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions, + PositionEncodingKind, Registration, SemanticTokens, SemanticTokensFullOptions, + SemanticTokensLegend, SemanticTokensOptions, SemanticTokensParams, SemanticTokensResult, + SemanticTokensServerCapabilities, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, + TextDocumentSyncKind, WorkDoneProgressOptions, }; use ropey::Rope; -use crate::{compile, state::FileBuilder, GlobalState}; +use crate::{compile, semantic_tokens, state::FileBuilder, GlobalState}; pub fn initialize( _state: &mut GlobalState<'_>, @@ -35,43 +35,8 @@ pub fn initialize( work_done_progress: Some(false), }, legend: SemanticTokensLegend { - token_types: vec![ - SemanticTokenType::NAMESPACE, - SemanticTokenType::TYPE, - SemanticTokenType::CLASS, - SemanticTokenType::ENUM, - SemanticTokenType::INTERFACE, - SemanticTokenType::STRUCT, - SemanticTokenType::TYPE_PARAMETER, - SemanticTokenType::PARAMETER, - SemanticTokenType::VARIABLE, - SemanticTokenType::PROPERTY, - SemanticTokenType::ENUM_MEMBER, - SemanticTokenType::EVENT, - SemanticTokenType::FUNCTION, - SemanticTokenType::METHOD, - SemanticTokenType::MACRO, - SemanticTokenType::KEYWORD, - SemanticTokenType::MODIFIER, - SemanticTokenType::COMMENT, - SemanticTokenType::STRING, - SemanticTokenType::NUMBER, - SemanticTokenType::REGEXP, - SemanticTokenType::OPERATOR, - SemanticTokenType::DECORATOR, - ], - token_modifiers: vec![ - SemanticTokenModifier::DECLARATION, - SemanticTokenModifier::DEFINITION, - SemanticTokenModifier::READONLY, - SemanticTokenModifier::STATIC, - SemanticTokenModifier::DEPRECATED, - SemanticTokenModifier::ABSTRACT, - SemanticTokenModifier::ASYNC, - SemanticTokenModifier::MODIFICATION, - SemanticTokenModifier::DOCUMENTATION, - SemanticTokenModifier::DEFAULT_LIBRARY, - ], + token_types: semantic_tokens::TOKEN_TYPES.to_vec(), + token_modifiers: semantic_tokens::TOKEN_MODIFIERS.to_vec(), }, range: Some(false), full: Some(SemanticTokensFullOptions::Bool(true)), @@ -204,10 +169,23 @@ pub fn did_close(state: &mut GlobalState<'_>, params: DidCloseTextDocumentParams } pub fn semantic_tokens_full( - _state: &mut GlobalState<'_>, + state: &mut GlobalState<'_>, params: SemanticTokensParams, ) -> Result> { debug!(uri = as_display!(params.text_document.uri); "requested semantic tokens"); + + if let Some((schema, index)) = state.files.get(¶ms.text_document.uri).and_then(|file| { + file.borrow_schema() + .as_ref() + .ok() + .zip(Some(file.borrow_index())) + }) { + return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens { + result_id: None, + data: semantic_tokens::Visitor::new(index).visit_schema(schema)?, + }))); + } + Ok(None) } diff --git a/crates/stef-lsp/src/main.rs b/crates/stef-lsp/src/main.rs index e04a9e3..bbd7a4e 100644 --- a/crates/stef-lsp/src/main.rs +++ b/crates/stef-lsp/src/main.rs @@ -24,6 +24,7 @@ mod compile; mod config; mod handlers; mod logging; +mod semantic_tokens; mod state; fn main() -> Result<()> { diff --git a/crates/stef-lsp/src/semantic_tokens.rs b/crates/stef-lsp/src/semantic_tokens.rs new file mode 100644 index 0000000..eaa30ef --- /dev/null +++ b/crates/stef-lsp/src/semantic_tokens.rs @@ -0,0 +1,330 @@ +use std::ops::Range; + +use anyhow::{ensure, Context, Result}; +use line_index::{LineIndex, TextSize, WideLineCol}; +use lsp_types::{SemanticToken, SemanticTokenModifier, SemanticTokenType}; +use stef_parser::{ + Comment, Const, DataType, Definition, Enum, Fields, Generics, Literal, LiteralValue, Module, + NamedField, Schema, Span, Spanned, Struct, Type, TypeAlias, UnnamedField, Variant, +}; + +pub(crate) use self::{modifiers::TOKEN_MODIFIERS, types::TOKEN_TYPES}; + +macro_rules! define_semantic_token_types { + ( + standard { + $($standard:ident),* $(,)? + } + + custom { + $(($custom:ident, $string:literal)),* $(,)? + } + ) => { + mod types { + use lsp_types::SemanticTokenType; + + $(pub(super) const $standard: SemanticTokenType = SemanticTokenType::$standard;)* + $(pub(super) const $custom: SemanticTokenType = SemanticTokenType::new($string);)* + + pub(crate) const TOKEN_TYPES: &[SemanticTokenType] = &[ + $(SemanticTokenType::$standard,)* + $($custom,)* + ]; + } + }; +} + +define_semantic_token_types! { + standard { + NAMESPACE, + TYPE, + ENUM, + STRUCT, + TYPE_PARAMETER, + VARIABLE, + PROPERTY, + ENUM_MEMBER, + // KEYWORD, + COMMENT, + // STRING, + NUMBER, + // OPERATOR, + // DECORATOR, + } + + custom { + (BOOLEAN, "boolean"), + (BUILTIN_TYPE, "builtinType"), + (TYPE_ALIAS, "typeAlias"), + } +} + +macro_rules! define_semantic_token_modifiers { + ( + standard { + $($standard:ident),* $(,)? + } + custom { + $(($custom:ident, $string:literal)),* $(,)? + } + ) => { + mod modifiers { + use lsp_types::SemanticTokenModifier; + + $(pub(super) const $standard: SemanticTokenModifier = SemanticTokenModifier::$standard;)* + $(pub(super) const $custom: SemanticTokenModifier = SemanticTokenModifier::new($string);)* + + pub(crate) const TOKEN_MODIFIERS: &[SemanticTokenModifier] = &[ + $(SemanticTokenModifier::$standard,)* + $($custom,)* + ]; + } + }; +} + +define_semantic_token_modifiers! { + standard { + DECLARATION, + STATIC, + DOCUMENTATION, + } + + custom { + (CONSTANT, "constant"), + } +} + +#[allow(clippy::cast_possible_truncation, clippy::expect_used)] +fn token_type_pos(ty: &SemanticTokenType) -> u32 { + // This should never fail as the above macros ensure every possible constant is in the global + // list as well. However, if passing a `SemanticTokenType` directly from the `lsp-types` crate + // it can fail. + types::TOKEN_TYPES + .iter() + .position(|tt| tt == ty) + .expect("token type missing from global list") as u32 +} + +fn token_modifier_bitset(modifiers: &[SemanticTokenModifier]) -> u32 { + modifiers::TOKEN_MODIFIERS + .iter() + .enumerate() + .filter_map(|(i, tm)| modifiers.contains(tm).then_some(i)) + .fold(0, |acc, modifier| acc + (1 << modifier)) +} + +pub struct Visitor<'a> { + index: &'a LineIndex, + tokens: Vec, + delta: (u32, u32), +} + +impl<'a> Visitor<'a> { + pub fn new(index: &'a LineIndex) -> Self { + Self { + index, + tokens: Vec::new(), + delta: (0, 0), + } + } + + #[allow(clippy::cast_possible_truncation)] + fn get_range(&self, span: Span) -> Result<(WideLineCol, WideLineCol)> { + let range = Range::from(span); + let (start, end) = self + .index + .to_wide( + line_index::WideEncoding::Utf16, + self.index.line_col(TextSize::new(range.start as u32)), + ) + .zip(self.index.to_wide( + line_index::WideEncoding::Utf16, + self.index.line_col(TextSize::new(range.end as u32)), + )) + .context("missing utf-16 positions")?; + + ensure!(start.line == end.line, "encountered a multi-line span"); + + Ok((start, end)) + } + + fn lsl(&self, start: WideLineCol, end: WideLineCol) -> (u32, u32, u32) { + ( + start.line - self.delta.0, + start.col + - if self.delta.0 == start.line { + self.delta.1 + } else { + 0 + }, + end.col - start.col, + ) + } + + fn add_span( + &mut self, + span: &impl Spanned, + token_type: &SemanticTokenType, + token_modifiers: &[SemanticTokenModifier], + ) -> Result<()> { + let (start, end) = self.get_range(span.span())?; + let (delta_line, delta_start, length) = self.lsl(start, end); + + self.tokens.push(SemanticToken { + delta_line, + delta_start, + length, + token_type: token_type_pos(token_type), + token_modifiers_bitset: token_modifier_bitset(token_modifiers), + }); + self.delta = (start.line, start.col); + + Ok(()) + } + + pub fn visit_schema(mut self, item: &Schema<'_>) -> Result> { + for def in &item.definitions { + self.visit_definition(def)?; + } + + Ok(self.tokens) + } + + fn visit_comment(&mut self, item: &Comment<'_>) -> Result<()> { + for line in &item.0 { + self.add_span(line, &types::COMMENT, &[modifiers::DOCUMENTATION])?; + } + + Ok(()) + } + + fn visit_definition(&mut self, item: &Definition<'_>) -> Result<()> { + match item { + Definition::Module(m) => self.visit_module(m), + Definition::Struct(s) => self.visit_struct(s), + Definition::Enum(e) => self.visit_enum(e), + Definition::TypeAlias(a) => self.visit_alias(a), + Definition::Const(c) => self.visit_const(c), + Definition::Import(_i) => Ok(()), + } + } + + fn visit_module(&mut self, item: &Module<'_>) -> Result<()> { + self.visit_comment(&item.comment)?; + self.add_span(&item.name, &types::NAMESPACE, &[modifiers::DECLARATION])?; + + for def in &item.definitions { + self.visit_definition(def)?; + } + + Ok(()) + } + + fn visit_struct(&mut self, item: &Struct<'_>) -> Result<()> { + self.visit_comment(&item.comment)?; + self.add_span(&item.name, &types::STRUCT, &[modifiers::DECLARATION])?; + self.visit_generics(&item.generics)?; + self.visit_fields(&item.fields) + } + + fn visit_enum(&mut self, item: &Enum<'_>) -> Result<()> { + self.visit_comment(&item.comment)?; + self.add_span(&item.name, &types::ENUM, &[modifiers::DECLARATION])?; + self.visit_generics(&item.generics)?; + + for variant in &item.variants { + self.visit_variant(variant)?; + } + + Ok(()) + } + + fn visit_variant(&mut self, item: &Variant<'_>) -> Result<()> { + self.visit_comment(&item.comment)?; + self.add_span(&item.name, &types::ENUM_MEMBER, &[modifiers::DECLARATION])?; + self.visit_fields(&item.fields) + } + + fn visit_fields(&mut self, item: &Fields<'_>) -> Result<()> { + match item { + Fields::Named(named) => { + for field in named { + self.visit_named_field(field)?; + } + } + Fields::Unnamed(unnamed) => { + for field in unnamed { + self.visit_unnamed_field(field)?; + } + } + Fields::Unit => {} + } + + Ok(()) + } + + fn visit_named_field(&mut self, item: &NamedField<'_>) -> Result<()> { + self.visit_comment(&item.comment)?; + self.add_span(&item.name, &types::PROPERTY, &[modifiers::DECLARATION])?; + self.visit_type(&item.ty) + } + + fn visit_unnamed_field(&mut self, item: &UnnamedField<'_>) -> Result<()> { + self.visit_type(&item.ty) + } + + fn visit_alias(&mut self, item: &TypeAlias<'_>) -> Result<()> { + self.visit_comment(&item.comment)?; + self.add_span(&item.name, &types::TYPE_ALIAS, &[modifiers::DECLARATION])?; + self.visit_generics(&item.generics)?; + self.visit_type(&item.target) + } + + fn visit_const(&mut self, item: &Const<'_>) -> Result<()> { + self.visit_comment(&item.comment)?; + self.add_span( + &item.name, + &types::VARIABLE, + &[ + modifiers::DECLARATION, + modifiers::STATIC, + modifiers::CONSTANT, + ], + )?; + self.visit_type(&item.ty)?; + self.visit_literal(&item.value) + } + + fn visit_type(&mut self, item: &Type<'_>) -> Result<()> { + self.add_span( + item, + &if matches!(item.value, DataType::External(_)) { + types::TYPE + } else { + types::BUILTIN_TYPE + }, + &[], + ) + } + + fn visit_literal(&mut self, item: &Literal) -> Result<()> { + let token_type = match item.value { + LiteralValue::Bool(_) => types::BOOLEAN, + LiteralValue::Int(_) | LiteralValue::Float(_) => types::NUMBER, + LiteralValue::String(_) | LiteralValue::Bytes(_) => { + // these can be multiline and need special handling + return Ok(()); + } + }; + + self.add_span(item, &token_type, &[]) + } + + fn visit_generics(&mut self, item: &Generics<'_>) -> Result<()> { + for generic in &item.0 { + self.add_span(generic, &types::TYPE_PARAMETER, &[modifiers::DECLARATION])?; + } + + Ok(()) + } +} diff --git a/crates/stef-lsp/src/state.rs b/crates/stef-lsp/src/state.rs index a028150..9bb0e64 100644 --- a/crates/stef-lsp/src/state.rs +++ b/crates/stef-lsp/src/state.rs @@ -21,7 +21,7 @@ pub struct GlobalState<'a> { #[derive(Debug)] pub struct File { rope: Rope, - index: LineIndex, + pub index: LineIndex, content: String, #[borrows(index, content)] #[covariant] diff --git a/crates/stef-parser/src/lib.rs b/crates/stef-parser/src/lib.rs index 0c96acf..d013667 100644 --- a/crates/stef-parser/src/lib.rs +++ b/crates/stef-parser/src/lib.rs @@ -605,7 +605,7 @@ impl Display for UnnamedField<'_> { /// ╰─── Content /// ``` #[derive(Debug, Default, Eq, PartialEq)] -pub struct Comment<'a>(pub Vec<&'a str>); +pub struct Comment<'a>(pub Vec>); impl Print for Comment<'_> { fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { @@ -613,7 +613,7 @@ impl Print for Comment<'_> { for line in lines { Self::indent(f, level)?; - writeln!(f, "/// {line}")?; + line.fmt(f)?; } Ok(()) @@ -626,6 +626,50 @@ impl Display for Comment<'_> { } } +/// Single [`Comment`] line, which additional tracks the location in the schema. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CommentLine<'a> { + /// Raw string value. + pub value: &'a str, + /// Source code location (including the leading `/// ` marker). + span: Span, +} + +impl CommentLine<'_> { + /// Retrieve the raw string value of this name. + #[must_use] + pub const fn get(&self) -> &str { + self.value + } +} + +impl Spanned for CommentLine<'_> { + fn span(&self) -> Span { + self.span + } +} + +impl Display for CommentLine<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "/// {}", self.value) + } +} + +impl<'a> From<(&'a str, Range)> for CommentLine<'a> { + fn from((value, span): (&'a str, Range)) -> Self { + Self { + value, + span: span.into(), + } + } +} + +impl AsRef for CommentLine<'_> { + fn as_ref(&self) -> &str { + self.value + } +} + /// Collection of attributes, aggregated together into a single declaration block. #[derive(Debug, Default, PartialEq)] pub struct Attributes<'a>(pub Vec>); @@ -904,6 +948,15 @@ impl Display for Id { } } +impl From<(u32, Range)> for Id { + fn from((value, span): (u32, Range)) -> Self { + Self { + value, + span: span.into(), + } + } +} + /// An arbitrary name of any element, which additionally carries a span into the schema to mark its /// location. #[derive(Clone, Debug, Eq, PartialEq)] @@ -978,9 +1031,40 @@ impl Print for Const<'_> { } } -/// In-schema definition of a literal value. +/// In-schema definition of a literal value, together with a span into the schema to mark where it +/// is defined. +#[derive(Clone, Debug, PartialEq)] +pub struct Literal { + /// The raw literal value. + pub value: LiteralValue, + /// Source code location. + span: Span, +} + +impl Spanned for Literal { + fn span(&self) -> Span { + self.span + } +} + +impl Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.value.fmt(f) + } +} + +impl From<(LiteralValue, Range)> for Literal { + fn from((value, span): (LiteralValue, Range)) -> Self { + Self { + value, + span: span.into(), + } + } +} + +/// Raw value of a [`Literal`]. #[derive(Clone, Debug, PartialEq)] -pub enum Literal { +pub enum LiteralValue { /// Boolean `true` or `false` value. Bool(bool), /// Integer number. @@ -993,14 +1077,14 @@ pub enum Literal { Bytes(Vec), } -impl Display for Literal { +impl Display for LiteralValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - Literal::Bool(v) => v.fmt(f), - Literal::Int(v) => v.fmt(f), - Literal::Float(v) => v.fmt(f), - Literal::String(ref v) => write!(f, "{v:?}"), - Literal::Bytes(ref v) => write!(f, "{v:?}"), + Self::Bool(v) => v.fmt(f), + Self::Int(v) => v.fmt(f), + Self::Float(v) => v.fmt(f), + Self::String(ref v) => write!(f, "{v:?}"), + Self::Bytes(ref v) => write!(f, "{v:?}"), } } } diff --git a/crates/stef-parser/src/parser.rs b/crates/stef-parser/src/parser.rs index 3125fda..e130fe6 100644 --- a/crates/stef-parser/src/parser.rs +++ b/crates/stef-parser/src/parser.rs @@ -123,10 +123,7 @@ mod ids { preceded('@', dec_uint) .with_span() .parse_next(input) - .map(|(value, span)| Id { - value, - span: span.into(), - }) + .map(Id::from) .map_err(|e| { e.map(|e: ErrorKind| ParseError { at: input.location()..input.location(), @@ -142,7 +139,7 @@ mod comments { use stef_derive::{ParserError, ParserErrorCause}; use winnow::{ ascii::space0, - combinator::{preceded, repeat, terminated}, + combinator::{delimited, preceded, repeat}, error::ErrorKind, stream::Stream, token::take_till, @@ -150,7 +147,7 @@ mod comments { }; use super::{Input, Result}; - use crate::{highlight, location, Comment}; + use crate::{highlight, location, Comment, CommentLine}; /// Encountered an invalid `/// ...` comment declaration. #[derive(Debug, ParserError)] @@ -183,10 +180,12 @@ mod comments { repeat( 0.., - preceded( - (space0, "///", space0), - terminated(take_till(0.., '\n'), '\n'), - ), + delimited( + space0, + preceded(("///", space0), take_till(0.., '\n')).with_span(), + '\n', + ) + .map(CommentLine::from), ) .parse_next(input) .map(Comment) diff --git a/crates/stef-parser/src/parser/literals.rs b/crates/stef-parser/src/parser/literals.rs index e5576f3..f9982d8 100644 --- a/crates/stef-parser/src/parser/literals.rs +++ b/crates/stef-parser/src/parser/literals.rs @@ -14,7 +14,7 @@ use winnow::{ }; use super::{ws, Input, Result}; -use crate::{highlight, Literal}; +use crate::{highlight, Literal, LiteralValue}; /// Encountered an invalid literal declaration. #[derive(Debug, ParserError)] @@ -89,18 +89,20 @@ pub(super) fn parse(input: &mut Input<'_>) -> Result { dispatch! { peek(any); - 't' => parse_true.map(Literal::Bool), - 'f' => parse_false.map(Literal::Bool), + 't' => parse_true.map(LiteralValue::Bool), + 'f' => parse_false.map(LiteralValue::Bool), '+' | '-' | '0'..='9' => cut_err(alt(( - parse_float.map(Literal::Float), - parse_int.map(Literal::Int) + parse_float.map(LiteralValue::Float), + parse_int.map(LiteralValue::Int) ))), - '"' => parse_string.map(Literal::String), - '[' => parse_bytes.map(Literal::Bytes), + '"' => parse_string.map(LiteralValue::String), + '[' => parse_bytes.map(LiteralValue::Bytes), '&' => fail, _ => fail, } + .with_span() .parse_next(input) + .map(Literal::from) .map_err(|e| { e.map(|cause| ParseError { at: start..start, diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 3ac1872..11e1818 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -53,6 +53,48 @@ "title": "Restart LSP Server", "category": "Stef" } + ], + "semanticTokenTypes": [ + { + "id": "boolean", + "description": "Style for boolean literals", + "superType": "keyword" + }, + { + "id": "builtinType", + "description": "Style for builtin types", + "superType": "type" + }, + { + "id": "typeAlias", + "description": "Style for type aliases", + "superType": "type" + } + ], + "semanticTokenModifiers": [ + { + "id": "constant", + "description": "Style for compile-time constants" + } + ], + "semanticTokenScopes": [ + { + "language": "stef", + "scopes": { + "boolean": [ + "constant.language.boolean.stef" + ], + "builtinType": [ + "support.type.primitive.stef" + ], + "typeAlias": [ + "entity.name.type.declaration.stef" + ], + "variable.constant": [ + "variable.other.constant.stef" + ] + } + } ] }, "vsce": {