1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//! Workspace symbols provider for pytest fixtures.
//!
//! Provides the workspace/symbol LSP feature, enabling fuzzy search for
//! fixtures across the entire workspace.
use super::Backend;
use tower_lsp_server::jsonrpc::Result;
use tower_lsp_server::ls_types::*;
use tracing::info;
impl Backend {
/// Handle workspace/symbol request.
///
/// Returns all fixture definitions matching the query string.
/// This enables "Go to Symbol in Workspace" (Cmd+T / Ctrl+T) in editors.
#[allow(deprecated)] // SymbolInformation::deprecated is required by LSP spec
pub async fn handle_workspace_symbol(
&self,
params: WorkspaceSymbolParams,
) -> Result<Option<Vec<SymbolInformation>>> {
let query = params.query.to_lowercase();
info!("workspace_symbol request: query={:?}", query);
let mut symbols: Vec<SymbolInformation> = Vec::new();
// Iterate over all fixture definitions
for entry in self.fixture_db.definitions.iter() {
for definition in entry.value() {
// Skip third-party fixtures
if definition.is_third_party {
continue;
}
// If query is empty, return all fixtures; otherwise filter by name
if !query.is_empty() && !definition.name.to_lowercase().contains(&query) {
continue;
}
let Some(uri) = self.path_to_uri(&definition.file_path) else {
continue;
};
let line = Self::internal_line_to_lsp(definition.line);
let start_char = definition.start_char as u32;
let end_char = definition.end_char as u32;
let location = Location {
uri,
range: Self::create_range(line, start_char, line, end_char),
};
let symbol = SymbolInformation {
name: definition.name.clone(),
kind: SymbolKind::FUNCTION,
tags: None,
deprecated: None,
location,
container_name: definition
.file_path
.file_name()
.and_then(|f| f.to_str())
.map(|s| s.to_string()),
};
symbols.push(symbol);
}
}
// Sort by name for consistent ordering
symbols.sort_by(|a, b| a.name.cmp(&b.name));
info!("Returning {} workspace symbols", symbols.len());
if symbols.is_empty() {
Ok(None)
} else {
Ok(Some(symbols))
}
}
}