add config file

This commit is contained in:
alice pellerin
2026-04-08 16:24:17 -05:00
parent d45e0a5032
commit 61f3dd0c7b
6 changed files with 86 additions and 39 deletions
+2 -3
View File
@@ -25,8 +25,7 @@ currently, hexapoda is very unpolished, and missing some major features. if you'
### notable features that are missing (for now) ### notable features that are missing (for now)
- inserting bytes
- only replacing and deleting right now
- custom keybinds
- search - search
- diffing - diffing
- inserting bytes
- only replacing and deleting right now
+8 -8
View File
@@ -27,10 +27,10 @@ impl TryFrom<&str> for Action {
type Error = String; type Error = String;
fn try_from(string: &str) -> Result<Self, String> { fn try_from(string: &str) -> Result<Self, String> {
AppAction::try_from(string).map(Action::from) AppAction::try_from(string).map(Self::from)
.or_else(|_| BufferAction::try_from(string).map(Action::from)) .or_else(|()| BufferAction::try_from(string).map(Self::from))
.or_else(|_| CursorAction::try_from(string).map(Action::from)) .or_else(|()| CursorAction::try_from(string).map(Self::from))
.map_err(|_| format!("invalid action: {}", string)) .map_err(|()| format!("invalid action: {string}"))
} }
} }
@@ -1249,13 +1249,13 @@ const fn nat_to_int_if_different(nat: u64, bytes: usize) -> Option<i64> {
fn nat_to_int_tests() { fn nat_to_int_tests() {
assert_eq!(nat_to_int_if_different(0, 1), None); 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), 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(i8::MAX as u64 + 1, 1), Some(i8::MIN.into()));
assert_eq!(nat_to_int_if_different(u8::MAX as u64, 1), Some(-1)); 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(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, 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(i16::MAX as u64 + 1, 2), Some(i16::MIN.into()));
assert_eq!(nat_to_int_if_different(u16::MAX as u64, 2), Some(-1)); assert_eq!(nat_to_int_if_different(u16::MAX.into(), 2), Some(-1));
} }
// or 0 if no mark is before // or 0 if no mark is before
+26 -3
View File
@@ -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 crossterm::{ExecutableCommand, event::{self, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}, terminal::window_size};
use ratatui::{DefaultTerminal, style::Stylize, text::Span}; 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; mod widget;
@@ -29,6 +29,29 @@ pub struct WindowSize {
impl App { impl App {
pub fn new() -> Self { 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 <ENTER> 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 <ENTER> to continue with default config");
let mut temp = String::new();
let _ = io::stdin().read_line(&mut temp);
}
_ => {}
}
config.unwrap_or_default()
};
let buffers: Vec<Buffer> = env::args() let buffers: Vec<Buffer> = env::args()
.skip(1) .skip(1)
.map(Into::into) .map(Into::into)
@@ -50,7 +73,7 @@ impl App {
}; };
Self { Self {
config: Config::default(), config,
buffers, buffers,
current_buffer_index: 0, current_buffer_index: 0,
+3 -5
View File
@@ -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) { if address.is_multiple_of(0x100) {
Style::new().fg(Color::Rgb(0x68, 0x99, 0xA0)) Style::new().fg(Color::Rgb(0x68, 0x99, 0xA0))
} else { } else {
@@ -108,8 +108,8 @@ mod address {
} }
mod hex { mod hex {
use std::{borrow::Cow, iter, mem}; use std::{borrow::Cow, iter::{self, repeat_n}, mem};
use itertools::{Itertools, repeat_n}; use itertools::Itertools;
use ratatui::{style::{Color, Style, Stylize}, text::Span}; 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}; 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, address: usize,
bytes: &[u8; BYTES_PER_CHUNK] bytes: &[u8; BYTES_PER_CHUNK]
) -> impl Iterator<Item=Span<'static>> { ) -> impl Iterator<Item=Span<'static>> {
#[allow(unstable_name_collisions)]
iter::once(self.render_large_space_before(address)) iter::once(self.render_large_space_before(address))
.chain( .chain(
bytes bytes
@@ -195,7 +194,6 @@ mod hex {
address: usize, address: usize,
bytes: &[u8] bytes: &[u8]
) -> impl Iterator<Item=Span<'static>> { ) -> impl Iterator<Item=Span<'static>> {
#[allow(unstable_name_collisions)]
iter::once(self.render_large_space_before(address)) iter::once(self.render_large_space_before(address))
.chain( .chain(
bytes bytes
+45 -5
View File
@@ -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 crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crate::{action::{Action, AppAction, BufferAction, CursorAction}, buffer::{Mode, PartialAction}}; use crate::{action::{Action, AppAction, BufferAction, CursorAction}, buffer::{Mode, PartialAction}};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::{Error, MapAccess, Unexpected, Visitor}, ser::SerializeMap}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::{Error, MapAccess, Unexpected, Visitor}, ser::SerializeMap};
@@ -30,6 +30,46 @@ pub struct Keypress {
modifiers: KeyModifiers modifiers: KeyModifiers
} }
impl Config {
#[cfg(unix)]
fn path() -> Option<PathBuf> {
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<PathBuf> {
// 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<Self, ConfigInitError> {
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<io::Error> for ConfigInitError {
fn from(error: io::Error) -> Self {
Self::IO(error)
}
}
impl From<toml::de::Error> for ConfigInitError {
fn from(error: toml::de::Error) -> Self {
Self::Deserialization(error)
}
}
impl Serialize for ModeConfig { impl Serialize for ModeConfig {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(None)?; let mut map = serializer.serialize_map(None)?;
@@ -47,7 +87,7 @@ impl Serialize for ModeConfig {
let Some(partial_action) = partial_action else { continue }; let Some(partial_action) = partial_action else { continue };
map.serialize_entry( map.serialize_entry(
partial_action.into(), partial_action,
keybinds keybinds
)?; )?;
} }
@@ -175,7 +215,7 @@ impl TryFrom<&str> for Keypress {
), ),
modifiers: modifier_from_character( modifiers: modifier_from_character(
string.chars().nth(0).unwrap() 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 => { 1 => {
@@ -185,7 +225,7 @@ impl TryFrom<&str> for Keypress {
).into() ).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<Keypress> for String { impl From<Keypress> for String {
fn from(value: Keypress) -> Self { fn from(value: Keypress) -> Self {
String::from(&value) Self::from(&value)
} }
} }
+1 -14
View File
@@ -1,17 +1,14 @@
#![warn(clippy::pedantic, clippy::nursery)] #![warn(clippy::pedantic, clippy::nursery)]
#![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_possible_truncation)]
#![allow(clippy::enum_glob_use)]
#![feature(get_disjoint_mut_helpers)] #![feature(get_disjoint_mut_helpers)]
#![feature(exact_bitshifts)] #![feature(exact_bitshifts)]
#![feature(hash_set_entry)] #![feature(hash_set_entry)]
#![feature(trim_prefix_suffix)] #![feature(trim_prefix_suffix)]
use std::fs::read_to_string;
use app::App; use app::App;
use crossterm::{QueueableCommand, event::{DisableMouseCapture, EnableMouseCapture}}; use crossterm::{QueueableCommand, event::{DisableMouseCapture, EnableMouseCapture}};
use crate::config::Config;
mod app; mod app;
mod buffer; mod buffer;
mod config; mod config;
@@ -59,16 +56,6 @@ const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
// - how to fit??! `-128` longer than `80` // - how to fit??! `-128` longer than `80`
fn main() { 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 app = App::new();
let mut terminal = ratatui::init(); let mut terminal = ratatui::init();
crossterm::terminal::enable_raw_mode().unwrap(); crossterm::terminal::enable_raw_mode().unwrap();