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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Configuration for both the daemon and client.

use std::{
  collections::{HashMap, HashSet},
  fs, io,
  path::PathBuf,
};

use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ConfigError {
  #[error("no configuration directory known for your system; please adjust XDG_CONFIG_HOME")]
  NoConfigDir,

  #[error("cannot read configuration: {err}")]
  CannotReadConfig { err: io::Error },

  #[error("cannot parse configuration: {err}")]
  CannotParseConfig { err: String },
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Config {
  pub highlight: HighlightConfig,

  #[serde(flatten)]
  pub languages: LanguagesConfig,
}

impl Config {
  /// Load the config from the default user location (XDG).
  pub fn load_from_xdg() -> Result<Config, ConfigError> {
    let dir = dirs::config_dir().ok_or(ConfigError::NoConfigDir)?;
    let path = dir.join("kak-tree-sitter/config.toml");
    let content = fs::read_to_string(path).map_err(|err| ConfigError::CannotReadConfig { err })?;

    toml::from_str(&content).map_err(|err| ConfigError::CannotParseConfig {
      err: err.to_string(),
    })
  }
}

/// Highlight configuration.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct HighlightConfig {
  pub groups: HashSet<String>,
}

/// Languages configuration.
///
/// It is possible to set the URI and path where to fetch grammars, as well as queries.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct LanguagesConfig {
  pub language: HashMap<String, LanguageConfig>,
}

impl LanguagesConfig {
  /// Get the configuration for `lang`.
  pub fn get_lang_conf(&self, lang: impl AsRef<str>) -> Option<&LanguageConfig> {
    self.language.get(lang.as_ref())
  }

  /// Get the directory where all grammars live in.
  pub fn get_grammars_dir() -> Option<PathBuf> {
    dirs::data_dir().map(|dir| dir.join("kak-tree-sitter/grammars"))
  }

  /// Get the grammar path for a given language.
  pub fn get_grammar_path(lang: impl AsRef<str>) -> Option<PathBuf> {
    let lang = lang.as_ref();
    dirs::data_dir().map(|dir| dir.join(format!("kak-tree-sitter/grammars/{lang}.so")))
  }

  /// Get the queries directory for a given language.
  pub fn get_queries_dir(lang: impl AsRef<str>) -> Option<PathBuf> {
    let lang = lang.as_ref();
    dirs::data_dir().map(|dir| dir.join(format!("kak-tree-sitter/queries/{lang}")))
  }
}

/// Specific language configuration.
///
/// Not providing one will default to the default language configuration.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LanguageConfig {
  pub grammar: LanguageGrammarConfig,
  pub queries: LanguageQueriesConfig,
  #[serde(default)]
  pub remove_default_highlighter: RemoveDefaultHighlighter,
}

#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct RemoveDefaultHighlighter(pub bool);

impl Default for RemoveDefaultHighlighter {
  fn default() -> Self {
    RemoveDefaultHighlighter(true)
  }
}

impl RemoveDefaultHighlighter {
  pub fn to_bool(self) -> bool {
    self.0
  }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LanguageGrammarConfig {
  /// URL to fetch the language grammar from.
  pub url: String,

  /// Pin to use. Can be a commit, a branch, etc.
  pub pin: Option<String>,

  /// Path to find the grammar source.
  pub path: PathBuf,

  /// Compile command to run to compile the grammar.
  ///
  /// Should always be `cc` but who knows.
  pub compile: String,

  /// Compiler arguments.
  ///
  /// Wherever the language must appear, you can use `{lang}` as placeholder.
  pub compile_args: Vec<String>,

  /// Compiler extra arguments.
  ///
  /// Should be used to pass optimization and debug flags, mainly.
  pub compile_flags: Vec<String>,

  /// Link command to run to link the grammar.
  ///
  /// Should always be `cc`, but, still, who knows.
  pub link: String,

  /// Linker arguments.
  ///
  /// Wherever the language must appear, you can use `{lang} as placeholder.
  pub link_args: Vec<String>,

  /// Linker extra arguments.
  ///
  /// Should be used to pass optimization and debug flags, mainly.
  pub link_flags: Vec<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LanguageQueriesConfig {
  /// URL to fetch the language queries from.
  ///
  /// The language is inserted via the `{lang}` placeholder.
  ///
  /// If set to [`None`], the URL used will be the same as the one for the grammar and no fetch will be done (the
  /// grammar is required).
  pub url: Option<String>,

  /// Pin to use. Can be a commit, a branch, etc.
  pub pin: Option<String>,

  /// Path to go to where to find the queries directory.
  pub path: PathBuf,
}