pub mod language_server;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::atomic::Ordering;
use std::sync::{Arc, LazyLock, RwLock};
use either::Either;
use language_server::{GeneratedLanguageServer, TymlLanguageServer};
use tokio::runtime::Runtime;
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer};
use tyml_core::tyml_diagnostic::message::Lang;
use tyml_core::tyml_formatter::GeneralFormatter;
pub static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| Runtime::new().unwrap());
#[derive(Debug)]
pub struct LSPBackend {
pub client: Client,
pub generated_language_servers: RwLock<HashMap<Url, Arc<GeneratedLanguageServer>>>,
pub tyml_language_servers: RwLock<HashMap<Url, Arc<TymlLanguageServer>>>,
}
impl LSPBackend {
fn get_server(
&self,
url: Url,
) -> Either<Arc<GeneratedLanguageServer>, Arc<TymlLanguageServer>> {
if let Some("tyml") = url.as_str().split(".").last() {
Either::Right(
self.tyml_language_servers
.write()
.unwrap()
.entry(url.clone())
.or_insert_with(|| Arc::new(TymlLanguageServer::new(url, Lang::system())))
.clone(),
)
} else {
Either::Left(
self.generated_language_servers
.write()
.unwrap()
.entry(url.clone())
.or_insert_with(|| Arc::new(GeneratedLanguageServer::new(url, Lang::system())))
.clone(),
)
}
}
}
static TOKEN_TYPES: &[SemanticTokenType] = &[
SemanticTokenType::KEYWORD,
SemanticTokenType::FUNCTION,
SemanticTokenType::VARIABLE,
SemanticTokenType::PROPERTY,
SemanticTokenType::STRING,
SemanticTokenType::NUMBER,
SemanticTokenType::STRUCT,
SemanticTokenType::TYPE,
SemanticTokenType::COMMENT,
SemanticTokenType::MACRO,
];
#[tower_lsp::async_trait]
impl LanguageServer for LSPBackend {
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
Ok(InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::FULL,
)),
semantic_tokens_provider: Some(
SemanticTokensServerCapabilities::SemanticTokensOptions(
SemanticTokensOptions {
legend: SemanticTokensLegend {
token_types: TOKEN_TYPES.to_vec(),
token_modifiers: vec![],
},
range: Some(false),
full: Some(SemanticTokensFullOptions::Bool(true)),
..Default::default()
},
),
),
completion_provider: Some(CompletionOptions {
trigger_characters: Some(vec![
".".to_string(),
"[".to_string(),
"@".to_string(),
]),
resolve_provider: Some(true),
work_done_progress_options: Default::default(),
all_commit_characters: None,
..Default::default()
}),
definition_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),
rename_provider: Some(OneOf::Left(true)),
hover_provider: Some(HoverProviderCapability::Simple(true)),
document_formatting_provider: Some(OneOf::Left(true)),
code_lens_provider: Some(CodeLensOptions {
resolve_provider: Some(false),
}),
..Default::default()
},
..Default::default()
})
}
async fn initialized(&self, _: InitializedParams) {
self.client
.log_message(MessageType::INFO, "TYML LSP server initialized!")
.await;
*CLIENT_COPY.write().unwrap() = Some(self.client.clone());
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
let server = self.get_server(params.text_document.uri.clone());
self.client
.log_message(MessageType::LOG, "Starting analyze...")
.await;
match server {
Either::Left(server) => {
server
.on_change(
params.text_document.uri,
Arc::new(params.text_document.text),
)
.await;
server.publish_diagnostics(&self.client).await;
}
Either::Right(server) => {
server.on_change(
params.text_document.uri.to_string(),
params.text_document.text,
);
server.publish_diagnostics(&self.client).await;
}
}
self.client.log_message(MessageType::LOG, "Analyzed!").await;
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
if let Some(change) = params.content_changes.into_iter().last() {
let server = self.get_server(params.text_document.uri.clone());
self.client
.log_message(MessageType::LOG, "Starting analyze...")
.await;
match server {
Either::Left(server) => {
server
.on_change(params.text_document.uri, Arc::new(change.text))
.await;
server.publish_diagnostics(&self.client).await;
}
Either::Right(server) => {
server.on_change(params.text_document.uri.to_string(), change.text);
server.publish_diagnostics(&self.client).await;
}
}
self.client.log_message(MessageType::LOG, "Analyzed!").await;
}
}
async fn semantic_tokens_full(
&self,
params: SemanticTokensParams,
) -> Result<Option<SemanticTokensResult>> {
let server = self.get_server(params.text_document.uri.clone());
let semantic_tokens = match server {
Either::Left(server) => {
while server.analyzing_flag.load(Ordering::Acquire) {
tokio::task::yield_now().await;
}
server.tokens.lock().unwrap().clone()
}
Either::Right(server) => {
while server.analyzing_flag.load(Ordering::Acquire) {
std::hint::spin_loop();
}
server.tokens.lock().unwrap().clone()
}
};
if semantic_tokens.is_empty() {
return Ok(None);
}
let mut tokens = Vec::new();
let mut prev_line = 0;
let mut prev_column = 0;
for (kind, (start, length)) in semantic_tokens.iter() {
if start.line != prev_line {
prev_column = 0;
}
tokens.push(SemanticToken {
delta_line: start.line - prev_line,
delta_start: start.character - prev_column,
length: *length as _,
token_type: TOKEN_TYPES
.iter()
.position(|token_type| token_type == kind)
.unwrap_or_default() as _,
token_modifiers_bitset: 0,
});
prev_line = start.line;
prev_column = start.character;
}
Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
result_id: None,
data: tokens,
})))
}
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
let server = self.get_server(params.text_document_position.text_document.uri);
match server {
Either::Left(server) => Ok(server
.provide_completion(params.text_document_position.position)
.map(|completions| CompletionResponse::Array(completions))),
Either::Right(server) => Ok(server
.provide_completion(params.text_document_position.position)
.map(|completions| CompletionResponse::Array(completions))),
}
}
async fn completion_resolve(&self, params: CompletionItem) -> Result<CompletionItem> {
Ok(params)
}
async fn goto_definition(
&self,
params: GotoDefinitionParams,
) -> Result<Option<GotoDefinitionResponse>> {
let server = self.get_server(params.text_document_position_params.text_document.uri);
match server {
Either::Left(server) => {
let (defined_url, defines) =
server.goto_define(params.text_document_position_params.position);
let defined_url = Url::parse(format!("file://{}", defined_url).as_str()).unwrap();
match defines.len() {
0 => Ok(None),
1 => Ok(Some(GotoDefinitionResponse::Scalar(Location {
uri: defined_url,
range: defines[0],
}))),
_ => Ok(Some(GotoDefinitionResponse::Array(
defines
.into_iter()
.map(|range| Location {
uri: defined_url.clone(),
range,
})
.collect(),
))),
}
}
Either::Right(server) => Ok(server
.goto_define(params.text_document_position_params.position)
.map(|range| {
GotoDefinitionResponse::Scalar(Location {
uri: server.url.clone(),
range,
})
})),
}
}
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
let server = self.get_server(params.text_document_position.text_document.uri);
match server {
Either::Left(_) => Ok(None),
Either::Right(server) => {
let users = server.get_references(params.text_document_position.position);
if users.is_empty() {
Ok(None)
} else {
Ok(Some(
users
.into_iter()
.map(|range| Location {
uri: server.url.clone(),
range,
})
.collect(),
))
}
}
}
}
async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
let server = self.get_server(params.text_document_position.text_document.uri);
match server {
Either::Left(_) => Ok(None),
Either::Right(server) => {
let references = server.get_references(params.text_document_position.position);
let mut changes = HashMap::new();
changes.insert(
server.url.clone(),
references
.into_iter()
.map(|range| TextEdit {
range,
new_text: params.new_name.clone(),
})
.collect(),
);
Ok(Some(WorkspaceEdit {
changes: Some(changes),
document_changes: None,
change_annotations: None,
}))
}
}
}
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
let server = self.get_server(params.text_document_position_params.text_document.uri);
let hover = match server {
Either::Left(server) => server.hover(params.text_document_position_params.position),
Either::Right(server) => server.hover(params.text_document_position_params.position),
};
Ok(hover.map(|hover| Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: hover,
}),
range: None,
}))
}
async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
self.client
.log_message(MessageType::LOG, "Starting formatting...")
.await;
let server = self.get_server(params.text_document.uri);
let code = match server {
Either::Left(server) => {
let Some((tyml, _)) = server.tyml.lock().unwrap().clone() else {
return Ok(None);
};
if tyml.has_ml_parse_error() || tyml.has_ml_validate_error() {
return Ok(None);
}
let tokens_lock = server.formatter_tokens.lock().unwrap();
let Some(tokens) = tokens_lock.as_ref() else {
return Ok(None);
};
let mut formatter = GeneralFormatter::new(tokens.tokens().iter().cloned(), 25);
formatter.format();
Some(formatter.generate_code())
}
Either::Right(server) => server.format(),
};
self.client
.log_message(MessageType::LOG, "Formatted!")
.await;
Ok(code.map(|code| {
vec![TextEdit {
range: Range::new(Position::new(0, 0), Position::new(u32::MAX, u32::MAX)),
new_text: code,
}]
}))
}
async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
let server = self.get_server(params.text_document.uri.clone());
match server {
Either::Left(_) => Ok(None),
Either::Right(server) => Ok(Some(
server.code_lens(url_to_native_path_string(¶ms.text_document.uri).unwrap()),
)),
}
}
}
fn url_to_native_path_string(uri: &Url) -> std::result::Result<String, &'static str> {
if uri.scheme() != "file" {
return Err("not a file URL");
}
match uri.to_file_path() {
Ok(path) => Ok(path.to_string_lossy().into_owned()),
Err(_) => Ok(uri.to_string().trim_start_matches("file://").to_string()),
}
}
static CLIENT_COPY: LazyLock<RwLock<Option<Client>>> = LazyLock::new(|| RwLock::new(None));
#[allow(unused)]
pub(crate) fn debug_log<T: ToString + Send + 'static>(log: T) {
RUNTIME.spawn(async move {
let client = {
CLIENT_COPY
.read()
.as_ref()
.unwrap()
.deref()
.clone()
.unwrap()
};
client.log_message(MessageType::LOG, log.to_string()).await
});
}