use super::{LspServerType, QueryIntent, LspSearchResult, HintType, TraversalBounds};
use anyhow::{anyhow, Result};
use lsp_types::*;
use serde_json::{json, Value};
use std::collections::{HashMap, VecDeque};
use std::io::{BufRead, BufReader, Write};
use std::process::{ChildStdin, ChildStdout};
use std::sync::atomic::{AtomicU64, AtomicBool, Ordering};
use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader as TokioBufReader};
use tokio::process::{ChildStdin as TokioChildStdin, ChildStdout as TokioChildStdout};
use tokio::sync::{mpsc, Mutex, RwLock};
use tracing::{debug, error, trace, warn};
pub struct LspClient {
stdin: Arc<Mutex<TokioChildStdin>>,
stdout: Arc<Mutex<TokioBufReader<TokioChildStdout>>>,
request_id: AtomicU64,
pending_requests: Arc<RwLock<HashMap<u64, tokio::sync::oneshot::Sender<Value>>>>,
server_capabilities: Arc<RwLock<Option<ServerCapabilities>>>,
initialized: Arc<RwLock<bool>>,
shutdown_flag: Arc<AtomicBool>,
response_handler_task: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
}
impl LspClient {
pub async fn new(stdin: TokioChildStdin, stdout: TokioChildStdout) -> Result<Self> {
let client = Self {
stdin: Arc::new(Mutex::new(stdin)),
stdout: Arc::new(Mutex::new(TokioBufReader::new(stdout))),
request_id: AtomicU64::new(1),
pending_requests: Arc::new(RwLock::new(HashMap::new())),
server_capabilities: Arc::new(RwLock::new(None)),
initialized: Arc::new(RwLock::new(false)),
shutdown_flag: Arc::new(AtomicBool::new(false)),
response_handler_task: Arc::new(Mutex::new(None)),
};
client.start_response_handler().await?;
Ok(client)
}
async fn start_response_handler(&self) -> Result<()> {
let stdout = self.stdout.clone();
let pending_requests = self.pending_requests.clone();
let shutdown_flag = self.shutdown_flag.clone();
let task = tokio::spawn(async move {
let mut stdout = stdout.lock().await;
let mut line = String::new();
loop {
if shutdown_flag.load(Ordering::Relaxed) {
debug!("LSP response handler shutting down");
break;
}
line.clear();
match stdout.read_line(&mut line).await {
Ok(0) => break, Ok(_) => {
if line.trim().is_empty() {
continue;
}
if line.starts_with("Content-Length:") {
let content_length: usize = line
.trim()
.strip_prefix("Content-Length: ")
.unwrap_or("0")
.parse()
.unwrap_or(0);
if content_length > 0 {
line.clear();
let _ = stdout.read_line(&mut line).await;
let mut content = vec![0u8; content_length];
if let Ok(_) = tokio::io::AsyncReadExt::read_exact(&mut *stdout, &mut content).await {
if let Ok(json_str) = String::from_utf8(content) {
if let Ok(message) = serde_json::from_str::<Value>(&json_str) {
Self::handle_message(message, &pending_requests).await;
}
}
}
}
}
}
Err(e) => {
error!("Error reading from LSP stdout: {:?}", e);
break;
}
}
}
});
{
let mut task_handle = self.response_handler_task.lock().await;
*task_handle = Some(task);
}
Ok(())
}
async fn handle_message(
message: Value,
pending_requests: &Arc<RwLock<HashMap<u64, tokio::sync::oneshot::Sender<Value>>>>,
) {
trace!("Received LSP message: {:?}", message);
if let Some(id) = message.get("id") {
if let Some(id_num) = id.as_u64() {
let mut pending = pending_requests.write().await;
if let Some(sender) = pending.remove(&id_num) {
let _ = sender.send(message);
}
}
} else {
debug!("Received LSP notification: {:?}", message.get("method"));
}
}
async fn send_request(&self, method: &str, params: Value) -> Result<Value> {
let id = self.request_id.fetch_add(1, Ordering::SeqCst);
let (tx, rx) = tokio::sync::oneshot::channel();
{
let mut pending = self.pending_requests.write().await;
pending.insert(id, tx);
}
let request = json!({
"jsonrpc": "2.0",
"id": id,
"method": method,
"params": params
});
self.send_message(&request).await?;
match tokio::time::timeout(tokio::time::Duration::from_secs(10), rx).await {
Ok(Ok(response)) => {
if let Some(error) = response.get("error") {
return Err(anyhow!("LSP error: {:?}", error));
}
Ok(response.get("result").cloned().unwrap_or(Value::Null))
}
Ok(Err(_)) => Err(anyhow!("LSP request channel closed")),
Err(_) => Err(anyhow!("LSP request timed out")),
}
}
async fn send_notification(&self, method: &str, params: Value) -> Result<()> {
let notification = json!({
"jsonrpc": "2.0",
"method": method,
"params": params
});
self.send_message(¬ification).await
}
async fn send_message(&self, message: &Value) -> Result<()> {
let content = serde_json::to_string(message)?;
let header = format!("Content-Length: {}\r\n\r\n", content.len());
let mut stdin = self.stdin.lock().await;
stdin.write_all(header.as_bytes()).await?;
stdin.write_all(content.as_bytes()).await?;
stdin.flush().await?;
trace!("Sent LSP message: {}", content);
Ok(())
}
pub async fn initialize(&self, server_type: LspServerType) -> Result<()> {
let capabilities = ClientCapabilities {
text_document: Some(TextDocumentClientCapabilities {
definition: Some(GotoCapability {
dynamic_registration: Some(true),
link_support: Some(true),
}),
references: Some(ReferenceClientCapabilities {
dynamic_registration: Some(true),
}),
hover: Some(HoverClientCapabilities {
dynamic_registration: Some(true),
content_format: Some(vec![MarkupKind::Markdown, MarkupKind::PlainText]),
}),
completion: Some(CompletionClientCapabilities {
dynamic_registration: Some(true),
completion_item: Some(CompletionItemCapability {
snippet_support: Some(true),
..Default::default()
}),
..Default::default()
}),
document_symbol: Some(DocumentSymbolClientCapabilities {
dynamic_registration: Some(true),
symbol_kind: Some(SymbolKindCapability {
value_set: Some(vec![
SymbolKind::FILE,
SymbolKind::MODULE,
SymbolKind::CLASS,
SymbolKind::METHOD,
SymbolKind::FUNCTION,
SymbolKind::VARIABLE,
SymbolKind::CONSTANT,
]),
}),
hierarchical_document_symbol_support: Some(true),
..Default::default()
}),
..Default::default()
}),
workspace: Some(WorkspaceClientCapabilities {
workspace_folders: Some(true),
symbol: Some(WorkspaceSymbolClientCapabilities {
dynamic_registration: Some(true),
..Default::default()
}),
..Default::default()
}),
..Default::default()
};
let params = InitializeParams {
process_id: Some(std::process::id()),
client_info: Some(ClientInfo {
name: "lens-core".to_string(),
version: Some(env!("CARGO_PKG_VERSION").to_string()),
}),
root_uri: Some(Url::parse("file://.")?), root_path: None, capabilities,
workspace_folders: None,
initialization_options: None,
trace: Some(TraceValue::Off),
locale: None,
};
let response = self.send_request("initialize", serde_json::to_value(params)?).await?;
if let Ok(init_result) = serde_json::from_value::<InitializeResult>(response) {
let mut caps = self.server_capabilities.write().await;
*caps = Some(init_result.capabilities);
}
self.send_notification("initialized", json!({})).await?;
{
let mut initialized = self.initialized.write().await;
*initialized = true;
}
debug!("LSP {:?} server initialized", server_type);
Ok(())
}
pub async fn bounded_search(
&self,
query: &str,
intent: &QueryIntent,
bounds: &TraversalBounds,
) -> Result<Vec<LspSearchResult>> {
let is_initialized = *self.initialized.read().await;
if !is_initialized {
return Err(anyhow!("LSP client not initialized"));
}
match intent {
QueryIntent::Definition => self.find_definitions(query, bounds).await,
QueryIntent::References => self.find_references(query, bounds).await,
QueryIntent::TypeDefinition => self.find_type_definitions(query, bounds).await,
QueryIntent::Implementation => self.find_implementations(query, bounds).await,
QueryIntent::Symbol => self.find_symbols(query, bounds).await,
QueryIntent::Hover => self.get_hover_info(query, bounds).await,
_ => Ok(vec![]),
}
}
async fn find_definitions(&self, query: &str, bounds: &TraversalBounds) -> Result<Vec<LspSearchResult>> {
let symbols = self.workspace_symbol_search(query, bounds.max_results as usize).await?;
let mut results = Vec::new();
for symbol in symbols {
match symbol.location {
lsp_types::OneOf::Left(location) => {
results.push(LspSearchResult {
file_path: location.uri.path().to_string(),
line_number: location.range.start.line,
column: location.range.start.character,
content: symbol.name.clone(),
hint_type: HintType::Definition,
server_type: LspServerType::TypeScript, confidence: self.calculate_confidence(&symbol.name, query),
context_lines: None,
});
}
lsp_types::OneOf::Right(_symbol_location) => {
}
}
if results.len() >= bounds.max_results as usize {
break;
}
}
Ok(results)
}
async fn find_references(&self, _query: &str, _bounds: &TraversalBounds) -> Result<Vec<LspSearchResult>> {
Ok(vec![])
}
async fn find_type_definitions(&self, query: &str, bounds: &TraversalBounds) -> Result<Vec<LspSearchResult>> {
self.find_definitions(query, bounds).await
}
async fn find_implementations(&self, query: &str, bounds: &TraversalBounds) -> Result<Vec<LspSearchResult>> {
self.find_definitions(query, bounds).await
}
async fn find_symbols(&self, query: &str, bounds: &TraversalBounds) -> Result<Vec<LspSearchResult>> {
let symbols = self.workspace_symbol_search(query, bounds.max_results as usize).await?;
let mut results = Vec::new();
for symbol in symbols {
match symbol.location {
lsp_types::OneOf::Left(location) => {
results.push(LspSearchResult {
file_path: location.uri.path().to_string(),
line_number: location.range.start.line,
column: location.range.start.character,
content: symbol.name.clone(),
hint_type: HintType::Symbol,
server_type: LspServerType::TypeScript, confidence: self.calculate_confidence(&symbol.name, query),
context_lines: None,
});
}
lsp_types::OneOf::Right(_symbol_location) => {
}
}
}
Ok(results)
}
async fn get_hover_info(&self, _query: &str, _bounds: &TraversalBounds) -> Result<Vec<LspSearchResult>> {
Ok(vec![])
}
async fn workspace_symbol_search(&self, query: &str, max_results: usize) -> Result<Vec<WorkspaceSymbol>> {
let caps = self.server_capabilities.read().await;
if let Some(capabilities) = caps.as_ref() {
if capabilities.workspace_symbol_provider.is_none() {
return Ok(vec![]);
}
}
let params = WorkspaceSymbolParams {
query: query.to_string(),
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
};
let response = self.send_request("workspace/symbol", serde_json::to_value(params)?).await?;
match serde_json::from_value::<Vec<WorkspaceSymbol>>(response) {
Ok(mut symbols) => {
if symbols.len() > max_results {
symbols.truncate(max_results);
}
Ok(symbols)
}
Err(e) => {
warn!("Failed to parse workspace symbols: {:?}", e);
Ok(vec![])
}
}
}
fn calculate_confidence(&self, symbol_name: &str, query: &str) -> f64 {
let query_lower = query.to_lowercase();
let symbol_lower = symbol_name.to_lowercase();
if symbol_lower == query_lower {
1.0
} else if symbol_lower.contains(&query_lower) {
0.8
} else if query_lower.contains(&symbol_lower) {
0.6
} else {
let similarity = self.string_similarity(&symbol_lower, &query_lower);
similarity * 0.5
}
}
fn string_similarity(&self, a: &str, b: &str) -> f64 {
let len_a = a.len();
let len_b = b.len();
if len_a == 0 || len_b == 0 {
return 0.0;
}
let max_len = len_a.max(len_b) as f64;
let distance = self.levenshtein_distance(a, b) as f64;
(max_len - distance) / max_len
}
fn levenshtein_distance(&self, a: &str, b: &str) -> usize {
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let len_a = a_chars.len();
let len_b = b_chars.len();
let mut dp = vec![vec![0; len_b + 1]; len_a + 1];
for i in 0..=len_a {
dp[i][0] = i;
}
for j in 0..=len_b {
dp[0][j] = j;
}
for i in 1..=len_a {
for j in 1..=len_b {
let cost = if a_chars[i - 1] == b_chars[j - 1] { 0 } else { 1 };
dp[i][j] = (dp[i - 1][j] + 1)
.min(dp[i][j - 1] + 1)
.min(dp[i - 1][j - 1] + cost);
}
}
dp[len_a][len_b]
}
pub async fn shutdown(&self) -> Result<()> {
self.shutdown_flag.store(true, Ordering::Relaxed);
if let Some(task) = {
let mut task_handle = self.response_handler_task.lock().await;
task_handle.take()
} {
debug!("Waiting for LSP response handler to shutdown...");
if let Err(e) = task.await {
warn!("LSP response handler task failed during shutdown: {:?}", e);
}
}
let is_initialized = *self.initialized.read().await;
if is_initialized {
let _ = self.send_request("shutdown", Value::Null).await;
let _ = self.send_notification("exit", Value::Null).await;
let mut initialized = self.initialized.write().await;
*initialized = false;
}
Ok(())
}
pub async fn ping(&self) -> Result<()> {
let is_initialized = *self.initialized.read().await;
if !is_initialized {
return Err(anyhow::anyhow!("LSP client not initialized"));
}
match self.send_request("textDocument/documentSymbol", serde_json::json!({
"textDocument": {
"uri": "file:///tmp/health_check.txt"
}
})).await {
Ok(_) => Ok(()),
Err(e) => {
debug!("Health check got response (server is alive): {:?}", e);
Ok(())
}
}
}
}
impl Drop for LspClient {
fn drop(&mut self) {
self.shutdown_flag.store(true, Ordering::Relaxed);
if self.shutdown_flag.load(Ordering::Relaxed) {
warn!("LspClient dropped without explicit shutdown - background task may still be running");
}
}
}
#[cfg(test)]
#[cfg(feature = "integration-tests")] mod tests {
use super::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::mpsc;
use std::time::Duration;
#[tokio::test]
async fn test_request_id_increment() {
if let Ok(client) = create_test_client().await {
let initial_id = client.request_id.load(Ordering::SeqCst);
let next_id = client.request_id.fetch_add(1, Ordering::SeqCst);
assert_eq!(next_id, initial_id);
assert_eq!(client.request_id.load(Ordering::SeqCst), initial_id + 1);
}
}
#[tokio::test]
async fn test_calculate_confidence_exact_match() {
if let Ok(client) = create_test_client().await {
let confidence = client.calculate_confidence("test", "test");
assert_eq!(confidence, 1.0);
}
}
#[tokio::test]
async fn test_calculate_confidence_contains() {
if let Ok(client) = create_test_client().await {
let confidence = client.calculate_confidence("testFunction", "test");
assert_eq!(confidence, 0.8);
let confidence_reverse = client.calculate_confidence("test", "testFunction");
assert_eq!(confidence_reverse, 0.6);
}
}
#[tokio::test]
async fn test_calculate_confidence_case_insensitive() {
if let Ok(client) = create_test_client().await {
let confidence = client.calculate_confidence("TEST", "test");
assert_eq!(confidence, 1.0);
let confidence_mixed = client.calculate_confidence("TestFunction", "test");
assert_eq!(confidence_mixed, 0.8);
}
}
#[tokio::test]
async fn test_calculate_confidence_similarity() {
if let Ok(client) = create_test_client().await {
let confidence = client.calculate_confidence("testing", "tester");
assert!(confidence > 0.0);
assert!(confidence < 0.6);
}
}
#[tokio::test]
async fn test_string_similarity_identical() {
if let Ok(client) = create_test_client().await {
let similarity = client.string_similarity("hello", "hello");
assert_eq!(similarity, 1.0);
}
}
#[tokio::test]
async fn test_string_similarity_empty() {
if let Ok(client) = create_test_client().await {
let similarity = client.string_similarity("", "hello");
assert_eq!(similarity, 0.0);
let similarity_both = client.string_similarity("", "");
assert_eq!(similarity_both, 0.0);
}
}
#[tokio::test]
async fn test_string_similarity_different() {
if let Ok(client) = create_test_client().await {
let similarity = client.string_similarity("hello", "world");
assert!(similarity > 0.0);
assert!(similarity < 0.5);
}
}
#[tokio::test]
async fn test_levenshtein_distance_identical() {
if let Ok(client) = create_test_client().await {
let distance = client.levenshtein_distance("hello", "hello");
assert_eq!(distance, 0);
}
}
#[tokio::test]
async fn test_levenshtein_distance_single_char() {
if let Ok(client) = create_test_client().await {
let distance = client.levenshtein_distance("hello", "hallo");
assert_eq!(distance, 1);
}
}
#[tokio::test]
async fn test_levenshtein_distance_empty() {
if let Ok(client) = create_test_client().await {
let distance = client.levenshtein_distance("", "hello");
assert_eq!(distance, 5);
let distance_reverse = client.levenshtein_distance("hello", "");
assert_eq!(distance_reverse, 5);
}
}
#[tokio::test]
async fn test_levenshtein_distance_complex() {
if let Ok(client) = create_test_client().await {
let distance = client.levenshtein_distance("kitten", "sitting");
assert_eq!(distance, 3);
}
}
#[tokio::test]
async fn test_bounded_search_not_initialized() {
if let Ok(client) = create_test_client().await {
let intent = QueryIntent::Definition;
let bounds = TraversalBounds {
max_depth: 3,
max_results: 10,
timeout_ms: 1000,
};
let result = client.bounded_search("test", &intent, &bounds).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("not initialized"));
}
}
}
#[tokio::test]
async fn test_bounded_search_unsupported_intent() {
if let Ok(mut client) = create_test_client().await {
{
let mut initialized = client.initialized.write().await;
*initialized = true;
}
let intent = QueryIntent::TextSearch; let bounds = TraversalBounds {
max_depth: 3,
max_results: 10,
timeout_ms: 1000,
};
let result = client.bounded_search("test", &intent, &bounds).await.unwrap();
assert!(result.is_empty());
}
}
#[tokio::test]
async fn test_shutdown_not_initialized() {
if let Ok(client) = create_test_client().await {
let result = client.shutdown().await;
assert!(result.is_ok());
}
}
#[tokio::test]
async fn test_shutdown_initialized() {
if let Ok(mut client) = create_test_client().await {
{
let mut initialized = client.initialized.write().await;
*initialized = true;
}
let result = client.shutdown().await;
match result {
Ok(_) => {
let initialized = *client.initialized.read().await;
assert!(!initialized);
}
Err(_) => {
}
}
}
}
#[tokio::test]
async fn test_handle_message_response() {
let pending_requests = Arc::new(RwLock::new(HashMap::new()));
let (tx, rx) = tokio::sync::oneshot::channel();
{
let mut pending = pending_requests.write().await;
pending.insert(1, tx);
}
let message = json!({
"id": 1,
"result": {"test": "response"}
});
LspClient::handle_message(message, &pending_requests).await;
let response = rx.await.unwrap();
assert_eq!(response["id"], 1);
assert_eq!(response["result"]["test"], "response");
let pending = pending_requests.read().await;
assert!(!pending.contains_key(&1));
}
#[tokio::test]
async fn test_handle_message_notification() {
let pending_requests = Arc::new(RwLock::new(HashMap::new()));
let message = json!({
"method": "textDocument/publishDiagnostics",
"params": {"uri": "file://test.ts"}
});
LspClient::handle_message(message, &pending_requests).await;
let pending = pending_requests.read().await;
assert!(pending.is_empty());
}
#[tokio::test]
async fn test_handle_message_invalid_id() {
let pending_requests = Arc::new(RwLock::new(HashMap::new()));
let message = json!({
"id": "invalid",
"result": {"test": "response"}
});
LspClient::handle_message(message, &pending_requests).await;
let pending = pending_requests.read().await;
assert!(pending.is_empty());
}
#[tokio::test]
async fn test_handle_message_unknown_id() {
let pending_requests = Arc::new(RwLock::new(HashMap::new()));
let message = json!({
"id": 999,
"result": {"test": "response"}
});
LspClient::handle_message(message, &pending_requests).await;
let pending = pending_requests.read().await;
assert!(pending.is_empty());
}
#[tokio::test]
async fn test_find_references_empty() {
if let Ok(mut client) = create_test_client().await {
{
let mut initialized = client.initialized.write().await;
*initialized = true;
}
let bounds = TraversalBounds {
max_depth: 3,
max_results: 10,
timeout_ms: 1000,
};
let result = client.find_references("test", &bounds).await.unwrap();
assert!(result.is_empty());
}
}
#[tokio::test]
async fn test_get_hover_info_empty() {
if let Ok(mut client) = create_test_client().await {
{
let mut initialized = client.initialized.write().await;
*initialized = true;
}
let bounds = TraversalBounds {
max_depth: 3,
max_results: 10,
timeout_ms: 1000,
};
let result = client.get_hover_info("test", &bounds).await.unwrap();
assert!(result.is_empty());
}
}
#[tokio::test]
async fn test_find_type_definitions() {
if let Ok(mut client) = create_test_client().await {
{
let mut initialized = client.initialized.write().await;
*initialized = true;
}
let bounds = TraversalBounds {
max_depth: 3,
max_results: 10,
timeout_ms: 1000,
};
let result = client.find_type_definitions("test", &bounds).await;
match result {
Ok(results) => assert!(results.len() <= bounds.max_results as usize),
Err(_) => {} }
}
}
#[tokio::test]
async fn test_find_implementations() {
if let Ok(mut client) = create_test_client().await {
{
let mut initialized = client.initialized.write().await;
*initialized = true;
}
let bounds = TraversalBounds {
max_depth: 3,
max_results: 10,
timeout_ms: 1000,
};
let result = client.find_implementations("test", &bounds).await;
match result {
Ok(results) => assert!(results.len() <= bounds.max_results as usize),
Err(_) => {} }
}
}
#[tokio::test]
async fn test_workspace_symbol_search_no_capabilities() {
if let Ok(client) = create_test_client().await {
let result = client.workspace_symbol_search("test", 10).await.unwrap();
assert!(result.is_empty());
}
}
#[tokio::test]
async fn test_concurrent_request_ids() {
if let Ok(client) = create_test_client().await {
let client = Arc::new(client);
let mut handles = vec![];
for _ in 0..10 {
let client = client.clone();
let handle = tokio::spawn(async move {
client.request_id.fetch_add(1, Ordering::SeqCst)
});
handles.push(handle);
}
let mut ids = vec![];
for handle in handles {
ids.push(handle.await.unwrap());
}
ids.sort();
for i in 1..ids.len() {
assert_ne!(ids[i-1], ids[i]);
}
}
}
#[test]
fn test_json_message_format() {
let request = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "test_method",
"params": {"test": "value"}
});
assert_eq!(request["jsonrpc"], "2.0");
assert_eq!(request["id"], 1);
assert_eq!(request["method"], "test_method");
}
#[test]
fn test_notification_format() {
let notification = json!({
"jsonrpc": "2.0",
"method": "test_notification",
"params": {"test": "value"}
});
assert!(notification.get("id").is_none());
assert_eq!(notification["method"], "test_notification");
}
#[tokio::test]
async fn test_pending_requests_cleanup() {
if let Ok(client) = create_test_client().await {
let (tx, _rx) = tokio::sync::oneshot::channel();
{
let mut pending = client.pending_requests.write().await;
pending.insert(1, tx);
}
let message = json!({"id": 1, "result": {}});
LspClient::handle_message(message, &client.pending_requests).await;
let pending = client.pending_requests.read().await;
assert!(!pending.contains_key(&1));
}
}
#[tokio::test]
async fn test_server_capabilities_storage() {
if let Ok(client) = create_test_client().await {
let capabilities = ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::FULL
)),
hover_provider: Some(HoverProviderCapability::Simple(true)),
..Default::default()
};
{
let mut caps = client.server_capabilities.write().await;
*caps = Some(capabilities);
}
let caps = client.server_capabilities.read().await;
assert!(caps.is_some());
assert!(caps.as_ref().unwrap().hover_provider.is_some());
}
}
#[tokio::test]
async fn test_initialization_state() {
if let Ok(client) = create_test_client().await {
let initialized = *client.initialized.read().await;
assert!(!initialized);
{
let mut init = client.initialized.write().await;
*init = true;
}
let initialized = *client.initialized.read().await;
assert!(initialized);
}
}
#[tokio::test]
async fn test_levenshtein_performance() {
if let Ok(client) = create_test_client().await {
let start = std::time::Instant::now();
for _ in 0..100 {
let _ = client.levenshtein_distance("hello_world_test", "hello_world_testing");
}
let duration = start.elapsed();
assert!(duration < Duration::from_millis(100)); }
}
#[tokio::test]
async fn test_confidence_calculation_performance() {
if let Ok(client) = create_test_client().await {
let start = std::time::Instant::now();
for _ in 0..1000 {
let _ = client.calculate_confidence("test_function_name", "test");
}
let duration = start.elapsed();
assert!(duration < Duration::from_millis(100)); }
}
#[tokio::test]
async fn test_very_long_strings() {
if let Ok(client) = create_test_client().await {
let long_a = "a".repeat(1000);
let long_b = "b".repeat(1000);
let similarity = client.string_similarity(&long_a, &long_b);
assert_eq!(similarity, 0.0);
let distance = client.levenshtein_distance(&long_a, &long_b);
assert_eq!(distance, 1000);
}
}
#[tokio::test]
async fn test_unicode_strings() {
if let Ok(client) = create_test_client().await {
let unicode_a = "Hello 世界";
let unicode_b = "Hello 世界";
let similarity = client.string_similarity(unicode_a, unicode_b);
assert_eq!(similarity, 1.0);
let confidence = client.calculate_confidence("测试", "测试");
assert_eq!(confidence, 1.0);
}
}
}