Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to Ratatui #42

Merged
merged 2 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
233 changes: 178 additions & 55 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ edition = "2021"

[dependencies]
csv = "1.2"
tui = "0.19"
crossterm = { version = "0.26.1", features = ["use-dev-tty"] }
ratatui = "0.25.0"
crossterm = { version = "0.27.0", features = ["use-dev-tty"] }
anyhow = "1.0"
clap = { version = "4.2", features = ["derive"] }
tempfile = "3.5"
regex = "1.8"
csv-sniffer = "0.3.1"

[target.'cfg(windows)'.dependencies]
crossterm = "0.25"
crossterm = "0.27.0"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible keep this pinned at 0.25 for windows? This is due to an on-going issue in crossterm. More info: #30

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3ce170a

Usually apps can filter to only include key_event.kind == Press to workaround this.
Is there a need to handle release events in this app? I didn't see anything obvious.

See ratatui-org/ratatui#347 for more info.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I think this should work. Thanks for the fix.


# The profile that 'cargo dist' will build with
[profile.dist]
Expand Down
14 changes: 7 additions & 7 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use crate::ui::{CsvTable, CsvTableState, FilterColumnsState, FinderState};
use crate::view;

use anyhow::ensure;
use tui::backend::Backend;
use tui::{Frame, Terminal};
use ratatui::backend::Backend;
use ratatui::{Frame, Terminal};

use anyhow::{Context, Result};
use regex::Regex;
Expand Down Expand Up @@ -532,7 +532,7 @@ impl App {
}
}

fn render_frame<B: Backend>(&mut self, f: &mut Frame<B>) {
fn render_frame(&mut self, f: &mut Frame) {
let size = f.size();

// Render help; if so exit early.
Expand Down Expand Up @@ -573,16 +573,16 @@ mod tests {
use std::thread;

use super::*;
use tui::backend::TestBackend;
use tui::buffer::Buffer;
use ratatui::backend::TestBackend;
use ratatui::buffer::Buffer;

fn to_lines(buf: &Buffer) -> Vec<String> {
let mut symbols: String = "".to_owned();
let area = buf.area();
for y in 0..area.bottom() {
for x in 0..area.right() {
let symbol = buf.get(x, y).symbol.clone();
symbols.push_str(&symbol);
let symbol = buf.get(x, y).symbol();
symbols.push_str(symbol);
}
if y != area.bottom() - 1 {
symbols.push('\n');
Expand Down
8 changes: 4 additions & 4 deletions src/help.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use tui::{
use ratatui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier, Style},
text::{Span, Spans},
text::{Line, Span},
widgets::{Block, Borders, Paragraph, StatefulWidget, Widget, Wrap},
};

Expand Down Expand Up @@ -118,9 +118,9 @@ impl StatefulWidget for HelpPage {
}
}

let text: Vec<Spans> = HELP_CONTENT
let text: Vec<Line> = HELP_CONTENT
.split('\n')
.map(|s| Spans::from(line_to_span(s)))
.map(|s| Line::from(line_to_span(s)))
.collect();

// Minus 2 to account for borders.
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ use crossterm::execute;
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::panic;
use std::thread::panicking;
use tempfile::NamedTempFile;
use tui::backend::CrosstermBackend;
use tui::Terminal;

struct SeekableFile {
filename: Option<String>,
Expand Down
37 changes: 18 additions & 19 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ use crate::input::InputMode;
use crate::view;
use crate::view::Header;
use crate::wrap;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::symbols::line;
use ratatui::text::{Line, Span};
use ratatui::widgets::Widget;
use ratatui::widgets::{Block, Borders, StatefulWidget};
use regex::Regex;
use tui::buffer::Buffer;
use tui::layout::Rect;
use tui::style::{Color, Modifier, Style};
use tui::symbols::line;
use tui::text::{Span, Spans};
use tui::widgets::Widget;
use tui::widgets::{Block, Borders, StatefulWidget};

use std::cmp::{max, min};
use std::collections::HashMap;
Expand Down Expand Up @@ -457,32 +457,31 @@ impl<'a> CsvTable<'a> {
NUM_SPACES_BETWEEN_COLUMNS
} as usize;

let mut spans_wrapper = wrap::SpansWrapper::new(spans, effective_width as usize);
let mut line_wrapper = wrap::LineWrapper::new(spans, effective_width as usize);

for offset in 0..height {
if let Some(mut spans) = spans_wrapper.next() {
if let Some(mut line) = line_wrapper.next() {
// There is some content to render. Truncate with ... if there is no more vertical
// space available.
if offset == height - 1 && !spans_wrapper.finished() {
if let Some(last_span) = spans.0.pop() {
if offset == height - 1 && !line_wrapper.finished() {
if let Some(last_span) = line.spans.pop() {
let truncate_length = last_span.width().saturating_sub(SUFFIX_LEN as usize);
let truncated_content: String =
last_span.content.chars().take(truncate_length).collect();
let truncated_span = Span::styled(truncated_content, last_span.style);
spans.0.push(truncated_span);
spans.0.push(Span::styled(SUFFIX, last_span.style));
line.spans.push(truncated_span);
line.spans.push(Span::styled(SUFFIX, last_span.style));
}
}
let padding_width = min(
(effective_width as usize).saturating_sub(spans.width()) + buffer_space,
(effective_width as usize).saturating_sub(line.width()) + buffer_space,
width as usize,
);
if padding_width > 0 {
spans
.0
line.spans
.push(Span::styled(" ".repeat(padding_width), filler_style.style));
}
buf.set_spans(x, y + offset, &spans, width);
buf.set_line(x, y + offset, &line, width);
} else {
// There are extra vertical spaces that are just empty lines. Fill them with the
// correct style.
Expand All @@ -491,15 +490,15 @@ impl<'a> CsvTable<'a> {

// It's possible that no spans are yielded due to insufficient remaining width.
// Render ... in this case.
if !spans_wrapper.finished() {
if !line_wrapper.finished() {
let truncated_content: String = content
.chars()
.take(content.len().saturating_sub(1))
.collect();
content = format!("{SUFFIX}{}", truncated_content.as_str());
}
let span = Span::styled(content, filler_style.style);
buf.set_spans(x, y + offset, &Spans::from(vec![span]), width);
buf.set_line(x, y + offset, &Line::from(vec![span]), width);
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions src/util/events.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use std::time::{Duration, Instant};

use crossterm::{
event::{poll, read, Event, KeyCode, KeyEvent},
ErrorKind,
};
use crossterm::event::{poll, read, Event, KeyCode, KeyEvent};

pub enum CsvlensEvent<I> {
Input(I),
Expand Down Expand Up @@ -42,7 +39,7 @@ impl CsvlensEvents {
}
}

pub fn next(&self) -> Result<CsvlensEvent<KeyEvent>, ErrorKind> {
pub fn next(&self) -> std::io::Result<CsvlensEvent<KeyEvent>> {
let now = Instant::now();
match poll(self.tick_rate) {
Ok(true) => {
Expand Down
68 changes: 34 additions & 34 deletions src/wrap.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
use tui::text::{Span, Spans};
use ratatui::text::{Line, Span};

pub struct SpansWrapper<'a> {
pub struct LineWrapper<'a> {
spans: &'a [Span<'a>],
max_width: usize,
index: usize,
pending: Option<Span<'a>>,
}

impl<'a> SpansWrapper<'a> {
impl<'a> LineWrapper<'a> {
pub fn new(spans: &'a [Span<'a>], max_width: usize) -> Self {
SpansWrapper {
LineWrapper {
spans,
max_width,
index: 0,
pending: None,
}
}

pub fn next(&mut self) -> Option<Spans<'a>> {
pub fn next(&mut self) -> Option<Line<'a>> {
let mut out_spans = vec![];
let mut remaining_width = self.max_width;
loop {
Expand Down Expand Up @@ -70,7 +70,7 @@ impl<'a> SpansWrapper<'a> {
if out_spans.is_empty() {
return None;
}
Some(Spans::from(out_spans))
Some(Line::from(out_spans))
}

pub fn finished(&self) -> bool {
Expand All @@ -82,47 +82,47 @@ impl<'a> SpansWrapper<'a> {
mod tests {

use super::*;
use tui::style::{Color, Style};
use ratatui::style::{Color, Style};

#[test]
fn test_no_wrapping() {
let s = Span::raw("hello");
let spans = vec![s.clone()];
let mut wrapper = SpansWrapper::new(&spans, 10);
assert_eq!(wrapper.next(), Some(Spans::from(vec![s.clone()])));
let mut wrapper = LineWrapper::new(&spans, 10);
assert_eq!(wrapper.next(), Some(Line::from(vec![s.clone()])));
assert_eq!(wrapper.next(), None);
}

#[test]
fn test_with_wrapping() {
let s = Span::raw("hello");
let spans = vec![s.clone()];
let mut wrapper = SpansWrapper::new(&spans, 2);
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("he")])));
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("ll")])));
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("o")])));
let mut wrapper = LineWrapper::new(&spans, 2);
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("he")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("ll")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("o")])));
assert_eq!(wrapper.next(), None);
}

#[test]
fn test_new_lines_before_max_width() {
let s = Span::raw("hello\nworld");
let spans = vec![s.clone()];
let mut wrapper = SpansWrapper::new(&spans, 10);
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("hello")])));
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("world")])));
let mut wrapper = LineWrapper::new(&spans, 10);
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("hello")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("world")])));
assert_eq!(wrapper.next(), None);
}

#[test]
fn test_new_lines_after_max_width() {
let s = Span::raw("hello\nworld");
let spans = vec![s.clone()];
let mut wrapper = SpansWrapper::new(&spans, 3);
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("hel")])));
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("lo")])));
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("wor")])));
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("ld")])));
let mut wrapper = LineWrapper::new(&spans, 3);
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("hel")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("lo")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("wor")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("ld")])));
assert_eq!(wrapper.next(), None);
}

Expand All @@ -134,16 +134,16 @@ mod tests {
Span::styled("my", style),
Span::raw("world"),
];
let mut wrapper = SpansWrapper::new(&spans, 5);
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("hello")])));
let mut wrapper = LineWrapper::new(&spans, 5);
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("hello")])));
assert_eq!(
wrapper.next(),
Some(Spans::from(vec![
Some(Line::from(vec![
Span::styled("my", style),
Span::raw("wor")
]))
);
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("ld")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("ld")])));
assert_eq!(wrapper.next(), None);
}

Expand All @@ -155,31 +155,31 @@ mod tests {
Span::styled("m\ny", style),
Span::raw("world"),
];
let mut wrapper = SpansWrapper::new(&spans, 5);
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("hello")])));
let mut wrapper = LineWrapper::new(&spans, 5);
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("hello")])));
assert_eq!(
wrapper.next(),
Some(Spans::from(vec![Span::styled("m", style)]))
Some(Line::from(vec![Span::styled("m", style)]))
);
assert_eq!(
wrapper.next(),
Some(Spans::from(vec![
Some(Line::from(vec![
Span::styled("y", style),
Span::raw("worl")
]))
);
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("d")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("d")])));
assert_eq!(wrapper.next(), None);
}

#[test]
fn test_unicode() {
let s = Span::raw("héllo");
let spans = vec![s.clone()];
let mut wrapper = SpansWrapper::new(&spans, 2);
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("hé")])));
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("ll")])));
assert_eq!(wrapper.next(), Some(Spans::from(vec![Span::raw("o")])));
let mut wrapper = LineWrapper::new(&spans, 2);
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("hé")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("ll")])));
assert_eq!(wrapper.next(), Some(Line::from(vec![Span::raw("o")])));
assert_eq!(wrapper.next(), None);
}
}