use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Default, Deserialize)]
#[serde(default)]
pub struct Config {
pub layout: LayoutConfig,
pub status: StatusConfig,
pub cursor: CursorConfig,
pub editor: EditorConfig,
pub input: InputConfig,
pub markdown: MarkdownConfig,
pub theme: ThemeConfig,
pub glyphs: GlyphConfig,
pub keys: KeysConfig,
pub splash: SplashConfig,
pub tree: TreeConfig,
pub transclude: TranscludeConfig,
pub hints: HintsConfig,
}
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct LayoutConfig {
pub measure: u16,
pub align: String,
pub text_align: String,
pub padding_top: u16,
pub padding_bottom: u16,
pub hanging_indent: bool,
pub line_spacing: u16,
pub scroll_hint: bool,
pub typewriter: bool,
pub typewriter_anchor: f32,
pub focus: String,
pub numbers: String,
pub conceal: bool,
pub stable_gutter: bool,
}
impl Default for LayoutConfig {
fn default() -> Self {
Self {
measure: 72,
align: "center".into(),
text_align: "left".into(),
padding_top: 2,
padding_bottom: 2,
hanging_indent: true,
line_spacing: 0,
scroll_hint: true,
typewriter: false,
typewriter_anchor: 0.5,
focus: "off".into(),
numbers: "off".into(),
conceal: true,
stable_gutter: true,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct StatusConfig {
pub enabled: bool,
pub flash_ms: u64,
pub show: Vec<String>,
}
impl Default for StatusConfig {
fn default() -> Self {
Self {
enabled: true,
flash_ms: 1500,
show: vec![
"file".into(),
"modified".into(),
"position".into(),
"words".into(),
],
}
}
}
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct CursorConfig {
pub normal: String,
pub insert: String,
pub visual: String,
pub blink: bool,
}
impl Default for CursorConfig {
fn default() -> Self {
Self {
normal: "block".into(),
insert: "bar".into(),
visual: "block".into(),
blink: false,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct EditorConfig {
pub tab_width: usize,
pub expand_tab: bool,
pub auto_indent: bool,
pub auto_pair: bool,
pub trim_on_save: bool,
pub final_newline: String,
pub autosave: bool,
pub autosave_interval: u8,
pub autoreload: bool,
pub clipboard: bool,
pub clipboard_background: bool,
pub undo_coalesce_ms: u64,
pub scroll_off: u16,
pub list_renumber: bool,
pub list_tab: bool,
pub table_align: String,
}
impl Default for EditorConfig {
fn default() -> Self {
Self {
tab_width: 4,
expand_tab: true,
auto_indent: true,
auto_pair: false,
trim_on_save: false,
final_newline: "preserve".into(),
autosave: false,
autosave_interval: crate::fs::save::AutosaveInterval::DEFAULT,
autoreload: true,
clipboard: false,
clipboard_background: false,
undo_coalesce_ms: 400,
scroll_off: 3,
list_renumber: true,
list_tab: true,
table_align: "auto".into(),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct HintsConfig {
pub enabled: bool,
}
impl Default for HintsConfig {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct TreeConfig {
pub show_hidden: bool,
pub close_on_open: bool,
pub root: String,
}
impl Default for TreeConfig {
fn default() -> Self {
Self {
show_hidden: false,
close_on_open: false,
root: "file".into(),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct InputConfig {
pub sequence_timeout_ms: u64,
pub leader: String,
pub escape_alias: String,
pub mouse: bool,
}
impl Default for InputConfig {
fn default() -> Self {
Self {
sequence_timeout_ms: 500,
leader: " ".into(),
escape_alias: "jk".into(),
mouse: true,
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(default)]
pub struct MarkdownConfig {
pub tags: bool,
pub wiki_links: bool,
pub highlight: bool,
pub tables: bool,
pub code_syntax: bool,
pub plain_text_extensions: Vec<String>,
}
impl Default for MarkdownConfig {
fn default() -> Self {
Self {
tags: true,
wiki_links: true,
highlight: true,
tables: true,
code_syntax: true,
plain_text_extensions: vec![".txt".into()],
}
}
}
#[derive(Debug, Default, Deserialize)]
#[serde(default)]
pub struct ThemeConfig {
pub name: Option<String>,
#[serde(flatten)]
pub colors: HashMap<String, toml::Value>,
pub styles: HashMap<String, bool>,
}
#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(default)]
pub struct GlyphConfig {
pub nerd_fonts: bool,
pub bullet: String,
pub bullet_alt: String,
pub task_todo: String,
pub task_done: String,
pub quote_bar: String,
pub indent_guide: String,
pub fence_bar: String,
pub folder: String,
pub folder_open: String,
pub modified: String,
pub conflict: String,
pub scroll_hint: String,
pub rule: String,
}
impl Default for GlyphConfig {
fn default() -> Self {
Self {
nerd_fonts: true,
bullet: "•".into(),
bullet_alt: "◦".into(),
task_todo: "☐".into(),
task_done: "☑".into(),
quote_bar: "▎".into(),
indent_guide: "│".into(),
fence_bar: "▊".into(),
folder: "\u{f07b}".into(),
folder_open: "\u{f07c}".into(),
modified: "●".into(),
conflict: "⚠".into(),
scroll_hint: "▐".into(),
rule: "─".into(),
}
}
}
impl GlyphConfig {
pub fn ascii() -> Self {
Self {
nerd_fonts: false,
bullet: "*".into(),
bullet_alt: "-".into(),
task_todo: "[ ]".into(),
task_done: "[x]".into(),
quote_bar: "|".into(),
indent_guide: "|".into(),
fence_bar: "|".into(),
folder: "".into(),
folder_open: "".into(),
modified: "*".into(),
conflict: "!".into(),
scroll_hint: "|".into(),
rule: "-".into(),
}
}
pub fn apply_ascii_fallback(&mut self, was_set: impl Fn(&str) -> bool) {
if self.nerd_fonts {
return;
}
let ascii = GlyphConfig::ascii();
let fall_back = |key: &str, field: &mut String, becomes: String| {
if !was_set(key) {
*field = becomes;
}
};
fall_back("bullet", &mut self.bullet, ascii.bullet);
fall_back("bullet_alt", &mut self.bullet_alt, ascii.bullet_alt);
fall_back("task_todo", &mut self.task_todo, ascii.task_todo);
fall_back("task_done", &mut self.task_done, ascii.task_done);
fall_back("quote_bar", &mut self.quote_bar, ascii.quote_bar);
fall_back("indent_guide", &mut self.indent_guide, ascii.indent_guide);
fall_back("fence_bar", &mut self.fence_bar, ascii.fence_bar);
fall_back("folder", &mut self.folder, ascii.folder);
fall_back("folder_open", &mut self.folder_open, ascii.folder_open);
fall_back("modified", &mut self.modified, ascii.modified);
fall_back("conflict", &mut self.conflict, ascii.conflict);
fall_back("scroll_hint", &mut self.scroll_hint, ascii.scroll_hint);
fall_back("rule", &mut self.rule, ascii.rule);
}
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(default)]
pub struct TranscludeConfig {
#[serde(alias = "preview", deserialize_with = "bool_or_string")]
pub embed: String,
pub root: String,
pub max_depth: u8,
pub heading_offset: u8,
pub strip_frontmatter: bool,
pub border: bool,
}
fn bool_or_string<'de, D>(d: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Either {
Bool(bool),
Text(String),
}
Ok(match Either::deserialize(d)? {
Either::Bool(true) => "short".into(),
Either::Bool(false) => "off".into(),
Either::Text(s) => s,
})
}
impl Default for TranscludeConfig {
fn default() -> Self {
Self {
embed: "none".into(),
root: ".".into(),
max_depth: 8,
heading_offset: 1,
strip_frontmatter: true,
border: true,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct SplashConfig {
pub art: String,
}
#[derive(Debug, Default, Deserialize)]
#[serde(default)]
pub struct KeysConfig {
pub normal: HashMap<String, String>,
pub insert: HashMap<String, String>,
pub visual: HashMap<String, String>,
}