use crate::server::configuration::{BackendConfiguration, RequestMethod};
use super::lsp::{
TextDocumentContentChangeEvent,
errors::{ErrorCode, LSPError},
textdocument::TextDocumentItem,
};
use curies::Converter;
use ll_sparql_parser::{SyntaxNode, parse};
use std::cell::RefCell;
use std::collections::HashMap;
#[derive(Debug, PartialEq)]
pub enum ServerStatus {
Initializing,
Running,
ShuttingDown,
}
#[derive(Debug)]
pub enum ClientType {
Monaco,
Neovim,
}
#[derive(Debug)]
pub struct CachedParseTree {
document_uri: String,
version: u32,
tree: SyntaxNode,
parse_time_ms: f64,
}
pub struct ServerState {
pub status: ServerStatus,
pub client_type: Option<ClientType>,
documents: HashMap<String, TextDocumentItem>,
backends: HashMap<String, BackendConfiguration>,
uri_converter: HashMap<String, Converter>,
default_backend: Option<String>,
parse_tree_cache: RefCell<Option<CachedParseTree>>,
request_id_counter: u32,
running_sparql_requests: HashMap<String, Box<dyn Fn()>>,
pub label_memory: HashMap<String, String>,
}
impl ServerState {
pub fn new() -> Self {
ServerState {
status: ServerStatus::Initializing,
client_type: None,
documents: HashMap::new(),
backends: HashMap::new(),
uri_converter: HashMap::new(),
default_backend: None,
parse_tree_cache: RefCell::new(None),
request_id_counter: 0,
running_sparql_requests: HashMap::new(),
label_memory: HashMap::new(),
}
}
pub fn bump_request_id(&mut self) -> u32 {
let current_id = self.request_id_counter;
self.request_id_counter += 1;
current_id
}
pub fn get_backend_name_by_url(&self, url: &str) -> Option<String> {
self.backends
.iter()
.find_map(|(key, backend)| (backend.url == url).then(|| key.clone()))
}
pub fn set_default_backend(&mut self, name: String) {
assert!(self.backends.contains_key(&name));
self.default_backend = Some(name)
}
pub(super) fn get_default_backend(&self) -> Option<&BackendConfiguration> {
self.backends.get(self.default_backend.as_ref()?)
}
pub fn add_backend(&mut self, backend: BackendConfiguration) {
self.backends.insert(backend.name.clone(), backend);
}
pub fn get_backend_request_method(&self, backend_name: &str) -> RequestMethod {
self.backends
.get(backend_name)
.and_then(|backend| backend.request_method.clone())
.unwrap_or(RequestMethod::GET)
}
pub fn load_prefix_map(
&mut self,
backend: String,
map: &HashMap<String, String>,
) -> Result<(), LSPError> {
let mut converter = Converter::default();
for (prefix, uri_prefix) in map {
converter.add_prefix(prefix, uri_prefix).map_err(|err| {
tracing::error!("{}", err);
LSPError::new(
ErrorCode::InvalidParams,
&format!("Could not load prefix map:\n\"{}\"", err),
)
})?;
}
self.uri_converter.insert(backend, converter);
Ok(())
}
pub fn get_backend(&self, backend_name: &str) -> Option<&BackendConfiguration> {
self.backends.get(backend_name)
}
pub(super) fn add_document(&mut self, text_document: TextDocumentItem) {
self.documents
.insert(text_document.uri.clone(), text_document);
}
pub(super) fn change_document(
&mut self,
uri: &String,
content_changes: Vec<TextDocumentContentChangeEvent>,
) -> Result<(), LSPError> {
let document = self.documents.get_mut(uri).ok_or(LSPError::new(
ErrorCode::InvalidParams,
&format!("Could not change unknown document {}", uri),
))?;
document.apply_content_changes(content_changes);
document.increase_version();
Ok(())
}
pub(super) fn get_document(&self, uri: &str) -> Result<&TextDocumentItem, LSPError> {
self.documents.get(uri).ok_or(LSPError::new(
ErrorCode::InvalidRequest,
&format!("Requested document \"{}\"could not be found", uri),
))
}
#[tracing::instrument(skip(self), fields(cache_hit, parse_time_ms))]
pub(super) fn get_cached_parse_tree(&self, uri: &str) -> Result<TimedParseResult, LSPError> {
let document = self.documents.get(uri).ok_or(LSPError::new(
ErrorCode::InvalidRequest,
&format!("Requested document \"{}\"could not be found", uri),
))?;
if let Some(cached_parse_tree) = self.parse_tree_cache.borrow().as_ref()
&& uri == cached_parse_tree.document_uri
&& cached_parse_tree.version == document.version()
{
tracing::Span::current().record("cache_hit", true);
return Ok(TimedParseResult {
tree: cached_parse_tree.tree.clone(),
parse_time_ms: cached_parse_tree.parse_time_ms,
});
}
tracing::Span::current().record("cache_hit", false);
let start = get_timestamp_ms();
let (root, _) = parse(&document.text);
let parse_time_ms = get_timestamp_ms() - start;
tracing::Span::current().record("parse_time_ms", parse_time_ms);
*self.parse_tree_cache.borrow_mut() = Some(CachedParseTree {
document_uri: uri.to_string(),
version: document.version(),
tree: root.clone(),
parse_time_ms,
});
Ok(TimedParseResult {
tree: root,
parse_time_ms,
})
}
pub(crate) fn get_default_converter(&self) -> Option<&Converter> {
self.uri_converter.get(self.default_backend.as_ref()?)
}
pub(crate) fn get_converter(&self, backend_name: &str) -> Option<&Converter> {
self.uri_converter.get(backend_name)
}
pub(crate) fn get_all_backends(&self) -> Vec<&BackendConfiguration> {
self.backends.values().collect()
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn add_running_request(&mut self, id: String, cancel_fn: Box<dyn Fn()>) {
self.running_sparql_requests.insert(id, cancel_fn);
}
#[allow(clippy::borrowed_box)]
pub(crate) fn get_running_request(&mut self, id: &str) -> Option<&Box<dyn Fn()>> {
self.running_sparql_requests.get(id)
}
}
pub struct TimedParseResult {
pub tree: SyntaxNode,
pub parse_time_ms: f64,
}
#[cfg(not(target_arch = "wasm32"))]
fn get_timestamp_ms() -> f64 {
use std::time::SystemTime;
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system time should be after epoch")
.as_secs_f64()
* 1000.0
}
#[cfg(target_arch = "wasm32")]
fn get_timestamp_ms() -> f64 {
use wasm_bindgen::JsCast;
use web_sys::WorkerGlobalScope;
let worker_global: WorkerGlobalScope = js_sys::global().unchecked_into();
worker_global
.performance()
.expect("performance should be available")
.now()
}