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