add config file
This commit is contained in:
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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,9 +108,9 @@ 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
@@ -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
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user