Skip to main content

swls_core/components/
config.rs

1use std::{collections::HashSet, path::PathBuf};
2
3use bevy_ecs::prelude::*;
4use serde::Deserialize;
5
6use crate::{
7    lsp_types::{Url, WorkspaceFolder},
8    util::fs::Fs,
9};
10
11#[derive(Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Disabled {
13    #[serde(alias = "SHAPES", alias = "shapes")]
14    Shapes,
15}
16
17#[derive(Resource, Debug, Default)]
18pub struct ServerConfig {
19    pub workspaces: Vec<WorkspaceFolder>,
20    pub config: Config,
21}
22
23#[derive(Debug, Deserialize)]
24pub struct Config {
25    /// Log level
26    #[serde(default = "debug")]
27    pub log: String,
28    /// Enable turtle
29    pub turtle: Option<bool>,
30    /// Enable jsonld
31    pub jsonld: Option<bool>,
32    /// Enable sparql
33    pub sparql: Option<bool>,
34    /// Extra local configuration
35    #[serde(flatten)]
36    pub local: LocalConfig,
37}
38
39#[derive(Debug, Deserialize, Default)]
40#[serde(default)]
41pub struct LocalConfig {
42    /// Extra ontologies to import
43    pub ontologies: HashSet<String>,
44    /// Extra shapes to import
45    pub shapes: HashSet<String>,
46    /// Features to disable
47    pub disabled: HashSet<Disabled>,
48    /// disable which prefices from prefix.cc to show
49    pub prefix_disabled: HashSet<String>,
50    /// confiure completion behavior
51    pub completion: CompletionConfig,
52}
53
54/// Lets the user configure how the property completion should happen.
55/// There are two main modes: strict and loose (default)
56/// On loose, the editor will suggest anything, not caring about the domain.
57/// On strict, the editor will only suggest property that have a matching domain or anything if the
58/// type could not be determined.
59///
60/// Both options can be specialized, ie only strict on these properties or only loose on these
61/// properties.
62///
63/// For example { loose: ["http://www.w3.org/2000/01/rdf-schema#"] }, here the editor will be strict, and show properties
64/// from rdfs
65/// On the other hand { strict: ["http://www.w3.org/ns/shacl#"] }, here the editor will be loose,
66/// and only show shacl properties if the objects is the correct type
67#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
68#[serde(untagged)]
69pub enum CompletionConfig {
70    // "strict" | "loose" | "none"
71    Mode(CompletionMode),
72
73    // { "except": [...] }
74    Except(ExceptRules),
75
76    // { "strict": [...] }
77    Strict(StrictRules),
78}
79
80impl Default for CompletionConfig {
81    fn default() -> Self {
82        Self::Mode(CompletionMode::None)
83    }
84}
85
86impl CompletionConfig {
87    fn combine(&mut self, other: CompletionConfig) {
88        use CompletionConfig::*;
89
90        if matches!(other, Mode(CompletionMode::None)) {
91            return;
92        }
93
94        if matches!(self, Mode(CompletionMode::None)) {
95            *self = other;
96            return;
97        }
98
99        if let Strict(r) = self {
100            if let Strict(r2) = other {
101                r.strict.extend(r2.strict);
102                return;
103            }
104        }
105
106        if let Except(r) = self {
107            if let Except(r2) = other {
108                r.loose.extend(r2.loose);
109                return;
110            }
111        }
112
113        *self = other;
114    }
115    pub fn correct_domain_required(&self, property: &str) -> bool {
116        match self {
117            CompletionConfig::Mode(CompletionMode::Loose)
118            | CompletionConfig::Mode(CompletionMode::None) => false,
119            CompletionConfig::Mode(CompletionMode::Strict) => true,
120            CompletionConfig::Except(completion_rules) => !completion_rules
121                .loose
122                .iter()
123                .any(|x| property.starts_with(x)),
124            CompletionConfig::Strict(completion_rules) => completion_rules
125                .strict
126                .iter()
127                .any(|x| property.starts_with(x)),
128        }
129    }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
133#[serde(rename_all = "lowercase")]
134pub enum CompletionMode {
135    #[default]
136    None,
137    Loose,
138    Strict,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
142#[serde(deny_unknown_fields)]
143pub struct ExceptRules {
144    #[serde(default)]
145    pub loose: Vec<String>,
146}
147#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
148#[serde(deny_unknown_fields)]
149pub struct StrictRules {
150    #[serde(default)]
151    pub strict: Vec<String>,
152}
153
154impl LocalConfig {
155    /// Combines this config with another config, giving precedence to the other config
156    pub fn combine(&mut self, other: LocalConfig) {
157        self.ontologies.extend(other.ontologies);
158        self.shapes.extend(other.shapes);
159        self.disabled.extend(other.disabled);
160        self.prefix_disabled.extend(other.prefix_disabled);
161        self.completion.combine(other.completion);
162    }
163    #[cfg(target_arch = "wasm32")]
164    pub async fn global(_: &Fs) -> Option<Self> {
165        None
166    }
167
168    #[cfg(not(target_arch = "wasm32"))]
169    pub async fn global(fs: &Fs) -> Option<Self> {
170        let global_path = dirs::config_dir()
171            .unwrap_or_else(|| PathBuf::from("."))
172            .join("swls/config.json");
173        let url = crate::lsp_types::Url::from_file_path(global_path).ok()?;
174
175        tracing::debug!("Found global config url {}", url.as_str());
176        let content = fs.0.read_file(&url).await?;
177        tracing::debug!("Read global config content");
178
179        match serde_json::from_str(&content) {
180            Ok(x) => Some(x),
181            Err(e) => {
182                tracing::error!("Deserialize failed\n{:?}", e);
183                None
184            }
185        }
186    }
187
188    pub async fn local(fs: &Fs, url: &Url) -> Option<Self> {
189        let url = Url::parse(&format!("{}/.swls/config.json", url.as_str())).ok()?;
190        tracing::debug!("Found local config url {}", url.as_str());
191        let content = fs.0.read_file(&url).await?;
192        tracing::debug!("Read local config content");
193        match serde_json::from_str(&content) {
194            Ok(x) => Some(x),
195            Err(e) => {
196                tracing::error!("Deserialize failed\n{:?}", e);
197                None
198            }
199        }
200    }
201}
202
203impl Default for Config {
204    fn default() -> Self {
205        Self {
206            log: "debug".to_string(),
207            turtle: None,
208            jsonld: None,
209            sparql: None,
210            local: LocalConfig::default(),
211        }
212    }
213}
214
215fn debug() -> String {
216    String::from("debug")
217}