feat: implement command mode

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-05-09 00:15:42 +02:00
parent 445a628ff5
commit ed5a5db7c5
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
8 changed files with 405 additions and 57 deletions

View File

@ -3,10 +3,14 @@ use ratatui::{
widgets::{Block, Borders, Padding, Paragraph},
};
use crate::{components::GraphExplorer, models::EditMsg, state::SharedState, Msg};
use crate::{commands::IntoCommand, components::GraphExplorer, state::SharedState, Msg};
use self::dialog::{CreateItem, CreateItemState};
use self::{
command_bar::{CommandBar, CommandBarState},
dialog::{CreateItem, CreateItemState},
};
mod command_bar;
pub mod dialog;
pub enum Dialog {
@ -16,6 +20,7 @@ pub enum Dialog {
pub enum Mode {
View,
Insert,
Command,
}
pub struct App<'a> {
@ -23,6 +28,7 @@ pub struct App<'a> {
pub mode: Mode,
dialog: Option<Dialog>,
command: Option<CommandBarState>,
graph_explorer: GraphExplorer<'a>,
}
@ -32,12 +38,13 @@ impl<'a> App<'a> {
Self {
mode: Mode::View,
dialog: None,
command: None,
state,
graph_explorer,
}
}
pub fn update(&mut self, msg: Msg) -> anyhow::Result<()> {
pub fn update(&mut self, msg: Msg) -> anyhow::Result<impl IntoCommand> {
tracing::trace!("handling msg: {:?}", msg);
match msg {
@ -47,17 +54,31 @@ impl<'a> App<'a> {
Msg::MoveUp => self.graph_explorer.move_up()?,
Msg::OpenCreateItemDialog => self.open_dialog(),
Msg::EnterInsertMode => self.mode = Mode::Insert,
Msg::EnterCommandMode => self.mode = Mode::View,
Msg::EnterViewMode => self.mode = Mode::View,
Msg::EnterCommandMode => {
self.command = Some(CommandBarState::default());
self.mode = Mode::Command
}
Msg::SubmitCommand => {
tracing::info!("submitting command");
self.command = None;
return Ok(Msg::EnterViewMode.into_command());
}
_ => {}
}
if let Some(dialog) = &mut self.dialog {
if let Some(command) = &mut self.command {
let cmd = command.update(&msg)?;
return Ok(cmd.into_command());
} else if let Some(dialog) = &mut self.dialog {
match dialog {
Dialog::CreateItem { state } => state.update(&msg)?,
}
}
Ok(())
Ok(().into_command())
}
fn open_dialog(&mut self) {
@ -106,17 +127,29 @@ pub fn render_app(frame: &mut Frame, state: &mut App) {
frame.render_widget(heading.block(block_heading), chunks[0]);
let powerbar = match &state.mode {
Mode::View => Line::raw("-- VIEW --"),
Mode::Insert => Line::raw("-- EDIT --"),
};
let powerbar_block = Block::default()
.borders(Borders::empty())
.padding(Padding::new(1, 1, 0, 0));
frame.render_widget(
Paragraph::new(vec![powerbar]).block(powerbar_block),
chunks[2],
);
match &state.mode {
Mode::View => {
let line = Line::raw("-- VIEW --");
let powerbar_block = Block::default()
.borders(Borders::empty())
.padding(Padding::new(1, 1, 0, 0));
frame.render_widget(Paragraph::new(vec![line]).block(powerbar_block), chunks[2]);
}
Mode::Insert => {
let line = Line::raw("-- EDIT --");
let powerbar_block = Block::default()
.borders(Borders::empty())
.padding(Padding::new(1, 1, 0, 0));
frame.render_widget(Paragraph::new(vec![line]).block(powerbar_block), chunks[2]);
}
Mode::Command => {
if let Some(command) = &mut state.command {
frame.render_stateful_widget(CommandBar::default(), chunks[2], command);
}
}
}
let Rect { width, height, .. } = chunks[1];

View File

@ -0,0 +1,53 @@
use ratatui::widgets::{Paragraph, StatefulWidget, Widget};
use crate::{
commands::IntoCommand,
models::{EditMsg, Msg},
};
use super::dialog::BufferState;
pub struct CommandBarState {
contents: BufferState,
}
impl Default for CommandBarState {
fn default() -> Self {
Self {
contents: BufferState::Focused {
content: ropey::Rope::default(),
position: 0,
},
}
}
}
#[derive(Default)]
pub struct CommandBar {}
impl CommandBarState {
pub fn update(&mut self, msg: &Msg) -> anyhow::Result<impl IntoCommand> {
if let Msg::Edit(e) = msg {
self.contents.update(e)?;
if let EditMsg::InsertNewLine = e {
return Ok(Msg::SubmitCommand.into_command());
}
}
Ok(().into_command())
}
}
impl StatefulWidget for CommandBar {
type State = CommandBarState;
fn render(
self,
area: ratatui::prelude::Rect,
buf: &mut ratatui::prelude::Buffer,
state: &mut Self::State,
) {
Paragraph::new(format!(":{}", state.contents.string())).render(area, buf);
}
}

View File

@ -1,5 +1,3 @@
use std::{ops::Deref, rc::Rc};
use ratatui::{prelude::*, widgets::*};
use crate::models::{EditMsg, Msg};
@ -25,13 +23,13 @@ impl Default for BufferState {
}
impl BufferState {
fn update(&mut self, msg: &EditMsg) -> anyhow::Result<()> {
pub fn update(&mut self, msg: &EditMsg) -> anyhow::Result<()> {
if let BufferState::Focused { content, position } = self {
let pos = *position;
match msg {
EditMsg::Delete => {
if pos > 0 && content.len_chars() > pos {
if pos > 0 && pos <= content.len_chars() {
content.remove((pos - 1)..pos);
*position = position.saturating_sub(1);
}
@ -41,8 +39,8 @@ impl BufferState {
content.remove((pos)..pos + 1);
}
}
EditMsg::InsertNewLine => todo!(),
EditMsg::InsertTab => todo!(),
EditMsg::InsertNewLine => {}
EditMsg::InsertTab => {}
EditMsg::InsertChar(c) => {
content.try_insert_char(pos, *c)?;
*position = position.saturating_add(1);
@ -51,7 +49,7 @@ impl BufferState {
*position = position.saturating_sub(1);
}
EditMsg::MoveRight => {
if pos + 1 < content.len_chars() {
if pos < content.len_chars() {
*position = pos.saturating_add(1);
}
}
@ -60,10 +58,17 @@ impl BufferState {
Ok(())
}
pub fn string(&self) -> String {
match self {
BufferState::Focused { content, .. } => content.to_string(),
BufferState::Static { content, .. } => content.to_owned(),
}
}
}
pub struct InputBuffer {
state: BufferState,
pub state: BufferState,
}
impl InputBuffer {
@ -111,7 +116,8 @@ impl InputBuffer {
pub fn update(&mut self, msg: &Msg) -> anyhow::Result<()> {
match msg {
Msg::EnterInsertMode => self.to_focused(),
Msg::EnterCommandMode => self.to_static(),
Msg::EnterCommandMode => self.to_focused(),
Msg::EnterViewMode => self.to_static(),
Msg::Edit(c) => {
self.state.update(c)?;
}
@ -120,13 +126,12 @@ impl InputBuffer {
Ok(())
}
}
impl Widget for InputBuffer {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
pub fn string(&self) -> String {
match &self.state {
BufferState::Focused { ref content, .. } => content.to_string(),
BufferState::Static { content, .. } => content.to_owned(),
}
}
}
@ -155,10 +160,13 @@ impl<'a> StatefulWidget for InputField<'a> {
let block = Block::bordered().title(self.title);
match &state.state {
BufferState::Focused { content, .. } => {
BufferState::Focused { content, position } => {
Paragraph::new(content.to_string().as_str())
.block(block)
.render(area, buf);
buf.get_mut(area.x + 1 + *position as u16, area.y + 1)
.set_style(Style::new().bg(Color::Magenta).fg(Color::Black));
}
BufferState::Static { content, .. } => {
Paragraph::new(content.as_str())

View File

@ -0,0 +1,31 @@
use crate::models::Msg;
pub trait IntoCommand {
fn into_command(self) -> Command;
}
impl IntoCommand for () {
fn into_command(self) -> Command {
Command::new(|| None)
}
}
impl IntoCommand for Command {
fn into_command(self) -> Command {
self
}
}
pub struct Command {
func: Box<dyn FnOnce() -> Option<Msg>>,
}
impl Command {
pub fn new<T: FnOnce() -> Option<Msg> + 'static>(f: T) -> Self {
Self { func: Box::new(f) }
}
pub fn execute(self) -> Option<Msg> {
self.func.call_once(())
}
}

View File

@ -151,8 +151,10 @@ impl RenderGraph for MovementGraph {
}) {
Some((true, rest)) => {
if rest.is_empty() {
lines
.push(Line::raw(format!("- {}", item.name)).style(Style::new().bold()));
lines.push(
Line::raw(format!("- {}", item.name))
.style(Style::new().bold().white()),
);
} else {
lines.push(
Line::raw(format!("- {}", item.name))
@ -203,7 +205,10 @@ impl RenderGraph for MovementGraph {
Some((true, rest)) => {
let mut line = Vec::new();
if rest.is_empty() {
line.push(Span::raw(format!("- {}", item.name)).style(Style::new().bold()));
line.push(
Span::raw(format!("- {}", item.name))
.style(Style::new().bold().white()),
);
} else {
line.push(
Span::raw(format!("- {}", item.name))

View File

@ -1,7 +1,10 @@
#![feature(fn_traits)]
use std::{io::Stdout, time::Duration};
use anyhow::{Context, Result};
use app::{render_app, App};
use commands::IntoCommand;
use components::GraphExplorer;
use crossterm::event::{self, Event, KeyCode};
use hyperlog_core::state::State;
@ -13,6 +16,7 @@ use crate::{state::SharedState, terminal::TerminalInstance};
pub mod models;
pub(crate) mod app;
pub(crate) mod commands;
pub(crate) mod components;
pub(crate) mod state;
@ -67,32 +71,24 @@ fn update(
) -> Result<UpdateConclusion> {
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
if let Event::Key(key) = event::read().context("event read failed")? {
match &app.mode {
let mut cmd = match &app.mode {
app::Mode::View => match key.code {
KeyCode::Char('q') => return Ok(UpdateConclusion::new(true)),
KeyCode::Char('l') => {
app.update(Msg::MoveRight)?;
}
KeyCode::Char('h') => {
app.update(Msg::MoveLeft)?;
}
KeyCode::Char('j') => {
app.update(Msg::MoveDown)?;
}
KeyCode::Char('k') => {
app.update(Msg::MoveUp)?;
}
KeyCode::Char('l') => app.update(Msg::MoveRight)?,
KeyCode::Char('h') => app.update(Msg::MoveLeft)?,
KeyCode::Char('j') => app.update(Msg::MoveDown)?,
KeyCode::Char('k') => app.update(Msg::MoveUp)?,
KeyCode::Char('a') => {
// TODO: batch commands
app.update(Msg::OpenCreateItemDialog)?;
app.update(Msg::EnterInsertMode)?;
app.update(Msg::EnterInsertMode)?
}
KeyCode::Char('i') => {
app.update(Msg::EnterInsertMode)?;
}
_ => {}
KeyCode::Char('i') => app.update(Msg::EnterInsertMode)?,
KeyCode::Char(':') => app.update(Msg::EnterCommandMode)?,
_ => return Ok(UpdateConclusion(false)),
},
app::Mode::Insert => match key.code {
app::Mode::Command | app::Mode::Insert => match key.code {
KeyCode::Backspace => app.update(Msg::Edit(EditMsg::Delete))?,
KeyCode::Enter => app.update(Msg::Edit(EditMsg::InsertNewLine))?,
KeyCode::Tab => app.update(Msg::Edit(EditMsg::InsertTab))?,
@ -100,9 +96,19 @@ fn update(
KeyCode::Char(c) => app.update(Msg::Edit(EditMsg::InsertChar(c)))?,
KeyCode::Left => app.update(Msg::Edit(EditMsg::MoveLeft))?,
KeyCode::Right => app.update(Msg::Edit(EditMsg::MoveRight))?,
KeyCode::Esc => app.update(Msg::EnterCommandMode)?,
_ => {}
KeyCode::Esc => app.update(Msg::EnterViewMode)?,
_ => return Ok(UpdateConclusion(false)),
},
};
loop {
let msg = cmd.into_command().execute();
match msg {
Some(msg) => {
cmd = app.update(msg)?;
}
None => break,
}
}
}
}

View File

@ -1,3 +1,5 @@
use crate::commands::{Command, IntoCommand};
#[derive(Debug)]
pub enum Msg {
MoveRight,
@ -7,10 +9,20 @@ pub enum Msg {
OpenCreateItemDialog,
EnterInsertMode,
EnterViewMode,
EnterCommandMode,
SubmitCommand,
Edit(EditMsg),
}
impl IntoCommand for Msg {
fn into_command(self) -> crate::commands::Command {
Command::new(|| Some(self))
}
}
#[derive(Debug)]
pub enum EditMsg {
Delete,

200
it-is-nice.cast Normal file
View File

@ -0,0 +1,200 @@
{"version": 2, "width": 121, "height": 31, "timestamp": 1715205039, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}}
[0.340402, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[0.415094, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0m\u001b[1;31mrs \u001b[0m\r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[0.416098, "o", "\u001b[6 q"]
[0.417144, "o", "\u001b[6 q"]
[0.417386, "o", "\u001b[?2004h"]
[1.099162, "o", "a"]
[1.137079, "o", "\b\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"]
[1.137376, "o", "\b\u001b[1m\u001b[31ma\u001b[0m\u001b[39m\u001b[90msciinema rec it-is-nice.cast\u001b[39m\u001b[28D"]
[1.895435, "o", "\b\u001b[0m\u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[29D"]
[4.16507, "o", "c"]
[4.170318, "o", "\b\u001b[32mc\u001b[39m"]
[4.205236, "o", "\b\u001b[32mc\u001b[39m\u001b[90mlear\u001b[39m\b\b\b\b"]
[4.314062, "o", "\b\u001b[32mc\u001b[32ma\u001b[39m\u001b[39m \u001b[39m \u001b[39m \b\b\b"]
[4.319326, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"]
[4.339872, "o", "\u001b[90mrgo run\u001b[39m\b\b\b\b\b\b\b"]
[4.375674, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[1m\u001b[31mr\u001b[0m\u001b[39m"]
[4.553571, "o", "\b\u001b[1m\u001b[31mr\u001b[1m\u001b[31mg\u001b[0m\u001b[39m"]
[4.624486, "o", "\b\u001b[1m\u001b[31mg\u001b[1m\u001b[31mo\u001b[0m\u001b[39m"]
[4.628551, "o", "\b\b\b\b\b\u001b[0m\u001b[32mc\u001b[0m\u001b[32ma\u001b[0m\u001b[32mr\u001b[0m\u001b[32mg\u001b[0m\u001b[32mo\u001b[39m"]
[4.743312, "o", "\b\u001b[32mo\u001b[32m \u001b[39m"]
[4.745288, "o", "\b\b\u001b[32mo\u001b[39m\u001b[39m "]
[4.85978, "o", "\u001b[39mr"]
[4.863278, "o", "\b\u001b[4mr\u001b[24m"]
[4.923563, "o", "\b\u001b[4mr\u001b[39m\u001b[4mu\u001b[24m"]
[4.927058, "o", "\b\b\u001b[24mr\u001b[24mu"]
[5.064694, "o", "\u001b[39mn"]
[5.322632, "o", "\u001b[?1l\u001b>"]
[5.323055, "o", "\u001b[?2004l"]
[5.330122, "o", "\u001b[0 q"]
[5.330526, "o", "\r\r\n"]
[5.677526, "o", "\u001b[1m\u001b[32m Compiling\u001b[0m hyperlog-tui v0.1.0 (/Users/kah/git/git.front.kjuulh.io/kjuulh/hyperlog/crates/hyperlog-tui)\r\n\u001b[1m\u001b[36m Building\u001b[0m [=======================> ] 315/317: hyperlog-tui \r"]
[5.790434, "o", "\u001b[K\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: methods `toggle` and `string` are never used\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-tui/src/app/dialog.rs:99:12\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m74\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mimpl InputBuffer {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----------------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mmethods in this implementation\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m99\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn toggle(&mut self) {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m130\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn string(&self) -> String {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(dead_code)]` on by default\u001b[0m\r\n\r\n"]
[5.790526, "o", "\u001b[1m\u001b[36m Building\u001b[0m [=======================> ] 315/317: hyperlog-tui \r"]
[5.964842, "o", "\u001b[K\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog-tui` (lib) generated 1 warning\r\n\u001b[1m\u001b[32m Compiling\u001b[0m hyperlog v0.1.0 (/Users/kah/git/git.front.kjuulh.io/kjuulh/hyperlog/crates/hyperlog)\r\n"]
[5.964868, "o", "\u001b[1m\u001b[36m Building\u001b[0m [=======================> ] 316/317: hyperlog(bin) \r"]
[7.029851, "o", "\u001b[K\u001b[1m\u001b[32m Finished\u001b[0m `dev` profile [unoptimized + debuginfo] target(s) in 1.65s\r\n"]
[7.039265, "o", "\u001b[1m\u001b[32m Running\u001b[0m `target/debug/hyperlog`\r\n"]
[7.397795, "o", "\u001b[?1049h"]
[7.400923, "o", "\u001b[1;1H\u001b[38;5;2mhyperlog\u001b[2;1H\u001b[39m─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[3;1H\u001b[38;5;8m- a\u001b[5;3H- b\u001b[7;1H- b\u001b[9;3H- a\u001b[11;3H- b\u001b[13;1H- something\u001b[31;2H\u001b[39m--\u001b[31;5HVIEW\u001b[31;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.659643, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[7.920329, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.183413, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.351457, "o", "\u001b[31;5HEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.609528, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.868582, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[8.942765, "o", "\u001b[31;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.200718, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.461421, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.512177, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.708596, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.905173, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[9.945318, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.09697, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.355132, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.39335, "o", "\u001b[3;1H\u001b[1m\u001b[38;5;15m- a\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.651726, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.655332, "o", "\u001b[3;1H\u001b[38;5;8m- a\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.843242, "o", "\u001b[7;1H\u001b[38;5;8m- b\u001b[13;1H\u001b[1m\u001b[38;5;15m- something\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[10.998559, "o", "\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[13;1H\u001b[22m\u001b[38;5;8m- something\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.202965, "o", "\u001b[3;1H\u001b[1m\u001b[38;5;15m- a\u001b[7;1H\u001b[22m\u001b[38;5;8m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.246379, "o", "\u001b[3;1H\u001b[38;5;8m- a\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.412109, "o", "\u001b[3;1H\u001b[1m\u001b[38;5;15m- a\u001b[7;1H\u001b[22m\u001b[38;5;8m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.518186, "o", "\u001b[3;1H\u001b[38;5;8m- a\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.657233, "o", "\u001b[3;1H\u001b[1m\u001b[38;5;15m- a\u001b[7;1H\u001b[22m\u001b[38;5;8m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.743079, "o", "\u001b[3;1H\u001b[38;5;8m- a\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.880111, "o", "\u001b[3;1H\u001b[1m\u001b[38;5;15m- a\u001b[7;1H\u001b[22m\u001b[38;5;8m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[11.945269, "o", "\u001b[3;1H\u001b[38;5;8m- a\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.205525, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.305051, "o", "\u001b[7;1H\u001b[38;5;8m- b\u001b[9;3H\u001b[1m\u001b[38;5;15m- a\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.563385, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.587834, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[12.847447, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.056384, "o", "\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[9;3H\u001b[22m\u001b[38;5;8m- a\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.073621, "o", "\u001b[7;1H\u001b[38;5;8m- b\u001b[13;1H\u001b[1m\u001b[38;5;15m- something\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.332382, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.590747, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.85099, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[13.941815, "o", "\u001b[13;1H\u001b[38;5;8m- something\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.200606, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.460463, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.549192, "o", "\u001b[3;1H\u001b[1m\u001b[38;5;15m- a\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.809497, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[14.94385, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.155403, "o", "\u001b[3;1H\u001b[38;5;8m- a\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.413804, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.578955, "o", "\u001b[3;1H\u001b[1m\u001b[38;5;15m- a\u001b[7;1H\u001b[22m\u001b[38;5;8m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[15.833893, "o", "\u001b[3;1H\u001b[38;5;8m- a\u001b[5;3H\u001b[1m\u001b[38;5;15m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.097133, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.248338, "o", "\u001b[3;1H\u001b[1m\u001b[38;5;15m- a\u001b[5;3H\u001b[22m\u001b[38;5;8m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.507132, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.572342, "o", "\u001b[3;1H\u001b[38;5;8m- a\u001b[7;1H\u001b[1m\u001b[38;5;15m- b\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[16.830893, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.092909, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.26512, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.524696, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.78239, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[17.887591, "o", "\u001b[31;2Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.146806, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.405362, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.431095, "o", "\u001b[31;2H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.560467, "o", "\u001b[31;2Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.670387, "o", "\u001b[31;3Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.834286, "o", "\u001b[31;4Hm\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[18.927281, "o", "\u001b[31;5He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.030836, "o", "\u001b[31;6Ht\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.071122, "o", "\u001b[31;7Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.211626, "o", "\u001b[31;8Hi\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.357607, "o", "\u001b[31;9Hn\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.452746, "o", "\u001b[31;10Hg\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.646601, "o", "\u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[19.907225, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.165462, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.423652, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.680919, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.939263, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[20.996048, "o", "\u001b[1;10H~\u001b[1;12Hcreate\u001b[1;19Hitem\u001b[3;1H┌title──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[4;1H│\u001b[38;5;0m\u001b[48;5;5m \u001b[4;121H\u001b[39m\u001b[49m│\u001b[5;1H└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\u001b[6;1H┌description─────────────────────────────────────────────────────────────────"]
[20.996104, "o", "───────────────────────────────────────────┐\u001b[7;1H│ \u001b[7;121H│\u001b[8;1H└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\u001b[9;3H \u001b[11;3H \u001b[13;1H \u001b[31;5HEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.010383, "o", "\u001b[4;2Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.271922, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.529104, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.790287, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[21.794558, "o", "\u001b[4;2H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.052204, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.18287, "o", "\u001b[4;2Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.189682, "o", "\u001b[4;3Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.211883, "o", "\u001b[4;4Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.214504, "o", "\u001b[4;5Hk\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.284859, "o", "\u001b[4;6Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.319941, "o", "\u001b[4;7Hj\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.342546, "o", "\u001b[4;8Hf\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.424274, "o", "\u001b[4;9Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.455243, "o", "\u001b[4;10Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.517656, "o", "\u001b[4;11Hk\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.521962, "o", "\u001b[4;12Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.553432, "o", "\u001b[4;13Hj\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.56918, "o", "\u001b[4;14Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.612324, "o", "\u001b[4;15Hf\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.692691, "o", "\u001b[4;16Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.723325, "o", "\u001b[4;17Hk\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.738287, "o", "\u001b[4;18Hj\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.765988, "o", "\u001b[4;19Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.826184, "o", "\u001b[4;20Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.900582, "o", "\u001b[4;21Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[22.953845, "o", "\u001b[4;22Hf\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.213295, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.420252, "o", "\u001b[4;23H \u001b[31;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.683026, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[23.815286, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.074879, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.159029, "o", "\u001b[7;2H\u001b[38;5;0m\u001b[48;5;5m \u001b[31;5H\u001b[39m\u001b[49mEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.301429, "o", "\u001b[7;2Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.345464, "o", "\u001b[7;3Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.348744, "o", "\u001b[7;4Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.427224, "o", "\u001b[7;5Hf\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.474234, "o", "\u001b[7;6Hj\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.554142, "o", "\u001b[7;7Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.603048, "o", "\u001b[7;8Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.632587, "o", "\u001b[7;9Hk\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.636099, "o", "\u001b[7;10Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.671615, "o", "\u001b[7;11Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.727905, "o", "\u001b[7;12Hf\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.823809, "o", "\u001b[7;13Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.839083, "o", "\u001b[7;14Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.887115, "o", "\u001b[7;15Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.94834, "o", "\u001b[7;16Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[24.995917, "o", "\u001b[7;17Hf\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.007211, "o", "\u001b[7;18Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.023711, "o", "\u001b[7;19Hk\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.118431, "o", "\u001b[7;20Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.159273, "o", "\u001b[7;21Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.184474, "o", "\u001b[7;22Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.209847, "o", "\u001b[7;23Hk\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.22384, "o", "\u001b[7;24Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.273719, "o", "\u001b[7;25Hf\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.533912, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[25.792562, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.052447, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.074376, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.337157, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.426805, "o", "\u001b[7;26H \u001b[31;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.689664, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[26.947668, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.000511, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.258436, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.376346, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.636429, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[27.895577, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.157791, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.417062, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.459318, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.716785, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[28.974774, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
[29.024351, "o", "\u001b[?1049l\u001b[?25h"]
[29.026254, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[29.086805, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0m\u001b[1;31mrs \u001b[0m\u001b[33m23s\u001b[0m \r\n\u001b[38;2;255;153;102m\u001b[0m \u001b[K"]
[29.087892, "o", "\u001b[6 q"]
[29.088951, "o", "\u001b[6 q"]
[29.089143, "o", "\u001b[?2004h"]
[30.381431, "o", "\u001b[?2004l\r\r\n"]