Skip to main content

pytest_language_server/providers/
workspace_symbol.rs

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