Skip to main content

pytest_language_server/providers/
document_symbol.rs

1//! Document symbols provider for pytest fixtures.
2//!
3//! Provides the textDocument/documentSymbol LSP feature, enabling file outline
4//! and breadcrumb navigation for fixtures in the editor.
5
6use super::Backend;
7use tower_lsp_server::jsonrpc::Result;
8use tower_lsp_server::ls_types::*;
9use tracing::info;
10
11impl Backend {
12    /// Handle textDocument/documentSymbol request.
13    ///
14    /// Returns all fixture definitions in the document as symbols.
15    /// This enables outline view and breadcrumb navigation in editors.
16    pub async fn handle_document_symbol(
17        &self,
18        params: DocumentSymbolParams,
19    ) -> Result<Option<DocumentSymbolResponse>> {
20        let uri = params.text_document.uri;
21
22        info!("document_symbol request: uri={:?}", uri);
23
24        let Some(file_path) = self.uri_to_path(&uri) else {
25            return Ok(None);
26        };
27
28        // Collect all fixture definitions for this file
29        let mut symbols: Vec<DocumentSymbol> = Vec::new();
30
31        // Iterate over all fixture definitions
32        for entry in self.fixture_db.definitions.iter() {
33            for definition in entry.value() {
34                // Only include fixtures from this file
35                if definition.file_path != file_path {
36                    continue;
37                }
38
39                // Skip third-party fixtures (they're from site-packages, not user files)
40                if definition.is_third_party {
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                // Selection range is the fixture name
49                let selection_range = Self::create_range(line, start_char, line, end_char);
50
51                // Full range includes the entire function body
52                let end_line = Self::internal_line_to_lsp(definition.end_line);
53                let range = Self::create_range(line, 0, end_line, 0);
54
55                // Build detail string with return type if available
56                let detail = definition
57                    .return_type
58                    .as_ref()
59                    .map(|rt| format!("-> {}", rt));
60
61                #[allow(deprecated)] // deprecated field is required by LSP spec
62                let symbol = DocumentSymbol {
63                    name: definition.name.clone(),
64                    detail,
65                    kind: SymbolKind::FUNCTION,
66                    tags: None,
67                    deprecated: None,
68                    range,
69                    selection_range,
70                    children: None,
71                };
72
73                symbols.push(symbol);
74            }
75        }
76
77        // Sort symbols by line number for consistent ordering
78        symbols.sort_by_key(|s| s.range.start.line);
79
80        info!(
81            "Returning {} document symbols for {:?}",
82            symbols.len(),
83            file_path
84        );
85
86        if symbols.is_empty() {
87            Ok(None)
88        } else {
89            Ok(Some(DocumentSymbolResponse::Nested(symbols)))
90        }
91    }
92}