Skip to content

Commit

Permalink
Merge pull request #93 from Etto48/fix/limited_buffers
Browse files Browse the repository at this point in the history
Fix/limited_buffers
  • Loading branch information
Etto48 authored Jul 14, 2024
2 parents 45ab9a0 + d42dffe commit 759c0f9
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 37 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ HexPatch is a binary patcher and editor with terminal user interface (TUI),
it's capable of disassembling instructions and assembling patches.
It supports a variety of architectures and file formats.
Also, it can edit remote files via SSH."""
version = "1.7.0"
version = "1.7.1"
authors = ["Ettore Ricci"]
edition = "2021"
readme = "docs/README.md"
Expand Down
2 changes: 1 addition & 1 deletion docs/PLUGIN_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ For more information on the types, see the following sections.

This type contains the settings of the application.
A setting can be accessed using the `.` operator with its full name (dots are replaced with underscores).
e.g. `context.settings.color_address_selected` or `context.settings.key_up`.
e.g. `context.settings.color_address_selected`, `context.settings.key_up`, `context.settings.app_history_limit`.

WARNING: You should get and set the setting altogether, e.g. `context.settings.color_address_selected = {fg = "Red"}`. Trying to set a single field will not work.

Expand Down
5 changes: 3 additions & 2 deletions src/app/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ impl App {
args: Args,
terminal: &mut ratatui::Terminal<B>,
) -> Result<Self, String> {
let mut logger = Logger::new();
let mut logger = Logger::default();
let settings = match Settings::load_or_create(args.config.as_deref()) {
Ok(settings) => settings,
Err(e) => {
Expand All @@ -110,6 +110,7 @@ impl App {
Settings::default()
}
};
logger.change_limit(settings.app.log_limit);
Self::print_loading_status(
&settings.color,
&format!("Opening \"{}\"...", args.path),
Expand Down Expand Up @@ -329,7 +330,7 @@ impl Default for App {
plugin_manager: PluginManager::default(),
filesystem: FileSystem::default(),
header: Header::None,
logger: Logger::new(),
logger: Logger::default(),
help_list: Self::help_list(&Settings::default().key),
data: Data::default(),
assembly_offsets: Vec::new(),
Expand Down
12 changes: 6 additions & 6 deletions src/app/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ pub struct Data {
}

impl Data {
pub fn new(bytes: Vec<u8>) -> Self {
pub fn new(bytes: Vec<u8>, history_limit: usize) -> Self {
Self {
bytes,
history: History::default(),
history: History::with_limit(history_limit),
dirty: false,
}
}
Expand Down Expand Up @@ -109,7 +109,7 @@ mod tests {

#[test]
fn test_data_push_change() {
let mut data = Data::new(vec![0, 1, 2, 3, 4]);
let mut data = Data::new(vec![0, 1, 2, 3, 4], 0);
assert_eq!(data.push_change(2, vec![9, 8, 7]), 3);
assert_eq!(data.bytes(), &[0, 1, 9, 8, 7]);
assert_eq!(data.push_change(2, vec![9, 8, 7]), 0);
Expand All @@ -133,13 +133,13 @@ mod tests {
#[test]
#[should_panic]
fn test_data_push_change_out_of_bounds() {
let mut data = Data::new(vec![0, 1, 2, 3, 4]);
let mut data = Data::new(vec![0, 1, 2, 3, 4], 0);
data.push_change(5, vec![9, 8, 7]);
}

#[test]
fn test_data_undo_redo() {
let mut data = Data::new(vec![0, 1, 2, 3, 4]);
let mut data = Data::new(vec![0, 1, 2, 3, 4], 0);
data.push_change(2, vec![9, 8, 7]);
assert_eq!(data.bytes(), &[0, 1, 9, 8, 7]);
data.push_change(0, vec![9, 8]);
Expand All @@ -158,7 +158,7 @@ mod tests {

#[test]
fn test_data_clear_history() {
let mut data = Data::new(vec![0, 1, 2, 3, 4]);
let mut data = Data::new(vec![0, 1, 2, 3, 4], 0);
data.push_change(2, vec![9, 8, 7]);
data.push_change(0, vec![9, 8]);
data.push_change(4, vec![9]);
Expand Down
5 changes: 4 additions & 1 deletion src/app/files/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ impl App {
} else {
None
};
self.data = Data::new(self.filesystem.read(self.filesystem.pwd())?);
self.data = Data::new(
self.filesystem.read(self.filesystem.pwd())?,
self.settings.app.history_limit,
);

terminal = if let Some(terminal) = terminal {
Self::print_loading_status(&self.settings.color, "Decoding binary data...", terminal)?;
Expand Down
2 changes: 1 addition & 1 deletion src/app/history/change.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Change {
offset: usize,
old: Vec<u8>,
Expand Down
75 changes: 73 additions & 2 deletions src/app/history/history.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
#![allow(clippy::module_inception)]

use std::collections::VecDeque;

use super::change::Change;
#[derive(Debug, Clone, Default)]
pub struct History {
changes: Vec<Change>,
limit: usize,
changes: VecDeque<Change>,
current: usize,
}

impl History {
/// If limit is 0, there is no limit.
pub fn with_limit(limit: usize) -> Self {
Self {
limit,
changes: VecDeque::with_capacity(limit),
current: 0,
}
}

pub fn push(&mut self, change: Change) {
self.changes.truncate(self.current);
self.changes.push(change);
if self.changes.len() >= self.limit && self.limit > 0 {
self.changes.remove(0);
self.current = self.current.saturating_sub(1);
}
self.changes.push_back(change);
self.current += 1;
}

Expand Down Expand Up @@ -38,8 +54,63 @@ impl History {
}
}

pub fn change_limit(&mut self, limit: usize) {
self.limit = limit;
if self.changes.len() > limit && limit > 0 {
self.changes.drain(0..self.changes.len() - limit);
self.current = limit;
}
if let Some(additional) = limit.checked_sub(self.changes.capacity()) {
self.changes.reserve(additional);
} else {
self.changes.shrink_to_fit();
}
}

pub fn clear(&mut self) {
self.changes.clear();
self.current = 0;
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_history_with_limit() {
let mut history = History::with_limit(2);
assert_eq!(history.limit, 2);
assert_eq!(history.changes.capacity(), 2);

history.push(Change::new(0, &[0], &[1]));
assert_eq!(history.current, 1);
history.push(Change::new(0, &[1], &[0]));
assert_eq!(history.current, 2);
history.push(Change::new(0, &[0], &[1]));
assert_eq!(history.current, 2);
assert_eq!(history.changes.len(), 2);

history.undo(&mut vec![0]);
assert_eq!(history.current, 1);
history.undo(&mut vec![0]);
assert_eq!(history.current, 0);
assert!(history.undo(&mut vec![0]).is_none());
assert_eq!(history.current, 0);
}

#[test]
fn test_history_change_limit() {
let mut history = History::with_limit(2);
history.push(Change::new(0, &[0], &[1]));
history.push(Change::new(0, &[1], &[2]));
history.push(Change::new(0, &[2], &[3]));
assert_eq!(history.changes.len(), 2);
assert_eq!(history.current, 2);

history.change_limit(1);
assert_eq!(history.changes.len(), 1);
assert_eq!(history.current, 1);
assert_eq!(history.changes[0], Change::new(0, &[2], &[3]));
}
}
84 changes: 65 additions & 19 deletions src/app/log/logger.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
use std::ops::Index;
use std::{collections::VecDeque, ops::Index};

use crate::app::App;

use super::{log_line::LogLine, notification::NotificationLevel};

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct Logger {
pub(super) log: Vec<LogLine>,
pub(super) limit: usize,
pub(super) log: VecDeque<LogLine>,
pub(super) notification: NotificationLevel,
}

impl Logger {
pub fn new() -> Self {
Self::default()
pub fn with_limit(limit: usize) -> Self {
Self {
limit,
log: VecDeque::with_capacity(limit),
notification: NotificationLevel::None,
}
}

pub fn log(&mut self, level: NotificationLevel, message: &str) {
self.notification.bump_notification_level(level);
self.log.push(LogLine::new(level, message.to_string()));
if self.log.len() >= self.limit && self.limit > 0 {
self.log.pop_front();
}
self.log.push_back(LogLine::new(level, message.to_string()));
}

pub fn clear(&mut self) {
Expand Down Expand Up @@ -45,24 +53,30 @@ impl Logger {
self.notification.reset();
}

pub fn change_limit(&mut self, limit: usize) {
self.limit = limit;
if self.log.len() > limit && limit > 0 {
self.log.drain(0..self.log.len() - limit);
}
if let Some(additional) = limit.checked_sub(self.log.capacity()) {
self.log.reserve(additional);
} else {
self.log.shrink_to_fit();
}
}

pub fn merge(&mut self, other: &Self) {
for log_line in &other.log {
self.log.push(log_line.clone());
if self.log.len() >= self.limit && self.limit > 0 {
self.log.pop_front();
}
self.log.push_back(log_line.clone());
}
self.notification
.bump_notification_level(other.notification);
}
}

impl Default for Logger {
fn default() -> Self {
Self {
log: Vec::new(),
notification: NotificationLevel::None,
}
}
}

impl Index<usize> for Logger {
type Output = LogLine;

Expand All @@ -83,7 +97,7 @@ mod test {

#[test]
fn test_logger() {
let mut logger = Logger::new();
let mut logger = Logger::default();
assert_eq!(logger.len(), 0);
assert!(logger.is_empty());
logger.log(NotificationLevel::Error, "Test error message");
Expand All @@ -98,8 +112,8 @@ mod test {

#[test]
fn test_logger_merge() {
let mut logger1 = Logger::new();
let mut logger2 = Logger::new();
let mut logger1 = Logger::default();
let mut logger2 = Logger::default();
logger1.log(NotificationLevel::Error, "Test error message");
logger2.log(NotificationLevel::Warning, "Test warning message");
logger1.merge(&logger2);
Expand All @@ -110,4 +124,36 @@ mod test {
assert_eq!(logger1[1].message, "Test warning message");
assert_eq!(logger1.get_notification_level(), NotificationLevel::Error);
}

#[test]
fn test_logger_with_limit() {
let mut logger = Logger::with_limit(5);
for i in 0..10 {
logger.log(
NotificationLevel::Error,
&format!("Test error message {}", i),
);
}
assert_eq!(logger.len(), 5);
assert_eq!(logger[0].message, "Test error message 5");
assert_eq!(logger[4].message, "Test error message 9");
}

#[test]
fn test_logger_change_limit() {
let mut logger = Logger::with_limit(2);
logger.log(NotificationLevel::Error, "Test error message 1");
logger.log(NotificationLevel::Error, "Test error message 2");
logger.log(NotificationLevel::Error, "Test error message 3");
assert_eq!(logger.len(), 2);
assert_eq!(logger[0].message, "Test error message 2");
assert_eq!(logger[1].message, "Test error message 3");
logger.change_limit(3);
assert_eq!(logger.len(), 2);
assert_eq!(logger[0].message, "Test error message 2");
assert_eq!(logger[1].message, "Test error message 3");
logger.change_limit(1);
assert_eq!(logger.len(), 1);
assert_eq!(logger[0].message, "Test error message 3");
}
}
3 changes: 2 additions & 1 deletion src/app/log/notification.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::fmt::{Display, Formatter};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NotificationLevel {
#[default]
None,
Debug,
Info,
Expand Down
5 changes: 3 additions & 2 deletions src/app/plugins/register_userdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use mlua::{FromLua, IntoLua, Lua, UserDataFields, UserDataMethods};
use ratatui::text::Text;

use crate::app::settings::{
color_settings::ColorSettings, key_settings::KeySettings, settings_value::SettingsValue,
Settings,
app_settings::AppSettings, color_settings::ColorSettings, key_settings::KeySettings,
settings_value::SettingsValue, Settings,
};

pub fn register_vec_u8(lua: &Lua) -> mlua::Result<()> {
Expand Down Expand Up @@ -70,6 +70,7 @@ pub fn register_settings(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type(|data: &mut mlua::UserDataRegistry<Settings>| {
ColorSettings::register_userdata(data);
KeySettings::register_userdata(data);
AppSettings::register_userdata(data);
data.add_method("get_custom", |_lua, this, key: String| {
Ok(this.custom.get(&key).cloned())
});
Expand Down
Loading

0 comments on commit 759c0f9

Please sign in to comment.