use crate::ast::{Node, NodeKind};
use crate::position::{Position, Range};
use lsp_types::*;
use std::collections::HashMap;
use url::Url;
#[derive(Debug, Clone)]
pub struct SemanticTokensProvider {
config: SemanticTokensConfig,
legend: SemanticTokensLegend,
token_cache: HashMap<Url, Vec<SemanticToken>>,
}
#[derive(Debug, Clone)]
pub struct SemanticTokensConfig {
pub highlight_variables: bool,
pub highlight_functions: bool,
pub highlight_types: bool,
pub highlight_operators: bool,
pub highlight_comments: bool,
pub highlight_strings: bool,
pub highlight_keywords: bool,
}
impl Default for SemanticTokensConfig {
fn default() -> Self {
Self {
highlight_variables: true,
highlight_functions: true,
highlight_types: true,
highlight_operators: true,
highlight_comments: true,
highlight_strings: true,
highlight_keywords: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PerlTokenType {
Variable,
Function,
Type,
Keyword,
Operator,
String,
Number,
Comment,
Regexp,
Builtin,
Pragma,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PerlTokenModifier {
Declaration,
Definition,
Readonly,
Static,
Deprecated,
Abstract,
Async,
Modification,
Documentation,
}
impl SemanticTokensProvider {
pub fn new() -> Self {
let config = SemanticTokensConfig::default();
let legend = Self::create_legend();
Self {
config,
legend,
token_cache: HashMap::new(),
}
}
pub fn with_config(config: SemanticTokensConfig) -> Self {
let legend = Self::create_legend();
Self {
config,
legend,
token_cache: HashMap::new(),
}
}
fn create_legend() -> SemanticTokensLegend {
let token_types = vec![
SemanticTokenType::VARIABLE,
SemanticTokenType::FUNCTION,
SemanticTokenType::TYPE,
SemanticTokenType::KEYWORD,
SemanticTokenType::OPERATOR,
SemanticTokenType::STRING,
SemanticTokenType::NUMBER,
SemanticTokenType::COMMENT,
SemanticTokenType::REGEXP,
SemanticTokenType::FUNCTION, SemanticTokenType::TYPE, ];
let token_modifiers = vec![
SemanticTokenModifier::DECLARATION,
SemanticTokenModifier::DEFINITION,
SemanticTokenModifier::READONLY,
SemanticTokenModifier::STATIC,
SemanticTokenModifier::DEPRECATED,
SemanticTokenModifier::ABSTRACT,
SemanticTokenModifier::ASYNC,
SemanticTokenModifier::MODIFICATION,
SemanticTokenModifier::DOCUMENTATION,
];
SemanticTokensLegend {
token_types,
token_modifiers,
}
}
pub fn semantic_tokens_full(&self, params: SemanticTokensParams) -> Option<SemanticTokens> {
let uri = params.text_document.uri;
let tokens = vec![
SemanticToken {
delta_line: 0,
delta_start_char: 0,
length: 3,
token_type: self.get_token_type_index(PerlTokenType::Variable) as u32,
token_modifiers_bitset: 0,
},
SemanticToken {
delta_line: 0,
delta_start_char: 4,
length: 5,
token_type: self.get_token_type_index(PerlTokenType::Operator) as u32,
token_modifiers_bitset: 0,
},
];
Some(SemanticTokens {
result_id: None,
data: tokens,
})
}
pub fn semantic_tokens_range(&self, params: SemanticTokensRangeParams) -> Option<SemanticTokens> {
let uri = params.text_document.uri;
let range = params.range;
let tokens = vec![
SemanticToken {
delta_line: 0,
delta_start_char: 0,
length: 3,
token_type: self.get_token_type_index(PerlTokenType::Variable) as u32,
token_modifiers_bitset: 0,
},
];
Some(SemanticTokens {
result_id: None,
data: tokens,
})
}
pub fn semantic_tokens_full_delta(&self, params: SemanticTokensDeltaParams) -> Option<SemanticTokensDelta> {
let uri = params.text_document.uri;
let previous_result_id = params.previous_result_id;
let edits = vec![
SemanticTokensEdit {
start: 0,
delete_count: 0,
data: Some(vec![
SemanticToken {
delta_line: 0,
delta_start_char: 0,
length: 3,
token_type: self.get_token_type_index(PerlTokenType::Variable) as u32,
token_modifiers_bitset: 0,
},
]),
},
];
Some(SemanticTokensDelta {
result_id: Some("new_id".to_string()),
edits,
})
}
fn get_token_type_index(&self, token_type: PerlTokenType) -> usize {
match token_type {
PerlTokenType::Variable => 0,
PerlTokenType::Function => 1,
PerlTokenType::Type => 2,
PerlTokenType::Keyword => 3,
PerlTokenType::Operator => 4,
PerlTokenType::String => 5,
PerlTokenType::Number => 6,
PerlTokenType::Comment => 7,
PerlTokenType::Regexp => 8,
PerlTokenType::Builtin => 9,
PerlTokenType::Pragma => 10,
}
}
fn get_token_modifiers_bitset(&self, modifiers: &[PerlTokenModifier]) -> u32 {
let mut bitset = 0u32;
for modifier in modifiers {
let bit = match modifier {
PerlTokenModifier::Declaration => 0,
PerlTokenModifier::Definition => 1,
PerlTokenModifier::Readonly => 2,
PerlTokenModifier::Static => 3,
PerlTokenModifier::Deprecated => 4,
PerlTokenModifier::Abstract => 5,
PerlTokenModifier::Async => 6,
PerlTokenModifier::Modification => 7,
PerlTokenModifier::Documentation => 8,
};
bitset |= 1 << bit;
}
bitset
}
pub fn clear_cache(&mut self) {
self.token_cache.clear();
}
pub fn legend(&self) -> &SemanticTokensLegend {
&self.legend
}
pub fn update_cache(&mut self, uri: Url, tokens: Vec<SemanticToken>) {
self.token_cache.insert(uri, tokens);
}
}
impl Default for SemanticTokensProvider {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use perl_tdd_support::{must, must_some};
#[test]
fn test_semantic_tokens_provider_creation() {
let provider = SemanticTokensProvider::new();
assert!(provider.config.highlight_variables);
assert!(provider.config.highlight_functions);
assert!(provider.config.highlight_types);
}
#[test]
fn test_custom_config() {
let config = SemanticTokensConfig {
highlight_variables: false,
highlight_functions: true,
highlight_types: false,
highlight_operators: true,
highlight_comments: false,
highlight_strings: true,
highlight_keywords: false,
};
let provider = SemanticTokensProvider::with_config(config);
assert!(!provider.config.highlight_variables);
assert!(provider.config.highlight_functions);
assert!(!provider.config.highlight_types);
}
#[test]
fn test_token_type_indices() {
let provider = SemanticTokensProvider::new();
assert_eq!(provider.get_token_type_index(PerlTokenType::Variable), 0);
assert_eq!(provider.get_token_type_index(PerlTokenType::Function), 1);
assert_eq!(provider.get_token_type_index(PerlTokenType::Type), 2);
assert_eq!(provider.get_token_type_index(PerlTokenType::Keyword), 3);
}
#[test]
fn test_token_modifiers_bitset() {
let provider = SemanticTokensProvider::new();
let empty = provider.get_token_modifiers_bitset(&[]);
assert_eq!(empty, 0);
let single = provider.get_token_modifiers_bitset(&[PerlTokenModifier::Declaration]);
assert_eq!(single, 1 << 0);
let multiple = provider.get_token_modifiers_bitset(&[
PerlTokenModifier::Declaration,
PerlTokenModifier::Definition,
]);
assert_eq!(multiple, (1 << 0) | (1 << 1));
}
#[test]
fn test_cache_operations() {
let mut provider = SemanticTokensProvider::new();
assert!(provider.token_cache.is_empty());
let uri = must(Url::parse("file:///test.pl"));
let tokens = vec![SemanticToken {
delta_line: 0,
delta_start_char: 0,
length: 3,
token_type: 0,
token_modifiers_bitset: 0,
}];
provider.update_cache(uri.clone(), tokens);
assert_eq!(provider.token_cache.len(), 1);
provider.clear_cache();
assert!(provider.token_cache.is_empty());
}
#[test]
fn test_legend() {
let provider = SemanticTokensProvider::new();
let legend = provider.legend();
assert!(!legend.token_types.is_empty());
assert!(!legend.token_modifiers.is_empty());
}
#[test]
fn test_semantic_tokens_full() {
let provider = SemanticTokensProvider::new();
let params = SemanticTokensParams {
text_document: lsp_types::TextDocumentIdentifier {
uri: must(Url::parse("file:///test.pl"))
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
};
let tokens = provider.semantic_tokens_full(params);
assert!(tokens.is_some());
assert!(!must_some(tokens).data.is_empty());
}
#[test]
fn test_semantic_tokens_range() {
let provider = SemanticTokensProvider::new();
let params = SemanticTokensRangeParams {
text_document: lsp_types::TextDocumentIdentifier {
uri: must(Url::parse("file:///test.pl"))
},
range: Range::new(Position::new(0, 0), Position::new(1, 0)),
work_done_progress_params: Default::default(),
};
let tokens = provider.semantic_tokens_range(params);
assert!(tokens.is_some());
}
#[test]
fn test_semantic_tokens_delta() {
let provider = SemanticTokensProvider::new();
let params = SemanticTokensDeltaParams {
text_document: lsp_types::TextDocumentIdentifier {
uri: must(Url::parse("file:///test.pl"))
},
previous_result_id: "previous_id".to_string(),
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
};
let delta = provider.semantic_tokens_full_delta(params);
assert!(delta.is_some());
assert!(!must_some(delta).edits.is_empty());
}
}