1extern crate self as escriba_config;
6
7use serde::{Deserialize, Serialize};
8use tatara_lisp::DeriveTataraDomain;
9
10#[derive(
11 DeriveTataraDomain,
12 Serialize,
13 Deserialize,
14 schemars::JsonSchema,
15 Debug,
16 Clone,
17 PartialEq,
18 Default,
19)]
20#[serde(rename_all = "camelCase")]
21#[tatara(keyword = "defescriba")]
22pub struct EscribaConfig {
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub tema: Option<String>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub numeros_linha: Option<bool>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub numeros_relativos: Option<bool>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub largura_tab: Option<i64>,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub quebra_suave: Option<bool>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub mostrar_statusline: Option<bool>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub mostrar_tabbar: Option<bool>,
37}
38
39#[derive(
40 DeriveTataraDomain, Serialize, Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq,
41)]
42#[serde(rename_all = "camelCase")]
43#[tatara(keyword = "defkeymap")]
44pub struct KeymapDecl {
45 pub modo: String,
46 pub tecla: String,
47 pub comando: String,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub descricao: Option<String>,
50}
51
52#[derive(
53 DeriveTataraDomain, Serialize, Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq,
54)]
55#[serde(rename_all = "camelCase")]
56#[tatara(keyword = "defcommand")]
57pub struct CommandDecl {
58 pub nome: String,
59 pub descricao: String,
60 #[serde(default)]
61 pub args: Vec<String>,
62}
63
64#[derive(
65 DeriveTataraDomain, Serialize, Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq,
66)]
67#[serde(rename_all = "camelCase")]
68#[tatara(keyword = "defplugin")]
69pub struct PluginDecl {
70 pub caixa: String,
71 pub versao: String,
72 #[serde(default)]
73 pub ativar_em: Vec<String>,
74}
75
76#[derive(
77 DeriveTataraDomain, Serialize, Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq,
78)]
79#[serde(rename_all = "camelCase")]
80#[tatara(keyword = "defmajor-mode")]
81pub struct MajorMode {
82 pub nome: String,
83 #[serde(default)]
84 pub extensoes: Vec<String>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub estrutural_lisp: Option<bool>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub tamanho_indent: Option<i64>,
89}
90
91#[derive(
92 DeriveTataraDomain, Serialize, Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq,
93)]
94#[serde(rename_all = "camelCase")]
95#[tatara(keyword = "defminor-mode")]
96pub struct MinorMode {
97 pub nome: String,
98 #[serde(default)]
99 pub hooks: Vec<String>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub descricao: Option<String>,
102}
103
104impl EscribaConfig {
105 pub fn from_lisp(src: &str) -> Result<Self, tatara_lisp::LispError> {
106 use tatara_lisp::domain::TataraDomain;
107 let forms = tatara_lisp::read(src)?;
108 let first = forms
109 .first()
110 .ok_or_else(|| tatara_lisp::LispError::Compile {
111 form: "defescriba".into(),
112 message: "empty config".into(),
113 })?;
114 Self::compile_from_sexp(first)
115 }
116
117 pub fn register_all() {
118 tatara_lisp::domain::register::<Self>();
119 tatara_lisp::domain::register::<KeymapDecl>();
120 tatara_lisp::domain::register::<CommandDecl>();
121 tatara_lisp::domain::register::<PluginDecl>();
122 tatara_lisp::domain::register::<MajorMode>();
123 tatara_lisp::domain::register::<MinorMode>();
124 }
125}
126
127impl shikumi::TieredConfig for EscribaConfig {
144 fn bare() -> Self {
146 Self {
147 tema: None,
148 numeros_linha: None,
149 numeros_relativos: None,
150 largura_tab: None,
151 quebra_suave: None,
152 mostrar_statusline: None,
153 mostrar_tabbar: None,
154 }
155 }
156
157 fn prescribed_default() -> Self {
165 Self {
166 tema: Some("vellum".into()),
167 numeros_linha: Some(true),
168 numeros_relativos: Some(true),
169 largura_tab: Some(2),
170 quebra_suave: Some(false),
171 mostrar_statusline: Some(true),
172 mostrar_tabbar: Some(false),
173 }
174 }
175}
176
177#[cfg(test)]
178mod tiered_tests {
179 use super::*;
180 use shikumi::{ConfigTier, TieredConfig};
181
182 #[test]
183 fn escriba_config_bare_is_zero_opinion() {
184 let b = <EscribaConfig as TieredConfig>::bare();
185 assert!(b.tema.is_none());
186 assert!(b.numeros_linha.is_none());
187 assert!(b.numeros_relativos.is_none());
188 assert!(b.largura_tab.is_none());
189 assert!(b.quebra_suave.is_none());
190 assert!(b.mostrar_statusline.is_none());
191 assert!(b.mostrar_tabbar.is_none());
192 }
193
194 #[test]
195 fn escriba_config_prescribed_mirrors_blnvim_baseline() {
196 let p = <EscribaConfig as TieredConfig>::prescribed_default();
199 assert_eq!(p.tema.as_deref(), Some("vellum"));
200 assert_eq!(p.numeros_linha, Some(true));
201 assert_eq!(p.numeros_relativos, Some(true));
202 assert_eq!(p.largura_tab, Some(2));
203 assert_eq!(p.quebra_suave, Some(false));
204 assert_eq!(p.mostrar_statusline, Some(true));
205 assert_eq!(p.mostrar_tabbar, Some(false));
206 let bare = <EscribaConfig as TieredConfig>::bare();
208 assert_ne!(p, bare);
209 }
210
211 #[test]
212 fn escriba_config_resolve_tier_dispatches() {
213 let bare = <EscribaConfig as TieredConfig>::resolve_tier(ConfigTier::Bare);
215 let default = <EscribaConfig as TieredConfig>::resolve_tier(ConfigTier::Default);
216 assert_eq!(bare, <EscribaConfig as TieredConfig>::bare());
217 assert_eq!(default, <EscribaConfig as TieredConfig>::prescribed_default());
218 assert_eq!(default.tema.as_deref(), Some("vellum"));
219 }
220
221 #[test]
222 fn escriba_config_diff_against_self_is_empty() {
223 let p = <EscribaConfig as TieredConfig>::prescribed_default();
226 assert!(p.diff_against(&p).is_empty_diff());
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn parses_defescriba() {
236 let src = r#"(defescriba :tema "nord" :numeros-linha #t :largura-tab 2)"#;
237 let c = EscribaConfig::from_lisp(src).unwrap();
238 assert_eq!(c.tema.as_deref(), Some("nord"));
239 assert_eq!(c.numeros_linha, Some(true));
240 assert_eq!(c.largura_tab, Some(2));
241 }
242
243 #[test]
244 fn parses_defkeymap() {
245 use tatara_lisp::domain::TataraDomain;
246 let forms = tatara_lisp::read(
247 r#"(defkeymap :modo "Normal" :tecla "<leader>w" :comando "save" :descricao "save")"#,
248 )
249 .unwrap();
250 let k = KeymapDecl::compile_from_sexp(&forms[0]).unwrap();
251 assert_eq!(k.modo, "Normal");
252 assert_eq!(k.comando, "save");
253 }
254
255 #[test]
256 fn parses_defmajor_mode_with_structural_lisp() {
257 use tatara_lisp::domain::TataraDomain;
258 let forms = tatara_lisp::read(
259 r#"(defmajor-mode :nome "lisp" :extensoes ("lisp" "el" "clj") :estrutural-lisp #t)"#,
260 )
261 .unwrap();
262 let m = MajorMode::compile_from_sexp(&forms[0]).unwrap();
263 assert_eq!(m.nome, "lisp");
264 assert_eq!(m.estrutural_lisp, Some(true));
265 }
266
267 #[test]
268 fn register_all_populates_registry() {
269 EscribaConfig::register_all();
270 let kws = tatara_lisp::domain::registered_keywords();
271 for keyword in [
272 "defescriba",
273 "defkeymap",
274 "defcommand",
275 "defplugin",
276 "defmajor-mode",
277 "defminor-mode",
278 ] {
279 assert!(kws.contains(&keyword), "missing keyword: {keyword}");
280 }
281 }
282}