use std::{
collections::BTreeMap,
fs::File,
io::Read,
mem::transmute,
ops::{Range, RangeInclusive},
sync::{
Arc, LazyLock, Mutex,
atomic::{AtomicBool, Ordering},
},
};
use either::Either;
use extension_fn::extension_fn;
use regex::Regex;
use serde_json::Value;
use tower_lsp::{
Client,
lsp_types::{
CodeLens, Command, CompletionItem, CompletionItemKind, Diagnostic, DiagnosticSeverity,
Documentation, MarkupContent, MarkupKind, NumberOrString, Position, SemanticTokenType, Url,
},
};
use tyml_core::{
Parsed, TymlContext, Validated,
header::TymlHeader,
tyml_diagnostic::{DiagnosticBuilder, message::get_text},
tyml_formatter::{FormatterToken, GeneralFormatter},
tyml_generator::{
registry::STYLE_REGISTRY,
style::{ASTTokenKind, language::LanguageStyle},
},
tyml_parser::{formatter::IntoFormatterToken, lexer::Lexer},
tyml_source::{AsUtf8ByteRange, SourceCode, SourceCodeSpan, ToByteSpan},
tyml_type::types::{NamedTypeMap, NamedTypeTree, Type, TypeTree},
tyml_validate::{
error::TymlValueValidateError,
validate::{CompletionCacheItemKind, MergedValueTree, ValidateValue, ValueTypeChecker},
},
};
type LSPRange = tower_lsp::lsp_types::Range;
#[derive(Debug)]
pub struct GeneratedLanguageServer {
pub url: Url,
pub lang: &'static str,
pub tyml: Mutex<Option<(TymlContext<Validated>, TymlHeader)>>,
pub has_tyml_file_error: AtomicBool,
pub style_not_found: AtomicBool,
pub tyml_file_name: Mutex<String>,
pub analyzing_flag: AtomicBool,
pub tokens: Mutex<Arc<Vec<(SemanticTokenType, (Position, usize))>>>,
pub language_style: Mutex<Arc<LanguageStyle>>,
pub formatter_tokens: Mutex<Option<FormatterTokenHolder>>,
}
#[derive(Debug)]
pub struct FormatterTokenHolder {
_code: Arc<String>,
tokens: Vec<FormatterToken<'static>>,
}
impl FormatterTokenHolder {
pub fn tokens<'this>(&'this self) -> &'this Vec<FormatterToken<'this>> {
&self.tokens
}
}
impl GeneratedLanguageServer {
pub fn new(url: Url, lang: &'static str) -> Self {
Self {
url,
lang,
tyml: Mutex::new(None),
has_tyml_file_error: AtomicBool::new(false),
style_not_found: AtomicBool::new(false),
tyml_file_name: Mutex::new(String::new()),
analyzing_flag: AtomicBool::new(true),
tokens: Mutex::new(Arc::new(Vec::new())),
language_style: Mutex::new(STYLE_REGISTRY.resolve("").unwrap()),
formatter_tokens: Mutex::new(None),
}
}
pub async fn on_change(&self, source_code_url: Url, source_code: Arc<String>) {
let Ok(source_code_path) = source_code_url.to_file_path() else {
return;
};
let source_code_name = Arc::new(source_code_path.to_string_lossy().to_string());
let header = TymlHeader::parse(&source_code).await;
let mut other_file_name = None;
match header {
Some(mut header) => {
let file = header
.tyml
.as_ref()
.map(|tyml| {
File::open(tyml).ok().or(source_code_path
.parent()
.map(|parent| {
let other_file_name_temp =
parent.join(tyml.as_str()).to_string_lossy().to_string();
other_file_name = Some(other_file_name_temp.clone());
File::open(other_file_name_temp).ok()
})
.flatten())
})
.ok()
.flatten()
.filter(|file| {
file.metadata()
.map(|metadata| metadata.is_file())
.unwrap_or(false)
});
if let Some(other_file_name) = other_file_name {
if let Ok(tyml) = header.tyml.as_mut() {
*tyml = other_file_name;
}
}
let file_open_result = file.is_some();
let mut tyml_source = String::new();
file.map(|mut file| file.read_to_string(&mut tyml_source));
if let Ok(tyml) = &header.tyml {
*self.tyml_file_name.lock().unwrap() = tyml.clone();
}
self.has_tyml_file_error
.store(!file_open_result, Ordering::Release);
if !file_open_result {
tyml_source = "*: any".to_string();
}
let tyml = TymlContext::new(SourceCode::new(
header
.tyml
.as_ref()
.map(|tyml| tyml.as_str())
.clone()
.unwrap_or("error")
.to_string(),
tyml_source,
))
.parse();
let url = self.url.to_string();
let style_fall_back = url.split(".").last().unwrap_or("");
let style = header
.style
.as_ref()
.map(|style| style.as_ref().map(|style| style.as_str()).ok())
.flatten()
.unwrap_or(style_fall_back);
let language = STYLE_REGISTRY
.resolve(style)
.unwrap_or(STYLE_REGISTRY.resolve("").unwrap());
self.style_not_found
.store(STYLE_REGISTRY.resolve(style).is_none(), Ordering::Release);
let mut tokens = BTreeMap::new();
let mut formatter_tokens = Vec::new();
let source = SourceCode::new(source_code_name, source_code.clone());
let tyml = tyml.ml_parse_and_validate(
&language,
&source,
Some(&mut tokens),
Some(&mut formatter_tokens),
);
let formatter_tokens = FormatterTokenHolder {
_code: source_code.clone(),
tokens: unsafe { transmute(formatter_tokens) },
};
let mut semantic_tokens = Vec::new();
for (kind, span) in tokens.values() {
let kind = match kind {
ASTTokenKind::Section => SemanticTokenType::STRUCT,
ASTTokenKind::Key => SemanticTokenType::PROPERTY,
ASTTokenKind::TreeKey => SemanticTokenType::TYPE,
ASTTokenKind::NumericValue => SemanticTokenType::NUMBER,
ASTTokenKind::InfNan => SemanticTokenType::KEYWORD,
ASTTokenKind::StringValue => SemanticTokenType::STRING,
ASTTokenKind::BoolValue => SemanticTokenType::KEYWORD,
ASTTokenKind::Comment => SemanticTokenType::COMMENT,
ASTTokenKind::Null => SemanticTokenType::KEYWORD,
};
for token in span
.as_utf8_byte_range()
.to_lsp_semantic_token(&source_code)
{
semantic_tokens.push((kind.clone(), token));
}
}
*self.tokens.lock().unwrap() = Arc::new(semantic_tokens);
*self.tyml.lock().unwrap() = Some((tyml, header));
*self.language_style.lock().unwrap() = language;
*self.formatter_tokens.lock().unwrap() = Some(formatter_tokens);
}
None => {
let url = self.url.to_string();
let style = url.split(".").last().unwrap_or("");
let language = STYLE_REGISTRY
.resolve(style)
.unwrap_or(STYLE_REGISTRY.resolve("").unwrap());
let tyml =
TymlContext::new(SourceCode::new("".to_string(), "*: any".to_string())).parse();
let mut tokens = BTreeMap::new();
let mut formatter_tokens = Vec::new();
let source = SourceCode::new(source_code_name, source_code.clone());
let tyml = tyml.ml_parse_and_validate(
&language,
&source,
Some(&mut tokens),
Some(&mut formatter_tokens),
);
let formatter_tokens = FormatterTokenHolder {
_code: source_code.clone(),
tokens: unsafe { transmute(formatter_tokens) },
};
let dummy_header = TymlHeader::parse("!tyml").await.unwrap();
let mut semantic_tokens = Vec::new();
for (kind, span) in tokens.values() {
let kind = match kind {
ASTTokenKind::Section => SemanticTokenType::STRUCT,
ASTTokenKind::Key => SemanticTokenType::PROPERTY,
ASTTokenKind::TreeKey => SemanticTokenType::TYPE,
ASTTokenKind::NumericValue => SemanticTokenType::NUMBER,
ASTTokenKind::InfNan => SemanticTokenType::KEYWORD,
ASTTokenKind::StringValue => SemanticTokenType::STRING,
ASTTokenKind::BoolValue => SemanticTokenType::KEYWORD,
ASTTokenKind::Comment => SemanticTokenType::COMMENT,
ASTTokenKind::Null => SemanticTokenType::KEYWORD,
};
for token in span
.as_utf8_byte_range()
.to_lsp_semantic_token(&source_code)
{
semantic_tokens.push((kind.clone(), token));
}
}
*self.tokens.lock().unwrap() = Arc::new(semantic_tokens);
*self.tyml.lock().unwrap() = Some((tyml, dummy_header));
*self.language_style.lock().unwrap() = language;
*self.formatter_tokens.lock().unwrap() = Some(formatter_tokens);
}
}
self.analyzing_flag.store(false, Ordering::Release);
}
pub async fn publish_diagnostics(&self, client: &Client) {
let Some((tyml, header)) = self.tyml.lock().unwrap().as_ref().cloned() else {
return;
};
if self.has_tyml_file_error.load(Ordering::Acquire)
|| self.style_not_found.load(Ordering::Acquire)
{
let tyml_file_name = self.tyml_file_name.lock().unwrap().clone();
if let Some(Err(error)) = header.style {
client
.publish_diagnostics(
self.url.clone(),
vec![Diagnostic {
range: header
.span
.as_utf8_byte_range()
.to_lsp_span(&tyml.ml_source_code().code),
severity: Some(DiagnosticSeverity::WARNING),
message: get_text("lsp.message.header_var_lookup_error", self.lang)
.replace("%0", &error.var_name),
..Default::default()
}],
None,
)
.await;
} else if let Err(error) = header.tyml {
match error {
Either::Left(lookup_error) => {
client
.publish_diagnostics(
self.url.clone(),
vec![Diagnostic {
range: header
.span
.as_utf8_byte_range()
.to_lsp_span(&tyml.ml_source_code().code),
severity: Some(DiagnosticSeverity::WARNING),
message: get_text(
"lsp.message.header_var_lookup_error",
self.lang,
)
.replace("%0", &lookup_error.var_name),
..Default::default()
}],
None,
)
.await;
}
Either::Right(error) => {
client
.publish_diagnostics(
self.url.clone(),
vec![Diagnostic {
range: header
.span
.as_utf8_byte_range()
.to_lsp_span(&tyml.ml_source_code().code),
severity: Some(DiagnosticSeverity::WARNING),
message: get_text(
"lsp.message.failed_to_download_tyml",
self.lang,
)
.replace("%0", error.as_str()),
..Default::default()
}],
None,
)
.await;
}
}
} else if self.style_not_found.load(Ordering::Relaxed) {
client
.publish_diagnostics(
self.url.clone(),
vec![Diagnostic {
range: header
.span
.as_utf8_byte_range()
.to_lsp_span(&tyml.ml_source_code().code),
severity: Some(DiagnosticSeverity::WARNING),
message: get_text("lsp.message.style_not_found", self.lang)
.replace("%0", header.style.unwrap().unwrap().as_str()),
..Default::default()
}],
None,
)
.await;
} else {
client
.publish_diagnostics(
self.url.clone(),
vec![Diagnostic {
range: header
.span
.as_utf8_byte_range()
.to_lsp_span(&tyml.ml_source_code().code),
severity: Some(DiagnosticSeverity::WARNING),
message: get_text("lsp.message.tyml_file_error", self.lang)
.replace("%0", tyml_file_name.as_str()),
..Default::default()
}],
None,
)
.await;
}
} else {
let mut diagnostics = Vec::new();
for error in tyml.ml_parse_error().iter() {
let diagnostic = error.build(&mut NamedTypeMap::new());
diagnostics.push(Diagnostic {
range: diagnostic.labels[0]
.span
.to_lsp_span(&tyml.ml_source_code().code),
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::Number(diagnostic.message.code as _)),
message: format!(
"{}: {}\n{}",
diagnostic.message.section_name(self.lang, false),
diagnostic.message.message(self.lang, false),
diagnostic.message.label(1, self.lang, false).unwrap(),
),
..Default::default()
});
}
if diagnostics.is_empty() {
for error in tyml.ml_validate_error().iter() {
let diagnostic = error.build(&mut NamedTypeMap::new());
if let TymlValueValidateError::InvalidValue {
found,
expected: _,
path: _,
caused_by: _,
} = error
{
for found in found.iter() {
diagnostics.push(Diagnostic {
range: found.to_lsp_span(&tyml.ml_source_code().code),
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::Number(diagnostic.message.code as _)),
message: format!(
"{}: {}\n{}\n{}\n{}",
diagnostic.message.section_name(self.lang, false),
diagnostic.message.message(self.lang, false),
diagnostic.message.label(0, self.lang, false).unwrap(),
diagnostic
.message
.note(self.lang, false)
.unwrap_or(String::new()),
get_text("lsp.message.array_error_diagnostic", self.lang)
),
..Default::default()
});
}
} else {
diagnostics.push(Diagnostic {
range: diagnostic.labels[0]
.span
.to_lsp_span(&tyml.ml_source_code().code),
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::Number(diagnostic.message.code as _)),
message: format!(
"{}: {}\n{}{}",
diagnostic.message.section_name(self.lang, false),
diagnostic.message.message(self.lang, false),
diagnostic.message.label(0, self.lang, false).unwrap(),
diagnostic
.message
.note(self.lang, false)
.map(|note| format!("\n{}", note))
.unwrap_or(String::new())
),
..Default::default()
});
}
}
}
client
.publish_diagnostics(self.url.clone(), diagnostics, None)
.await;
}
}
pub fn provide_completion(&self, position: Position) -> Option<Vec<CompletionItem>> {
let Some(tyml) = self.tyml.lock().unwrap().as_ref().cloned() else {
return None;
};
let byte_position = position.to_byte_position(&tyml.0.ml_source_code().code);
let field_completion_cache = tyml
.0
.validator()
.completion_cache
.field_completion_cache
.read()
.unwrap();
let enum_completion_cache = tyml
.0
.validator()
.completion_cache
.enum_completion_cache
.read()
.unwrap();
let completion_items = field_completion_cache
.iter()
.chain(enum_completion_cache.iter())
.filter(|item| match &item.value_span {
SourceCodeSpan::UTF8Byte(range) => range.to_inclusive().contains(&byte_position),
SourceCodeSpan::UnicodeCharacter(_) => false,
})
.filter(|item| !item.completion_name_and_spans.is_empty());
let mut sorted_map = BTreeMap::new();
for item in completion_items {
if let SourceCodeSpan::UTF8Byte(span) = &item.value_span {
sorted_map.insert(span.len(), item);
}
}
match sorted_map.pop_first() {
Some((_, items)) => Some(
items
.completion_name_and_spans
.iter()
.map(|item| CompletionItem {
label: item.value.to_string(),
kind: Some(match items.kind {
CompletionCacheItemKind::Field => CompletionItemKind::FIELD,
CompletionCacheItemKind::Enum => CompletionItemKind::ENUM,
}),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: ml_completion::take_documents(
item.span(),
tyml.0.tyml().ast(),
tyml.0.tyml_source.code.as_str(),
),
})),
..Default::default()
})
.collect(),
),
None => None,
}
}
pub fn goto_define(&self, position: Position) -> (String, Vec<LSPRange>) {
let Some((tyml, header)) = self.tyml.lock().unwrap().as_ref().cloned() else {
return (String::new(), Vec::new());
};
if let Ok(tyml) = &header.tyml {
if tyml.is_empty() {
return (String::new(), Vec::new());
}
}
let byte_position = position.to_byte_position(&tyml.ml_source_code().code);
if header.span.to_inclusive().contains(&byte_position) {
return (
header.tyml.unwrap_or_default(),
vec![
(0..tyml.tyml_source.code.len())
.as_utf8_byte_range()
.to_lsp_span(&tyml.tyml_source.code),
],
);
}
let defines = tyml
.validator()
.goto_define_and_documents(byte_position, &tyml.ml_source_code().code);
let defines = defines
.into_iter()
.map(|range| {
range
.0
.as_utf8_byte_range()
.to_lsp_span(&tyml.tyml_source.code)
})
.collect();
(header.tyml.unwrap_or_default(), defines)
}
pub fn hover(&self, position: Position) -> Option<String> {
let Some((tyml, _)) = self.tyml.lock().unwrap().clone() else {
return None;
};
let byte_position = position.to_byte_position(&tyml.ml_source_code().code);
let documents = tyml
.validator()
.goto_define_and_documents(byte_position, &tyml.ml_source_code().code);
if let Some((span, documents)) = documents.get(0) {
let documents = documents
.iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
.join("");
Some(format!(
"{}{}",
create_hover_code_block(&tyml.tyml_source.code[span.clone()]),
documents
))
} else {
None
}
}
}
#[derive(Debug)]
pub struct TymlLanguageServer {
pub url: Url,
pub lang: &'static str,
pub tyml: Mutex<Option<TymlContext<Parsed>>>,
pub analyzing_flag: AtomicBool,
pub tokens: Mutex<Arc<Vec<(SemanticTokenType, (Position, usize))>>>,
}
impl TymlLanguageServer {
pub fn new(url: Url, lang: &'static str) -> Self {
Self {
url,
lang,
tyml: Mutex::new(None),
analyzing_flag: AtomicBool::new(false),
tokens: Mutex::new(Arc::new(Vec::new())),
}
}
pub fn on_change(&self, name: String, code: String) {
let (tyml, changed) = match self.tyml.lock().unwrap().clone() {
Some(old_tyml) => {
if code.as_str() != old_tyml.tyml_source.code.as_str() {
(TymlContext::new(SourceCode::new(name, code)).parse(), true)
} else {
(old_tyml, false)
}
}
None => (TymlContext::new(SourceCode::new(name, code)).parse(), true),
};
if changed {
let mut tokens = BTreeMap::new();
tyml_semantic_tokens::collect_tokens_for_defines(&tyml.tyml().ast(), &mut tokens);
for comment_range in tyml.tyml().comment_ranges().iter() {
tokens.insert(
comment_range.start,
(SemanticTokenType::COMMENT, comment_range.clone()),
);
}
let mut semantic_tokens = Vec::new();
for (token_type, span) in tokens.values() {
for token in span
.as_utf8_byte_range()
.to_lsp_semantic_token(&tyml.tyml_source.code)
{
semantic_tokens.push((token_type.clone(), token));
}
}
*self.tokens.lock().unwrap() = Arc::new(semantic_tokens);
}
*self.tyml.lock().unwrap() = Some(tyml);
self.analyzing_flag.store(false, Ordering::Release);
}
pub async fn publish_diagnostics(&self, client: &Client) {
let Some(tyml) = self.tyml.lock().unwrap().as_ref().cloned() else {
return;
};
let mut diagnostics = Vec::new();
for error in tyml.tyml().parse_errors() {
let diagnostic = error.build(&mut NamedTypeMap::new());
diagnostics.push(Diagnostic {
range: diagnostic.labels[0]
.span
.to_lsp_span(&tyml.tyml_source.code),
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::Number(diagnostic.message.code as _)),
message: format!(
"{}: {}",
diagnostic.message.section_name(self.lang, false),
diagnostic.message.message(self.lang, false),
),
..Default::default()
});
}
if diagnostics.is_empty() {
for error in tyml.tyml().type_errors() {
let diagnostic = error.build(&mut NamedTypeMap::new());
diagnostics.push(Diagnostic {
range: diagnostic.labels[0]
.span
.to_lsp_span(&tyml.tyml_source.code),
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::Number(diagnostic.message.code as _)),
message: format!(
"{}: {}",
diagnostic.message.section_name(self.lang, false),
diagnostic.message.message(self.lang, false),
),
..Default::default()
});
}
}
client
.publish_diagnostics(self.url.clone(), diagnostics, None)
.await;
}
pub fn provide_completion(&self, position: Position) -> Option<Vec<CompletionItem>> {
let Some(tyml) = self.tyml.lock().unwrap().as_ref().cloned() else {
return None;
};
let byte_position = position.to_byte_position(&tyml.tyml_source.code);
let non_whitespace_byte_position = tyml.tyml_source.code[..byte_position]
.char_indices()
.rev()
.find_map(|(i, ch)| {
if ch.is_whitespace() {
None
} else {
Some(i + ch.len_utf8())
}
})
.unwrap_or(0);
let mut has_at = false;
for char in tyml.tyml_source.code[..byte_position].chars().rev() {
if char == '@' {
has_at = true;
break;
}
if !char.is_ascii_alphabetic() && !char.is_whitespace() {
break;
}
}
if has_at {
return Some(
["value", "length", "u8size", "regex", "body", "claim"]
.into_iter()
.map(|name| CompletionItem {
label: name.to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
})
.collect(),
);
}
let ast = tyml.tyml().ast();
let mut completions = Vec::new();
if on_type_tag::is_position_on_type_tag_for_defines(
ast,
non_whitespace_byte_position,
&mut Vec::new(),
&mut completions,
) {
Some(
completions
.into_iter()
.map(|(name, kind)| CompletionItem {
label: name.to_string(),
kind: Some(kind),
..Default::default()
})
.collect(),
)
} else {
let interfaces = tyml.tyml().interfaces();
let json_values = interfaces
.iter()
.map(|interface| interface.functions.iter())
.flatten()
.map(|function| {
[function
.return_info
.as_ref()
.map(|return_info| return_info.default_value.as_ref())
.flatten()]
.into_iter()
.chain(
function
.arguments
.iter()
.map(|argument| argument.default_value.as_ref()),
)
})
.flatten()
.flatten();
for &json_value in json_values {
if !tyml_json::is_position_on_json_value_literal(json_value, byte_position) {
let span_and_fields = interfaces
.iter()
.map(|interface| interface.json_tree_type_cache.field_completion_map.iter())
.flatten()
.filter(|(span, _)| span.to_inclusive().contains(&byte_position));
let mut sorted = BTreeMap::new();
for (span, fields) in span_and_fields {
sorted.insert(span.len(), fields);
}
if let Some((_, fields)) = sorted.pop_first() {
return Some(
fields
.iter()
.map(|field_name| CompletionItem {
label: field_name.value.to_string(),
kind: Some(CompletionItemKind::FIELD),
documentation: Some(Documentation::MarkupContent(
MarkupContent {
kind: MarkupKind::Markdown,
value: tyml_json::take_documents(
field_name.span(),
ast,
&tyml.tyml_source.code,
),
},
)),
..Default::default()
})
.collect(),
);
}
}
}
Some(
[
"function",
"interface",
"type",
"enum",
"kind",
"rename",
"authed",
"cookie",
"return",
]
.into_iter()
.map(|name| CompletionItem {
label: name.to_string(),
kind: Some(CompletionItemKind::KEYWORD),
..Default::default()
})
.collect(),
)
}
}
pub fn goto_define(&self, position: Position) -> Option<LSPRange> {
let Some(tyml) = self.tyml.lock().unwrap().clone() else {
return None;
};
let byte_position = position.to_byte_position(&tyml.tyml_source.code);
let named_type_map = tyml.tyml().named_type_map();
let name_id = named_type_map
.use_link_map
.iter()
.find(|(_, ranges)| {
ranges
.iter()
.any(|range| range.to_inclusive().contains(&byte_position))
})
.map(|(&name_id, _)| name_id);
let interfaces = tyml.tyml().interfaces();
let define_span = interfaces
.iter()
.map(|interface| interface.json_tree_type_cache.field_user_map.iter())
.flatten()
.find(|(_, user_spans)| {
user_spans
.iter()
.any(|user_span| user_span.to_inclusive().contains(&byte_position))
})
.map(|(define_span, _)| define_span.clone());
name_id
.map(|name_id| {
named_type_map
.get_define_span(name_id)
.unwrap()
.as_utf8_byte_range()
.to_lsp_span(&tyml.tyml_source.code)
})
.or(define_span.map(|define_span| {
define_span
.as_utf8_byte_range()
.to_lsp_span(&tyml.tyml_source.code)
}))
}
pub fn get_references(&self, position: Position) -> Vec<LSPRange> {
let Some(tyml) = self.tyml.lock().unwrap().clone() else {
return Vec::new();
};
let byte_position = position.to_byte_position(&tyml.tyml_source.code);
let named_type_map = tyml.tyml().named_type_map();
let name_id = named_type_map
.use_link_map
.iter()
.find(|(_, ranges)| {
ranges
.iter()
.any(|range| range.to_inclusive().contains(&byte_position))
})
.map(|(&name_id, _)| name_id);
match name_id {
Some(name_id) => {
let Some(users) = named_type_map.use_link_map.get(&name_id) else {
return Vec::new();
};
users
.iter()
.map(|span| {
span.as_utf8_byte_range()
.to_lsp_span(&tyml.tyml_source.code)
})
.collect()
}
None => {
tyml.tyml()
.interfaces()
.iter()
.map(|interface| interface.json_tree_type_cache.field_user_map.iter())
.flatten()
.find(|(define_span, user_spans)| {
define_span.to_inclusive().contains(&byte_position)
|| user_spans
.iter()
.any(|user_span| user_span.to_inclusive().contains(&byte_position))
})
.map(|(define_span, user_spans)| {
[define_span.clone()]
.into_iter()
.chain(user_spans.iter().cloned())
})
.map(|spans| {
spans
.map(|span| {
span.as_utf8_byte_range()
.to_lsp_span(&tyml.tyml_source.code)
})
.collect()
})
.unwrap_or(Vec::new())
}
}
}
pub fn hover(&self, position: Position) -> Option<String> {
let Some(tyml) = self.tyml.lock().unwrap().clone() else {
return None;
};
let byte_position = position.to_byte_position(&tyml.tyml_source.code);
let mut result = None;
tyml_documents_from_ast::get_documents_from_defines(
tyml.tyml().ast(),
byte_position,
&mut result,
);
if let Some(lines) = result {
return Some(
lines
.into_iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
.join(""),
);
}
let interfaces = tyml.tyml().interfaces();
let json_field_define_span = interfaces
.iter()
.map(|interface| interface.json_tree_type_cache.field_user_map.iter())
.flatten()
.find(|(_, user_spans)| {
user_spans
.iter()
.any(|user_span| user_span.to_inclusive().contains(&byte_position))
})
.map(|(define_span, _)| define_span.clone());
if let Some(json_field_define_span) = json_field_define_span {
return Some(tyml_json::take_documents(
json_field_define_span,
&tyml.tyml().ast(),
&tyml.tyml_source.code,
));
}
let named_type_map = tyml.tyml().named_type_map();
let name_id = named_type_map
.use_link_map
.iter()
.find(|(_, ranges)| {
ranges
.iter()
.any(|range| range.to_inclusive().contains(&byte_position))
})
.map(|(&name_id, _)| name_id);
let Some(name_id) = name_id else { return None };
let type_tree = named_type_map.get_type(name_id).unwrap();
let documents = match type_tree {
NamedTypeTree::Struct { tree } => &tree.documents(),
NamedTypeTree::Enum {
elements: _,
documents,
} => documents,
};
if documents.is_empty() {
None
} else {
Some(
documents
.iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
.join(""),
)
}
}
pub fn format(&self) -> Option<String> {
let tyml = self.tyml.lock().unwrap().clone()?;
if !tyml.tyml().parse_errors().is_empty() {
return None;
}
let ast = tyml.tyml().ast();
let code = tyml.tyml_source.code.as_str();
let mut formatter = GeneralFormatter::new(
Lexer::new(code)
.enable_comment_token()
.into_formatter_token(ast)
.into_iter(),
25,
);
formatter.format();
Some(formatter.generate_code())
}
pub fn code_lens(&self, uri: String) -> Vec<CodeLens> {
let Some(tyml) = self.tyml.lock().unwrap().clone() else {
return Vec::new();
};
let mut code_lens = Vec::new();
for interface in tyml.tyml().interfaces().iter() {
let range = interface
.keyword_span
.as_utf8_byte_range()
.to_lsp_span(&tyml.tyml_source.code);
code_lens.push(CodeLens {
range,
command: Some(Command {
title: "â–¶ Run server".to_string(),
command: "tyml.mock.serve".to_string(),
arguments: Some(vec![
Value::String(uri.clone()),
Value::String(interface.name.value.clone()),
]),
}),
data: None,
});
for function in interface.functions.iter() {
let range = function
.keyword_span
.as_utf8_byte_range()
.to_lsp_span(&tyml.tyml_source.code);
code_lens.push(CodeLens {
range,
command: Some(Command {
title: "â–¶ Run client".to_string(),
command: "tyml.mock.send".to_string(),
arguments: Some(vec![
Value::String(uri.clone()),
Value::String(interface.name.value.clone()),
Value::String(function.name.value.clone()),
]),
}),
data: None,
});
}
}
code_lens
}
}
fn create_hover_code_block(code: &str) -> String {
static DOCUMENTS_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(^|\n|\r|\r\n)[  \t]*(///|###).*").unwrap());
static INDENT_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(\n|\r|\r\n| | |\t)+").unwrap());
let code = DOCUMENTS_REGEX.replace_all(code, "");
let code = INDENT_REGEX.replace_all(code.as_ref(), "");
static FOUND_INDENT_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(\n|\r|\r\n)([ \t]*)\}").unwrap());
let indent = FOUND_INDENT_REGEX
.captures_iter(code.as_ref())
.map(|captured| captured[2].chars().count())
.min()
.unwrap_or(0);
let indent = " ".repeat(indent);
format!("```tyml\n{}{}\n```\n---\n", indent, code)
}
mod on_type_tag {
use std::mem::swap;
use tower_lsp::lsp_types::CompletionItemKind;
use tyml_core::tyml_parser::ast::{AST, Define, Defines, ElementInlineType, TypeDefine};
use super::ToInclusive;
pub fn is_position_on_type_tag_for_defines<'input>(
ast: &Defines<'input, '_>,
position: usize,
names: &mut Vec<(&'input str, CompletionItemKind)>,
completions: &mut Vec<(&'input str, CompletionItemKind)>,
) -> bool {
if !ast.span.to_inclusive().contains(&position) {
return false;
}
for define in ast.defines.iter() {
if let Define::Type(type_define) = define {
let (name, kind) = match type_define {
TypeDefine::Struct(struct_define) => {
(struct_define.name.value, CompletionItemKind::STRUCT)
}
TypeDefine::Enum(enum_define) => {
(enum_define.name.value, CompletionItemKind::ENUM)
}
};
names.push((name, kind));
}
}
for define in ast.defines.iter() {
if is_position_on_type_tag_for_define(define, position, names, completions) {
return true;
}
}
false
}
fn is_position_on_type_tag_for_define<'input>(
ast: &Define<'input, '_>,
position: usize,
names: &mut Vec<(&'input str, CompletionItemKind)>,
completions: &mut Vec<(&'input str, CompletionItemKind)>,
) -> bool {
if !ast.span().to_inclusive().contains(&position) {
return false;
}
match ast {
Define::Element(element_define) => {
if let Some(_) = &element_define.ty {
swap(names, completions);
true
} else if let Some(inline_type) = &element_define.inline_type {
is_position_on_type_tag_for_element_inline_type(
inline_type,
position,
names,
completions,
)
} else {
swap(names, completions);
true
}
}
Define::Type(type_define) => match type_define {
TypeDefine::Struct(struct_define) => is_position_on_type_tag_for_defines(
&struct_define.defines,
position,
names,
completions,
),
TypeDefine::Enum(_) => false,
},
Define::Interface(interface) => {
for function in interface.functions.iter() {
for argument in function.arguments.iter() {
if argument.ty.span.to_inclusive().contains(&position) {
swap(names, completions);
return true;
}
}
if let Some(throws) = &function.throws {
if throws.ty.span.to_inclusive().contains(&position) {
swap(names, completions);
return true;
}
}
if let Some(return_type) = &function.return_type {
if return_type.span.to_inclusive().contains(&position) {
swap(names, completions);
return true;
}
}
}
false
}
}
}
fn is_position_on_type_tag_for_element_inline_type<'input>(
ast: &ElementInlineType<'input, '_>,
position: usize,
names: &mut Vec<(&'input str, CompletionItemKind)>,
completions: &mut Vec<(&'input str, CompletionItemKind)>,
) -> bool {
if !ast.span.to_inclusive().contains(&position) {
return false;
}
is_position_on_type_tag_for_defines(&ast.defines, position, names, completions)
}
}
mod ml_completion {
use crate::language_server::create_hover_code_block;
use std::ops::Range;
use tyml_core::tyml_parser::ast::{AST, Define, Defines, TypeDefine};
pub fn take_documents<'input>(
define_span: Range<usize>,
ast: &Defines<'input, '_>,
code: &str,
) -> String {
let mut result = None;
take_documents_span_for_defines(&define_span, ast, &mut result);
match result {
Some((span, documents)) => {
format!(
"{}\n{}",
create_hover_code_block(&code[span]),
documents
.into_iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
.join(""),
)
}
None => create_hover_code_block(&code[define_span]),
}
}
fn take_documents_span_for_defines<'input>(
define_span: &Range<usize>,
ast: &Defines<'input, '_>,
result: &mut Option<(Range<usize>, Vec<&'input str>)>,
) {
for define in ast.defines.iter() {
match define {
Define::Element(element_define) => {
if &element_define.node.span() == define_span {
*result = Some((
element_define.span.clone(),
element_define.documents.lines.iter().cloned().collect(),
));
return;
}
if let Some(inline_type) = &element_define.inline_type {
take_documents_span_for_defines(define_span, inline_type.defines, result);
}
}
Define::Type(type_define) => match type_define {
TypeDefine::Struct(struct_define) => {
take_documents_span_for_defines(define_span, struct_define.defines, result);
}
TypeDefine::Enum(enum_define) => {
if &enum_define.span == define_span {
*result = Some((
enum_define.span.clone(),
enum_define.documents.lines.iter().cloned().collect(),
));
return;
}
for element in enum_define.elements.iter() {
if &element.literal.span == define_span {
*result = Some((
element.span.clone(),
element.documents.lines.iter().cloned().collect(),
));
return;
}
}
}
},
Define::Interface(_) => {}
}
}
}
}
mod tyml_json {
use std::ops::Range;
use tyml_core::tyml_parser::ast::{AST, Define, Defines, JsonValue, TypeDefine};
use crate::language_server::{ToInclusive, create_hover_code_block};
pub fn is_position_on_json_value_literal(value: &JsonValue, position: usize) -> bool {
match value {
JsonValue::Value(value_literal) => {
if value_literal.span().to_inclusive().contains(&position) {
return true;
}
}
JsonValue::Array(json_array) => {
for element in json_array.elements.iter() {
if is_position_on_json_value_literal(element, position) {
return true;
}
}
}
JsonValue::Object(json_object) => {
for element in json_object.elements.iter() {
if is_position_on_json_value_literal(&element.value, position) {
return true;
}
}
}
}
false
}
pub fn take_documents<'input>(
define_span: Range<usize>,
ast: &Defines<'input, '_>,
code: &str,
) -> String {
let mut result = None;
take_documents_span_for_defines(&define_span, ast, &mut result);
match result {
Some((span, documents)) => {
format!(
"{}\n{}",
create_hover_code_block(&code[span]),
documents
.into_iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
.join(""),
)
}
None => String::new(),
}
}
fn take_documents_span_for_defines<'input>(
define_span: &Range<usize>,
ast: &Defines<'input, '_>,
result: &mut Option<(Range<usize>, Vec<&'input str>)>,
) {
for define in ast.defines.iter() {
match define {
Define::Element(element_define) => {
if &element_define.node.span() == define_span {
*result = Some((
element_define.span.clone(),
element_define.documents.lines.iter().cloned().collect(),
));
return;
}
if let Some(inline_type) = &element_define.inline_type {
take_documents_span_for_defines(define_span, inline_type.defines, result);
}
}
Define::Type(type_define) => match type_define {
TypeDefine::Struct(struct_define) => {
take_documents_span_for_defines(define_span, struct_define.defines, result);
}
TypeDefine::Enum(_) => {}
},
Define::Interface(_) => {}
}
}
}
}
mod tyml_semantic_tokens {
use std::{collections::BTreeMap, ops::Range};
use tower_lsp::lsp_types::SemanticTokenType;
use tyml_core::tyml_parser::ast::{
AST, ArgumentName, AttributeAnd, AttributeOr, Define, Defines, ElementType, FromTo,
Interface, JsonValue, OrType, Properties, TypeAttribute, TypeDefine, ValueLiteral,
either::Either,
};
pub fn collect_tokens_for_defines(
ast: &Defines,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
for define in ast.defines.iter() {
match define {
Define::Element(element_define) => {
let span = element_define.documents.span.clone();
tokens.insert(span.start, (SemanticTokenType::COMMENT, span));
let span = element_define.node.span();
tokens.insert(span.start, (SemanticTokenType::PROPERTY, span));
if let Some(ty) = &element_define.ty {
collect_tokens_for_element_type(ty, tokens);
}
if let Some(inline_type) = &element_define.inline_type {
collect_tokens_for_defines(&inline_type.defines, tokens);
}
if let Some(default_value) = &element_define.default {
collect_tokens_for_value_literal(&default_value.value, tokens);
}
}
Define::Type(type_define) => {
collect_tokens_for_type_define(type_define, tokens);
}
Define::Interface(interface) => {
collect_tokens_for_interface(interface, tokens);
}
}
}
}
fn collect_tokens_for_value_literal(
ast: &ValueLiteral,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
let token = match ast {
ValueLiteral::String(literal) => (SemanticTokenType::STRING, literal.span.clone()),
ValueLiteral::Float(literal) => (SemanticTokenType::NUMBER, literal.span()),
ValueLiteral::Binary(literal) => (SemanticTokenType::NUMBER, literal.span()),
ValueLiteral::Bool(literal) => (SemanticTokenType::MACRO, literal.span.clone()),
ValueLiteral::Null(literal) => (SemanticTokenType::KEYWORD, literal.span.clone()),
};
tokens.insert(token.1.start, token);
}
fn collect_tokens_for_interface(
ast: &Interface,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
let span = ast.documents.span.clone();
tokens.insert(span.start, (SemanticTokenType::COMMENT, span));
collect_tokens_for_properties(&ast.properties, tokens);
let span = ast.keyword_span.clone();
tokens.insert(span.start, (SemanticTokenType::KEYWORD, span));
let span = ast.name.span.clone();
tokens.insert(span.start, (SemanticTokenType::TYPE, span));
for function in ast.functions.iter() {
let span = function.documents.span.clone();
tokens.insert(span.start, (SemanticTokenType::COMMENT, span));
collect_tokens_for_properties(&function.properties, tokens);
if let Some(span) = &function.authed {
tokens.insert(span.start, (SemanticTokenType::MACRO, span.clone()));
}
if let Some(span) = &function.cookie {
tokens.insert(span.start, (SemanticTokenType::MACRO, span.clone()));
}
let span = function.keyword_span.clone();
tokens.insert(span.start, (SemanticTokenType::MACRO, span));
let span = function.name.span.clone();
tokens.insert(span.start, (SemanticTokenType::FUNCTION, span));
for argument in function.arguments.iter() {
match &argument.name {
ArgumentName::Name(name) => {
let span = name.span();
tokens.insert(span.start, (SemanticTokenType::VARIABLE, span));
}
ArgumentName::AtBody(name) => {
let span = name.span();
tokens.insert(span.start, (SemanticTokenType::MACRO, span));
}
ArgumentName::AtClaim(name) => {
let span = name.span();
tokens.insert(span.start, (SemanticTokenType::MACRO, span));
}
}
collect_tokens_for_or_type(&argument.ty.type_info, tokens);
if let Some(default_value) = &argument.default_value {
collect_tokens_for_json_value(default_value, tokens);
}
}
if let Some(return_type) = &function.return_type {
collect_tokens_for_or_type(&return_type.type_info, tokens);
}
if let Some(throws) = &function.throws {
let span = throws.keyword.clone();
tokens.insert(span.start, (SemanticTokenType::KEYWORD, span));
collect_tokens_for_or_type(&throws.ty, tokens);
}
if let Some(return_block) = &function.return_block {
let span = return_block.return_expression.keyword_span.clone();
tokens.insert(span.start, (SemanticTokenType::KEYWORD, span));
collect_tokens_for_json_value(&return_block.return_expression.value, tokens);
}
}
}
fn collect_tokens_for_properties(
ast: &Properties,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
for property in ast.elements.iter() {
let span = property.name.span.clone();
tokens.insert(span.start, (SemanticTokenType::VARIABLE, span));
for value in property.values.iter() {
collect_tokens_for_value_literal(value, tokens);
}
}
}
fn collect_tokens_for_json_value(
ast: &JsonValue,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
match ast {
JsonValue::Value(value_literal) => {
collect_tokens_for_value_literal(value_literal, tokens)
}
JsonValue::Array(json_array) => {
for element in json_array.elements.iter() {
collect_tokens_for_json_value(element, tokens);
}
}
JsonValue::Object(json_object) => {
for element in json_object.elements.iter() {
let span = element.name.span.clone();
tokens.insert(span.start, (SemanticTokenType::VARIABLE, span));
collect_tokens_for_json_value(&element.value, tokens);
}
}
}
}
fn collect_tokens_for_element_type(
ast: &ElementType,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
collect_tokens_for_or_type(&ast.type_info, tokens);
}
fn collect_tokens_for_or_type(
ast: &OrType,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
for ty in ast.or_types.iter() {
match &ty.ty {
Either::Left(named_type) => {
let span = named_type.name.span.clone();
tokens.insert(span.start, (SemanticTokenType::TYPE, span));
}
Either::Right(array_type) => {
collect_tokens_for_or_type(&array_type.base, tokens);
}
}
if let Some(attribute) = &ty.attribute {
collect_tokens_for_attribute_or(attribute, tokens);
}
}
}
fn collect_tokens_for_attribute_or(
ast: &AttributeOr,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
for attribute in ast.attributes.iter() {
collect_tokens_for_attribute_and(attribute, tokens);
}
for or_span in ast.or_spans.iter() {
tokens.insert(or_span.start, (SemanticTokenType::KEYWORD, or_span.clone()));
}
}
fn collect_tokens_for_attribute_and(
ast: &AttributeAnd,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
for attribute in ast.attributes.iter() {
collect_tokens_for_type_attribute(attribute, tokens);
}
for and_span in ast.and_spans.iter() {
tokens.insert(
and_span.start,
(SemanticTokenType::KEYWORD, and_span.clone()),
);
}
}
fn collect_tokens_for_type_attribute(
ast: &TypeAttribute,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
match ast {
TypeAttribute::NumericAttribute(attribute) => {
tokens.insert(
attribute.kind.span.start,
(SemanticTokenType::KEYWORD, attribute.kind.span.clone()),
);
let (from, to) = match &attribute.from_to {
FromTo::FromToExclusive { from, to } => (Some(from.span()), Some(to.span())),
FromTo::FromToInclusive { from, to } => (Some(from.span()), Some(to.span())),
FromTo::From { from } => (Some(from.span()), None),
FromTo::ToExclusive { to } => (None, Some(to.span())),
FromTo::ToInclusive { to } => (None, Some(to.span())),
};
if let Some(from) = from {
tokens.insert(from.start, (SemanticTokenType::NUMBER, from));
}
if let Some(to) = to {
tokens.insert(to.start, (SemanticTokenType::NUMBER, to));
}
}
TypeAttribute::RegexAttribute(attribute) => {
tokens.insert(
attribute.regex_keyword_span.start,
(
SemanticTokenType::KEYWORD,
attribute.regex_keyword_span.clone(),
),
);
tokens.insert(
attribute.regex_literal.span.start,
(
SemanticTokenType::STRING,
attribute.regex_literal.span.clone(),
),
);
}
TypeAttribute::AttributeTree(attribute) => {
collect_tokens_for_attribute_or(attribute, tokens)
}
}
}
fn collect_tokens_for_type_define(
ast: &TypeDefine,
tokens: &mut BTreeMap<usize, (SemanticTokenType, Range<usize>)>,
) {
match ast {
TypeDefine::Struct(struct_define) => {
let span = struct_define.documents.span.clone();
tokens.insert(span.start, (SemanticTokenType::COMMENT, span));
let span = struct_define.name.span.clone();
tokens.insert(span.start, (SemanticTokenType::TYPE, span));
let span = struct_define.keyword_span.clone();
tokens.insert(span.start, (SemanticTokenType::KEYWORD, span));
collect_tokens_for_defines(&struct_define.defines, tokens);
}
TypeDefine::Enum(enum_define) => {
let span = enum_define.documents.span.clone();
tokens.insert(span.start, (SemanticTokenType::COMMENT, span));
let span = enum_define.name.span.clone();
tokens.insert(span.start, (SemanticTokenType::TYPE, span));
let span = enum_define.keyword_span.clone();
tokens.insert(span.start, (SemanticTokenType::KEYWORD, span));
for element in enum_define.elements.iter() {
let span = element.documents.span.clone();
tokens.insert(span.start, (SemanticTokenType::COMMENT, span));
let span = element.literal.span.clone();
tokens.insert(span.start, (SemanticTokenType::STRING, span));
}
}
}
}
}
mod tyml_documents_from_ast {
use tyml_core::tyml_parser::ast::{AST, Define, Defines, TypeDefine};
use super::ToInclusive;
pub fn get_documents_from_defines<'input>(
defines: &Defines<'input, '_>,
position: usize,
result: &mut Option<Vec<&'input str>>,
) {
for define in defines.defines.iter() {
match define {
Define::Element(element_define) => {
if element_define
.node
.span()
.to_inclusive()
.contains(&position)
{
*result = Some(element_define.documents.lines.iter().cloned().collect());
return;
}
if let Some(inline_type) = &element_define.inline_type {
get_documents_from_defines(&inline_type.defines, position, result);
}
}
Define::Type(type_define) => match type_define {
TypeDefine::Struct(struct_define) => {
get_documents_from_defines(&struct_define.defines, position, result);
}
TypeDefine::Enum(enum_define) => {
for element in enum_define.elements.iter() {
if element.literal.span.to_inclusive().contains(&position) {
*result = Some(element.documents.lines.iter().cloned().collect());
return;
}
}
}
},
Define::Interface(interface) => {
if interface.name.span.to_inclusive().contains(&position) {
*result = Some(interface.documents.lines.iter().cloned().collect());
return;
}
for function in interface.functions.iter() {
if function.name.span.to_inclusive().contains(&position) {
*result = Some(function.documents.lines.iter().cloned().collect());
return;
}
}
}
}
}
}
}
#[extension_fn(SourceCodeSpan)]
fn to_lsp_span(&self, code: &str) -> LSPRange {
match self {
SourceCodeSpan::UTF8Byte(range) => LSPRange::new(
to_line_column(code, range.start),
to_line_column(code, range.end),
),
SourceCodeSpan::UnicodeCharacter(range) => LSPRange::new(
to_line_column(code, character_to_byte(code, range.start)),
to_line_column(code, character_to_byte(code, range.end)),
),
}
}
#[extension_fn(SourceCodeSpan)]
fn to_lsp_semantic_token(&self, code: &str) -> impl Iterator<Item = (Position, usize)> {
let range = self.to_byte_span(code);
LINE_REGEX
.find_iter(&code[range.clone()])
.map(move |matched| {
(
to_line_column(code, range.start + matched.start()),
matched.as_str().chars().count(),
)
})
}
static LINE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[^\n\r]*(\n|\r\n|$)").unwrap());
fn to_line_column(code: &str, byte: usize) -> Position {
let mut last_line_index = 0;
let mut last_line = "";
for (line, line_matched) in LINE_REGEX.find_iter(code).enumerate() {
if (line_matched.start()..line_matched.end()).contains(&byte) {
let column = code[line_matched.start()..byte].chars().count();
return Position::new(line as _, column as _);
}
last_line_index = line;
last_line = &code[line_matched.start()..line_matched.end()];
}
Position::new(
last_line_index as _,
last_line
.chars()
.filter(|&char| char != '\n' && char != '\r')
.count() as _,
)
}
pub trait ToBytePosition {
fn to_byte_position(&self, code: &str) -> usize;
}
impl ToBytePosition for Position {
fn to_byte_position(&self, code: &str) -> usize {
for (line, line_matched) in LINE_REGEX.find_iter(code).enumerate() {
if line as u32 == self.line {
let column_byte = line_matched
.as_str()
.char_indices()
.nth(self.character as _)
.map(|(position, _)| position)
.unwrap_or(line_matched.len());
return line_matched.start() + column_byte;
}
}
code.len()
}
}
fn character_to_byte(code: &str, character: usize) -> usize {
code.char_indices()
.nth(character)
.map(|(position, _)| position)
.unwrap_or(code.len())
}
#[derive(Debug, Clone)]
pub struct Completion {
pub kind: CompletionKind,
pub documents: String,
pub span: Range<usize>,
pub name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompletionKind {
EnumValue,
SectionName,
Section,
}
impl CompletionKind {
pub fn to_completion_item_kind(&self) -> CompletionItemKind {
match self {
CompletionKind::EnumValue => CompletionItemKind::ENUM_MEMBER,
CompletionKind::SectionName => CompletionItemKind::FIELD,
CompletionKind::Section => CompletionItemKind::STRUCT,
}
}
}
#[extension_fn(Range<usize>)]
fn to_inclusive(&self) -> RangeInclusive<usize> {
self.start..=self.end
}
#[extension_fn(<'a> ValueTypeChecker<'a, 'a, 'a, 'a, 'a>)]
fn goto_define_and_documents(
&self,
position: usize,
code: &str,
) -> Vec<(Range<usize>, &'a [&'a str])> {
let mut result = Vec::new();
if let Some(merged_value_tree) = &self.merged_value_tree {
if let MergedValueTree::Section {
elements,
name_spans: _,
define_spans: _,
} = merged_value_tree
{
if let Some(root_tree) = elements.get("root") {
goto_define_and_documents_recursive(
&self.type_tree,
&self.named_type_map,
root_tree,
position,
code,
&mut result,
true,
);
}
}
}
result
}
fn goto_define_and_documents_recursive<'a>(
type_tree: &'a TypeTree<'a>,
named_type_map: &'a NamedTypeMap<'a>,
merged_value_tree: &MergedValueTree,
position: usize,
code: &str,
result: &mut Vec<(Range<usize>, &'a [&'a str])>,
is_root: bool,
) {
match type_tree {
TypeTree::Node {
node,
any_node,
node_key_span: _,
any_node_key_span: _,
documents,
span,
} => {
let MergedValueTree::Section {
elements,
name_spans,
define_spans,
} = merged_value_tree
else {
return;
};
if !is_root {
if name_spans
.iter()
.any(|span| span.to_byte_span(code).to_inclusive().contains(&position))
{
result.push((span.clone(), documents.as_slice()));
return;
}
}
if !define_spans
.iter()
.any(|span| span.to_byte_span(code).to_inclusive().contains(&position))
{
return;
}
for (element_name, element) in elements.iter() {
let Some(type_tree) = node.get(element_name.as_ref()).or(any_node.as_deref())
else {
continue;
};
goto_define_and_documents_recursive(
type_tree,
named_type_map,
element,
position,
code,
result,
false,
);
}
}
TypeTree::Leaf {
ty,
documents,
span: define_span,
} => {
match merged_value_tree {
MergedValueTree::Section {
elements,
name_spans,
define_spans,
} => {
if !is_root {
if name_spans
.iter()
.any(|span| span.to_byte_span(code).to_inclusive().contains(&position))
{
result.push((define_span.clone(), documents.as_slice()));
return;
}
}
if !define_spans
.iter()
.any(|span| span.to_byte_span(code).to_inclusive().contains(&position))
{
return;
}
let mut trees = Vec::new();
collect_type_tree(ty, named_type_map, &mut trees);
for tree in trees {
let TypeTree::Node {
node,
any_node,
node_key_span: _,
any_node_key_span: _,
documents: _,
span: _,
} = tree
else {
continue;
};
for (element_name, element) in elements.iter() {
let Some(type_tree) =
node.get(element_name.as_ref()).or(any_node.as_deref())
else {
continue;
};
goto_define_and_documents_recursive(
type_tree,
named_type_map,
element,
position,
code,
result,
false,
);
}
}
}
MergedValueTree::Array {
elements,
key_span,
span: _,
} => {
if key_span
.to_byte_span(code)
.to_inclusive()
.contains(&position)
{
result.push((define_span.clone(), documents.as_slice()));
return;
}
for element in elements.iter() {
if let MergedValueTree::Section {
elements: _,
name_spans,
define_spans: _,
} = element
{
if name_spans.iter().any(|span| {
span.to_byte_span(code).to_inclusive().contains(&position)
}) {
result.push((define_span.clone(), documents));
return;
}
}
}
for element in elements.iter() {
if let MergedValueTree::Value {
value,
key_span: _,
span,
} = element
{
if !span.to_byte_span(code).to_inclusive().contains(&position) {
continue;
}
let ValidateValue::String(value) = value else {
continue;
};
return find_enum_define_and_documents(
ty,
named_type_map,
value.as_ref(),
result,
);
}
}
let TypeTree::Leaf {
ty,
documents: _,
span: _,
} = type_tree
else {
return;
};
let mut trees = Vec::new();
collect_type_tree(&ty, named_type_map, &mut trees);
for tree in trees {
for element in elements.iter() {
goto_define_and_documents_recursive(
tree,
named_type_map,
element,
position,
code,
result,
false,
);
}
}
}
MergedValueTree::Value {
value,
key_span,
span,
} => {
if key_span
.to_byte_span(code)
.to_inclusive()
.contains(&position)
{
result.push((define_span.clone(), documents.as_slice()));
return;
}
if !(key_span.to_byte_span(code).start..span.to_byte_span(code).end)
.to_inclusive()
.contains(&position)
{
return;
}
let ValidateValue::String(value) = value else {
return;
};
find_enum_define_and_documents(ty, named_type_map, value.as_ref(), result);
}
}
}
}
}
fn collect_type_tree<'a>(
ty: &Type,
named_type_map: &'a NamedTypeMap<'a>,
trees: &mut Vec<&'a TypeTree<'a>>,
) {
match ty {
Type::Named(name_id) => {
if let NamedTypeTree::Struct { tree } = named_type_map.get_type(*name_id).unwrap() {
trees.push(tree);
}
}
Type::Or(or_types) => {
for ty in or_types.iter() {
collect_type_tree(ty, named_type_map, trees);
}
}
Type::Array(base_type) => collect_type_tree(&base_type, named_type_map, trees),
Type::Optional(ty) => collect_type_tree(&ty, named_type_map, trees),
_ => {}
}
}
fn find_enum_define_and_documents<'a>(
ty: &Type,
named_type_map: &'a NamedTypeMap<'a>,
value: &str,
result: &mut Vec<(Range<usize>, &'a [&'a str])>,
) {
match ty {
Type::Named(name_id) => {
if let NamedTypeTree::Enum {
elements,
documents: _,
} = named_type_map.get_type(*name_id).unwrap()
{
for (element, documents) in elements.iter() {
if element.value == value {
result.push((element.span.clone(), documents.as_slice()));
}
}
}
}
Type::Or(types) => {
for ty in types.iter() {
find_enum_define_and_documents(ty, named_type_map, value, result);
}
}
Type::Array(base) => find_enum_define_and_documents(base, named_type_map, value, result),
Type::Optional(base) => find_enum_define_and_documents(base, named_type_map, value, result),
_ => return,
}
}