undo/redo
This commit is contained in:
parent
8a4df3307d
commit
8cfe1a0b65
8 changed files with 247 additions and 154 deletions
|
|
@ -13,7 +13,7 @@ jiff = {version = "0.2", features = ["serde"]}
|
||||||
ratatui = {version = "0.30.0", features=["unstable-rendered-line-info"]}
|
ratatui = {version = "0.30.0", features=["unstable-rendered-line-info"]}
|
||||||
ratatui-themes = { version = "0.2", features = ["serde"] }
|
ratatui-themes = { version = "0.2", features = ["serde"] }
|
||||||
tui-widget-list = "0.15"
|
tui-widget-list = "0.15"
|
||||||
serde = {version = "1", features = ["derive"]}
|
serde = {version = "1", features = ["derive", "rc"]}
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,34 @@
|
||||||
use regex::bytes::Regex;
|
use regex::bytes::Regex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
log_viewer::{FieldMatcher, InputTarget, LogViewer},
|
log_viewer::{
|
||||||
|
LogViewer,
|
||||||
|
input::{FieldMatcher, InputTarget},
|
||||||
|
},
|
||||||
model::LogEntry,
|
model::LogEntry,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod serialize_regex {
|
||||||
|
use regex::bytes::Regex;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
|
||||||
|
|
||||||
|
pub fn serialize<S: Serializer>(r: &Regex, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
r.as_str().serialize(serializer)
|
||||||
|
}
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Regex, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Regex::new(&s).map_err(|e| D::Error::custom(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub enum MatcherValue {
|
pub enum MatcherValue {
|
||||||
Exact(String),
|
Exact(String),
|
||||||
Regex(Regex),
|
Regex(#[serde(with = "serialize_regex")] Regex),
|
||||||
Prefix(String),
|
Prefix(String),
|
||||||
Contains(String),
|
Contains(String),
|
||||||
}
|
}
|
||||||
|
|
@ -32,6 +53,7 @@ impl MatcherValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub enum Matcher {
|
pub enum Matcher {
|
||||||
Field { name: String, value: MatcherValue },
|
Field { name: String, value: MatcherValue },
|
||||||
Message { value: MatcherValue },
|
Message { value: MatcherValue },
|
||||||
|
|
@ -76,12 +98,13 @@ impl Matcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub enum FilterKind {
|
pub enum FilterKind {
|
||||||
Inline,
|
Inline,
|
||||||
Remove,
|
Remove,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Filter {
|
pub struct Filter {
|
||||||
pub matcher: Matcher,
|
pub matcher: Matcher,
|
||||||
pub kind: FilterKind,
|
pub kind: FilterKind,
|
||||||
|
|
|
||||||
41
src/tui/log_viewer/filters.rs
Normal file
41
src/tui/log_viewer/filters.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::tui::filter::Filter;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Filters {
|
||||||
|
filters: Vec<Rc<Filter>>,
|
||||||
|
undo_pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Filters {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
filters: Vec::new(),
|
||||||
|
undo_pos: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> &[Rc<Filter>] {
|
||||||
|
&self.filters[0..self.undo_pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, filter: Rc<Filter>) {
|
||||||
|
self.filters.truncate(self.undo_pos);
|
||||||
|
self.filters.push(filter);
|
||||||
|
self.undo_pos = self.filters.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self) {
|
||||||
|
self.undo_pos += 1;
|
||||||
|
if self.undo_pos > self.filters.len() {
|
||||||
|
self.undo_pos = self.filters.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn undo(&mut self) {
|
||||||
|
self.undo_pos = self.undo_pos.saturating_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/tui/log_viewer/input.rs
Normal file
109
src/tui/log_viewer/input.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
||||||
|
|
||||||
|
use crate::tui::widgets::styled::Styled;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum FieldMatcher {
|
||||||
|
EqualTo,
|
||||||
|
Prefix(String),
|
||||||
|
Regex(String),
|
||||||
|
Contains(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FieldMatcher {
|
||||||
|
pub fn show(&self) -> String {
|
||||||
|
match self {
|
||||||
|
FieldMatcher::EqualTo => "equal to selected value".to_string(),
|
||||||
|
Self::Prefix(s) => format!("with a prefix of `{s}`"),
|
||||||
|
Self::Regex(s) => format!("matching /{s}/"),
|
||||||
|
Self::Contains(s) => format!("containing `{s}`"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum InputTarget {
|
||||||
|
Fields(Option<FieldMatcher>),
|
||||||
|
Text(FieldMatcher),
|
||||||
|
This,
|
||||||
|
Surround,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputTarget {
|
||||||
|
pub fn show(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Fields(None) => "logs with a field...".to_string(),
|
||||||
|
Self::Fields(Some(fm)) => format!("logs with the selected field {}", fm.show()),
|
||||||
|
Self::Text(fm) => format!("logs {}", fm.show()),
|
||||||
|
Self::This => format!("this log"),
|
||||||
|
Self::Surround => format!("the log surrounding the current view"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum InputState {
|
||||||
|
None,
|
||||||
|
Target(InputTarget),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Styled<'_, &InputState> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Line::from(self.inner.show())
|
||||||
|
.style(self.styles.default)
|
||||||
|
.render(area, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputState {
|
||||||
|
pub fn capture_string(&mut self) -> Option<&mut String> {
|
||||||
|
match self {
|
||||||
|
InputState::None => None,
|
||||||
|
InputState::Target(InputTarget::This) => None,
|
||||||
|
InputState::Target(InputTarget::Fields(None)) => None,
|
||||||
|
InputState::Target(InputTarget::Surround) => None,
|
||||||
|
|
||||||
|
// require arbitrary text input
|
||||||
|
InputState::Target(InputTarget::Fields(Some(FieldMatcher::Contains(s)))) => Some(s),
|
||||||
|
InputState::Target(InputTarget::Text(FieldMatcher::Contains(s))) => Some(s),
|
||||||
|
InputState::Target(InputTarget::Fields(Some(FieldMatcher::Prefix(s)))) => Some(s),
|
||||||
|
InputState::Target(InputTarget::Text(FieldMatcher::Prefix(s))) => Some(s),
|
||||||
|
InputState::Target(InputTarget::Fields(Some(FieldMatcher::Regex(s)))) => Some(s),
|
||||||
|
InputState::Target(InputTarget::Text(FieldMatcher::Regex(s))) => Some(s),
|
||||||
|
|
||||||
|
InputState::Target(InputTarget::Fields(Some(FieldMatcher::EqualTo))) => None,
|
||||||
|
InputState::Target(InputTarget::Text(FieldMatcher::EqualTo)) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn captures_input(&mut self) -> bool {
|
||||||
|
self.capture_string().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&self) -> String {
|
||||||
|
match self {
|
||||||
|
InputState::None => "".to_string(),
|
||||||
|
InputState::Target(input_target) => input_target.show(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
*self = Self::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target(&mut self, target: InputTarget) {
|
||||||
|
if let Self::Target(t) = self
|
||||||
|
&& mem::discriminant(t) == mem::discriminant(&target)
|
||||||
|
&& !self.captures_input()
|
||||||
|
{
|
||||||
|
self.reset();
|
||||||
|
} else {
|
||||||
|
*self = Self::Target(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,147 +2,24 @@ use std::{collections::HashMap, iter, mem, rc::Rc};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
filter::Filter,
|
filter::Filter,
|
||||||
|
log_viewer::{
|
||||||
|
filters::Filters,
|
||||||
|
input::{FieldMatcher, InputState, InputTarget},
|
||||||
|
view::LogView,
|
||||||
|
},
|
||||||
model::LogEntry,
|
model::LogEntry,
|
||||||
processing::{IntoLogStream, LogStream},
|
processing::{IntoLogStream, LogStream},
|
||||||
widgets::styled::Styled,
|
|
||||||
};
|
};
|
||||||
use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget};
|
|
||||||
use tui_widget_list::ListState;
|
use tui_widget_list::ListState;
|
||||||
|
|
||||||
pub struct LogView {
|
pub mod filters;
|
||||||
iter: Box<dyn LogStream>,
|
pub mod input;
|
||||||
selection_offset: usize,
|
pub mod view;
|
||||||
}
|
|
||||||
|
|
||||||
impl LogView {
|
|
||||||
pub fn selected(&self) -> Option<(Rc<LogEntry>, usize)> {
|
|
||||||
let mut temp_iter = self.iter.clone();
|
|
||||||
for _ in 0..self.selection_offset {
|
|
||||||
let _ = temp_iter.next()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
temp_iter.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for LogView {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
iter: self.iter.clone(),
|
|
||||||
selection_offset: self.selection_offset.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum FieldMatcher {
|
|
||||||
EqualTo,
|
|
||||||
Prefix(String),
|
|
||||||
Regex(String),
|
|
||||||
Contains(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FieldMatcher {
|
|
||||||
pub fn show(&self) -> String {
|
|
||||||
match self {
|
|
||||||
FieldMatcher::EqualTo => "equal to selected value".to_string(),
|
|
||||||
Self::Prefix(s) => format!("with a prefix of `{s}`"),
|
|
||||||
Self::Regex(s) => format!("matching /{s}/"),
|
|
||||||
Self::Contains(s) => format!("containing `{s}`"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum InputTarget {
|
|
||||||
Fields(Option<FieldMatcher>),
|
|
||||||
Text(FieldMatcher),
|
|
||||||
This,
|
|
||||||
Surround,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputTarget {
|
|
||||||
pub fn show(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Self::Fields(None) => "logs with a field...".to_string(),
|
|
||||||
Self::Fields(Some(fm)) => format!("logs with the selected field {}", fm.show()),
|
|
||||||
Self::Text(fm) => format!("logs {}", fm.show()),
|
|
||||||
Self::This => format!("this log"),
|
|
||||||
Self::Surround => format!("the log surrounding the current view"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum InputState {
|
|
||||||
None,
|
|
||||||
Target(InputTarget),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Styled<'_, &InputState> {
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer)
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
Line::from(self.inner.show())
|
|
||||||
.style(self.styles.default)
|
|
||||||
.render(area, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputState {
|
|
||||||
pub fn capture_string(&mut self) -> Option<&mut String> {
|
|
||||||
match self {
|
|
||||||
InputState::None => None,
|
|
||||||
InputState::Target(InputTarget::This) => None,
|
|
||||||
InputState::Target(InputTarget::Fields(None)) => None,
|
|
||||||
InputState::Target(InputTarget::Surround) => None,
|
|
||||||
|
|
||||||
// require arbitrary text input
|
|
||||||
InputState::Target(InputTarget::Fields(Some(FieldMatcher::Contains(s)))) => Some(s),
|
|
||||||
InputState::Target(InputTarget::Text(FieldMatcher::Contains(s))) => Some(s),
|
|
||||||
InputState::Target(InputTarget::Fields(Some(FieldMatcher::Prefix(s)))) => Some(s),
|
|
||||||
InputState::Target(InputTarget::Text(FieldMatcher::Prefix(s))) => Some(s),
|
|
||||||
InputState::Target(InputTarget::Fields(Some(FieldMatcher::Regex(s)))) => Some(s),
|
|
||||||
InputState::Target(InputTarget::Text(FieldMatcher::Regex(s))) => Some(s),
|
|
||||||
|
|
||||||
InputState::Target(InputTarget::Fields(Some(FieldMatcher::EqualTo))) => None,
|
|
||||||
InputState::Target(InputTarget::Text(FieldMatcher::EqualTo)) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn captures_input(&mut self) -> bool {
|
|
||||||
self.capture_string().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show(&self) -> String {
|
|
||||||
match self {
|
|
||||||
InputState::None => "".to_string(),
|
|
||||||
InputState::Target(input_target) => input_target.show(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
*self = Self::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn target(&mut self, target: InputTarget) {
|
|
||||||
if let Self::Target(t) = self
|
|
||||||
&& mem::discriminant(t) == mem::discriminant(&target)
|
|
||||||
&& !self.captures_input()
|
|
||||||
{
|
|
||||||
self.reset();
|
|
||||||
} else {
|
|
||||||
*self = Self::Target(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LogViewer {
|
pub struct LogViewer {
|
||||||
pub stack: Vec<LogView>,
|
pub stack: Vec<LogView>,
|
||||||
curr: LogView,
|
curr: LogView,
|
||||||
cache: HashMap<Vec<usize>, LogView>,
|
cache: HashMap<Vec<usize>, LogView>,
|
||||||
filters: Vec<Rc<Filter>>,
|
|
||||||
|
|
||||||
pub root_stream: Box<dyn LogStream>,
|
pub root_stream: Box<dyn LogStream>,
|
||||||
|
|
||||||
|
|
@ -152,7 +29,7 @@ pub struct LogViewer {
|
||||||
pub last_fields_height: usize,
|
pub last_fields_height: usize,
|
||||||
|
|
||||||
pub footer_list: ListState,
|
pub footer_list: ListState,
|
||||||
|
filters: Filters,
|
||||||
pub input_state: InputState,
|
pub input_state: InputState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,22 +50,21 @@ impl LogViewer {
|
||||||
last_fields_offset: 0,
|
last_fields_offset: 0,
|
||||||
last_fields_height: 0,
|
last_fields_height: 0,
|
||||||
|
|
||||||
filters: Vec::new(),
|
filters: Filters::new(),
|
||||||
input_state: InputState::None,
|
input_state: InputState::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filtered_root_stream(&self) -> Box<dyn LogStream> {
|
pub fn filtered_root_stream(&self) -> Box<dyn LogStream> {
|
||||||
let mut curr = self.root_stream.clone();
|
let mut curr = self.root_stream.clone();
|
||||||
for filter in &self.filters {
|
for filter in self.filters.get() {
|
||||||
curr = Box::new(curr.filter(Rc::clone(filter)));
|
curr = Box::new(curr.filter(Rc::clone(filter)));
|
||||||
}
|
}
|
||||||
|
|
||||||
curr
|
curr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_filter(&mut self, filter: Rc<Filter>) {
|
fn update_filters(&mut self) {
|
||||||
self.filters.push(Rc::clone(&filter));
|
|
||||||
self.cache.clear();
|
self.cache.clear();
|
||||||
let offsets_list: Vec<_> = self
|
let offsets_list: Vec<_> = self
|
||||||
.stack
|
.stack
|
||||||
|
|
@ -230,9 +106,9 @@ impl LogViewer {
|
||||||
|
|
||||||
// If the value we're looking for is removed by the filter,
|
// If the value we're looking for is removed by the filter,
|
||||||
// we'll have a hard time finding it so quit
|
// we'll have a hard time finding it so quit
|
||||||
if filter.removes(&elem) {
|
// if filter.removes(&elem) {
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// find the nearest stream in which this element can be found
|
// find the nearest stream in which this element can be found
|
||||||
let mut curr = current_stream.as_ref();
|
let mut curr = current_stream.as_ref();
|
||||||
|
|
@ -286,6 +162,11 @@ impl LogViewer {
|
||||||
self.stack = new_stack;
|
self.stack = new_stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_filter(&mut self, filter: Rc<Filter>) {
|
||||||
|
self.filters.push(Rc::clone(&filter));
|
||||||
|
self.update_filters();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_num_items(&mut self, num_visible_items: usize) {
|
pub fn update_num_items(&mut self, num_visible_items: usize) {
|
||||||
while self.curr.selection_offset >= num_visible_items {
|
while self.curr.selection_offset >= num_visible_items {
|
||||||
if self.curr.selection_offset == 0 {
|
if self.curr.selection_offset == 0 {
|
||||||
|
|
@ -436,6 +317,16 @@ impl LogViewer {
|
||||||
self.input_state.reset();
|
self.input_state.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn undo(&mut self) {
|
||||||
|
self.filters.undo();
|
||||||
|
self.update_filters();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self) {
|
||||||
|
self.filters.redo();
|
||||||
|
self.update_filters();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn enter(&mut self) {
|
pub fn enter(&mut self) {
|
||||||
match self.input_state {
|
match self.input_state {
|
||||||
InputState::None => {
|
InputState::None => {
|
||||||
28
src/tui/log_viewer/view.rs
Normal file
28
src/tui/log_viewer/view.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::tui::{model::LogEntry, processing::LogStream};
|
||||||
|
|
||||||
|
pub struct LogView {
|
||||||
|
pub iter: Box<dyn LogStream>,
|
||||||
|
pub selection_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LogView {
|
||||||
|
pub fn selected(&self) -> Option<(Rc<LogEntry>, usize)> {
|
||||||
|
let mut temp_iter = self.iter.clone();
|
||||||
|
for _ in 0..self.selection_offset {
|
||||||
|
let _ = temp_iter.next()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_iter.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for LogView {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
iter: self.iter.clone(),
|
||||||
|
selection_offset: self.selection_offset.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,15 +14,13 @@ use std::{
|
||||||
use tui_widget_list::{ListBuilder, ListView};
|
use tui_widget_list::{ListBuilder, ListView};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
filter::FilterKind,
|
filter::{Filter, FilterKind, Matcher},
|
||||||
log_viewer::{InputState, InputTarget, LogViewer},
|
log_viewer::{
|
||||||
widgets::{hyperlink::Hyperlink, items::Items, last_error::LastError},
|
LogViewer,
|
||||||
};
|
input::{FieldMatcher, InputState, InputTarget},
|
||||||
use crate::tui::{
|
},
|
||||||
filter::{Filter, Matcher},
|
|
||||||
log_viewer::FieldMatcher,
|
|
||||||
reader::LogfileReader,
|
reader::LogfileReader,
|
||||||
widgets::styled::IntoStyled,
|
widgets::{hyperlink::Hyperlink, items::Items, last_error::LastError, styled::IntoStyled},
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal, Terminal,
|
DefaultTerminal, Terminal,
|
||||||
|
|
@ -67,7 +65,6 @@ targeting logs:
|
||||||
either a field after `f` or the text of the current log:
|
either a field after `f` or the text of the current log:
|
||||||
p ... with a prefix
|
p ... with a prefix
|
||||||
r ... matching a regex
|
r ... matching a regex
|
||||||
/ ... matching a regex
|
|
||||||
e ... equal to selected
|
e ... equal to selected
|
||||||
c ... containing
|
c ... containing
|
||||||
|
|
||||||
|
|
@ -298,6 +295,10 @@ impl App {
|
||||||
KeyCode::Backspace | KeyCode::Left => lv.back(),
|
KeyCode::Backspace | KeyCode::Left => lv.back(),
|
||||||
KeyCode::Right => lv.enter(),
|
KeyCode::Right => lv.enter(),
|
||||||
KeyCode::Enter => lv.enter(),
|
KeyCode::Enter => lv.enter(),
|
||||||
|
|
||||||
|
KeyCode::Char('u') => lv.undo(),
|
||||||
|
KeyCode::Char('r') => lv.redo(),
|
||||||
|
|
||||||
KeyCode::Char('f') => {
|
KeyCode::Char('f') => {
|
||||||
lv.input_state.target(InputTarget::Fields(None));
|
lv.input_state.target(InputTarget::Fields(None));
|
||||||
lv.footer_list.select(Some(0));
|
lv.footer_list.select(Some(0));
|
||||||
|
|
@ -309,7 +310,7 @@ impl App {
|
||||||
KeyCode::Char('t') => {
|
KeyCode::Char('t') => {
|
||||||
lv.input_state.target(InputTarget::This);
|
lv.input_state.target(InputTarget::This);
|
||||||
}
|
}
|
||||||
KeyCode::Char('r') | KeyCode::Char('/') => {
|
KeyCode::Char('/') => {
|
||||||
let v = FieldMatcher::Regex(String::new());
|
let v = FieldMatcher::Regex(String::new());
|
||||||
if let InputState::Target(InputTarget::Fields(f @ None)) = &mut lv.input_state {
|
if let InputState::Target(InputTarget::Fields(f @ None)) = &mut lv.input_state {
|
||||||
*f = Some(v);
|
*f = Some(v);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use ratatui::widgets::{List, ListItem, Widget};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
log_viewer::{FieldMatcher, InputState, InputTarget},
|
log_viewer::input::{FieldMatcher, InputState, InputTarget},
|
||||||
model::LogEntry,
|
model::LogEntry,
|
||||||
widgets::{
|
widgets::{
|
||||||
last_error::LastError,
|
last_error::LastError,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue