#![doc = include_str!("../README.md")]
mod binder;
mod checker;
mod config;
mod data_set;
mod features;
mod helpers;
mod idx;
mod refactorings;
mod syntax_tree;
mod types_analyzer;
mod uri;
use self::features::SemanticTokenKind;
pub use crate::config::*;
use crate::{
binder::SymbolTables,
idx::Idents,
syntax_tree::{SyntaxTree, SyntaxTreeCtx},
types_analyzer::TypesAnalyzer,
uri::{Uris, UrisCtx},
};
use indexmap::{IndexMap, IndexSet};
use lsp_types::{
notification::DidChangeConfiguration, CallHierarchyServerCapability, CodeActionKind,
CodeActionOptions, CodeActionProviderCapability, CompletionOptions, DeclarationCapability,
DiagnosticOptions, DiagnosticServerCapabilities, FoldingRangeProviderCapability,
HoverProviderCapability, InitializeParams, InitializeResult, OneOf, Registration,
RegistrationParams, RenameOptions, SelectionRangeProviderCapability, SemanticTokenType,
SemanticTokensClientCapabilities, SemanticTokensFullOptions, SemanticTokensLegend,
SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities, ServerInfo,
SignatureHelpOptions, TextDocumentClientCapabilities, TextDocumentSyncCapability,
TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions,
TypeDefinitionProviderCapability, Uri,
};
use rustc_hash::{FxBuildHasher, FxHashMap};
use salsa::{Database, ParallelDatabase, Snapshot};
#[salsa::database(Uris, Idents, SyntaxTree, SymbolTables, TypesAnalyzer)]
#[derive(Default)]
pub struct LanguageService {
storage: salsa::Storage<Self>,
semantic_token_kinds: IndexSet<SemanticTokenKind, FxBuildHasher>,
configs: FxHashMap<crate::uri::InternUri, ServiceConfig>,
global_config: ServiceConfig,
}
impl Database for LanguageService {}
impl ParallelDatabase for LanguageService {
fn snapshot(&self) -> Snapshot<Self> {
Snapshot::new(LanguageService {
storage: self.storage.snapshot(),
semantic_token_kinds: self.semantic_token_kinds.clone(),
configs: self.configs.clone(),
global_config: self.global_config.clone(),
})
}
}
impl LanguageService {
pub fn initialize(&mut self, params: InitializeParams) -> InitializeResult {
let mut kinds_map = IndexMap::<_, _, FxBuildHasher>::default();
if let Some(TextDocumentClientCapabilities {
semantic_tokens: Some(SemanticTokensClientCapabilities { token_types, .. }),
..
}) = params.capabilities.text_document
{
token_types.iter().for_each(|token_type| {
if *token_type == SemanticTokenType::TYPE {
kinds_map.insert(SemanticTokenKind::Type, SemanticTokenType::TYPE);
} else if *token_type == SemanticTokenType::PARAMETER {
kinds_map.insert(SemanticTokenKind::Param, SemanticTokenType::PARAMETER);
} else if *token_type == SemanticTokenType::VARIABLE {
kinds_map.insert(SemanticTokenKind::Var, SemanticTokenType::VARIABLE);
} else if *token_type == SemanticTokenType::FUNCTION {
kinds_map.insert(SemanticTokenKind::Func, SemanticTokenType::FUNCTION);
} else if *token_type == SemanticTokenType::KEYWORD {
kinds_map.insert(SemanticTokenKind::Keyword, SemanticTokenType::KEYWORD);
} else if *token_type == SemanticTokenType::COMMENT {
kinds_map.insert(SemanticTokenKind::Comment, SemanticTokenType::COMMENT);
} else if *token_type == SemanticTokenType::STRING {
kinds_map.insert(SemanticTokenKind::String, SemanticTokenType::STRING);
} else if *token_type == SemanticTokenType::NUMBER {
kinds_map.insert(SemanticTokenKind::Number, SemanticTokenType::NUMBER);
} else if *token_type == SemanticTokenType::OPERATOR {
kinds_map.insert(SemanticTokenKind::Op, SemanticTokenType::OPERATOR);
}
});
self.semantic_token_kinds = kinds_map.keys().cloned().collect();
}
if let Some(config) = params
.initialization_options
.and_then(|config| serde_json::from_value(config).ok())
{
self.global_config = config;
}
InitializeResult {
capabilities: ServerCapabilities {
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
code_action_provider: Some(CodeActionProviderCapability::Options(
CodeActionOptions {
code_action_kinds: Some(vec![
CodeActionKind::QUICKFIX,
CodeActionKind::REFACTOR_REWRITE,
CodeActionKind::REFACTOR_INLINE,
]),
resolve_provider: Some(false),
..Default::default()
},
)),
completion_provider: Some(CompletionOptions {
trigger_characters: Some(
[
'$', '(', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.',
]
.iter()
.map(char::to_string)
.collect(),
),
all_commit_characters: Some(vec![")".into()]),
..Default::default()
}),
definition_provider: Some(OneOf::Left(true)),
diagnostic_provider: Some(DiagnosticServerCapabilities::Options(
DiagnosticOptions {
identifier: Some("wat".into()),
inter_file_dependencies: false,
workspace_diagnostics: false,
..Default::default()
},
)),
type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
declaration_provider: Some(DeclarationCapability::Simple(true)),
document_formatting_provider: Some(OneOf::Left(true)),
document_highlight_provider: Some(OneOf::Left(true)),
document_range_formatting_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
hover_provider: Some(HoverProviderCapability::Simple(true)),
inlay_hint_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),
rename_provider: Some(OneOf::Right(RenameOptions {
prepare_provider: Some(true),
work_done_progress_options: Default::default(),
})),
selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
semantic_tokens_provider: Some(
SemanticTokensServerCapabilities::SemanticTokensOptions(
SemanticTokensOptions {
legend: SemanticTokensLegend {
token_types: kinds_map.into_values().collect(),
token_modifiers: vec![],
},
range: Some(true),
full: Some(SemanticTokensFullOptions::Bool(true)),
..Default::default()
},
),
),
signature_help_provider: Some(SignatureHelpOptions {
trigger_characters: Some(['(', ')'].iter().map(char::to_string).collect()),
..Default::default()
}),
text_document_sync: Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::FULL),
will_save: Some(false),
will_save_wait_until: Some(false),
save: Some(TextDocumentSyncSaveOptions::Supported(false)),
},
)),
..Default::default()
},
server_info: Some(ServerInfo {
name: "WebAssembly Language Tools".into(),
version: Some(env!("CARGO_PKG_VERSION").into()),
}),
}
}
#[inline]
pub fn commit(&mut self, uri: Uri, source: String) {
let uri = self.uri(uri);
self.set_source(uri, source);
}
#[inline]
fn get_config(&self, uri: crate::uri::InternUri) -> &ServiceConfig {
self.configs.get(&uri).unwrap_or(&self.global_config)
}
#[inline]
pub fn get_configs(&self) -> impl Iterator<Item = (Uri, &ServiceConfig)> {
self.configs
.iter()
.map(|(uri, config)| (self.lookup_uri(*uri), config))
}
#[inline]
pub fn set_config(&mut self, uri: Uri, config: ServiceConfig) {
self.configs.insert(self.uri(uri), config);
}
#[inline]
pub fn set_global_config(&mut self, config: ServiceConfig) {
self.global_config = config;
}
#[inline]
pub fn dynamic_capabilities(&self) -> RegistrationParams {
use lsp_types::notification::Notification;
RegistrationParams {
registrations: vec![Registration {
id: DidChangeConfiguration::METHOD.into(),
method: DidChangeConfiguration::METHOD.into(),
register_options: None,
}],
}
}
#[inline]
pub fn is_cancelled(&self) -> bool {
self.storage.salsa_runtime().is_current_revision_canceled()
}
#[inline]
pub fn fork(&self) -> Snapshot<Self> {
self.snapshot()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_and_set_configs() {
let mut service = LanguageService::default();
assert_eq!(service.get_configs().count(), 0);
let uri = "untitled://test".parse::<Uri>().unwrap();
service.set_config(uri.clone(), ServiceConfig::default());
assert_eq!(service.get_configs().next().unwrap().0, uri);
}
}