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