diff --git a/README.md b/README.md index 3392611..b6a86b8 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,7 @@ currently, hexapoda is very unpolished, and missing some major features. if you' ### notable features that are missing (for now) -- inserting bytes - - only replacing and deleting right now -- custom keybinds - search - diffing +- inserting bytes + - only replacing and deleting right now diff --git a/src/action.rs b/src/action.rs index d35c832..4eebbf2 100644 --- a/src/action.rs +++ b/src/action.rs @@ -27,10 +27,10 @@ impl TryFrom<&str> for Action { type Error = String; fn try_from(string: &str) -> Result { - AppAction::try_from(string).map(Action::from) - .or_else(|_| BufferAction::try_from(string).map(Action::from)) - .or_else(|_| CursorAction::try_from(string).map(Action::from)) - .map_err(|_| format!("invalid action: {}", string)) + AppAction::try_from(string).map(Self::from) + .or_else(|()| BufferAction::try_from(string).map(Self::from)) + .or_else(|()| CursorAction::try_from(string).map(Self::from)) + .map_err(|()| format!("invalid action: {string}")) } } @@ -1249,13 +1249,13 @@ const fn nat_to_int_if_different(nat: u64, bytes: usize) -> Option { fn nat_to_int_tests() { assert_eq!(nat_to_int_if_different(0, 1), None); assert_eq!(nat_to_int_if_different(i8::MAX as u64, 1), None); - assert_eq!(nat_to_int_if_different(i8::MAX as u64 + 1, 1), Some(i8::MIN as i64)); - assert_eq!(nat_to_int_if_different(u8::MAX as u64, 1), Some(-1)); + assert_eq!(nat_to_int_if_different(i8::MAX as u64 + 1, 1), Some(i8::MIN.into())); + assert_eq!(nat_to_int_if_different(u8::MAX.into(), 1), Some(-1)); assert_eq!(nat_to_int_if_different(0, 2), None); assert_eq!(nat_to_int_if_different(i16::MAX as u64, 2), None); - assert_eq!(nat_to_int_if_different(i16::MAX as u64 + 1, 2), Some(i16::MIN as i64)); - assert_eq!(nat_to_int_if_different(u16::MAX as u64, 2), Some(-1)); + assert_eq!(nat_to_int_if_different(i16::MAX as u64 + 1, 2), Some(i16::MIN.into())); + assert_eq!(nat_to_int_if_different(u16::MAX.into(), 2), Some(-1)); } // or 0 if no mark is before diff --git a/src/app/mod.rs b/src/app/mod.rs index f1c6abf..c5023c9 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,7 +1,7 @@ -use std::{env, process::exit, time::Duration}; +use std::{env::{self}, io::{self}, process::exit, time::Duration}; use crossterm::{ExecutableCommand, event::{self, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}, terminal::window_size}; use ratatui::{DefaultTerminal, style::Stylize, text::Span}; -use crate::{BYTES_PER_LINE, action::AppAction, buffer::Buffer, config::Config, cursor::Cursor}; +use crate::{BYTES_PER_LINE, action::AppAction, buffer::Buffer, config::{Config, ConfigInitError}, cursor::Cursor}; mod widget; @@ -29,6 +29,29 @@ pub struct WindowSize { impl App { pub fn new() -> Self { + let config = { + let config = Config::init(); + + match &config { + Err(ConfigInitError::IO(io_error)) if io_error.kind() != io::ErrorKind::NotFound => { + eprintln!("IO error while reading config, press to continue with default config"); + + let mut temp = String::new(); + let _ = io::stdin().read_line(&mut temp); + } + Err(ConfigInitError::Deserialization(deserialization_error)) => { + eprintln!("bad config: {deserialization_error}"); + eprintln!("press to continue with default config"); + + let mut temp = String::new(); + let _ = io::stdin().read_line(&mut temp); + } + _ => {} + } + + config.unwrap_or_default() + }; + let buffers: Vec = env::args() .skip(1) .map(Into::into) @@ -50,7 +73,7 @@ impl App { }; Self { - config: Config::default(), + config, buffers, current_buffer_index: 0, diff --git a/src/buffer/widget.rs b/src/buffer/widget.rs index 5334868..cee27c0 100644 --- a/src/buffer/widget.rs +++ b/src/buffer/widget.rs @@ -98,7 +98,7 @@ mod address { } } - pub fn style_for_address(address: usize) -> Style { + pub const fn style_for_address(address: usize) -> Style { if address.is_multiple_of(0x100) { Style::new().fg(Color::Rgb(0x68, 0x99, 0xA0)) } else { @@ -108,9 +108,9 @@ mod address { } mod hex { - use std::{borrow::Cow, iter, mem}; - use itertools::{Itertools, repeat_n}; - use ratatui::{style::{Color, Style, Stylize}, text::Span}; + use std::{borrow::Cow, iter::{self, repeat_n}, mem}; + use itertools::Itertools; +use ratatui::{style::{Color, Style, Stylize}, text::Span}; use crate::{BYTES_PER_CHUNK, BYTES_PER_LINE, CHUNKS_PER_LINE, buffer::{Buffer, Mode, PartialAction}, cardinality::HasCardinality, cursor::InCursor, custom_greys::CustomGreys, empty_span::empty_span}; @@ -173,7 +173,6 @@ mod hex { address: usize, bytes: &[u8; BYTES_PER_CHUNK] ) -> impl Iterator> { - #[allow(unstable_name_collisions)] iter::once(self.render_large_space_before(address)) .chain( bytes @@ -195,7 +194,6 @@ mod hex { address: usize, bytes: &[u8] ) -> impl Iterator> { - #[allow(unstable_name_collisions)] iter::once(self.render_large_space_before(address)) .chain( bytes diff --git a/src/config.rs b/src/config.rs index ae67c49..9fe7ba5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use std::{collections::{HashMap, hash_map::Entry}, fmt::{self, Formatter}}; +use std::{collections::{HashMap, hash_map::Entry}, env::{self, home_dir}, fmt::{self, Formatter}, fs::read_to_string, io, path::PathBuf}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crate::{action::{Action, AppAction, BufferAction, CursorAction}, buffer::{Mode, PartialAction}}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::{Error, MapAccess, Unexpected, Visitor}, ser::SerializeMap}; @@ -30,6 +30,46 @@ pub struct Keypress { modifiers: KeyModifiers } +impl Config { + #[cfg(unix)] + fn path() -> Option { + env::var_os("XDG_CONFIG_HOME") + .map(PathBuf::from) + .take_if(|xdg_config_home| xdg_config_home.is_absolute()) + .or_else(|| home_dir().map(|home| home.join(".config"))) + .map(|config_path| config_path.join("hexapoda.toml")) + } + + #[cfg(windows)] + fn path() -> Option { + // this isn't technically the right way but it should be good enough + home_dir().map(|home| home.join("AppData").join("Roaming")) + } + + pub fn init() -> Result { + let path = Self::path().ok_or(ConfigInitError::NoConfigPath)?; + let raw_config = read_to_string(path)?; + + Ok(toml::from_str(&raw_config)?) + } +} + +pub enum ConfigInitError { + NoConfigPath, IO(io::Error), Deserialization(toml::de::Error) +} + +impl From for ConfigInitError { + fn from(error: io::Error) -> Self { + Self::IO(error) + } +} + +impl From for ConfigInitError { + fn from(error: toml::de::Error) -> Self { + Self::Deserialization(error) + } +} + impl Serialize for ModeConfig { fn serialize(&self, serializer: S) -> Result { let mut map = serializer.serialize_map(None)?; @@ -47,7 +87,7 @@ impl Serialize for ModeConfig { let Some(partial_action) = partial_action else { continue }; map.serialize_entry( - partial_action.into(), + partial_action, keybinds )?; } @@ -175,7 +215,7 @@ impl TryFrom<&str> for Keypress { ), modifiers: modifier_from_character( string.chars().nth(0).unwrap() - ).ok_or(format!("unknown modifier: {}", string.chars().nth(0).unwrap()))?, + ).ok_or_else(|| format!("unknown modifier: {}", string.chars().nth(0).unwrap()))?, }) } 1 => { @@ -185,7 +225,7 @@ impl TryFrom<&str> for Keypress { ).into() ) } - _ => Err(format!("invalid keypress: {}. only one modifier is allowed", string)) + _ => Err(format!("invalid keypress: {string}. only one modifier is allowed")) } } } @@ -202,7 +242,7 @@ impl From<&Keypress> for String { impl From for String { fn from(value: Keypress) -> Self { - String::from(&value) + Self::from(&value) } } diff --git a/src/main.rs b/src/main.rs index e1de11c..c778b55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,14 @@ #![warn(clippy::pedantic, clippy::nursery)] #![allow(clippy::cast_possible_truncation)] +#![allow(clippy::enum_glob_use)] #![feature(get_disjoint_mut_helpers)] #![feature(exact_bitshifts)] #![feature(hash_set_entry)] #![feature(trim_prefix_suffix)] -use std::fs::read_to_string; - use app::App; use crossterm::{QueueableCommand, event::{DisableMouseCapture, EnableMouseCapture}}; -use crate::config::Config; - mod app; mod buffer; mod config; @@ -59,16 +56,6 @@ const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE; // - how to fit??! `-128` longer than `80` fn main() { - // let config = Config::default(); - // let toml_string = toml::to_string_pretty(&config); - // println!("{}", toml_string.unwrap()); - - // let string = read_to_string("/Users/simonomi/Desktop/hexapoda_config.toml").unwrap(); - // let config: Config = toml::from_str(&string).unwrap(); - // dbg!(config.0); - - // return; - let mut app = App::new(); let mut terminal = ratatui::init(); crossterm::terminal::enable_raw_mode().unwrap();