use std::path::PathBuf;
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::LanguageServer;
use super::document::{compute_line_starts, DocumentData};
use super::util::{position_to_byte_offset, word_at_position};
use super::{actions, folding, hints, incremental, indent, selection, semantic, TridentLsp};
#[tower_lsp::async_trait]
impl LanguageServer for TridentLsp {
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
Ok(InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::INCREMENTAL,
)),
document_formatting_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
definition_provider: Some(OneOf::Left(true)),
hover_provider: Some(HoverProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..Default::default()
}),
signature_help_provider: Some(SignatureHelpOptions {
trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
..Default::default()
}),
semantic_tokens_provider: Some(
SemanticTokensServerCapabilities::SemanticTokensOptions(
SemanticTokensOptions {
legend: semantic::token_legend(),
full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
range: None,
work_done_progress_options: Default::default(),
},
),
),
code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
references_provider: Some(OneOf::Left(true)),
rename_provider: Some(OneOf::Left(true)),
document_highlight_provider: Some(OneOf::Left(true)),
workspace_symbol_provider: Some(OneOf::Left(true)),
inlay_hint_provider: Some(OneOf::Left(true)),
folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
first_trigger_character: "\n".to_string(),
more_trigger_character: Some(vec!["}".to_string()]),
}),
..Default::default()
},
..Default::default()
})
}
async fn initialized(&self, _: InitializedParams) {
self.client
.log_message(MessageType::INFO, "trident-lsp initialized")
.await;
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
let uri = params.text_document.uri.clone();
let source = params.text_document.text;
let mut doc = DocumentData::new(source);
// Initial parse: cache AST and name_kinds
if let Ok(file) = crate::parse_source_silent(&doc.source, "") {
doc.name_kinds = semantic::build_name_kinds(&file);
doc.cached_ast = Some(file);
}
let diag_source = doc.source.clone();
self.documents
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(uri.clone(), doc);
self.publish_diagnostics(uri, &diag_source).await;
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
let uri = params.text_document.uri.clone();
let diag_source = {
let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
let doc = match docs.get_mut(&uri) {
Some(d) => d,
None => return,
};
for change in params.content_changes {
if let Some(range) = change.range {
let edit_start = position_to_byte_offset(&doc.source, range.start).unwrap_or(0);
let edit_old_end =
position_to_byte_offset(&doc.source, range.end).unwrap_or(doc.source.len());
let mut new_source = String::with_capacity(
doc.source.len() - (edit_old_end - edit_start) + change.text.len(),
);
new_source.push_str(&doc.source[..edit_start]);
new_source.push_str(&change.text);
new_source.push_str(&doc.source[edit_old_end..]);
let edit_new_end = edit_start + change.text.len();
let result = incremental::incremental_lex(
&new_source,
&doc.tokens,
&doc.comments,
edit_start,
edit_old_end,
edit_new_end,
);
doc.source = new_source;
doc.tokens = result.tokens;
doc.comments = result.comments;
doc.line_starts = compute_line_starts(&doc.source);
} else {
// Full replacement (fallback)
doc.source = change.text;
let (tokens, comments, _) =
crate::syntax::lexer::Lexer::new(&doc.source, 0).tokenize();
doc.tokens = tokens;
doc.comments = comments;
doc.line_starts = compute_line_starts(&doc.source);
}
}
// Re-parse and cache AST (cheap for contract-sized files)
if let Ok(file) = crate::parse_source_silent(&doc.source, "") {
doc.name_kinds = semantic::build_name_kinds(&file);
doc.cached_ast = Some(file);
}
doc.source.clone()
}; // lock dropped here
self.publish_diagnostics(uri, &diag_source).await;
}
async fn did_close(&self, params: DidCloseTextDocumentParams) {
self.documents
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(¶ms.text_document.uri);
}
async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
let uri = ¶ms.text_document.uri;
let source = match self
.documents
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(uri)
{
Some(doc) => doc.source.clone(),
None => return Ok(None),
};
let filename = uri.path();
match crate::format_source(&source, filename) {
Ok(formatted) => {
if formatted == source {
return Ok(None);
}
let line_count = source.lines().count() as u32;
let last_line_len = source.lines().last().map_or(0, |l| l.len()) as u32;
Ok(Some(vec![TextEdit {
range: Range::new(
Position::new(0, 0),
Position::new(line_count, last_line_len),
),
new_text: formatted,
}]))
}
Err(_) => Ok(None),
}
}
async fn on_type_formatting(
&self,
params: DocumentOnTypeFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
let uri = ¶ms.text_document_position.text_document.uri;
let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
let doc = match docs.get(uri) {
Some(d) => d,
None => return Ok(None),
};
Ok(indent::on_type_formatting(
&doc.source,
&doc.tokens,
params.text_document_position.position,
¶ms.ch,
))
}
async fn document_symbol(
&self,
params: DocumentSymbolParams,
) -> Result<Option<DocumentSymbolResponse>> {
let uri = ¶ms.text_document.uri;
let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
let doc = match docs.get(uri) {
Some(d) => d,
None => return Ok(None),
};
let file = match &doc.cached_ast {
Some(f) => f.clone(),
None => return Ok(None),
};
let symbols = self.document_symbols(&doc.source, &file);
Ok(Some(DocumentSymbolResponse::Nested(symbols)))
}
async fn goto_definition(
&self,
params: GotoDefinitionParams,
) -> Result<Option<GotoDefinitionResponse>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position;
let source = match self
.documents
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(uri)
{
Some(doc) => doc.source.clone(),
None => return Ok(None),
};
let word = word_at_position(&source, pos);
if word.is_empty() {
return Ok(None);
}
let file_path = PathBuf::from(uri.path());
let index = self.build_symbol_index(&file_path);
if let Some((target_uri, range)) = index.get(&word) {
return Ok(Some(GotoDefinitionResponse::Scalar(Location {
uri: target_uri.clone(),
range: *range,
})));
}
for (key, (target_uri, range)) in &index {
if key.ends_with(&format!(".{}", word)) {
return Ok(Some(GotoDefinitionResponse::Scalar(Location {
uri: target_uri.clone(),
range: *range,
})));
}
}
Ok(None)
}
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position;
self.do_hover(uri, pos).await
}
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
let uri = ¶ms.text_document_position.text_document.uri;
let pos = params.text_document_position.position;
self.do_completion(uri, pos).await
}
async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position;
self.do_signature_help(uri, pos).await
}
async fn semantic_tokens_full(
&self,
params: SemanticTokensParams,
) -> Result<Option<SemanticTokensResult>> {
let uri = ¶ms.text_document.uri;
let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
let doc = match docs.get_mut(uri) {
Some(d) => d,
None => return Ok(None),
};
let tokens = semantic::semantic_tokens_from_cache(doc);
doc.last_semantic_tokens = tokens.clone();
doc.result_version += 1;
Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
result_id: Some(doc.result_id()),
data: tokens,
})))
}
async fn semantic_tokens_full_delta(
&self,
params: SemanticTokensDeltaParams,
) -> Result<Option<SemanticTokensFullDeltaResult>> {
let uri = ¶ms.text_document.uri;
let mut docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
let doc = match docs.get_mut(uri) {
Some(d) => d,
None => return Ok(None),
};
// If client's previous_result_id doesn't match, send full tokens
if params.previous_result_id != doc.result_id() {
let tokens = semantic::semantic_tokens_from_cache(doc);
doc.last_semantic_tokens = tokens.clone();
doc.result_version += 1;
return Ok(Some(SemanticTokensFullDeltaResult::Tokens(
SemanticTokens {
result_id: Some(doc.result_id()),
data: tokens,
},
)));
}
let new_tokens = semantic::semantic_tokens_from_cache(doc);
let edits = semantic::compute_semantic_delta(&doc.last_semantic_tokens, &new_tokens);
doc.last_semantic_tokens = new_tokens;
doc.result_version += 1;
Ok(Some(SemanticTokensFullDeltaResult::TokensDelta(
SemanticTokensDelta {
result_id: Some(doc.result_id()),
edits,
},
)))
}
async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
let uri = ¶ms.text_document.uri;
let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
let doc = match docs.get(uri) {
Some(d) => d,
None => return Ok(None),
};
let file = match &doc.cached_ast {
Some(f) => f.clone(),
None => return Ok(None),
};
Ok(Some(folding::folding_ranges(
&doc.source,
&file,
&doc.comments,
)))
}
async fn selection_range(
&self,
params: SelectionRangeParams,
) -> Result<Option<Vec<SelectionRange>>> {
let uri = ¶ms.text_document.uri;
let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
let doc = match docs.get(uri) {
Some(d) => d,
None => return Ok(None),
};
let file = match &doc.cached_ast {
Some(f) => f.clone(),
None => return Ok(None),
};
Ok(Some(selection::selection_ranges(
&doc.source,
&file,
¶ms.positions,
)))
}
async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
let uri = ¶ms.text_document.uri;
let source = match self
.documents
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(uri)
{
Some(doc) => doc.source.clone(),
None => return Ok(None),
};
let diags: Vec<_> = params.context.diagnostics;
let result = actions::code_actions(&source, &diags, uri);
Ok(if result.is_empty() {
None
} else {
Some(result)
})
}
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
let uri = ¶ms.text_document_position.text_document.uri;
let pos = params.text_document_position.position;
let refs = self.do_references(uri, pos);
Ok(if refs.is_empty() { None } else { Some(refs) })
}
async fn prepare_rename(
&self,
params: TextDocumentPositionParams,
) -> Result<Option<PrepareRenameResponse>> {
Ok(self.do_prepare_rename(¶ms.text_document.uri, params.position))
}
async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
let uri = ¶ms.text_document_position.text_document.uri;
let pos = params.text_document_position.position;
Ok(self.do_rename(uri, pos, ¶ms.new_name))
}
async fn document_highlight(
&self,
params: DocumentHighlightParams,
) -> Result<Option<Vec<DocumentHighlight>>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position;
let highlights = self.do_document_highlight(uri, pos);
Ok(if highlights.is_empty() {
None
} else {
Some(highlights)
})
}
async fn symbol(
&self,
params: WorkspaceSymbolParams,
) -> Result<Option<Vec<SymbolInformation>>> {
let docs = self.documents.lock().unwrap_or_else(|e| e.into_inner());
let symbols = self.workspace_symbols(¶ms.query, &docs);
Ok(if symbols.is_empty() {
None
} else {
Some(symbols)
})
}
async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
let uri = ¶ms.text_document.uri;
let source = match self
.documents
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(uri)
{
Some(doc) => doc.source.clone(),
None => return Ok(None),
};
let result = hints::inlay_hints(&source, params.range);
Ok(if result.is_empty() {
None
} else {
Some(result)
})
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}
}
trident/src/lsp/server.rs
ฯ 0.0%
use PathBuf;
use Result;
use *;
use LanguageServer;
use ;
use ;
use ;