1use color_eyre::eyre::eyre;
7use nvim_oxi::Dictionary;
8use nvim_oxi::api::SuperIterator;
9use nvim_oxi::api::opts::GetHighlightOpts;
10use nvim_oxi::api::opts::GetHighlightOptsBuilder;
11use nvim_oxi::api::opts::SetHighlightOpts;
12use nvim_oxi::api::opts::SetHighlightOptsBuilder;
13use nvim_oxi::api::types::GetHlInfos;
14use nvim_oxi::api::types::HighlightInfos;
15
16const GLOBAL_BG: &str = "#002020";
17const GLOBAL_FG: &str = "#dcdcd7";
18
19const CURSOR_BG: &str = "white";
20const CURSOR_FG: &str = "black";
21const NON_TEXT_FG: &str = "NvimDarkGrey4";
22const COMMENTS_FG: &str = "NvimLightGrey4";
23const NONE: &str = "none";
24
25const DIAG_ERROR_FG: &str = "#ec635c";
26const DIAG_OK_FG: &str = "#8ce479";
27const DIAG_WARN_FG: &str = "#ffaa33";
28const DIAG_HINT_FG: &str = "NvimLightGrey3";
29const DIAG_INFO_FG: &str = "white";
30
31const GITSIGNS_ADDED: &str = DIAG_OK_FG;
32const GITSIGNS_CHANGED: &str = "#6a6adf";
33const GITSIGNS_REMOVED: &str = DIAG_ERROR_FG;
34
35const TREESITTER_CONTEXT_BG: &str = "NvimDarkGrey3";
36
37const DIAGNOSTICS_FG: [(&str, &str); 5] = [
38 ("Error", DIAG_ERROR_FG),
39 ("Warn", DIAG_WARN_FG),
40 ("Ok", DIAG_OK_FG),
41 ("Hint", DIAG_HINT_FG),
42 ("Info", DIAG_INFO_FG),
43];
44
45const GITSIGNS_FG: [(&str, &str); 3] = [
46 ("Added", GITSIGNS_ADDED),
47 ("Changed", GITSIGNS_CHANGED),
48 ("Removed", GITSIGNS_REMOVED),
49];
50
51pub fn dict() -> Dictionary {
53 dict! {
54 "set": fn_from!(set),
55 }
56}
57
58#[allow(clippy::needless_pass_by_value)]
60pub fn set(colorscheme: Option<String>) {
61 if let Some(cs) = colorscheme {
62 let _ = ytil_noxi::common::exec_vim_cmd("colorscheme", Some(&[cs]));
63 }
64
65 let opts = crate::vim_opts::global_scope();
66 crate::vim_opts::set("background", "dark", &opts);
67 crate::vim_opts::set("termguicolors", true, &opts);
68
69 let non_text_hl = get_default_hl_opts().foreground(NON_TEXT_FG).background(NONE).build();
70
71 for (hl_name, hl_opts) in [
72 (
73 "Cursor",
74 get_default_hl_opts()
75 .foreground(CURSOR_FG)
76 .background(CURSOR_BG)
77 .build(),
78 ),
79 ("CursorLine", get_default_hl_opts().foreground(NONE).build()),
80 ("ErrorMsg", get_default_hl_opts().foreground(DIAG_ERROR_FG).build()),
81 (
82 "MsgArea",
83 get_default_hl_opts().foreground(COMMENTS_FG).background(NONE).build(),
84 ),
85 ("LineNr", non_text_hl.clone()),
86 ("Normal", get_default_hl_opts().background(GLOBAL_BG).build()),
87 ("NormalFloat", get_default_hl_opts().background(GLOBAL_BG).build()),
88 ("StatusLine", non_text_hl),
89 (
90 "TreesitterContext",
91 get_default_hl_opts().background(TREESITTER_CONTEXT_BG).build(),
92 ),
93 (
94 "WinSeparator",
95 get_default_hl_opts().foreground(TREESITTER_CONTEXT_BG).build(),
96 ),
97 ("@variable", get_default_hl_opts().foreground(GLOBAL_FG).build()),
99 ("Comment", get_default_hl_opts().foreground(COMMENTS_FG).build()),
100 ("Constant", get_default_hl_opts().foreground(GLOBAL_FG).build()),
101 ("Delimiter", get_default_hl_opts().foreground(GLOBAL_FG).build()),
102 ("PreProc", get_default_hl_opts().foreground(GLOBAL_FG).build()),
104 ("Operator", get_default_hl_opts().foreground(GLOBAL_FG).build()),
105 (
106 "Statement",
107 get_default_hl_opts().foreground(GLOBAL_FG).bold(true).build(),
108 ),
109 ("Type", get_default_hl_opts().foreground(GLOBAL_FG).build()),
110 ] {
111 set_hl(0, hl_name, &hl_opts);
112 }
113
114 for (lvl, fg) in DIAGNOSTICS_FG {
115 let _ = get_overridden_set_hl_opts(
117 &format!("Diagnostic{lvl}"),
118 |mut hl_opts| hl_opts.foreground(fg).background(NONE).bold(true).build(),
119 None,
120 )
121 .map(|set_hl_opts| {
122 set_hl(0, &format!("Diagnostic{lvl}"), &set_hl_opts);
123 set_hl(0, &format!("DiagnosticStatusLine{lvl}"), &set_hl_opts);
124 });
125
126 let diag_underline_hl_name = format!("DiagnosticUnderline{lvl}");
127 let _ = get_overridden_set_hl_opts(
129 &diag_underline_hl_name,
130 |mut hl_opts| hl_opts.special(fg).background(NONE).build(),
131 None,
132 )
133 .map(|set_hl_opts| set_hl(0, &diag_underline_hl_name, &set_hl_opts));
134 }
135
136 for (hl_name, fg) in GITSIGNS_FG {
137 set_hl(0, hl_name, &get_default_hl_opts().foreground(fg).build());
138 }
139}
140
141fn get_overridden_set_hl_opts(
151 hl_name: &str,
152 override_set_hl_opts: impl FnMut(SetHighlightOptsBuilder) -> SetHighlightOpts,
153 opts_builder: Option<GetHighlightOptsBuilder>,
154) -> color_eyre::Result<SetHighlightOpts> {
155 let mut get_hl_opts = opts_builder.unwrap_or_default();
156 let hl_infos = get_hl_single(0, &get_hl_opts.name(hl_name).build())?;
157 hl_opts_from_hl_infos(&hl_infos).map(override_set_hl_opts)
158}
159
160fn get_default_hl_opts() -> SetHighlightOptsBuilder {
162 SetHighlightOptsBuilder::default()
163}
164
165fn set_hl(ns_id: u32, hl_name: &str, hl_opts: &SetHighlightOpts) {
174 if let Err(err) = nvim_oxi::api::set_hl(ns_id, hl_name, hl_opts) {
175 ytil_noxi::notify::error(format!(
176 "error setting highlight opts | hl_opts={hl_opts:#?} hl_name={hl_name:?} namespace={ns_id:?} error={err:#?}"
177 ));
178 }
179}
180
181fn get_hl_single(ns_id: u32, hl_opts: &GetHighlightOpts) -> color_eyre::Result<HighlightInfos> {
187 get_hl(ns_id, hl_opts).and_then(|hl| match hl {
188 GetHlInfos::Single(highlight_infos) => Ok(highlight_infos),
189 GetHlInfos::Map(hl_infos) => Err(eyre!(
190 "multiple highlight infos returned | hl_infos={:#?} hl_opts={hl_opts:#?}",
191 hl_infos.collect::<Vec<_>>()
192 )),
193 })
194}
195
196#[allow(dead_code)]
202fn get_hl_multiple(
203 ns_id: u32,
204 hl_opts: &GetHighlightOpts,
205) -> color_eyre::Result<Vec<(nvim_oxi::String, HighlightInfos)>> {
206 get_hl(ns_id, hl_opts).and_then(|hl| match hl {
207 GetHlInfos::Single(hl_info) => Err(eyre!(
208 "single highlight info returned | hl_info={hl_info:#?} hl_opts={hl_opts:#?}",
209 )),
210 GetHlInfos::Map(hl_infos) => Ok(hl_infos.into_iter().collect()),
211 })
212}
213
214fn get_hl(
219 ns_id: u32,
220 hl_opts: &GetHighlightOpts,
221) -> color_eyre::Result<GetHlInfos<impl SuperIterator<(nvim_oxi::String, HighlightInfos)>>> {
222 nvim_oxi::api::get_hl(ns_id, hl_opts)
223 .inspect_err(|err| {
224 ytil_noxi::notify::error(format!(
225 "cannot get highlight infos | hl_opts={hl_opts:#?} error={err:#?}"
226 ));
227 })
228 .map_err(From::from)
229}
230
231fn hl_opts_from_hl_infos(hl_infos: &HighlightInfos) -> color_eyre::Result<SetHighlightOptsBuilder> {
238 let mut opts = get_default_hl_opts();
239 hl_infos.altfont.map(|value| opts.altfont(value));
240 hl_infos
241 .background
242 .map(|value| opts.background(&decimal_to_hex_color(value)));
243 hl_infos.bg_indexed.map(|value| opts.bg_indexed(value));
244 hl_infos
245 .blend
246 .map(u8::try_from)
247 .transpose()
248 .inspect_err(|err| {
249 ytil_noxi::notify::error(format!(
250 "cannot convert blend value to u8 | value={:?} error={err:#?}",
251 hl_infos.blend
252 ));
253 })?
254 .map(|value| opts.blend(value));
255 hl_infos.bold.map(|value| opts.bold(value));
256 hl_infos.fallback.map(|value| opts.fallback(value));
257 hl_infos.fg_indexed.map(|value| opts.fg_indexed(value));
258 hl_infos.force.map(|value| opts.force(value));
259 hl_infos
260 .foreground
261 .map(|value| opts.foreground(&decimal_to_hex_color(value)));
262 hl_infos.italic.map(|value| opts.italic(value));
263 hl_infos.reverse.map(|value| opts.reverse(value));
264 hl_infos.special.map(|value| opts.special(&decimal_to_hex_color(value)));
265 hl_infos.standout.map(|value| opts.standout(value));
266 hl_infos.strikethrough.map(|value| opts.strikethrough(value));
267 hl_infos.undercurl.map(|value| opts.undercurl(value));
268 hl_infos.underdash.map(|value| opts.underdashed(value));
269 hl_infos.underdot.map(|value| opts.underdotted(value));
270 hl_infos.underline.map(|value| opts.underline(value));
271 Ok(opts)
272}
273
274fn decimal_to_hex_color(decimal: u32) -> String {
276 format!("#{decimal:06X}")
277}