pytest_language_server/providers/
mod.rs1pub mod call_hierarchy;
6pub mod code_action;
7pub mod code_lens;
8pub mod completion;
9pub mod definition;
10pub mod diagnostics;
11pub mod document_symbol;
12pub mod hover;
13pub mod implementation;
14pub mod inlay_hint;
15mod language_server;
16pub mod references;
17pub mod workspace_symbol;
18
19use crate::config::Config;
20use crate::fixtures::FixtureDatabase;
21use dashmap::DashMap;
22use std::path::PathBuf;
23use std::sync::Arc;
24use tower_lsp_server::ls_types::*;
25use tower_lsp_server::Client;
26use tracing::warn;
27
28pub struct Backend {
30 pub client: Client,
31 pub fixture_db: Arc<FixtureDatabase>,
32 pub workspace_root: Arc<tokio::sync::RwLock<Option<PathBuf>>>,
34 pub original_workspace_root: Arc<tokio::sync::RwLock<Option<PathBuf>>>,
36 pub scan_task: Arc<tokio::sync::Mutex<Option<tokio::task::JoinHandle<()>>>>,
38 pub uri_cache: Arc<DashMap<PathBuf, Uri>>,
41 pub config: Arc<tokio::sync::RwLock<Config>>,
43}
44
45impl Backend {
46 pub fn new(client: Client, fixture_db: Arc<FixtureDatabase>) -> Self {
48 Self {
49 client,
50 fixture_db,
51 workspace_root: Arc::new(tokio::sync::RwLock::new(None)),
52 original_workspace_root: Arc::new(tokio::sync::RwLock::new(None)),
53 scan_task: Arc::new(tokio::sync::Mutex::new(None)),
54 uri_cache: Arc::new(DashMap::new()),
55 config: Arc::new(tokio::sync::RwLock::new(Config::default())),
56 }
57 }
58
59 pub fn uri_to_path(&self, uri: &Uri) -> Option<PathBuf> {
62 match uri.to_file_path() {
63 Some(path) => {
64 let path = path.to_path_buf();
67 Some(path.canonicalize().unwrap_or(path))
68 }
69 None => {
70 warn!("Failed to convert URI to file path: {:?}", uri);
71 None
72 }
73 }
74 }
75
76 pub fn path_to_uri(&self, path: &std::path::Path) -> Option<Uri> {
79 if let Some(cached_uri) = self.uri_cache.get(path) {
82 return Some(cached_uri.clone());
83 }
84
85 let path_to_use: Option<PathBuf> = if cfg!(target_os = "macos") {
90 path.to_str().and_then(|path_str| {
91 if path_str.starts_with("/private/var/") || path_str.starts_with("/private/tmp/") {
92 Some(PathBuf::from(path_str.replacen("/private", "", 1)))
93 } else {
94 None
95 }
96 })
97 } else if cfg!(target_os = "windows") {
98 path.to_str()
101 .and_then(|path_str| path_str.strip_prefix(r"\\?\"))
102 .map(PathBuf::from)
103 } else {
104 None
105 };
106
107 let final_path = path_to_use.as_deref().unwrap_or(path);
108
109 match Uri::from_file_path(final_path) {
111 Some(uri) => Some(uri),
112 None => {
113 warn!("Failed to convert path to URI: {:?}", path);
114 None
115 }
116 }
117 }
118
119 pub fn lsp_line_to_internal(line: u32) -> usize {
121 (line + 1) as usize
122 }
123
124 pub fn internal_line_to_lsp(line: usize) -> u32 {
126 line.saturating_sub(1) as u32
127 }
128
129 pub fn create_range(start_line: u32, start_char: u32, end_line: u32, end_char: u32) -> Range {
131 Range {
132 start: Position {
133 line: start_line,
134 character: start_char,
135 },
136 end: Position {
137 line: end_line,
138 character: end_char,
139 },
140 }
141 }
142
143 pub fn create_point_range(line: u32, character: u32) -> Range {
145 Self::create_range(line, character, line, character)
146 }
147
148 pub fn format_fixture_documentation(
150 fixture: &crate::fixtures::FixtureDefinition,
151 workspace_root: Option<&PathBuf>,
152 ) -> String {
153 let mut content = String::new();
154
155 let relative_path = if let Some(root) = workspace_root {
157 fixture
158 .file_path
159 .strip_prefix(root)
160 .ok()
161 .and_then(|p| p.to_str())
162 .map(|s| s.to_string())
163 .unwrap_or_else(|| {
164 fixture
165 .file_path
166 .file_name()
167 .and_then(|f| f.to_str())
168 .unwrap_or("unknown")
169 .to_string()
170 })
171 } else {
172 fixture
173 .file_path
174 .file_name()
175 .and_then(|f| f.to_str())
176 .unwrap_or("unknown")
177 .to_string()
178 };
179
180 content.push_str(&format!("**from** `{}`\n", relative_path));
182
183 let return_annotation = if let Some(ref ret_type) = &fixture.return_type {
185 format!(" -> {}", ret_type)
186 } else {
187 String::new()
188 };
189
190 content.push_str(&format!(
191 "```python\n@pytest.fixture\ndef {}(...){}:\n```",
192 fixture.name, return_annotation
193 ));
194
195 if let Some(ref docstring) = fixture.docstring {
197 content.push_str("\n\n---\n\n");
198 content.push_str(docstring);
199 }
200
201 content
202 }
203}