chore: fix test breaking changes
All checks were successful
continuous-integration/drone/push Build is passing

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2024-05-10 12:03:38 +02:00
parent f284171f5a
commit 86310c6764
13 changed files with 763 additions and 270 deletions

View File

@@ -1,3 +1,4 @@
use hyperlog_core::log::GraphItem;
use ratatui::{
prelude::*,
widgets::{Block, Borders, Padding, Paragraph},
@@ -10,7 +11,10 @@ use crate::{
use self::{
command_bar::{CommandBar, CommandBarState},
dialog::{CreateItem, CreateItemState},
dialog::{
create_item::{CreateItem, CreateItemState},
edit_item::{EditItem, EditItemState},
},
};
mod command_bar;
@@ -18,12 +22,14 @@ pub mod dialog;
pub enum Dialog {
CreateItem { state: CreateItemState },
EditItem { state: EditItemState },
}
impl Dialog {
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
match self {
Dialog::CreateItem { state } => state.get_command(),
Dialog::EditItem { state } => state.get_command(),
}
}
}
@@ -76,12 +82,13 @@ impl<'a> App<'a> {
pub fn update(&mut self, msg: Msg) -> anyhow::Result<impl IntoCommand> {
tracing::trace!("handling msg: {:?}", msg);
match msg {
match &msg {
Msg::MoveRight => self.graph_explorer.move_right()?,
Msg::MoveLeft => self.graph_explorer.move_left()?,
Msg::MoveDown => self.graph_explorer.move_down()?,
Msg::MoveUp => self.graph_explorer.move_up()?,
Msg::OpenCreateItemDialog => self.open_dialog(),
Msg::OpenEditItemDialog { item } => self.open_edit_item_dialog(item),
Msg::EnterInsertMode => self.mode = Mode::Insert,
Msg::EnterViewMode => self.mode = Mode::View,
Msg::EnterCommandMode => {
@@ -95,7 +102,7 @@ impl<'a> App<'a> {
Msg::SubmitCommand { command } => {
tracing::info!("submitting command");
if let Some(command) = CommandParser::parse(&command) {
if let Some(command) = CommandParser::parse(command) {
match self.focus {
AppFocus::Dialog => {
if command.is_write() {
@@ -113,7 +120,12 @@ impl<'a> App<'a> {
self.dialog = None;
}
}
AppFocus::Graph => self.graph_explorer.execute_command(&command)?,
AppFocus::Graph => {
if let Some(msg) = self.graph_explorer.execute_command(&command)? {
self.command = None;
return Ok(msg.into_command());
}
}
}
}
self.command = None;
@@ -128,6 +140,7 @@ impl<'a> App<'a> {
} else if let Some(dialog) = &mut self.dialog {
match dialog {
Dialog::CreateItem { state } => state.update(&msg)?,
Dialog::EditItem { state } => state.update(&msg)?,
}
}
@@ -145,6 +158,20 @@ impl<'a> App<'a> {
});
}
}
fn open_edit_item_dialog(&mut self, item: &GraphItem) {
if self.dialog.is_none() {
let root = self.root.clone();
let path = self.graph_explorer.get_current_path();
self.dialog = Some(Dialog::EditItem {
state: EditItemState::new(root, path, item),
});
self.command = None;
self.focus = AppFocus::Dialog;
self.mode = Mode::Insert;
}
}
}
impl<'a> Widget for &mut App<'a> {
@@ -176,6 +203,7 @@ pub fn render_app(frame: &mut Frame, state: &mut App) {
match dialog {
Dialog::CreateItem { .. } => heading_parts.push(Span::raw("create item")),
Dialog::EditItem { .. } => heading_parts.push(Span::raw("edit item")),
}
}
@@ -238,6 +266,9 @@ pub fn render_app(frame: &mut Frame, state: &mut App) {
Dialog::CreateItem { state } => {
frame.render_stateful_widget(&mut CreateItem::default(), chunks[1], state)
}
Dialog::EditItem { state } => {
frame.render_stateful_widget(&mut EditItem::default(), chunks[1], state)
}
}
return;

View File

@@ -1,4 +1,3 @@
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use crate::models::{EditMsg, Msg};
@@ -74,7 +73,16 @@ pub struct InputBuffer {
}
impl InputBuffer {
fn transform_focused(&mut self) {
pub fn new(input: String) -> Self {
Self {
state: BufferState::Static {
content: input,
position: 0,
},
}
}
pub fn transform_focused(&mut self) {
match &mut self.state {
BufferState::Focused { .. } => {}
BufferState::Static { content, position } => {
@@ -136,6 +144,14 @@ impl InputBuffer {
BufferState::Static { content, .. } => content.to_owned(),
}
}
fn set_position(&mut self, title_len: usize) {
match &mut self.state {
BufferState::Focused { position, .. } | BufferState::Static { position, .. } => {
*position = title_len
}
}
}
}
pub struct InputField<'a> {
@@ -189,110 +205,5 @@ fn clamp_x(area: &Rect, x: u16) -> u16 {
}
}
enum CreateItemFocused {
Title,
Description,
}
impl Default for CreateItemFocused {
fn default() -> Self {
Self::Title
}
}
pub struct CreateItemState {
root: String,
path: Vec<String>,
title: InputBuffer,
description: InputBuffer,
focused: CreateItemFocused,
}
impl CreateItemState {
pub fn new(root: impl Into<String>, path: impl IntoIterator<Item = impl Into<String>>) -> Self {
let root = root.into();
let path = path.into_iter().map(|p| p.into()).collect_vec();
Self {
root,
path,
title: Default::default(),
description: Default::default(),
focused: Default::default(),
}
}
pub fn update(&mut self, msg: &Msg) -> anyhow::Result<()> {
match &msg {
Msg::MoveDown | Msg::MoveUp => match self.focused {
CreateItemFocused::Title => self.focused = CreateItemFocused::Description,
CreateItemFocused::Description => self.focused = CreateItemFocused::Title,
},
_ => {}
}
match self.focused {
CreateItemFocused::Title => {
self.title.update(msg)?;
}
CreateItemFocused::Description => {
self.description.update(msg)?;
}
}
Ok(())
}
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
let title = self.title.string();
let description = self.description.string();
if !title.is_empty() {
let mut path = self.path.clone();
path.push(title.replace([' ', '.'], "-"));
Some(hyperlog_core::commander::Command::CreateItem {
root: self.root.clone(),
path,
title: title.trim().into(),
description: description.trim().into(),
state: hyperlog_core::log::ItemState::NotDone,
})
} else {
None
}
}
}
#[derive(Default)]
pub struct CreateItem {}
impl StatefulWidget for &mut CreateItem {
type State = CreateItemState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let chunks = Layout::vertical(vec![
Constraint::Length(2),
Constraint::Length(3),
Constraint::Length(3),
])
.split(area);
let path = format!("path: {}.{}", state.root, state.path.join("."));
let path_header = Paragraph::new(path).dark_gray();
path_header.render(chunks[0], buf);
let mut title_input = InputField::new("title");
let mut description_input = InputField::new("description");
match state.focused {
CreateItemFocused::Title => title_input.focused = true,
CreateItemFocused::Description => description_input.focused = true,
}
title_input.render(chunks[1], buf, &mut state.title);
description_input.render(chunks[2], buf, &mut state.description);
}
}
pub mod create_item;
pub mod edit_item;

View File

@@ -0,0 +1,114 @@
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use crate::models::Msg;
use super::{InputBuffer, InputField};
enum CreateItemFocused {
Title,
Description,
}
impl Default for CreateItemFocused {
fn default() -> Self {
Self::Title
}
}
pub struct CreateItemState {
root: String,
path: Vec<String>,
title: InputBuffer,
description: InputBuffer,
focused: CreateItemFocused,
}
impl CreateItemState {
pub fn new(root: impl Into<String>, path: impl IntoIterator<Item = impl Into<String>>) -> Self {
let root = root.into();
let path = path.into_iter().map(|p| p.into()).collect_vec();
Self {
root,
path,
title: Default::default(),
description: Default::default(),
focused: Default::default(),
}
}
pub fn update(&mut self, msg: &Msg) -> anyhow::Result<()> {
match &msg {
Msg::MoveDown | Msg::MoveUp => match self.focused {
CreateItemFocused::Title => self.focused = CreateItemFocused::Description,
CreateItemFocused::Description => self.focused = CreateItemFocused::Title,
},
_ => {}
}
match self.focused {
CreateItemFocused::Title => {
self.title.update(msg)?;
}
CreateItemFocused::Description => {
self.description.update(msg)?;
}
}
Ok(())
}
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
let title = self.title.string();
let description = self.description.string();
if !title.is_empty() {
let mut path = self.path.clone();
path.push(title.replace([' ', '.'], "-"));
Some(hyperlog_core::commander::Command::CreateItem {
root: self.root.clone(),
path,
title: title.trim().into(),
description: description.trim().into(),
state: hyperlog_core::log::ItemState::NotDone,
})
} else {
None
}
}
}
#[derive(Default)]
pub struct CreateItem {}
impl StatefulWidget for &mut CreateItem {
type State = CreateItemState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let chunks = Layout::vertical(vec![
Constraint::Length(2),
Constraint::Length(3),
Constraint::Length(3),
])
.split(area);
let path = format!("path: {}.{}", state.root, state.path.join("."));
let path_header = Paragraph::new(path).dark_gray();
path_header.render(chunks[0], buf);
let mut title_input = InputField::new("title");
let mut description_input = InputField::new("description");
match state.focused {
CreateItemFocused::Title => title_input.focused = true,
CreateItemFocused::Description => description_input.focused = true,
}
title_input.render(chunks[1], buf, &mut state.title);
description_input.render(chunks[2], buf, &mut state.description);
}
}

View File

@@ -0,0 +1,138 @@
use hyperlog_core::log::GraphItem;
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use crate::models::Msg;
use super::{InputBuffer, InputField};
enum EditItemFocused {
Title,
Description,
}
impl Default for EditItemFocused {
fn default() -> Self {
Self::Title
}
}
pub struct EditItemState {
root: String,
path: Vec<String>,
title: InputBuffer,
description: InputBuffer,
item: GraphItem,
focused: EditItemFocused,
}
impl EditItemState {
pub fn new(
root: impl Into<String>,
path: impl IntoIterator<Item = impl Into<String>>,
item: &GraphItem,
) -> Self {
let root = root.into();
let path = path.into_iter().map(|p| p.into()).collect_vec();
match item {
GraphItem::Item {
title, description, ..
} => {
let title_len = title.len();
let mut title = InputBuffer::new(title.clone());
title.transform_focused();
title.set_position(title_len);
Self {
root,
path,
item: item.clone(),
title,
description: InputBuffer::new(description.clone()),
focused: Default::default(),
}
}
_ => todo!("cannot edit item from other than GraphItem::Item"),
}
}
pub fn update(&mut self, msg: &Msg) -> anyhow::Result<()> {
match &msg {
Msg::MoveDown | Msg::MoveUp => match self.focused {
EditItemFocused::Title => self.focused = EditItemFocused::Description,
EditItemFocused::Description => self.focused = EditItemFocused::Title,
},
_ => {}
}
match self.focused {
EditItemFocused::Title => {
self.title.update(msg)?;
}
EditItemFocused::Description => {
self.description.update(msg)?;
}
}
Ok(())
}
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
let title = self.title.string();
let description = self.description.string();
if !title.is_empty() {
let path = self.path.clone();
Some(hyperlog_core::commander::Command::UpdateItem {
root: self.root.clone(),
path,
title: title.trim().into(),
description: description.trim().into(),
state: match &self.item {
GraphItem::User(_) => Default::default(),
GraphItem::Section(_) => Default::default(),
GraphItem::Item { state, .. } => state.clone(),
},
})
} else {
None
}
}
}
#[derive(Default)]
pub struct EditItem {}
impl StatefulWidget for &mut EditItem {
type State = EditItemState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let chunks = Layout::vertical(vec![
Constraint::Length(2),
Constraint::Length(3),
Constraint::Length(3),
])
.split(area);
let path = format!("path: {}.{}", state.root, state.path.join("."));
let path_header = Paragraph::new(path).dark_gray();
path_header.render(chunks[0], buf);
let mut title_input = InputField::new("title");
let mut description_input = InputField::new("description");
match state.focused {
EditItemFocused::Title => title_input.focused = true,
EditItemFocused::Description => description_input.focused = true,
}
title_input.render(chunks[1], buf, &mut state.title);
description_input.render(chunks[2], buf, &mut state.description);
}
}

View File

@@ -6,6 +6,7 @@ pub enum Commands {
WriteQuit,
Archive,
CreateSection { name: String },
Edit,
}
impl Commands {
@@ -35,6 +36,7 @@ impl CommandParser {
"cs" | "create-section" => rest.first().map(|name| Commands::CreateSection {
name: name.to_string(),
}),
"e" | "edit" => Some(Commands::Edit),
_ => None,
},
None => None,

View File

@@ -5,7 +5,7 @@ use hyperlog_core::log::{GraphItem, ItemState};
use itertools::Itertools;
use ratatui::{prelude::*, widgets::*};
use crate::{command_parser::Commands, state::SharedState};
use crate::{command_parser::Commands, models::Msg, state::SharedState};
pub struct GraphExplorer<'a> {
state: SharedState,
@@ -145,7 +145,17 @@ impl<'a> GraphExplorer<'a> {
}
}
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<()> {
fn get_current_item(&self) -> Option<MovementGraphItem> {
let graph = self.linearize_graph();
if let Some(graph) = graph {
graph.get_graph_item(&self.inner.current_position).cloned()
} else {
None
}
}
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<Option<Msg>> {
match command {
Commands::Archive => {
if !self.get_current_path().is_empty() {
@@ -165,12 +175,35 @@ impl<'a> GraphExplorer<'a> {
)?;
}
}
Commands::Edit => {
if let Some(item) = self.get_current_item() {
let path = self.get_current_path();
tracing::debug!(
"found item to edit: path: {}, item: {}",
path.join("."),
item.name
);
match item.item_type {
GraphItemType::Section => {
todo!("cannot edit section at the moment")
}
GraphItemType::Item { .. } => {
if let Some(item) = self.state.querier.get(&self.inner.root, path) {
if let GraphItem::Item { .. } = item {
return Ok(Some(Msg::OpenEditItemDialog { item }));
}
}
}
}
}
}
_ => (),
}
self.update_graph()?;
Ok(())
Ok(None)
}
pub(crate) fn interact(&mut self) -> anyhow::Result<()> {
@@ -240,7 +273,7 @@ impl RenderGraph for MovementGraph {
let embedded_sections = item.values.render_graph_spans(rest);
for section in &embedded_sections {
let mut line = vec![Span::raw(" ")];
let mut line = vec![Span::raw(" ".repeat(4))];
line.extend_from_slice(section);
lines.push(Line::from(line));
}
@@ -255,7 +288,7 @@ impl RenderGraph for MovementGraph {
let embedded_sections = item.values.render_graph_spans(&[]);
for section in &embedded_sections {
let mut line = vec![Span::raw(" ")];
let mut line = vec![Span::raw(" ".repeat(4))];
line.extend_from_slice(section);
lines.push(Line::from(line));
}
@@ -306,7 +339,7 @@ impl RenderGraph for MovementGraph {
let embedded_sections = item.values.render_graph_spans(rest);
for section in &embedded_sections {
let mut line = vec![Span::raw(" ")];
let mut line = vec![Span::raw(" ".repeat(4))];
line.extend_from_slice(section);
lines.push(line);
}
@@ -319,7 +352,7 @@ impl RenderGraph for MovementGraph {
let embedded_sections = item.values.render_graph_spans(&[]);
for section in &embedded_sections {
let mut line = vec![Span::raw(" ")];
let mut line = vec![Span::raw(" ".repeat(4))];
line.extend_from_slice(section);
lines.push(line);
}
@@ -396,7 +429,7 @@ impl MovementGraph {
fn next_down(&self, items: &[usize]) -> Option<Vec<usize>> {
match items.split_last() {
Some((current_index, rest)) => {
if let Some(current_item) = self.get_graph_item(rest) {
if let Some(current_item) = self.get_graph(rest) {
if *current_index + 1 < current_item.items.len() {
let mut vec = rest.to_vec();
vec.push(current_index + 1);
@@ -415,16 +448,29 @@ impl MovementGraph {
}
}
fn get_graph_item(&self, items: &[usize]) -> Option<&MovementGraph> {
fn get_graph(&self, items: &[usize]) -> Option<&MovementGraph> {
match items.split_first() {
Some((first, rest)) => match self.items.get(*first).map(|s| &s.values) {
Some(next_graph) => next_graph.get_graph_item(rest),
Some(next_graph) => next_graph.get_graph(rest),
None => Some(self),
},
None => Some(self),
}
}
fn get_graph_item(&self, items: &[usize]) -> Option<&MovementGraphItem> {
match items.split_first() {
Some((first, rest)) => match self.items.get(*first) {
Some(next_graph) => match next_graph.values.get_graph_item(rest) {
Some(graph_item) => Some(graph_item),
None => Some(next_graph),
},
None => None,
},
None => None,
}
}
fn to_current_path(&self, position_items: &[usize]) -> Vec<String> {
match position_items.split_first() {
Some((first, rest)) => match self.items.get(*first) {
@@ -462,7 +508,7 @@ impl From<GraphItem> for MovementGraph {
index: i,
name: key.clone(),
values: value.clone().into(),
item_type: match value.deref() {
item_type: match value {
GraphItem::User(_) => GraphItemType::Section,
GraphItem::Section(_) => GraphItemType::Section,
GraphItem::Item { state, .. } => GraphItemType::Item {
@@ -522,33 +568,30 @@ mod test {
fn test_can_transform_to_movement_graph() {
let graph = GraphItem::User(BTreeMap::from([(
"0".to_string(),
Box::new(GraphItem::Section(BTreeMap::from([
(
"00".to_string(),
Box::new(GraphItem::Section(BTreeMap::new())),
),
GraphItem::Section(BTreeMap::from([
("00".to_string(), GraphItem::Section(BTreeMap::new())),
(
"01".to_string(),
Box::new(GraphItem::Section(BTreeMap::from([
GraphItem::Section(BTreeMap::from([
(
"010".to_string(),
Box::new(GraphItem::Item {
GraphItem::Item {
title: "some-title".into(),
description: "some-desc".into(),
state: ItemState::NotDone,
}),
},
),
(
"011".to_string(),
Box::new(GraphItem::Item {
GraphItem::Item {
title: "some-title".into(),
description: "some-desc".into(),
state: ItemState::NotDone,
}),
},
),
]))),
])),
),
]))),
])),
)]));
let actual: MovementGraph = graph.into();
@@ -672,16 +715,16 @@ mod test {
],
};
let actual_default = graph.get_graph_item(&[]);
let actual_default = graph.get_graph(&[]);
assert_eq!(Some(&graph), actual_default);
let actual_first = graph.get_graph_item(&[0]);
let actual_first = graph.get_graph(&[0]);
assert_eq!(graph.items.first().map(|i| &i.values), actual_first);
let actual_second = graph.get_graph_item(&[1]);
let actual_second = graph.get_graph(&[1]);
assert_eq!(graph.items.get(1).map(|i| &i.values), actual_second);
let actual_nested = graph.get_graph_item(&[0, 0]);
let actual_nested = graph.get_graph(&[0, 0]);
assert_eq!(
graph
.items
@@ -691,7 +734,7 @@ mod test {
actual_nested
);
let actual_nested = graph.get_graph_item(&[0, 1]);
let actual_nested = graph.get_graph(&[0, 1]);
assert_eq!(
graph
.items
@@ -701,7 +744,7 @@ mod test {
actual_nested
);
let actual_nested = graph.get_graph_item(&[1, 2]);
let actual_nested = graph.get_graph(&[1, 2]);
assert_eq!(
graph
.items

View File

@@ -1,3 +1,5 @@
use hyperlog_core::log::GraphItem;
use crate::commands::{Command, IntoCommand};
#[derive(Debug)]
@@ -7,6 +9,7 @@ pub enum Msg {
MoveDown,
MoveUp,
OpenCreateItemDialog,
OpenEditItemDialog { item: GraphItem },
Interact,
EnterInsertMode,