diff --git a/Cargo.lock b/Cargo.lock index 6768509..ff8832e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,6 +247,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.1", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "ctrlc" version = "3.4.1" @@ -513,6 +538,21 @@ dependencies = [ "adler", ] +[[package]] +name = "minus" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f9991684a4c6cad95f3cdc3c07b1fcf643654a2d3186b3b309fa77eb48cfb19" +dependencies = [ + "crossbeam-channel", + "crossterm", + "once_cell", + "parking_lot", + "regex", + "textwrap", + "thiserror", +] + [[package]] name = "mio" version = "0.8.9" @@ -577,6 +617,9 @@ name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +dependencies = [ + "parking_lot_core", +] [[package]] name = "option-ext" @@ -827,6 +870,27 @@ dependencies = [ "dirs", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -890,6 +954,7 @@ dependencies = [ "ctrlc", "lazy_static", "linemux", + "minus", "once_cell", "rand", "regex", @@ -924,6 +989,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.40" @@ -1066,6 +1140,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index b01d501..c9aea97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ colored = "2" ctrlc = "3.4.1" lazy_static = "1.4.0" linemux = "0.3" +minus = { version = "5.5", features = ["search", "dynamic_output"] } once_cell = "1.19.0" rand = "0.8.5" regex = "1.10.2" diff --git a/README.md b/README.md index dd634d7..c11207d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ A log file highlighter - 🌈 Highlights numbers, dates, IP-addresses, UUIDs, URLs and more - ⚙️ All highlight groups are customizable - 🧬 Easy to integrate with other commands -- 🔍 Uses `less` under the hood for scrollback, search and filtering +- 🔍 Uses [`minus`](https://github.com/arijit79/minus) under the hood for scrollback, search and filtering # @@ -31,7 +31,7 @@ A log file highlighter * [Watching folders](#watching-folders) * [Customizing Highlight Groups](#customizing-highlight-groups) * [Working with `stdin` and `stdout`](#working-with-stdin-and-stdout) -* [Using the pager `less`](#using-the-pager-less) +* [Using the pager `minus`](#using-the-pager-minus) * [Settings](#settings) *** @@ -79,10 +79,6 @@ cargo install --path . Binary will be placed in `~/.cargo/bin`, make sure you add the folder to your `PATH` environment variable. -> [!NOTE] -> When building from source, make sure that you are using the latest version -> of [`less`](http://greenwoodsoftware.com/less/). - ## Highlight Groups ### Dates @@ -286,7 +282,7 @@ of words to be highlighted. ## Working with `stdin` and `stdout` -By default, `tailspin` will open a file in the pager `less`. However, if you pipe something into `tailspin`, it will +By default, `tailspin` will open a file in the pager `minus`. However, if you pipe something into `tailspin`, it will print the highlighted output directly to `stdout`. This is similar to running `tspin [file] --print`. To let `tailspin` highlight the logs of different commands, you can pipe the output of those commands into `tailspin` @@ -298,47 +294,12 @@ cat /var/log/syslog | tspin kubectl logs -f pod_name | tspin ``` -## Using the pager `less` +## Using the pager `minus` ### Overview -`tailspin` uses `less` as its pager to view the highlighted log files. You can get more info on `less` via the **man** -command (`man less`) or by hitting the h button to access the help screen. - -### Navigating - -Navigating within `less` uses a set of keybindings that may be familiar to users of `vim` or other `vi`-like -editors. Here's a brief overview of the most useful navigation commands: - -- j/k: Scroll one line up / down -- d/u: Scroll one half-page up / down -- g/G: Go to the top / bottom of the file - -### Follow mode - -When you run `tailspin` with the `-f` or `--follow` flag, it will scroll to the bottom and print new lines to the screen -as they're added to the file. - -To stop following the file, interrupt with Ctrl + C. This will stop the tailing, but keep the -file open, allowing you to review the existing content. - -To resume following the file from within `less`, press Shift + F. - -### Search - -Use / followed by your search query. For example, `/ERROR` finds the first occurrence of -**ERROR**. - -After the search, n finds the next instance, and N finds the previous instance. - -### Filtering - -`less` allows filtering lines by a keyword, using & followed by the pattern. For instance, `&ERROR` shows -only lines with **ERROR**. - -To only show lines containing either `ERROR` or `WARN`, use a regular expression: `&\(ERROR\|WARN\)`. - -To clear the filter, use & with no pattern. +`tailspin` uses `minus` as its pager to view the highlighted log files. +You can see the available keyibdings [here](https://docs.rs/minus/latest/minus/index.html#standard-actions). ## Settings diff --git a/src/config/mod.rs b/src/config/mod.rs index 625abd4..c625e19 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -111,7 +111,7 @@ fn get_output(has_data_from_stdin: bool, is_print_flag: bool) -> Output { return Output::Stdout; } - Output::TempFile + Output::Pager } fn determine_input(path: String) -> Result { diff --git a/src/io/controller/mod.rs b/src/io/controller/mod.rs index 427bafd..8937953 100644 --- a/src/io/controller/mod.rs +++ b/src/io/controller/mod.rs @@ -1,87 +1,39 @@ -use async_trait::async_trait; -use tokio::io; - -use crate::io::presenter::empty::NoPresenter; -use crate::io::presenter::less::Less; -use crate::io::presenter::Present; use crate::io::reader::command::CommandReader; use crate::io::reader::linemux::Linemux; use crate::io::reader::stdin::StdinReader; use crate::io::reader::AsyncLineReader; use crate::io::writer::stdout::StdoutWriter; -use crate::io::writer::temp_file::TempFile; + use crate::io::writer::AsyncLineWriter; use crate::types::{Config, Input, Output}; -use tokio::sync::oneshot::Sender; -pub struct Io { - reader: Box, - writer: Box, -} +use super::reader::EOFSignaler; +use super::writer::minus::Minus; -pub struct Presenter { - presenter: Box, -} +pub type Reader = Box; +pub type Writer = Box; -pub async fn get_io_and_presenter(config: Config, reached_eof_tx: Option>) -> (Io, Presenter) { - let reader = get_reader(config.input, config.follow, config.tail, reached_eof_tx).await; - let (writer, presenter) = get_writer(config.output, config.follow).await; +pub async fn get_reader_and_writer(config: Config, eof_signaler: EOFSignaler) -> (Reader, Writer) { + let reader = get_reader(config.input, config.follow, config.tail, eof_signaler).await; + let writer = get_writer(config.output).await; - (Io { reader, writer }, Presenter { presenter }) + (reader, writer) } -async fn get_reader( - input: Input, - follow: bool, - tail: bool, - reached_eof_tx: Option>, -) -> Box { +async fn get_reader(input: Input, follow: bool, tail: bool, eof_signaler: EOFSignaler) -> Reader { match input { Input::File(file_info) => { - Linemux::get_reader_single(file_info.path, file_info.line_count, follow, tail, reached_eof_tx).await + Linemux::get_reader_single(file_info.path, file_info.line_count, follow, tail, eof_signaler).await } - Input::Folder(info) => Linemux::get_reader_multiple(info.folder_name, info.file_paths, reached_eof_tx).await, - Input::Stdin => StdinReader::get_reader(reached_eof_tx), - Input::Command(cmd) => CommandReader::get_reader(cmd, reached_eof_tx).await, + Input::Folder(info) => Linemux::get_reader_multiple(info.folder_name, info.file_paths, eof_signaler).await, + Input::Stdin => StdinReader::get_reader(eof_signaler), + Input::Command(cmd) => CommandReader::get_reader(cmd, eof_signaler).await, } } -async fn get_writer(output: Output, follow: bool) -> (Box, Box) { +async fn get_writer(output: Output) -> Writer { match output { - Output::TempFile => { - let result = TempFile::get_writer_result().await; - let writer = result.writer; - let temp_file_path = result.temp_file_path; - - let presenter = Less::get_presenter(temp_file_path, follow); - - (writer, presenter) - } - Output::Stdout => { - let writer = StdoutWriter::init(); - let presenter = NoPresenter::get_presenter(); - - (writer, presenter) - } - } -} - -#[async_trait] -impl AsyncLineReader for Io { - async fn next_line(&mut self) -> io::Result> { - self.reader.next_line().await - } -} - -#[async_trait] -impl AsyncLineWriter for Io { - async fn write_line(&mut self, line: &str) -> io::Result<()> { - self.writer.write_line(line).await - } -} - -impl Present for Presenter { - fn present(&self) { - self.presenter.present() + Output::Pager => Minus::init().await, + Output::Stdout => StdoutWriter::init(), } } diff --git a/src/io/mod.rs b/src/io/mod.rs index 82945de..a8c4cb9 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,4 +1,3 @@ pub mod controller; -pub mod presenter; pub mod reader; pub mod writer; diff --git a/src/io/presenter/empty.rs b/src/io/presenter/empty.rs deleted file mode 100644 index 87ac024..0000000 --- a/src/io/presenter/empty.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::io::presenter::Present; - -pub struct NoPresenter {} - -impl NoPresenter { - pub fn get_presenter() -> Box { - Box::new(Self {}) - } -} - -impl Present for NoPresenter { - fn present(&self) { - // no-op - } -} diff --git a/src/io/presenter/less.rs b/src/io/presenter/less.rs deleted file mode 100644 index 8748a74..0000000 --- a/src/io/presenter/less.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::io::presenter::Present; -use std::process::Command; -use std::{io, process}; - -pub struct Less { - file_path: String, - follow: bool, -} - -impl Less { - pub fn get_presenter(file_path: String, follow: bool) -> Box { - Box::new(Self { file_path, follow }) - } -} - -impl Present for Less { - fn present(&self) { - pass_ctrl_c_events_to_child_process(); - - let args = get_args(self.follow); - let result = Command::new("less") - .env("LESSSECURE", "1") - .args(args.as_slice()) - .arg(self.file_path.clone()) - .status(); - - match result { - Ok(_) => {} - Err(err) => { - if err.kind() == io::ErrorKind::NotFound { - eprintln!("'less' command not found. Please ensure it is installed and on your PATH."); - } else { - eprintln!("Failed to run less: {}", err); - } - process::exit(1); - } - } - } -} - -fn pass_ctrl_c_events_to_child_process() { - // Without this handling, pressing Ctrl + C causes the program to exit - // immediately instead of passing the signal down to the child process (less) - ctrlc::set_handler(|| {}).expect("Error setting Ctrl-C handler"); -} - -fn get_args(follow: bool) -> Vec { - let mut args = vec![ - "--ignore-case".to_string(), - "--RAW-CONTROL-CHARS".to_string(), - "--".to_string(), // End of option arguments - ]; - - if follow { - args.insert(0, "+F".to_string()); - } - - args -} diff --git a/src/io/presenter/mod.rs b/src/io/presenter/mod.rs deleted file mode 100644 index 88c8112..0000000 --- a/src/io/presenter/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod empty; -pub mod less; - -pub trait Present: Send { - fn present(&self); -} diff --git a/src/io/reader/command.rs b/src/io/reader/command.rs index 939cb40..56ac5dc 100644 --- a/src/io/reader/command.rs +++ b/src/io/reader/command.rs @@ -1,25 +1,20 @@ +use crate::io::controller::Reader; use crate::io::reader::AsyncLineReader; use async_trait::async_trait; use std::process::Stdio; use tokio::io; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::Command as AsyncCommand; -use tokio::sync::oneshot::Sender; + +use super::EOFSignaler; pub struct CommandReader { reader: BufReader, } impl CommandReader { - pub async fn get_reader( - command: String, - mut reached_eof_tx: Option>, - ) -> Box { - if let Some(reached_eof) = reached_eof_tx.take() { - reached_eof - .send(()) - .expect("Failed sending EOF signal to oneshot channel"); - }; + pub async fn get_reader(command: String, mut eof_signaler: EOFSignaler) -> Reader { + super::send_eof_signal(eof_signaler.take()); let trap_command = format!("trap '' INT; {}", command); diff --git a/src/io/reader/linemux.rs b/src/io/reader/linemux.rs index 85ac488..a552d55 100644 --- a/src/io/reader/linemux.rs +++ b/src/io/reader/linemux.rs @@ -4,14 +4,15 @@ use color_eyre::owo_colors::OwoColorize; use linemux::MuxedLines; use std::io; use terminal_size::{terminal_size, Height, Width}; -use tokio::sync::oneshot::Sender; + +use super::EOFSignaler; pub struct Linemux { custom_message: Option, number_of_lines: Option, current_line: usize, - reached_eof_tx: Option>, lines: MuxedLines, + eof_signaler: EOFSignaler, } impl Linemux { @@ -20,17 +21,12 @@ impl Linemux { number_of_lines: usize, follow: bool, tail: bool, - mut reached_eof_tx: Option>, + mut eof_signaler: EOFSignaler, ) -> Box { let mut lines = MuxedLines::new().expect("Could not instantiate linemux"); if tail || number_of_lines == 0 { - if let Some(reached_eof) = reached_eof_tx.take() { - reached_eof - .send(()) - .expect("Failed sending EOF signal to oneshot channel"); - } - + super::send_eof_signal(eof_signaler.take()); lines.add_file(&file_path).await.expect("Could not add file to linemux"); } else { lines @@ -45,23 +41,19 @@ impl Linemux { custom_message: None, number_of_lines, current_line: 0, - reached_eof_tx, lines, + eof_signaler, }) } pub async fn get_reader_multiple( folder_name: String, file_paths: Vec, - mut reached_eof_tx: Option>, + mut eof_signaler: EOFSignaler, ) -> Box { use std::path::Path; - if let Some(reached_eof) = reached_eof_tx.take() { - reached_eof - .send(()) - .expect("Failed sending EOF signal to oneshot channel"); - } + super::send_eof_signal(eof_signaler.take()); let mut lines = MuxedLines::new().expect("Could not instantiate linemux"); @@ -100,18 +92,10 @@ impl Linemux { custom_message: Some(custom_message), number_of_lines: None, current_line: 0, - reached_eof_tx, lines, + eof_signaler, }) } - - fn send_eof_signal(&mut self) { - if let Some(reached_eof) = self.reached_eof_tx.take() { - reached_eof - .send(()) - .expect("Failed sending EOF signal to oneshot channel"); - } - } } fn get_separator() -> String { @@ -134,14 +118,16 @@ impl AsyncLineReader for Linemux { let line = match self.lines.next_line().await { Ok(Some(line)) => line, - _ => return Ok(None), + _ => { + return Ok(None); + } }; let next_line = line.line().to_owned(); if let Some(number_of_lines) = self.number_of_lines { if self.current_line >= number_of_lines { - self.send_eof_signal(); + super::send_eof_signal(self.eof_signaler.take()); } } diff --git a/src/io/reader/mod.rs b/src/io/reader/mod.rs index 3020197..cb0256c 100644 --- a/src/io/reader/mod.rs +++ b/src/io/reader/mod.rs @@ -3,9 +3,20 @@ pub mod linemux; pub mod stdin; use async_trait::async_trait; -use tokio::io; +use tokio::{io, sync::oneshot::Sender}; +/// A helper type, if set, can be used to throw a signal +// / that the EOF has been reached by the reader. +pub type EOFSignaler = Option>; #[async_trait] pub trait AsyncLineReader { async fn next_line(&mut self) -> io::Result>; } + +fn send_eof_signal(eof_signaler: EOFSignaler) { + if let Some(eof_signaler) = eof_signaler { + eof_signaler + .send(()) + .expect("Failed sending EOF signal to oneshot channel"); + } +} diff --git a/src/io/reader/stdin.rs b/src/io/reader/stdin.rs index de41865..3f83109 100644 --- a/src/io/reader/stdin.rs +++ b/src/io/reader/stdin.rs @@ -2,18 +2,19 @@ use crate::io::reader::AsyncLineReader; use async_trait::async_trait; use tokio::io; use tokio::io::{AsyncBufReadExt, BufReader, Stdin}; -use tokio::sync::oneshot::Sender; + +use super::EOFSignaler; pub struct StdinReader { reader: BufReader, - reached_eof_tx: Option>, + eof_signaler: EOFSignaler, } impl StdinReader { - pub fn get_reader(reached_eof_tx: Option>) -> Box { + pub fn get_reader(eof_signaler: EOFSignaler) -> Box { Box::new(StdinReader { reader: BufReader::new(tokio::io::stdin()), - reached_eof_tx, + eof_signaler, }) } @@ -36,14 +37,6 @@ impl StdinReader { buf } - - fn send_eof_signal(&mut self) { - if let Some(reached_eof) = self.reached_eof_tx.take() { - reached_eof - .send(()) - .expect("Failed sending EOF signal to oneshot channel"); - } - } } #[async_trait] @@ -52,7 +45,7 @@ impl AsyncLineReader for StdinReader { let buffer = self.read_bytes_until_newline().await?; if buffer.is_empty() { - self.send_eof_signal(); + super::send_eof_signal(self.eof_signaler.take()); return Ok(None); } diff --git a/src/io/writer/minus.rs b/src/io/writer/minus.rs new file mode 100644 index 0000000..0245ac3 --- /dev/null +++ b/src/io/writer/minus.rs @@ -0,0 +1,31 @@ +use minus::{dynamic_paging, Pager}; +use std::fmt::Write; +use tokio::{io, task}; + +use crate::io::controller::Writer; + +use super::AsyncLineWriter; + +pub struct Minus { + pager: Pager, +} + +impl Minus { + pub async fn init() -> Writer { + let pager = Pager::new(); + let actual_pager = pager.clone(); + + task::spawn(async { dynamic_paging(actual_pager) }); + + Box::new(Self { pager }) + } +} + +#[async_trait::async_trait] +impl AsyncLineWriter for Minus { + async fn write_line(&mut self, line: &str) -> io::Result<()> { + let mut pager = self.pager.clone(); + writeln!(pager, "{}", line).unwrap(); + Ok(()) + } +} diff --git a/src/io/writer/mod.rs b/src/io/writer/mod.rs index b69dd98..10d2d2c 100644 --- a/src/io/writer/mod.rs +++ b/src/io/writer/mod.rs @@ -1,5 +1,5 @@ +pub mod minus; pub mod stdout; -pub mod temp_file; use async_trait::async_trait; use tokio::io; diff --git a/src/io/writer/stdout.rs b/src/io/writer/stdout.rs index 2ca9c45..16e64b0 100644 --- a/src/io/writer/stdout.rs +++ b/src/io/writer/stdout.rs @@ -1,11 +1,11 @@ -use crate::io::writer::AsyncLineWriter; +use crate::io::{controller::Writer, writer::AsyncLineWriter}; use async_trait::async_trait; use tokio::io; pub struct StdoutWriter {} impl StdoutWriter { - pub fn init() -> Box { + pub fn init() -> Writer { Box::new(StdoutWriter {}) } } @@ -14,7 +14,6 @@ impl StdoutWriter { impl AsyncLineWriter for StdoutWriter { async fn write_line(&mut self, line: &str) -> io::Result<()> { println!("{}", line); - Ok(()) } } diff --git a/src/io/writer/temp_file.rs b/src/io/writer/temp_file.rs deleted file mode 100644 index 340349a..0000000 --- a/src/io/writer/temp_file.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::io::writer::AsyncLineWriter; -use async_trait::async_trait; -use rand::random; -use std::path::PathBuf; -use tempfile::TempDir; -use tokio::fs::File; -use tokio::io; -use tokio::io::{AsyncWriteExt, BufWriter}; - -pub struct TempFile { - _temp_dir: TempDir, - temp_file_writer: BufWriter, -} - -pub struct TempFileWriterResult { - pub writer: Box, - pub temp_file_path: String, -} - -impl TempFile { - pub async fn get_writer_result() -> TempFileWriterResult { - let (temp_dir, temp_file_path, temp_file_writer) = create_temp_file().await; - - let temp_file_path_string = temp_file_path - .to_str() - .expect("Could not get path to temp file") - .to_owned(); - - TempFileWriterResult { - writer: Box::new(TempFile { - _temp_dir: temp_dir, - temp_file_writer, - }), - temp_file_path: temp_file_path_string, - } - } -} - -#[async_trait] -impl AsyncLineWriter for TempFile { - async fn write_line(&mut self, line: &str) -> io::Result<()> { - let line_with_newline = format!("{}\n", line); - self.temp_file_writer.write_all(line_with_newline.as_bytes()).await?; - self.temp_file_writer.flush().await?; - - Ok(()) - } -} - -async fn create_temp_file() -> (TempDir, PathBuf, BufWriter) { - let unique_id: u32 = random(); - let filename = format!("tailspin.temp.{}", unique_id); - - let temp_dir = tempfile::tempdir().unwrap(); - - let temp_file_path = temp_dir.path().join(filename); - let output_file = File::create(&temp_file_path).await.unwrap(); - let output_writer = BufWriter::new(output_file); - - (temp_dir, temp_file_path, output_writer) -} diff --git a/src/main.rs b/src/main.rs index ff22739..5c6d53c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,13 +14,10 @@ mod types; use crate::cli::Cli; use crate::highlight_processor::HighlightProcessor; -use crate::io::controller::get_io_and_presenter; -use crate::io::presenter::Present; -use crate::io::reader::AsyncLineReader; -use crate::io::writer::AsyncLineWriter; use crate::theme::Theme; use crate::types::Config; use color_eyre::eyre::Result; +use io::controller::{get_reader_and_writer, Reader, Writer}; use tokio::sync::oneshot; #[tokio::main] @@ -37,27 +34,23 @@ async fn main() -> Result<()> { } pub async fn run(theme: Theme, config: Config, cli: Cli) { - let (reached_eof_tx, reached_eof_rx) = oneshot::channel::<()>(); - let (io, presenter) = get_io_and_presenter(config, Some(reached_eof_tx)).await; - - let highlighter = highlighters::Highlighters::new(&theme, &cli); - let highlight_processor = HighlightProcessor::new(highlighter); - - tokio::spawn(process_lines(io, highlight_processor)); - - reached_eof_rx - .await - .expect("Could not receive EOF signal from oneshot channel"); - - presenter.present(); + let (eof_signaler, eof_receiver) = oneshot::channel::<()>(); + let (reader, writer) = get_reader_and_writer(config, Some(eof_signaler)).await; + let highlight_processor = { + let highlighter = highlighters::Highlighters::new(&theme, &cli); + HighlightProcessor::new(highlighter) + }; + + let page_process = tokio::spawn(start(reader, writer, highlight_processor)); + + let (res1, res2) = tokio::join!(page_process, eof_receiver); + res1.unwrap(); + res2.expect("Could not receive EOF signal from oneshot channel"); } -async fn process_lines( - mut io: T, - highlight_processor: HighlightProcessor, -) { - while let Ok(Some(line)) = io.next_line().await { +async fn start(mut reader: Reader, mut writer: Writer, highlight_processor: HighlightProcessor) { + while let Ok(Some(line)) = reader.next_line().await { let highlighted_line = highlight_processor.apply(&line); - io.write_line(&highlighted_line).await.unwrap(); + writer.write_line(&highlighted_line).await.unwrap(); } } diff --git a/src/types.rs b/src/types.rs index 74d5913..486b682 100644 --- a/src/types.rs +++ b/src/types.rs @@ -39,6 +39,6 @@ pub enum Input { } pub enum Output { - TempFile, + Pager, Stdout, } diff --git a/util/tspin.adoc b/util/tspin.adoc index b8dfd65..b34d5f5 100644 --- a/util/tspin.adoc +++ b/util/tspin.adoc @@ -34,7 +34,7 @@ Start at the end of the file. Always true if opening a folder. *-p, --print*:: -Print the output to stdout instead of viewing the contents in the pager _less_. +Print the output to stdout instead of viewing the contents in the pager. Always true if using stdin. *-c, --config-path* _CONFIG_PATH_:: @@ -43,7 +43,7 @@ Defaults to _XDG_CONFIG_HOME/tailspin/config.toml_ or _~/.config/tailspin/config *-l, --follow-command* _COMMAND_:: Continuously listen to stdout of the provided command. -The command traps the interrupt signal to allow for cancelling and resuming follow mode while inside _less_. +The command traps the interrupt signal to allow for cancelling and resuming follow mode while inside the pager. *--words-[red|green|yellow|blue|magenta|cyan]*:: Highlight the provided comma separated words in the desired color. @@ -62,7 +62,7 @@ Disables the highlighting of REST verbs. == SEE ALSO -*less*(1), *tail*(1) +*tail*(1) == About