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 rename;
18pub mod workspace_symbol;
19
20use crate::config::Config;
21use crate::fixtures::FixtureDatabase;
22use dashmap::DashMap;
23use std::path::PathBuf;
24use std::sync::Arc;
25use tower_lsp_server::ls_types::*;
26use tower_lsp_server::Client;
27use tracing::warn;
28
29pub struct Backend {
31 pub client: Client,
32 pub fixture_db: Arc<FixtureDatabase>,
33 pub workspace_root: Arc<tokio::sync::RwLock<Option<PathBuf>>>,
35 pub original_workspace_root: Arc<tokio::sync::RwLock<Option<PathBuf>>>,
37 pub scan_task: Arc<tokio::sync::Mutex<Option<tokio::task::JoinHandle<()>>>>,
39 pub uri_cache: Arc<DashMap<PathBuf, Uri>>,
42 pub config: Arc<tokio::sync::RwLock<Config>>,
44}
45
46impl Backend {
47 pub fn new(client: Client, fixture_db: Arc<FixtureDatabase>) -> Self {
49 Self {
50 client,
51 fixture_db,
52 workspace_root: Arc::new(tokio::sync::RwLock::new(None)),
53 original_workspace_root: Arc::new(tokio::sync::RwLock::new(None)),
54 scan_task: Arc::new(tokio::sync::Mutex::new(None)),
55 uri_cache: Arc::new(DashMap::new()),
56 config: Arc::new(tokio::sync::RwLock::new(Config::default())),
57 }
58 }
59
60 pub fn uri_to_path(&self, uri: &Uri) -> Option<PathBuf> {
63 match uri.to_file_path() {
64 Some(path) => {
65 let path = path.to_path_buf();
68 Some(path.canonicalize().unwrap_or(path))
69 }
70 None => {
71 warn!("Failed to convert URI to file path: {:?}", uri);
72 None
73 }
74 }
75 }
76
77 pub fn path_to_uri(&self, path: &std::path::Path) -> Option<Uri> {
80 if let Some(cached_uri) = self.uri_cache.get(path) {
83 return Some(cached_uri.clone());
84 }
85
86 let path_to_use: Option<PathBuf> = if cfg!(target_os = "macos") {
91 path.to_str().and_then(|path_str| {
92 if path_str.starts_with("/private/var/") || path_str.starts_with("/private/tmp/") {
93 Some(PathBuf::from(path_str.replacen("/private", "", 1)))
94 } else {
95 None
96 }
97 })
98 } else if cfg!(target_os = "windows") {
99 path.to_str()
102 .and_then(|path_str| path_str.strip_prefix(r"\\?\"))
103 .map(PathBuf::from)
104 } else {
105 None
106 };
107
108 let final_path = path_to_use.as_deref().unwrap_or(path);
109
110 match Uri::from_file_path(final_path) {
112 Some(uri) => Some(uri),
113 None => {
114 warn!("Failed to convert path to URI: {:?}", path);
115 None
116 }
117 }
118 }
119
120 pub fn lsp_line_to_internal(line: u32) -> usize {
122 (line + 1) as usize
123 }
124
125 pub fn internal_line_to_lsp(line: usize) -> u32 {
127 line.saturating_sub(1) as u32
128 }
129
130 pub fn create_range(start_line: u32, start_char: u32, end_line: u32, end_char: u32) -> Range {
132 Range {
133 start: Position {
134 line: start_line,
135 character: start_char,
136 },
137 end: Position {
138 line: end_line,
139 character: end_char,
140 },
141 }
142 }
143
144 pub fn create_point_range(line: u32, character: u32) -> Range {
146 Self::create_range(line, character, line, character)
147 }
148
149 pub fn format_fixture_documentation(
151 fixture: &crate::fixtures::FixtureDefinition,
152 workspace_root: Option<&PathBuf>,
153 ) -> String {
154 let mut content = String::new();
155
156 let relative_path = if let Some(root) = workspace_root {
158 fixture
159 .file_path
160 .strip_prefix(root)
161 .ok()
162 .and_then(|p| p.to_str())
163 .map(|s| s.to_string())
164 .unwrap_or_else(|| {
165 fixture
166 .file_path
167 .file_name()
168 .and_then(|f| f.to_str())
169 .unwrap_or("unknown")
170 .to_string()
171 })
172 } else {
173 fixture
174 .file_path
175 .file_name()
176 .and_then(|f| f.to_str())
177 .unwrap_or("unknown")
178 .to_string()
179 };
180
181 content.push_str(&format!("**from** `{}`\n", relative_path));
183
184 let return_annotation = if let Some(ref ret_type) = &fixture.return_type {
186 format!(" -> {}", ret_type)
187 } else {
188 String::new()
189 };
190
191 content.push_str(&format!(
192 "```python\n@pytest.fixture\ndef {}(...){}:\n```",
193 fixture.name, return_annotation
194 ));
195
196 if let Some(ref docstring) = fixture.docstring {
198 content.push_str("\n\n---\n\n");
199 content.push_str(docstring);
200 }
201
202 content
203 }
204}