use std::sync::Arc;
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer, LspService, Server};
mod analysis;
mod completion;
mod diagnostics;
mod document;
mod hover;
mod semantic_tokens;
mod symbols;
use document::DocumentStore;
#[derive(Debug)]
pub struct AetherShellLsp {
client: Client,
documents: Arc<DocumentStore>,
}
impl AetherShellLsp {
pub fn new(client: Client) -> Self {
Self {
client,
documents: Arc::new(DocumentStore::new()),
}
}
}
#[tower_lsp::async_trait]
impl LanguageServer for AetherShellLsp {
async fn initialize(&self, _params: InitializeParams) -> Result<InitializeResult> {
Ok(InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::FULL),
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
include_text: Some(true),
})),
..Default::default()
},
)),
completion_provider: Some(CompletionOptions {
trigger_characters: Some(vec![
".".to_string(),
"|".to_string(),
"(".to_string(),
" ".to_string(),
]),
resolve_provider: Some(true),
..Default::default()
}),
hover_provider: Some(HoverProviderCapability::Simple(true)),
definition_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
semantic_tokens_provider: Some(
SemanticTokensServerCapabilities::SemanticTokensOptions(
SemanticTokensOptions {
legend: SemanticTokensLegend {
token_types: semantic_tokens::TOKEN_TYPES.to_vec(),
token_modifiers: semantic_tokens::TOKEN_MODIFIERS.to_vec(),
},
full: Some(SemanticTokensFullOptions::Bool(true)),
range: Some(true),
..Default::default()
},
),
),
signature_help_provider: Some(SignatureHelpOptions {
trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
retrigger_characters: Some(vec![",".to_string()]),
..Default::default()
}),
rename_provider: Some(OneOf::Right(RenameOptions {
prepare_provider: Some(true),
work_done_progress_options: Default::default(),
})),
document_formatting_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),
..Default::default()
},
server_info: Some(ServerInfo {
name: "AetherShell Language Server".to_string(),
version: Some(env!("CARGO_PKG_VERSION").to_string()),
}),
})
}
async fn initialized(&self, _params: InitializedParams) {
self.client
.log_message(MessageType::INFO, "AetherShell LSP initialized!")
.await;
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
let uri = params.text_document.uri.clone();
let text = params.text_document.text.clone();
let version = params.text_document.version;
self.documents.open(&uri, text, version);
let diagnostics = diagnostics::analyze(&self.documents, &uri);
self.client
.publish_diagnostics(uri, diagnostics, Some(version))
.await;
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
let uri = params.text_document.uri.clone();
let version = params.text_document.version;
if let Some(change) = params.content_changes.into_iter().next() {
self.documents.update(&uri, change.text, version);
let diagnostics = diagnostics::analyze(&self.documents, &uri);
self.client
.publish_diagnostics(uri, diagnostics, Some(version))
.await;
}
}
async fn did_save(&self, params: DidSaveTextDocumentParams) {
if let Some(text) = params.text {
self.documents.update(¶ms.text_document.uri, text, 0);
}
}
async fn did_close(&self, params: DidCloseTextDocumentParams) {
self.documents.close(¶ms.text_document.uri);
}
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
let uri = ¶ms.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let completions = completion::get_completions(&self.documents, uri, position);
Ok(Some(CompletionResponse::Array(completions)))
}
async fn completion_resolve(&self, item: CompletionItem) -> Result<CompletionItem> {
Ok(completion::resolve_completion(item))
}
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
Ok(hover::get_hover(&self.documents, uri, position))
}
async fn goto_definition(
&self,
params: GotoDefinitionParams,
) -> Result<Option<GotoDefinitionResponse>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
Ok(analysis::goto_definition(&self.documents, uri, position))
}
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
let uri = ¶ms.text_document_position.text_document.uri;
let position = params.text_document_position.position;
Ok(analysis::find_references(&self.documents, uri, position))
}
async fn document_symbol(
&self,
params: DocumentSymbolParams,
) -> Result<Option<DocumentSymbolResponse>> {
let uri = ¶ms.text_document.uri;
let symbols = symbols::get_document_symbols(&self.documents, uri);
Ok(Some(DocumentSymbolResponse::Nested(symbols)))
}
async fn semantic_tokens_full(
&self,
params: SemanticTokensParams,
) -> Result<Option<SemanticTokensResult>> {
let uri = ¶ms.text_document.uri;
let tokens = semantic_tokens::get_semantic_tokens(&self.documents, uri);
Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
result_id: None,
data: tokens,
})))
}
async fn semantic_tokens_range(
&self,
params: SemanticTokensRangeParams,
) -> Result<Option<SemanticTokensRangeResult>> {
let uri = ¶ms.text_document.uri;
let tokens = semantic_tokens::get_semantic_tokens_range(&self.documents, uri, params.range);
Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
result_id: None,
data: tokens,
})))
}
async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
Ok(completion::get_signature_help(
&self.documents,
uri,
position,
))
}
async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
let uri = ¶ms.text_document.uri;
Ok(analysis::format_document(&self.documents, uri))
}
async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
let uri = ¶ms.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let new_name = ¶ms.new_name;
Ok(analysis::rename(&self.documents, uri, position, new_name))
}
async fn prepare_rename(
&self,
params: TextDocumentPositionParams,
) -> Result<Option<PrepareRenameResponse>> {
let uri = ¶ms.text_document.uri;
let position = params.position;
Ok(analysis::prepare_rename(&self.documents, uri, position))
}
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive(tracing::Level::INFO.into()),
)
.with_writer(std::io::stderr)
.init();
tracing::info!("Starting AetherShell Language Server");
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, socket) = LspService::new(AetherShellLsp::new);
Server::new(stdin, stdout, socket).serve(service).await;
}