use std::collections::HashSet;
use cpp_code_analysis::VfsFile;
use lsp_types::{SemanticToken, SemanticTokenModifier, SemanticTokenType};
use crate::util::position_in_file;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeKind {
Namespace,
Type,
Class,
Enum,
TypeParameter,
Parameter,
Variable,
Property,
EnumMember,
Function,
Method,
Macro,
Keyword,
Modifier,
Operator,
DocCommand,
VariableTemplate,
FormatSpecifier,
FormatArgument,
FormatConversion,
FormatEscape,
Regexp,
}
impl TypeKind {
pub const fn to_u32(self) -> u32 {
match self {
TypeKind::Namespace => 0,
TypeKind::Type => 1,
TypeKind::Class => 2,
TypeKind::Enum => 3,
TypeKind::TypeParameter => 4,
TypeKind::Parameter => 5,
TypeKind::Variable => 6,
TypeKind::Property => 7,
TypeKind::EnumMember => 8,
TypeKind::Function => 9,
TypeKind::Method => 10,
TypeKind::Macro => 11,
TypeKind::Keyword => 12,
TypeKind::Modifier => 13,
TypeKind::Operator => 14,
TypeKind::DocCommand => 15,
TypeKind::VariableTemplate => 16,
TypeKind::FormatSpecifier => 17,
TypeKind::FormatArgument => 18,
TypeKind::FormatConversion => 19,
TypeKind::FormatEscape => 20,
TypeKind::Regexp => 21,
}
}
pub const fn to_semantic_token_type(self) -> SemanticTokenType {
match self {
TypeKind::Namespace => SemanticTokenType::NAMESPACE,
TypeKind::Type => SemanticTokenType::TYPE,
TypeKind::Class => SemanticTokenType::CLASS,
TypeKind::Enum => SemanticTokenType::ENUM,
TypeKind::TypeParameter => SemanticTokenType::TYPE_PARAMETER,
TypeKind::Parameter => SemanticTokenType::PARAMETER,
TypeKind::Variable => SemanticTokenType::VARIABLE,
TypeKind::Property => SemanticTokenType::PROPERTY,
TypeKind::EnumMember => SemanticTokenType::ENUM_MEMBER,
TypeKind::Function => SemanticTokenType::FUNCTION,
TypeKind::Method => SemanticTokenType::METHOD,
TypeKind::Macro => SemanticTokenType::MACRO,
TypeKind::Keyword => SemanticTokenType::KEYWORD,
TypeKind::Modifier => SemanticTokenType::MODIFIER,
TypeKind::Operator => SemanticTokenType::OPERATOR,
TypeKind::DocCommand => SemanticTokenType::DECORATOR,
TypeKind::VariableTemplate => SemanticTokenType::new("variableTemplate"),
TypeKind::FormatSpecifier => SemanticTokenType::new("formatSpecifier"),
TypeKind::FormatArgument => SemanticTokenType::new("formatArgument"),
TypeKind::FormatConversion => SemanticTokenType::new("formatConversion"),
TypeKind::FormatEscape => SemanticTokenType::new("formatEscape"),
TypeKind::Regexp => SemanticTokenType::REGEXP,
}
}
pub const ALL: &'static [TypeKind] = &[
TypeKind::Namespace,
TypeKind::Type,
TypeKind::Class,
TypeKind::Enum,
TypeKind::TypeParameter,
TypeKind::Parameter,
TypeKind::Variable,
TypeKind::Property,
TypeKind::EnumMember,
TypeKind::Function,
TypeKind::Method,
TypeKind::Macro,
TypeKind::Keyword,
TypeKind::Modifier,
TypeKind::Operator,
TypeKind::DocCommand,
TypeKind::VariableTemplate,
TypeKind::FormatSpecifier,
TypeKind::FormatArgument,
TypeKind::FormatConversion,
TypeKind::FormatEscape,
TypeKind::Regexp,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifier(u32);
impl Modifier {
pub const DECLARATION: Modifier = Modifier(1 << 0);
pub const READONLY: Modifier = Modifier(1 << 1);
pub const STATIC: Modifier = Modifier(1 << 2);
pub const DEPRECATED: Modifier = Modifier(1 << 3);
pub const fn none() -> Self {
Modifier(0)
}
pub const fn to_u32(self) -> u32 {
self.0
}
pub const ALL: &'static [(Modifier, SemanticTokenModifier)] = &[
(Modifier::DECLARATION, SemanticTokenModifier::DECLARATION),
(Modifier::READONLY, SemanticTokenModifier::READONLY),
(Modifier::STATIC, SemanticTokenModifier::STATIC),
(Modifier::DEPRECATED, SemanticTokenModifier::DEPRECATED),
];
pub fn all_modifiers() -> Vec<SemanticTokenModifier> {
Modifier::ALL
.iter()
.map(|(_, advertised)| advertised.clone())
.collect()
}
}
impl std::ops::BitOr for Modifier {
type Output = Modifier;
fn bitor(self, rhs: Modifier) -> Modifier {
Modifier(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for Modifier {
fn bitor_assign(&mut self, rhs: Modifier) {
self.0 |= rhs.0;
}
}
#[derive(Debug, Clone, Copy)]
struct Claim {
start: usize,
end: usize,
token_type: u32,
modifiers: u32,
}
struct Placed {
line: u32,
column: u32,
length: u32,
token_type: u32,
modifiers: u32,
}
pub struct Builder<'a> {
file: &'a VfsFile,
drawable: Option<&'a [SemanticTokenType]>,
multi_line_support: bool,
claims: Vec<Claim>,
claimed: HashSet<usize>,
unplaceable: usize,
}
impl<'a> Builder<'a> {
pub fn new(
file: &'a VfsFile,
drawable: Option<&'a [SemanticTokenType]>,
multi_line_support: bool,
) -> Self {
Self {
file,
drawable,
multi_line_support,
claims: Vec::new(),
claimed: HashSet::new(),
unplaceable: 0,
}
}
pub fn legend() -> Vec<SemanticTokenType> {
TypeKind::ALL
.iter()
.map(|kind| kind.to_semantic_token_type())
.collect()
}
pub fn push_range(
&mut self,
start: usize,
end: usize,
token_type: TypeKind,
modifiers: Modifier,
) -> bool {
if end <= start {
return false;
}
if let Some(drawable) = self.drawable
&& !drawable.contains(&token_type.to_semantic_token_type())
{
return false;
}
if !self.claimed.insert(start) {
return false;
}
if end > self.file.text.len() {
self.unplaceable += 1;
return false;
}
self.claims.push(Claim {
start,
end,
token_type: token_type.to_u32(),
modifiers: modifiers.to_u32(),
});
true
}
#[allow(dead_code)]
pub fn push_at_range(
&mut self,
range: std::ops::Range<usize>,
token_type: TypeKind,
modifiers: Option<Modifier>,
) -> bool {
self.push_range(
range.start,
range.end,
token_type,
modifiers.unwrap_or(Modifier::none()),
)
}
#[allow(dead_code)]
pub fn has_claimed(&self, start: usize) -> bool {
self.claimed.contains(&start)
}
pub fn unplaceable(&self) -> usize {
self.unplaceable
}
fn end_of_line(file: &VfsFile, line: u32) -> Option<u32> {
let index = &file.line_index;
let start = usize::from(index.get_line_offset(line as usize)?);
let end = index
.get_line_offset(line as usize + 1)
.map_or(file.text.len(), usize::from)
.min(file.text.len());
let text = file.text.get(start..end)?;
let body = text.strip_suffix('\n').unwrap_or(text);
let body = body.strip_suffix('\r').unwrap_or(body);
Some(body.chars().count() as u32)
}
pub fn build(self) -> Vec<SemanticToken> {
let Builder {
file,
claims,
multi_line_support,
..
} = self;
let mut claims = claims;
claims.sort_unstable_by_key(|claim| (claim.start, claim.end));
let mut placed: Vec<Placed> = Vec::with_capacity(claims.len());
for claim in claims {
let Some(from) = position_in_file(file, claim.start) else {
continue;
};
let Some(to) = position_in_file(file, claim.end.saturating_sub(1)) else {
continue;
};
if multi_line_support || from.line == to.line {
let length = file
.text
.get(claim.start..claim.end)
.map(|text| text.encode_utf16().count() as u32)
.unwrap_or(0);
placed.push(Placed {
line: from.line,
column: from.character,
length,
token_type: claim.token_type,
modifiers: claim.modifiers,
});
continue;
}
for line in from.line..=to.line {
let (column, length) = if line == from.line {
(
from.character,
Self::end_of_line(file, line)
.map(|end| end.saturating_sub(from.character))
.unwrap_or(to.character + 1),
)
} else if line == to.line {
(0, to.character + 1)
} else {
(0, Self::end_of_line(file, line).unwrap_or(0))
};
placed.push(Placed {
line,
column,
length,
token_type: claim.token_type,
modifiers: claim.modifiers,
});
}
}
placed.sort_unstable_by_key(|token| (token.line, token.column));
let mut answer = Vec::with_capacity(placed.len());
let mut previous: Option<(u32, u32)> = None;
for token in placed {
let (delta_line, delta_start) = match previous {
Some((line, column)) if line == token.line => {
(0, token.column.saturating_sub(column))
}
Some((line, _)) => (token.line - line, token.column),
None => (token.line, token.column),
};
answer.push(SemanticToken {
delta_line,
delta_start,
length: token.length,
token_type: token.token_type,
token_modifiers_bitset: token.modifiers,
});
previous = Some((token.line, token.column));
}
answer
}
}