1#![cfg_attr(allow_unknown_lints, allow(unknown_lints))]
5#![cfg_attr(allow_unknown_lints, allow(renamed_and_removed_lints))]
6#![deny(
8 future_incompatible,
9 nonstandard_style,
10 rust_2018_compatibility,
11 rust_2018_idioms,
12 rust_2021_compatibility,
13 unused,
14 warnings
15)]
16#![deny(
18 absolute_paths_not_starting_with_crate,
19 deprecated_in_future,
20 elided_lifetimes_in_paths,
21 explicit_outlives_requirements,
22 ffi_unwind_calls,
23 keyword_idents,
24 let_underscore_drop,
25 macro_use_extern_crate,
26 meta_variable_misuse,
27 missing_abi,
28 missing_copy_implementations,
29 missing_debug_implementations,
30 missing_docs,
31 non_ascii_idents,
32 noop_method_call,
33 pointer_structural_match,
34 rust_2021_incompatible_closure_captures,
35 rust_2021_incompatible_or_patterns,
36 rust_2021_prefixes_incompatible_syntax,
37 rust_2021_prelude_collisions,
38 single_use_lifetimes,
39 trivial_casts,
40 trivial_numeric_casts,
41 unreachable_pub,
42 unsafe_code,
43 unsafe_op_in_unsafe_fn,
44 unused_crate_dependencies,
45 unused_extern_crates,
46 unused_import_braces,
47 unused_lifetimes,
48 unused_macro_rules,
49 unused_qualifications,
50 unused_results,
51 unused_tuple_struct_fields,
52 variant_size_differences
53)]
54#![deny(clippy::all, clippy::cargo, clippy::pedantic, clippy::restriction)]
56#![cfg_attr(include_nightly_lints, deny(clippy::nursery))]
57#![allow(
58 clippy::arithmetic_side_effects,
59 clippy::arithmetic_side_effects,
60 clippy::blanket_clippy_restriction_lints,
61 clippy::bool_to_int_with_if,
62 clippy::default_numeric_fallback,
63 clippy::else_if_without_else,
64 clippy::expect_used,
65 clippy::float_arithmetic,
66 clippy::implicit_return,
67 clippy::indexing_slicing,
68 clippy::map_err_ignore,
69 clippy::missing_docs_in_private_items,
70 clippy::missing_trait_methods,
71 clippy::mod_module_files,
72 clippy::module_name_repetitions,
73 clippy::new_without_default,
74 clippy::non_ascii_literal,
75 clippy::option_if_let_else,
76 clippy::pub_use,
77 clippy::question_mark_used,
78 clippy::redundant_pub_crate,
79 clippy::ref_patterns,
80 clippy::std_instead_of_alloc,
81 clippy::std_instead_of_core,
82 clippy::tabs_in_doc_comments,
83 clippy::tests_outside_test_module,
84 clippy::too_many_lines,
85 clippy::unwrap_used
86)]
87#![deny(
88 rustdoc::bare_urls,
89 rustdoc::broken_intra_doc_links,
90 rustdoc::invalid_codeblock_attributes,
91 rustdoc::invalid_html_tags,
92 rustdoc::missing_crate_level_docs,
93 rustdoc::private_doc_tests,
94 rustdoc::private_intra_doc_links
95)]
96#![cfg_attr(
98 test,
99 allow(
100 let_underscore_drop,
101 clippy::cognitive_complexity,
102 clippy::let_underscore_must_use,
103 clippy::let_underscore_untyped,
104 clippy::needless_pass_by_value,
105 clippy::panic,
106 clippy::shadow_reuse,
107 clippy::shadow_unrelated,
108 clippy::undocumented_unsafe_blocks,
109 clippy::unimplemented,
110 clippy::unreachable
111 )
112)]
113#![cfg_attr(
115 include_nightly_lints,
116 allow(
117 clippy::arc_with_non_send_sync,
118 clippy::min_ident_chars,
119 clippy::needless_raw_strings,
120 clippy::pub_with_shorthand,
121 clippy::redundant_closure_call,
122 clippy::single_call_fn
123 )
124)]
125mod color;
143mod diff_ignore_whitespace_setting;
144mod diff_show_whitespace_setting;
145pub mod errors;
146mod git_config;
147mod key_bindings;
148mod theme;
149mod utils;
150
151#[cfg(test)]
152mod testutils;
153
154use git::Repository;
155use proc_macro2 as _;
157
158use self::utils::{get_bool, get_diff_ignore_whitespace, get_diff_show_whitespace, get_string, get_unsigned_integer};
159pub use self::{
160 color::Color,
161 diff_ignore_whitespace_setting::DiffIgnoreWhitespaceSetting,
162 diff_show_whitespace_setting::DiffShowWhitespaceSetting,
163 git_config::GitConfig,
164 key_bindings::KeyBindings,
165 theme::Theme,
166};
167use crate::errors::{ConfigError, ConfigErrorCause};
168
169const DEFAULT_SPACE_SYMBOL: &str = "\u{b7}"; const DEFAULT_TAB_SYMBOL: &str = "\u{2192}"; #[derive(Clone, Debug)]
174#[non_exhaustive]
175pub struct Config {
176 pub auto_select_next: bool,
178 pub diff_ignore_whitespace: DiffIgnoreWhitespaceSetting,
180 pub diff_ignore_blank_lines: bool,
182 pub diff_show_whitespace: DiffShowWhitespaceSetting,
184 pub diff_space_symbol: String,
186 pub diff_tab_symbol: String,
188 pub diff_tab_width: u32,
190 pub undo_limit: u32,
192 pub git: GitConfig,
194 pub key_bindings: KeyBindings,
196 pub theme: Theme,
198}
199
200impl Config {
201 #[inline]
203 #[must_use]
204 #[allow(clippy::missing_panics_doc)]
205 pub fn new() -> Self {
206 Self::new_with_config(None).unwrap() }
208
209 fn new_with_config(git_config: Option<&git::Config>) -> Result<Self, ConfigError> {
210 Ok(Self {
211 auto_select_next: get_bool(git_config, "interactive-rebase-tool.autoSelectNext", false)?,
212 diff_ignore_whitespace: get_diff_ignore_whitespace(
213 git_config,
214 "interactive-rebase-tool.diffIgnoreWhitespace",
215 )?,
216 diff_ignore_blank_lines: get_bool(git_config, "interactive-rebase-tool.diffIgnoreBlankLines", false)?,
217 diff_show_whitespace: get_diff_show_whitespace(git_config, "interactive-rebase-tool.diffShowWhitespace")?,
218 diff_space_symbol: get_string(
219 git_config,
220 "interactive-rebase-tool.diffSpaceSymbol",
221 DEFAULT_SPACE_SYMBOL,
222 )?,
223 diff_tab_symbol: get_string(git_config, "interactive-rebase-tool.diffTabSymbol", DEFAULT_TAB_SYMBOL)?,
224 diff_tab_width: get_unsigned_integer(git_config, "interactive-rebase-tool.diffTabWidth", 4)?,
225 undo_limit: get_unsigned_integer(git_config, "interactive-rebase-tool.undoLimit", 5000)?,
226 git: GitConfig::new_with_config(git_config)?,
227 key_bindings: KeyBindings::new_with_config(git_config)?,
228 theme: Theme::new_with_config(git_config)?,
229 })
230 }
231}
232
233impl TryFrom<&Repository> for Config {
234 type Error = ConfigError;
235
236 #[inline]
242 fn try_from(repo: &Repository) -> Result<Self, Self::Error> {
243 let config = repo
244 .load_config()
245 .map_err(|e| ConfigError::new_read_error("", ConfigErrorCause::GitError(e)))?;
246 Self::new_with_config(Some(&config))
247 }
248}
249
250impl TryFrom<&git::Config> for Config {
251 type Error = ConfigError;
252
253 #[inline]
254 fn try_from(config: &git::Config) -> Result<Self, Self::Error> {
255 Self::new_with_config(Some(config))
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use std::fmt::Debug;
262
263 use ::testutils::assert_err_eq;
264 use claim::assert_ok;
265 use git::testutil::with_temp_bare_repository;
266 use rstest::rstest;
267
268 use super::*;
269 use crate::testutils::{invalid_utf, with_git_config};
270
271 #[test]
272 fn new() {
273 let _config = Config::new();
274 }
275
276 #[test]
277 fn try_from_repository() {
278 with_temp_bare_repository(|repository| {
279 assert_ok!(Config::try_from(&repository));
280 });
281 }
282
283 #[test]
284 fn try_from_git_config() {
285 with_git_config(&[], |git_config| {
286 assert_ok!(Config::try_from(&git_config));
287 });
288 }
289
290 #[test]
291 fn try_from_git_config_error() {
292 with_git_config(
293 &["[interactive-rebase-tool]", "autoSelectNext = invalid"],
294 |git_config| {
295 _ = Config::try_from(&git_config).unwrap_err();
296 },
297 );
298 }
299
300 #[rstest]
301 #[case::auto_select_next_default("autoSelectNext", "", false, |config: Config| config.auto_select_next)]
302 #[case::auto_select_next_false("autoSelectNext", "false", false, |config: Config| config.auto_select_next)]
303 #[case::auto_select_next_true("autoSelectNext", "true", true, |config: Config| config.auto_select_next)]
304 #[case::diff_ignore_whitespace_default(
305 "diffIgnoreWhitespace",
306 "",
307 DiffIgnoreWhitespaceSetting::None,
308 |config: Config| config.diff_ignore_whitespace)
309 ]
310 #[case::diff_ignore_whitespace_true(
311 "diffIgnoreWhitespace",
312 "true",
313 DiffIgnoreWhitespaceSetting::All,
314 |config: Config| config.diff_ignore_whitespace)
315 ]
316 #[case::diff_ignore_whitespace_on(
317 "diffIgnoreWhitespace",
318 "on",
319 DiffIgnoreWhitespaceSetting::All,
320 |config: Config| config.diff_ignore_whitespace)
321 ]
322 #[case::diff_ignore_whitespace_all(
323 "diffIgnoreWhitespace",
324 "all",
325 DiffIgnoreWhitespaceSetting::All,
326 |config: Config| config.diff_ignore_whitespace)
327 ]
328 #[case::diff_ignore_whitespace_change(
329 "diffIgnoreWhitespace",
330 "change",
331 DiffIgnoreWhitespaceSetting::Change,
332 |config: Config| config.diff_ignore_whitespace)
333 ]
334 #[case::diff_ignore_whitespace_false(
335 "diffIgnoreWhitespace",
336 "false",
337 DiffIgnoreWhitespaceSetting::None,
338 |config: Config| config.diff_ignore_whitespace)
339 ]
340 #[case::diff_ignore_whitespace_off(
341 "diffIgnoreWhitespace",
342 "off",
343 DiffIgnoreWhitespaceSetting::None,
344 |config: Config| config.diff_ignore_whitespace)
345 ]
346 #[case::diff_ignore_whitespace_none(
347 "diffIgnoreWhitespace",
348 "none",
349 DiffIgnoreWhitespaceSetting::None,
350 |config: Config| config.diff_ignore_whitespace)
351 ]
352 #[case::diff_ignore_whitespace_mixed_case(
353 "diffIgnoreWhitespace",
354 "ChAnGe",
355 DiffIgnoreWhitespaceSetting::Change,
356 |config: Config| config.diff_ignore_whitespace)
357 ]
358 #[case::diff_ignore_blank_lines_default(
359 "diffIgnoreBlankLines",
360 "",
361 false,
362 |config: Config| config.diff_ignore_blank_lines
363 )]
364 #[case::diff_ignore_blank_lines_false(
365 "diffIgnoreBlankLines",
366 "false",
367 false,
368 |config: Config| config.diff_ignore_blank_lines
369 )]
370 #[case::diff_ignore_blank_lines_true(
371 "diffIgnoreBlankLines",
372 "true",
373 true,
374 |config: Config| config.diff_ignore_blank_lines
375 )]
376 #[case::diff_show_whitespace_default(
377 "diffShowWhitespace",
378 "",
379 DiffShowWhitespaceSetting::Both,
380 |config: Config| config.diff_show_whitespace)
381 ]
382 #[case::diff_show_whitespace_true(
383 "diffShowWhitespace",
384 "true",
385 DiffShowWhitespaceSetting::Both,
386 |config: Config| config.diff_show_whitespace)
387 ]
388 #[case::diff_show_whitespace_on(
389 "diffShowWhitespace",
390 "on",
391 DiffShowWhitespaceSetting::Both,
392 |config: Config| config.diff_show_whitespace)
393 ]
394 #[case::diff_show_whitespace_both(
395 "diffShowWhitespace",
396 "both",
397 DiffShowWhitespaceSetting::Both,
398 |config: Config| config.diff_show_whitespace)
399 ]
400 #[case::diff_show_whitespace_trailing(
401 "diffShowWhitespace",
402 "trailing",
403 DiffShowWhitespaceSetting::Trailing,
404 |config: Config| config.diff_show_whitespace)
405 ]
406 #[case::diff_show_whitespace_leading(
407 "diffShowWhitespace",
408 "leading",
409 DiffShowWhitespaceSetting::Leading,
410 |config: Config| config.diff_show_whitespace)
411 ]
412 #[case::diff_show_whitespace_false(
413 "diffShowWhitespace",
414 "false",
415 DiffShowWhitespaceSetting::None,
416 |config: Config| config.diff_show_whitespace)
417 ]
418 #[case::diff_show_whitespace_off(
419 "diffShowWhitespace",
420 "off",
421 DiffShowWhitespaceSetting::None,
422 |config: Config| config.diff_show_whitespace)
423 ]
424 #[case::diff_show_whitespace_none(
425 "diffShowWhitespace",
426 "none",
427 DiffShowWhitespaceSetting::None,
428 |config: Config| config.diff_show_whitespace)
429 ]
430 #[case::diff_show_whitespace_mixed_case(
431 "diffShowWhitespace",
432 "tRaIlInG",
433 DiffShowWhitespaceSetting::Trailing,
434 |config: Config| config.diff_show_whitespace)
435 ]
436 #[case::diff_tab_width_default("diffTabWidth", "", 4, |config: Config| config.diff_tab_width)]
437 #[case::diff_tab_width("diffTabWidth", "42", 42, |config: Config| config.diff_tab_width)]
438 #[case::diff_tab_symbol_default("diffTabSymbol", "", String::from("→"), |config: Config| config.diff_tab_symbol)]
439 #[case::diff_tab_symbol("diffTabSymbol", "|", String::from("|"), |config: Config| config.diff_tab_symbol)]
440 #[case::diff_tab_symbol("diffTabSymbol", "|", String::from("|"), |config: Config| config.diff_tab_symbol)]
441 #[case::diff_space_symbol_default(
442 "diffSpaceSymbol",
443 "",
444 String::from("·"),
445 |config: Config| config.diff_space_symbol)
446 ]
447 #[case::diff_space_symbol("diffSpaceSymbol", "-", String::from("-"), |config: Config| config.diff_space_symbol)]
448 #[case::undo_limit_default("undoLimit", "", 5000, |config: Config| config.undo_limit)]
449 #[case::undo_limit_default("undoLimit", "42", 42, |config: Config| config.undo_limit)]
450 pub(crate) fn theme_color<F, T>(
451 #[case] config_name: &str,
452 #[case] config_value: &str,
453 #[case] expected: T,
454 #[case] access: F,
455 ) where
456 F: Fn(Config) -> T + 'static,
457 T: Debug + PartialEq,
458 {
459 let value = format!("{config_name} = \"{config_value}\"");
460 let lines = if config_value.is_empty() {
461 vec![]
462 }
463 else {
464 vec!["[interactive-rebase-tool]", value.as_str()]
465 };
466 with_git_config(&lines, |config| {
467 let config = Config::new_with_config(Some(&config)).unwrap();
468 assert_eq!(access(config), expected);
469 });
470 }
471
472 #[rstest]
473 #[case::auto_select_next("autoSelectNext", "invalid", ConfigErrorCause::InvalidBoolean)]
474 #[case::diff_ignore_whitespace("diffIgnoreWhitespace", "invalid", ConfigErrorCause::InvalidDiffIgnoreWhitespace)]
475 #[case::diff_ignore_blank_lines("diffIgnoreBlankLines", "invalid", ConfigErrorCause::InvalidBoolean)]
476 #[case::diff_show_whitespace("diffShowWhitespace", "invalid", ConfigErrorCause::InvalidShowWhitespace)]
477 #[case::diff_tab_width_non_integer("diffTabWidth", "invalid", ConfigErrorCause::InvalidUnsignedInteger)]
478 #[case::diff_tab_width_non_poitive_integer("diffTabWidth", "-100", ConfigErrorCause::InvalidUnsignedInteger)]
479 #[case::undo_limit_non_integer("undoLimit", "invalid", ConfigErrorCause::InvalidUnsignedInteger)]
480 #[case::undo_limit_non_positive_integer("undoLimit", "-100", ConfigErrorCause::InvalidUnsignedInteger)]
481 fn value_parsing_invalid(#[case] config_name: &str, #[case] config_value: &str, #[case] cause: ConfigErrorCause) {
482 with_git_config(
483 &[
484 "[interactive-rebase-tool]",
485 format!("{config_name} = {config_value}").as_str(),
486 ],
487 |git_config| {
488 assert_err_eq!(
489 Config::new_with_config(Some(&git_config)),
490 ConfigError::new(
491 format!("interactive-rebase-tool.{config_name}").as_str(),
492 config_value,
493 cause
494 )
495 );
496 },
497 );
498 }
499
500 #[rstest]
501 #[case::diff_tab_symbol("diffIgnoreWhitespace")]
502 #[case::diff_tab_symbol("diffShowWhitespace")]
503 #[case::diff_tab_symbol("diffTabSymbol")]
504 #[case::diff_space_symbol("diffSpaceSymbol")]
505 fn value_parsing_invalid_utf(#[case] config_name: &str) {
506 with_git_config(
507 &[
508 "[interactive-rebase-tool]",
509 format!("{config_name} = {}", invalid_utf()).as_str(),
510 ],
511 |git_config| {
512 assert_err_eq!(
513 Config::new_with_config(Some(&git_config)),
514 ConfigError::new_read_error(
515 format!("interactive-rebase-tool.{config_name}").as_str(),
516 ConfigErrorCause::InvalidUtf
517 )
518 );
519 },
520 );
521 }
522}