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
use std::path::{Path, PathBuf};
use tower_lsp_server::Client;
use tower_lsp_server::ls_types::{MessageType, Uri};
/// Load config from workspace root, falling back to default
///
/// If `document_uri` is provided, the file extension will be used to auto-detect
/// the flavor (.qmd → Quarto, .Rmd/.Rmarkdown → RMarkdown)
pub(crate) async fn load_config(
client: &Client,
workspace_root: &Option<PathBuf>,
document_uri: Option<&Uri>,
) -> crate::Config {
// Convert URI to file path for flavor detection
let input_file: Option<PathBuf> = if let Some(uri) = document_uri {
uri.to_file_path().map(|p| p.into_owned())
} else {
None
};
if let Some(root) = workspace_root.as_ref() {
// Start the config walk at the file's directory (so a `panache.toml`
// closer to the file shadows one at the workspace root). Project-root
// discovery via `.git` happens inside `config::load`, so CLI and LSP
// pick the same project boundary symmetrically.
let start_dir = input_file
.as_deref()
.and_then(|p| p.parent())
.filter(|p| p.starts_with(root))
.map(Path::to_path_buf)
.unwrap_or_else(|| root.clone());
match crate::config::load(None, &start_dir, input_file.as_deref(), None) {
Ok((config, path)) => {
if let Some(p) = path {
client
.log_message(
MessageType::INFO,
format!("Loaded config from {}", p.display()),
)
.await;
}
return config;
}
Err(e) => {
client
.log_message(
MessageType::WARNING,
format!("Failed to load config: {}", e),
)
.await;
}
}
}
// Even if there's no workspace root, try to detect flavor from file extension
if let Some(file_path) = &input_file {
let mut config = crate::Config::default();
if let Some(ext) = file_path.extension().and_then(|e| e.to_str()) {
let detected_flavor = match ext.to_lowercase().as_str() {
"qmd" => Some(crate::config::Flavor::Quarto),
"rmd" | "rmarkdown" => Some(crate::config::Flavor::RMarkdown),
_ => None,
};
if let Some(flavor) = detected_flavor {
config.flavor = flavor;
config.extensions = crate::config::Extensions::for_flavor(flavor);
}
}
return config;
}
crate::Config::default()
}