use super::{LineIndex, OpenDocument, completion_programs, file_uri_path};
use lsp_types::{Hover, HoverContents, Location, MarkupContent, MarkupKind, Uri};
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tess::ast::{
BinaryOp, Cardinality, Declaration, Effect, Expr, ExprKind, InvariantAssertion, Parameter,
Program, SourceRef, TypeRef, UnaryOp,
};
use tess::compiler::ValueType;
use tess::source::Spanned;
use tess::{
CompiledProgram, SourceFile, Span, compile_project_with_dependency_graph_and_overrides, parse,
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum SymbolKey {
Type(String),
Field(String, String),
Variant(String, String),
Derive(String),
Decision(String),
Action(String),
Rule(String),
Transition(String),
Source(String),
Case(String),
Invariant(String),
Trace(String),
Local {
uri: String,
owner: usize,
name: String,
},
}
#[derive(Clone, Debug)]
struct NavigationSpan {
uri: String,
span: Span,
}
#[derive(Clone, Debug)]
struct SymbolDefinition {
name: String,
kind: &'static str,
detail: String,
location: NavigationSpan,
}
#[derive(Clone, Debug)]
struct SymbolOccurrence {
key: SymbolKey,
location: NavigationSpan,
}
#[derive(Clone, Debug)]
struct SourceDocument {
uri: Uri,
text: String,
}
#[derive(Clone, Debug)]
struct CallableInfo {
parameters: Vec<TypeRef>,
return_type: TypeRef,
}
#[derive(Clone, Debug, Default)]
struct EntityInfo {
fields: BTreeMap<String, TypeRef>,
}
#[derive(Debug, Default)]
pub(super) struct NavigationIndex {
definitions: BTreeMap<SymbolKey, SymbolDefinition>,
occurrences: Vec<SymbolOccurrence>,
documents: BTreeMap<String, SourceDocument>,
entities: BTreeMap<String, EntityInfo>,
states: BTreeMap<String, EntityInfo>,
enums: BTreeSet<String>,
variant_owners: BTreeMap<String, Vec<String>>,
derives: BTreeMap<String, CallableInfo>,
decisions: BTreeMap<String, CallableInfo>,
}
impl NavigationIndex {
pub(super) fn hover(&self, uri: &Uri, cursor: usize) -> Option<Hover> {
let (occurrence, definition) = self.target_at(uri, cursor)?;
let document = self.documents.get(&occurrence.location.uri)?;
Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: format!(
"**{} `{}`**\n\n```tess\n{}\n```",
definition.kind, definition.name, definition.detail
),
}),
range: Some(LineIndex::new(&document.text).range(occurrence.location.span)),
})
}
pub(super) fn definition(&self, uri: &Uri, cursor: usize) -> Option<Location> {
let (_, definition) = self.target_at(uri, cursor)?;
let document = self.documents.get(&definition.location.uri)?;
Some(Location {
uri: document.uri.clone(),
range: LineIndex::new(&document.text).range(definition.location.span),
})
}
fn target_at(
&self,
uri: &Uri,
cursor: usize,
) -> Option<(&SymbolOccurrence, &SymbolDefinition)> {
self.occurrences
.iter()
.filter(|occurrence| occurrence.location.uri == uri.as_str())
.filter(|occurrence| contains_cursor(occurrence.location.span, cursor))
.filter_map(|occurrence| {
self.definitions
.get(&occurrence.key)
.map(|definition| (occurrence, definition))
})
.min_by(|(left, _), (right, _)| {
span_len(left.location.span)
.cmp(&span_len(right.location.span))
.then_with(|| left.key.cmp(&right.key))
})
}
fn collect_definitions(&mut self, program: &Program, mapper: &SpanMapper<'_>) {
for declaration in &program.declarations {
match declaration {
Declaration::Enum(value) => {
self.enums.insert(value.name.value.clone());
self.add_definition(
SymbolKey::Type(value.name.value.clone()),
value.name.value.clone(),
"enum",
format!(
"enum {} · {} variants",
value.name.value,
value.variants.len()
),
value.name.span,
mapper,
);
for variant in &value.variants {
self.variant_owners
.entry(variant.value.clone())
.or_default()
.push(value.name.value.clone());
self.add_definition(
SymbolKey::Variant(value.name.value.clone(), variant.value.clone()),
variant.value.clone(),
"enum variant",
format!("{}::{}", value.name.value, variant.value),
variant.span,
mapper,
);
}
}
Declaration::Entity(value) => {
let fields = value
.fields
.iter()
.map(|field| (field.name.value.clone(), field.ty.clone()))
.collect();
self.entities
.insert(value.name.value.clone(), EntityInfo { fields });
self.add_definition(
SymbolKey::Type(value.name.value.clone()),
value.name.value.clone(),
"entity",
format!(
"entity {} · {} fields",
value.name.value,
value.fields.len()
),
value.name.span,
mapper,
);
for field in &value.fields {
self.add_definition(
SymbolKey::Field(value.name.value.clone(), field.name.value.clone()),
field.name.value.clone(),
"field",
format!(
"{}: {}",
field.name.value,
super::field_detail(&value.name.value, field)
),
field.name.span,
mapper,
);
}
}
Declaration::State(value) => {
let fields = value
.fields
.iter()
.map(|field| (field.name.value.clone(), field.ty.clone()))
.collect();
self.states
.insert(value.name.value.clone(), EntityInfo { fields });
self.add_definition(
SymbolKey::Type(value.name.value.clone()),
value.name.value.clone(),
"state",
format!("state {} · {} fields", value.name.value, value.fields.len()),
value.name.span,
mapper,
);
for field in &value.fields {
self.add_definition(
SymbolKey::Field(value.name.value.clone(), field.name.value.clone()),
field.name.value.clone(),
"field",
format!(
"{}: {}",
field.name.value,
super::field_detail(&value.name.value, field)
),
field.name.span,
mapper,
);
}
}
Declaration::Derive(value) => {
let info = CallableInfo {
parameters: parameter_types(&value.parameters),
return_type: value.return_type.value.clone(),
};
self.derives.insert(value.name.value.clone(), info);
self.add_definition(
SymbolKey::Derive(value.name.value.clone()),
value.name.value.clone(),
"derive",
format!(
"derive {}{} -> {}",
value.name.value,
super::parameter_list(&value.parameters),
super::type_name(&value.return_type.value)
),
value.name.span,
mapper,
);
}
Declaration::Decision(value) => {
let info = CallableInfo {
parameters: parameter_types(&value.parameters),
return_type: value.return_type.value.clone(),
};
self.decisions.insert(value.name.value.clone(), info);
self.add_definition(
SymbolKey::Decision(value.name.value.clone()),
value.name.value.clone(),
"decision",
format!(
"decision {}{} -> {}{}",
value.name.value,
super::parameter_list(&value.parameters),
super::type_name(&value.return_type.value),
cardinality_suffix(value.cardinality)
),
value.name.span,
mapper,
);
}
Declaration::Action(value) => self.add_definition(
SymbolKey::Action(value.name.value.clone()),
value.name.value.clone(),
"action",
format!(
"action {}{}",
value.name.value,
super::parameter_list(&value.parameters)
),
value.name.span,
mapper,
),
Declaration::Transition(value) => self.add_definition(
SymbolKey::Transition(value.name.value.clone()),
value.name.value.clone(),
"transition",
format!(
"transition {}{} on {}",
value.name.value,
super::parameter_list(&value.parameters),
value.action.value
),
value.name.span,
mapper,
),
Declaration::Source(value) => self.add_definition(
SymbolKey::Source(value.name.value.clone()),
value.name.value.clone(),
"source",
format!(
"source {} · {}",
value.name.value,
super::source_detail(value)
),
value.name.span,
mapper,
),
Declaration::Rule(value) => self.add_definition(
SymbolKey::Rule(value.name.value.clone()),
value.name.value.clone(),
"rule",
format!(
"rule {}{}",
value.name.value,
super::parameter_list(&value.parameters)
),
value.name.span,
mapper,
),
Declaration::Case(value) => self.add_definition(
SymbolKey::Case(value.name.value.clone()),
value.name.value.clone(),
"case",
format!("case {}", value.name.value),
value.name.span,
mapper,
),
Declaration::Invariant(value) => self.add_definition(
SymbolKey::Invariant(value.name.value.clone()),
value.name.value.clone(),
"invariant",
format!("invariant {}", value.name.value),
value.name.span,
mapper,
),
Declaration::Trace(value) => self.add_definition(
SymbolKey::Trace(value.name.value.clone()),
value.name.value.clone(),
"trace",
format!("trace {} within {}", value.name.value, value.within.value),
value.name.span,
mapper,
),
}
}
for owners in self.variant_owners.values_mut() {
owners.sort();
owners.dedup();
}
}
fn collect_occurrences(
&mut self,
program: &Program,
semantic: Option<&CompiledProgram>,
mapper: &SpanMapper<'_>,
) {
for declaration in &program.declarations {
match declaration {
Declaration::Enum(_)
| Declaration::Entity(_)
| Declaration::State(_)
| Declaration::Source(_) => {}
Declaration::Derive(value) => {
self.add_type_ref(&value.return_type.value, value.return_type.span, mapper);
let scope =
self.parameter_scope(&value.parameters, value.span, "parameter", mapper);
self.index_expr(
&value.expression,
Some(&value.return_type.value),
&scope,
semantic,
mapper,
);
}
Declaration::Decision(value) => {
self.add_type_ref(&value.return_type.value, value.return_type.span, mapper);
self.parameter_scope(&value.parameters, value.span, "parameter", mapper);
}
Declaration::Action(value) => {
self.parameter_scope(&value.parameters, value.span, "parameter", mapper);
}
Declaration::Transition(value) => {
let scope =
self.parameter_scope(&value.parameters, value.span, "parameter", mapper);
self.add_occurrence(
SymbolKey::Action(value.action.value.clone()),
value.action.span,
mapper,
);
for argument in &value.action_arguments {
self.index_expr(argument, None, &scope, semantic, mapper);
}
self.index_expr(
&value.condition,
Some(&TypeRef::Bool),
&scope,
semantic,
mapper,
);
for update in &value.updates {
if let Some((key, ty)) = scope.get(&update.receiver.value) {
self.add_occurrence(key.clone(), update.receiver.span, mapper);
if let TypeRef::Named(owner) = ty {
self.add_occurrence(
SymbolKey::Field(owner.clone(), update.field.value.clone()),
update.field.span,
mapper,
);
}
}
self.index_expr(&update.value, None, &scope, semantic, mapper);
}
}
Declaration::Rule(value) => {
let scope =
self.parameter_scope(&value.parameters, value.span, "parameter", mapper);
self.index_expr(
&value.condition,
Some(&TypeRef::Bool),
&scope,
semantic,
mapper,
);
for effect in &value.effects {
match effect {
Effect::Decide {
decision,
arguments,
value,
..
} => self.index_decision_use(
decision,
arguments,
Some(value),
&scope,
semantic,
mapper,
),
Effect::Override { rule, .. } => self.add_occurrence(
SymbolKey::Rule(rule.value.clone()),
rule.span,
mapper,
),
}
}
for source in &value.sources {
if let SourceRef::Named(source) = source {
self.add_occurrence(
SymbolKey::Source(source.value.clone()),
source.span,
mapper,
);
}
}
}
Declaration::Case(value) => {
let mut scope = BTreeMap::new();
for binding in &value.bindings {
let key = self.add_local_definition(
value.span,
&binding.name.value,
&binding.entity.value,
"binding",
binding.name.span,
mapper,
);
self.add_type_name(&binding.entity.value, binding.entity.span, mapper);
scope.insert(
binding.name.value.clone(),
(key, TypeRef::Named(binding.entity.value.clone())),
);
for field in &binding.fields {
self.add_occurrence(
SymbolKey::Field(
binding.entity.value.clone(),
field.name.value.clone(),
),
field.name.span,
mapper,
);
let expected = self
.field_type(&binding.entity.value, &field.name.value)
.cloned();
self.index_expr(
&field.value,
expected.as_ref(),
&scope,
semantic,
mapper,
);
}
}
for expectation in &value.expectations {
self.index_decision_use(
&expectation.decision,
&expectation.arguments,
Some(&expectation.value),
&scope,
semantic,
mapper,
);
}
}
Declaration::Invariant(value) => {
let scope = self.parameter_scope(
&value.variables,
value.span,
"quantified parameter",
mapper,
);
match &value.assertion {
InvariantAssertion::Cardinality {
decision,
arguments,
..
} => self.index_decision_use(
decision, arguments, None, &scope, semantic, mapper,
),
InvariantAssertion::Implication {
condition,
expectation,
..
} => {
self.index_expr(
condition,
Some(&TypeRef::Bool),
&scope,
semantic,
mapper,
);
self.index_decision_use(
&expectation.decision,
&expectation.arguments,
Some(&expectation.value),
&scope,
semantic,
mapper,
);
}
}
}
Declaration::Trace(value) => {
let key = self.add_local_definition(
value.span,
&value.variable.name.value,
&value.variable.ty.value,
"trace variable",
value.variable.name.span,
mapper,
);
self.add_type_name(&value.variable.ty.value, value.variable.ty.span, mapper);
let scope = BTreeMap::from([(
value.variable.name.value.clone(),
(key, type_ref_from_name(&value.variable.ty.value)),
)]);
self.index_expr(&value.initial, None, &scope, semantic, mapper);
for assertion in &value.always {
self.index_expr(assertion, Some(&TypeRef::Bool), &scope, semantic, mapper);
}
if let Some(terminal) = &value.terminal {
self.index_expr(terminal, Some(&TypeRef::Bool), &scope, semantic, mapper);
}
}
}
}
}
fn index_decision_use(
&mut self,
decision: &Spanned<String>,
arguments: &[Expr],
value: Option<&Expr>,
scope: &Scope,
semantic: Option<&CompiledProgram>,
mapper: &SpanMapper<'_>,
) {
self.add_occurrence(
SymbolKey::Decision(decision.value.clone()),
decision.span,
mapper,
);
let info = self.decisions.get(&decision.value).cloned();
for (index, argument) in arguments.iter().enumerate() {
let expected = info.as_ref().and_then(|info| info.parameters.get(index));
self.index_expr(argument, expected, scope, semantic, mapper);
}
if let Some(value) = value {
let expected = info.as_ref().map(|info| &info.return_type);
self.index_expr(value, expected, scope, semantic, mapper);
}
}
fn index_expr(
&mut self,
expression: &Expr,
expected: Option<&TypeRef>,
scope: &Scope,
semantic: Option<&CompiledProgram>,
mapper: &SpanMapper<'_>,
) -> Option<TypeRef> {
let semantic_type = semantic
.and_then(|program| program.type_of_span(expression.span))
.and_then(value_type_ref);
match &expression.kind {
ExprKind::Literal(literal) => semantic_type.or_else(|| literal_type(literal)),
ExprKind::Name(name) => {
if let Some((key, ty)) = scope.get(name) {
self.add_occurrence(key.clone(), expression.span, mapper);
return Some(ty.clone());
}
let enum_name = semantic_type
.as_ref()
.and_then(named_enum)
.or_else(|| expected.and_then(named_enum))
.map(str::to_owned)
.or_else(|| {
self.variant_owners
.get(name)
.filter(|owners| owners.len() == 1)
.and_then(|owners| owners.first().cloned())
});
if let Some(enum_name) = enum_name {
self.add_occurrence(
SymbolKey::Variant(enum_name.clone(), name.clone()),
expression.span,
mapper,
);
return Some(TypeRef::Named(enum_name));
}
semantic_type.or_else(|| expected.cloned())
}
ExprKind::Field { receiver, field } => {
let receiver_type = self.index_expr(receiver, None, scope, semantic, mapper);
let owner = receiver_type.as_ref().and_then(named_type);
if let Some(owner) = owner {
self.add_occurrence(
SymbolKey::Field(owner.to_owned(), field.value.clone()),
field.span,
mapper,
);
return semantic_type.or_else(|| self.field_type(owner, &field.value).cloned());
}
semantic_type
}
ExprKind::Call { callee, arguments } => {
if let Some(info) = self.derives.get(&callee.value).cloned() {
self.add_occurrence(
SymbolKey::Derive(callee.value.clone()),
callee.span,
mapper,
);
for (index, argument) in arguments.iter().enumerate() {
self.index_expr(
argument,
info.parameters.get(index),
scope,
semantic,
mapper,
);
}
semantic_type.or(Some(info.return_type))
} else {
let builtin = builtin_signature(&callee.value, expected);
for (index, argument) in arguments.iter().enumerate() {
self.index_expr(
argument,
builtin
.as_ref()
.and_then(|(parameters, _)| parameters.get(index)),
scope,
semantic,
mapper,
);
}
semantic_type.or_else(|| builtin.map(|(_, result)| result))
}
}
ExprKind::Unary { operator, operand } => {
let operand_expected = match operator {
UnaryOp::Not => Some(&TypeRef::Bool),
UnaryOp::Negate => expected,
};
let operand_type =
self.index_expr(operand, operand_expected, scope, semantic, mapper);
semantic_type.or(match operator {
UnaryOp::Not => Some(TypeRef::Bool),
UnaryOp::Negate => operand_type,
})
}
ExprKind::Binary {
left,
operator,
right,
} => {
let left_hint = self.infer_expr_type(right, scope, semantic);
let right_hint = self.infer_expr_type(left, scope, semantic);
let (left_expected, right_expected) =
if matches!(operator, BinaryOp::And | BinaryOp::Or) {
(Some(TypeRef::Bool), Some(TypeRef::Bool))
} else {
(left_hint, right_hint)
};
let left_type = self.index_expr(
left,
left_expected.as_ref().or(expected),
scope,
semantic,
mapper,
);
let right_type = self.index_expr(
right,
right_expected.as_ref().or(left_type.as_ref()),
scope,
semantic,
mapper,
);
semantic_type.or_else(|| {
if matches!(
operator,
BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::Greater
| BinaryOp::GreaterEqual
| BinaryOp::Less
| BinaryOp::LessEqual
| BinaryOp::And
| BinaryOp::Or
) {
Some(TypeRef::Bool)
} else if *operator == BinaryOp::Divide
|| matches!(left_type, Some(TypeRef::Decimal))
|| matches!(right_type, Some(TypeRef::Decimal))
{
Some(TypeRef::Decimal)
} else {
left_type.or(right_type)
}
})
}
}
}
fn infer_expr_type(
&self,
expression: &Expr,
scope: &Scope,
semantic: Option<&CompiledProgram>,
) -> Option<TypeRef> {
if let Some(value) = semantic
.and_then(|program| program.type_of_span(expression.span))
.and_then(value_type_ref)
{
return Some(value);
}
match &expression.kind {
ExprKind::Literal(literal) => literal_type(literal),
ExprKind::Name(name) => scope.get(name).map(|(_, ty)| ty.clone()).or_else(|| {
self.variant_owners
.get(name)
.filter(|owners| owners.len() == 1)
.and_then(|owners| owners.first())
.map(|owner| TypeRef::Named(owner.clone()))
}),
ExprKind::Field { receiver, field } => self
.infer_expr_type(receiver, scope, semantic)
.as_ref()
.and_then(named_type)
.and_then(|owner| self.field_type(owner, &field.value))
.cloned(),
ExprKind::Call { callee, .. } => self
.derives
.get(&callee.value)
.map(|info| info.return_type.clone())
.or_else(|| builtin_signature(&callee.value, None).map(|(_, result)| result)),
ExprKind::Unary { operator, operand } => match operator {
UnaryOp::Not => Some(TypeRef::Bool),
UnaryOp::Negate => self.infer_expr_type(operand, scope, semantic),
},
ExprKind::Binary {
left,
operator,
right,
} => {
if matches!(
operator,
BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::Greater
| BinaryOp::GreaterEqual
| BinaryOp::Less
| BinaryOp::LessEqual
| BinaryOp::And
| BinaryOp::Or
) {
Some(TypeRef::Bool)
} else if *operator == BinaryOp::Divide {
Some(TypeRef::Decimal)
} else {
let left = self.infer_expr_type(left, scope, semantic);
let right = self.infer_expr_type(right, scope, semantic);
if matches!(left, Some(TypeRef::Decimal))
|| matches!(right, Some(TypeRef::Decimal))
{
Some(TypeRef::Decimal)
} else {
left.or(right)
}
}
}
}
}
fn parameter_scope(
&mut self,
parameters: &[Parameter],
owner: Span,
kind: &'static str,
mapper: &SpanMapper<'_>,
) -> Scope {
parameters
.iter()
.map(|parameter| {
let key = self.add_local_definition(
owner,
¶meter.name.value,
¶meter.ty.value,
kind,
parameter.name.span,
mapper,
);
self.add_type_name(¶meter.ty.value, parameter.ty.span, mapper);
(
parameter.name.value.clone(),
(key, type_ref_from_name(¶meter.ty.value)),
)
})
.collect()
}
fn add_local_definition(
&mut self,
owner: Span,
name: &str,
ty: &str,
kind: &'static str,
span: Span,
mapper: &SpanMapper<'_>,
) -> SymbolKey {
let owner = mapper.map(owner).unwrap_or_else(|| NavigationSpan {
uri: String::new(),
span: owner,
});
let key = SymbolKey::Local {
uri: owner.uri,
owner: owner.span.start,
name: name.to_owned(),
};
self.add_definition(
key.clone(),
name.to_owned(),
kind,
format!("{name}: {ty}"),
span,
mapper,
);
key
}
fn add_type_ref(&mut self, ty: &TypeRef, span: Span, mapper: &SpanMapper<'_>) {
if let TypeRef::Named(name) = ty {
self.add_occurrence(SymbolKey::Type(name.clone()), span, mapper);
}
}
fn add_type_name(&mut self, name: &str, span: Span, mapper: &SpanMapper<'_>) {
if !is_builtin_type(name) {
self.add_occurrence(SymbolKey::Type(name.to_owned()), span, mapper);
}
}
fn field_type(&self, owner: &str, field: &str) -> Option<&TypeRef> {
self.entities
.get(owner)
.or_else(|| self.states.get(owner))?
.fields
.get(field)
}
fn add_definition(
&mut self,
key: SymbolKey,
name: String,
kind: &'static str,
detail: String,
span: Span,
mapper: &SpanMapper<'_>,
) {
let Some(location) = mapper.map(span) else {
return;
};
self.add_document(mapper, &location.uri);
self.definitions
.entry(key.clone())
.or_insert_with(|| SymbolDefinition {
name,
kind,
detail,
location: location.clone(),
});
self.occurrences.push(SymbolOccurrence { key, location });
}
fn add_occurrence(&mut self, key: SymbolKey, span: Span, mapper: &SpanMapper<'_>) {
let Some(location) = mapper.map(span) else {
return;
};
self.add_document(mapper, &location.uri);
self.occurrences.push(SymbolOccurrence { key, location });
}
fn add_document(&mut self, mapper: &SpanMapper<'_>, uri: &str) {
if self.documents.contains_key(uri) {
return;
}
if let Some(document) = mapper.document(uri) {
self.documents.insert(uri.to_owned(), document.clone());
}
}
fn finish(mut self) -> Self {
self.occurrences.sort_by(|left, right| {
(
&left.location.uri,
left.location.span.start,
left.location.span.end,
&left.key,
)
.cmp(&(
&right.location.uri,
right.location.span.start,
right.location.span.end,
&right.key,
))
});
self.occurrences.dedup_by(|left, right| {
left.key == right.key
&& left.location.uri == right.location.uri
&& left.location.span == right.location.span
});
self
}
}
type Scope = BTreeMap<String, (SymbolKey, TypeRef)>;
struct SpanMapper<'a> {
source: &'a SourceFile,
sources: &'a BTreeMap<String, SourceDocument>,
}
impl SpanMapper<'_> {
fn map(&self, span: Span) -> Option<NavigationSpan> {
let source_name = self.source.name_at(span.start);
let document = self.sources.get(source_name)?;
Some(NavigationSpan {
uri: document.uri.as_str().to_owned(),
span: self.source.local_span(span)?,
})
}
fn document(&self, uri: &str) -> Option<&SourceDocument> {
self.sources
.values()
.find(|document| document.uri.as_str() == uri)
}
}
pub(super) fn navigation_index(
uri: &Uri,
document: &OpenDocument,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<NavigationIndex> {
compiled_index(uri, documents).or_else(|| recovery_index(uri, document, documents))
}
fn compiled_index(
uri: &Uri,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<NavigationIndex> {
let requested_path = file_uri_path(uri)?;
let canonical_requested = requested_path.canonicalize().ok()?;
let target = crate::project::resolve_document_target(&canonical_requested).ok()?;
let overrides = open_overrides(documents);
let output = compile_project_with_dependency_graph_and_overrides(
&target.entry,
&target.root,
&target.dependencies,
&overrides,
)
.ok()?;
if !output.files.iter().any(|path| path == &canonical_requested) {
return None;
}
let program = output.compile.program.as_ref()?;
let sources = project_sources(&output.files, &overrides, uri, documents);
let mapper = SpanMapper {
source: &output.source,
sources: &sources,
};
let mut index = NavigationIndex::default();
index.collect_definitions(program.ast(), &mapper);
index.collect_occurrences(program.ast(), Some(program), &mapper);
Some(index.finish())
}
fn recovery_index(
uri: &Uri,
document: &OpenDocument,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<NavigationIndex> {
let mut programs = completion_programs(uri, document, documents);
let current = parse(SourceFile::new(
uri.as_str().to_owned(),
document.text.clone(),
))
.program;
programs.retain(|program| {
source_uri(&program.source.name, uri, documents).is_none_or(|source_uri| source_uri != *uri)
});
if let Some(current) = current {
programs.push(current);
}
if programs.is_empty() {
return None;
}
let mut sources = BTreeMap::new();
for program in &programs {
let source_name = program.source.name.to_string();
let source_uri = source_uri(&source_name, uri, documents)?;
sources.insert(
source_name,
SourceDocument {
uri: source_uri,
text: program.source.text.to_string(),
},
);
}
let mut index = NavigationIndex::default();
for program in &programs {
let mapper = SpanMapper {
source: &program.source,
sources: &sources,
};
index.collect_definitions(program, &mapper);
}
for program in &programs {
let mapper = SpanMapper {
source: &program.source,
sources: &sources,
};
index.collect_occurrences(program, None, &mapper);
}
Some(index.finish())
}
fn open_overrides(documents: &BTreeMap<String, OpenDocument>) -> BTreeMap<PathBuf, String> {
documents
.iter()
.filter_map(|(uri, document)| {
let uri = Uri::from_str(uri).ok()?;
let path = file_uri_path(&uri)?.canonicalize().ok()?;
Some((path, document.text.clone()))
})
.collect()
}
fn project_sources(
files: &[PathBuf],
overrides: &BTreeMap<PathBuf, String>,
requested_uri: &Uri,
documents: &BTreeMap<String, OpenDocument>,
) -> BTreeMap<String, SourceDocument> {
files
.iter()
.filter_map(|path| {
let text = overrides
.get(path)
.cloned()
.or_else(|| fs::read_to_string(path).ok())?;
let uri = uri_for_path(path, requested_uri, documents)?;
Some((path.display().to_string(), SourceDocument { uri, text }))
})
.collect()
}
fn source_uri(
source_name: &str,
requested_uri: &Uri,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<Uri> {
if source_name.starts_with("file://") {
return Uri::from_str(source_name).ok();
}
uri_for_path(Path::new(source_name), requested_uri, documents)
}
fn uri_for_path(
path: &Path,
requested_uri: &Uri,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<Uri> {
let canonical = path
.canonicalize()
.ok()
.unwrap_or_else(|| path.to_path_buf());
if file_uri_path(requested_uri)
.and_then(|path| path.canonicalize().ok())
.is_some_and(|path| path == canonical)
{
return Some(requested_uri.clone());
}
for open_uri in documents.keys() {
let Ok(open_uri) = Uri::from_str(open_uri) else {
continue;
};
if file_uri_path(&open_uri)
.and_then(|path| path.canonicalize().ok())
.is_some_and(|path| path == canonical)
{
return Some(open_uri);
}
}
file_uri(&canonical)
}
fn file_uri(path: &Path) -> Option<Uri> {
let path = path.to_string_lossy();
let mut encoded = String::with_capacity(path.len());
for byte in path.bytes() {
if byte.is_ascii_alphanumeric() || matches!(byte, b'/' | b'-' | b'_' | b'.' | b'~') {
encoded.push(char::from(byte));
} else {
use std::fmt::Write as _;
let _ = write!(encoded, "%{byte:02X}");
}
}
Uri::from_str(&format!("file://{encoded}")).ok()
}
fn parameter_types(parameters: &[Parameter]) -> Vec<TypeRef> {
parameters
.iter()
.map(|parameter| type_ref_from_name(¶meter.ty.value))
.collect()
}
fn type_ref_from_name(name: &str) -> TypeRef {
match name {
"Bool" => TypeRef::Bool,
"Int" => TypeRef::Int,
"Decimal" => TypeRef::Decimal,
"String" => TypeRef::String,
"Date" => TypeRef::Date,
"Duration" => TypeRef::Duration,
_ => TypeRef::Named(name.to_owned()),
}
}
fn value_type_ref(value: &ValueType) -> Option<TypeRef> {
match value {
ValueType::Bool => Some(TypeRef::Bool),
ValueType::Int => Some(TypeRef::Int),
ValueType::Decimal => Some(TypeRef::Decimal),
ValueType::String => Some(TypeRef::String),
ValueType::Date => Some(TypeRef::Date),
ValueType::Duration => Some(TypeRef::Duration),
ValueType::Enum(name) | ValueType::Entity(name) | ValueType::State(name) => {
Some(TypeRef::Named(name.clone()))
}
ValueType::Unknown | ValueType::Error => None,
}
}
fn literal_type(literal: &tess::ast::Literal) -> Option<TypeRef> {
match literal {
tess::ast::Literal::Bool(_) => Some(TypeRef::Bool),
tess::ast::Literal::Number(tess::ast::NumericLiteral::Int(_)) => Some(TypeRef::Int),
tess::ast::Literal::Number(tess::ast::NumericLiteral::Decimal(_)) => Some(TypeRef::Decimal),
tess::ast::Literal::String(_) => Some(TypeRef::String),
tess::ast::Literal::Unknown => None,
}
}
fn builtin_signature(name: &str, expected: Option<&TypeRef>) -> Option<(Vec<TypeRef>, TypeRef)> {
match name {
"max" | "min" => {
let ty = expected
.filter(|ty| matches!(ty, TypeRef::Int | TypeRef::Decimal))
.cloned()
.unwrap_or(TypeRef::Int);
Some((vec![ty.clone(), ty.clone()], ty))
}
"abs" => {
let ty = expected
.filter(|ty| matches!(ty, TypeRef::Int | TypeRef::Decimal))
.cloned()
.unwrap_or(TypeRef::Int);
Some((vec![ty.clone()], ty))
}
"date" => Some((vec![TypeRef::String], TypeRef::Date)),
"days" | "hours" | "minutes" => Some((vec![TypeRef::Int], TypeRef::Duration)),
_ => None,
}
}
fn named_type(ty: &TypeRef) -> Option<&str> {
let TypeRef::Named(name) = ty else {
return None;
};
Some(name)
}
fn named_enum(ty: &TypeRef) -> Option<&str> {
named_type(ty)
}
fn is_builtin_type(name: &str) -> bool {
matches!(
name,
"Bool" | "Int" | "Decimal" | "String" | "Date" | "Duration"
)
}
fn cardinality_suffix(cardinality: Cardinality) -> &'static str {
match cardinality {
Cardinality::ExactlyOne => "",
Cardinality::ZeroOrOne => " zero or one",
Cardinality::Many => " many",
}
}
fn contains_cursor(span: Span, cursor: usize) -> bool {
span.start <= cursor && cursor <= span.end && span.start < span.end
}
const fn span_len(span: Span) -> usize {
span.end.saturating_sub(span.start)
}