193 lines
7.1 KiB
Rust
193 lines
7.1 KiB
Rust
use std::sync::Arc;
|
|
use tokio::sync::RwLock;
|
|
use tower_lsp::{Client, LspService, Server};
|
|
use tower_lsp::lsp_types as lsp;
|
|
|
|
mod analysis_db;
|
|
mod rebuild;
|
|
|
|
use analysis_db::SharedDb;
|
|
|
|
struct Backend {
|
|
db: SharedDb,
|
|
client: Client,
|
|
}
|
|
|
|
#[tower_lsp::async_trait]
|
|
impl tower_lsp::LanguageServer for Backend {
|
|
async fn initialize(
|
|
&self,
|
|
_: tower_lsp::lsp_types::InitializeParams,
|
|
) -> tower_lsp::jsonrpc::Result<tower_lsp::lsp_types::InitializeResult> {
|
|
Ok(tower_lsp::lsp_types::InitializeResult {
|
|
capabilities: tower_lsp::lsp_types::ServerCapabilities {
|
|
text_document_sync: Some(
|
|
tower_lsp::lsp_types::TextDocumentSyncCapability::Kind(
|
|
tower_lsp::lsp_types::TextDocumentSyncKind::FULL,
|
|
),
|
|
),
|
|
// MVP capabilities only (PR-08):
|
|
definition_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)),
|
|
document_symbol_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)),
|
|
// workspace_symbol is not available in tower-lsp 0.20 trait
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
})
|
|
}
|
|
|
|
async fn initialized(&self, _: tower_lsp::lsp_types::InitializedParams) {}
|
|
|
|
async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
// didOpen: upsert texto, solicita rebuild
|
|
async fn did_open(&self, params: tower_lsp::lsp_types::DidOpenTextDocumentParams) {
|
|
let uri = params.text_document.uri.to_string();
|
|
let text = params.text_document.text;
|
|
{
|
|
let mut guard = self.db.write().await;
|
|
guard.file_db.upsert(&uri, text);
|
|
}
|
|
rebuild::request_rebuild(self.db.clone(), self.client.clone()).await;
|
|
}
|
|
|
|
// didChange (FULL): receber conteúdo completo e upsert
|
|
async fn did_change(&self, params: tower_lsp::lsp_types::DidChangeTextDocumentParams) {
|
|
let uri = params.text_document.uri.to_string();
|
|
// Full-sync: esperamos 1 conteúdo completo
|
|
if let Some(change) = params.content_changes.into_iter().last() {
|
|
let text = change.text;
|
|
let mut guard = self.db.write().await;
|
|
guard.file_db.upsert(&uri, text);
|
|
}
|
|
rebuild::request_rebuild(self.db.clone(), self.client.clone()).await;
|
|
}
|
|
|
|
// didClose: opcionalmente remover do db e limpar diagnostics
|
|
async fn did_close(&self, params: tower_lsp::lsp_types::DidCloseTextDocumentParams) {
|
|
let uri = params.text_document.uri;
|
|
// Estratégia simples: manter FileDB para estabilidade de IDs, mas limpar diagnostics
|
|
let _ = self
|
|
.client
|
|
.publish_diagnostics(uri.clone(), vec![], Some(0))
|
|
.await;
|
|
}
|
|
|
|
async fn goto_definition(
|
|
&self,
|
|
params: tower_lsp::lsp_types::GotoDefinitionParams,
|
|
) -> tower_lsp::jsonrpc::Result<Option<tower_lsp::lsp_types::GotoDefinitionResponse>> {
|
|
let tdp = params.text_document_position_params;
|
|
let uri = tdp.text_document.uri;
|
|
let pos = tdp.position;
|
|
|
|
let guard = self.db.read().await;
|
|
// Map URI to current text and index
|
|
let Some(fid) = guard.file_db.file_id(uri.as_str()) else { return Ok(None) };
|
|
let text = guard.file_db.text(fid).to_string();
|
|
let idx = prometeu_analysis::TextIndex::new(&text);
|
|
let byte = idx.lsp_to_byte(pos.line, pos.character);
|
|
let ident = ident_at(&text, byte);
|
|
|
|
if let Some(name) = ident {
|
|
if let Some(snap) = &guard.last_good {
|
|
let mut hits: Vec<lsp::Location> = Vec::new();
|
|
for s in &snap.symbols_flat {
|
|
if s.name == name {
|
|
hits.push(s.location.clone());
|
|
}
|
|
}
|
|
if !hits.is_empty() {
|
|
return Ok(Some(lsp::GotoDefinitionResponse::Array(hits)));
|
|
}
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
|
|
// MVP stubs: documentSymbol/workspaceSymbol/definition retornam vazio até PRs seguintes
|
|
async fn document_symbol(
|
|
&self,
|
|
params: tower_lsp::lsp_types::DocumentSymbolParams,
|
|
) -> tower_lsp::jsonrpc::Result<Option<tower_lsp::lsp_types::DocumentSymbolResponse>> {
|
|
let uri = params.text_document.uri;
|
|
let guard = self.db.read().await;
|
|
if let Some(snap) = &guard.last_good {
|
|
let mut items: Vec<lsp::SymbolInformation> = Vec::new();
|
|
for s in &snap.symbols_flat {
|
|
if s.location.uri == uri {
|
|
items.push(lsp::SymbolInformation {
|
|
name: s.name.clone(),
|
|
kind: s.kind,
|
|
location: s.location.clone(),
|
|
tags: None,
|
|
deprecated: None,
|
|
container_name: None,
|
|
});
|
|
}
|
|
}
|
|
return Ok(Some(lsp::DocumentSymbolResponse::Flat(items)));
|
|
}
|
|
Ok(Some(lsp::DocumentSymbolResponse::Flat(vec![])))
|
|
}
|
|
|
|
// async fn workspace_symbol(
|
|
// &self,
|
|
// params: lsp::WorkspaceSymbolParams,
|
|
// ) -> tower_lsp::jsonrpc::Result<Option<Vec<lsp::SymbolInformation>>> {
|
|
// let query = params.query.to_lowercase();
|
|
// let guard = self.db.read().await;
|
|
// if let Some(snap) = &guard.last_good {
|
|
// let mut out: Vec<lsp::SymbolInformation> = Vec::new();
|
|
// for s in &snap.symbols_flat {
|
|
// if s.name.to_lowercase().contains(&query) {
|
|
// out.push(lsp::SymbolInformation {
|
|
// name: s.name.clone(),
|
|
// kind: s.kind,
|
|
// location: s.location.clone(),
|
|
// tags: None,
|
|
// deprecated: None,
|
|
// container_name: None,
|
|
// });
|
|
// if out.len() >= 50 { break; }
|
|
// }
|
|
// }
|
|
// return Ok(Some(out));
|
|
// }
|
|
// Ok(Some(vec![]))
|
|
// }
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let stdin = tokio::io::stdin();
|
|
let stdout = tokio::io::stdout();
|
|
|
|
let db: SharedDb = Arc::new(RwLock::new(analysis_db::AnalysisDb::default()));
|
|
|
|
let (service, socket) = LspService::new(|client| Backend { db: db.clone(), client });
|
|
Server::new(stdin, stdout, socket).serve(service).await;
|
|
}
|
|
|
|
// Simple textual identifier extraction for MVP definition lookup.
|
|
fn ident_at(text: &str, byte: u32) -> Option<String> {
|
|
let b = byte as usize;
|
|
if b > text.len() { return None; }
|
|
// Expand left and right over identifier characters (ASCII + underscore; acceptable MVP)
|
|
let bytes = text.as_bytes();
|
|
let mut start = b;
|
|
while start > 0 {
|
|
let c = bytes[start - 1];
|
|
if (c as char).is_ascii_alphanumeric() || c == b'_' { start -= 1; } else { break; }
|
|
}
|
|
let mut end = b;
|
|
while end < bytes.len() {
|
|
let c = bytes[end];
|
|
if (c as char).is_ascii_alphanumeric() || c == b'_' { end += 1; } else { break; }
|
|
}
|
|
if start < end { Some(text[start..end].to_string()) } else { None }
|
|
}
|