Skip to content

Commit

Permalink
feat: changes how the display widget content is displayed
Browse files Browse the repository at this point in the history
  • Loading branch information
rvigo committed Dec 27, 2024
1 parent 424b2f8 commit 5888510
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 86 deletions.
28 changes: 9 additions & 19 deletions cl-gui/src/context/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,26 +51,15 @@ impl<'fields> Fields<'fields> {

pub fn popuplate(&mut self, current_command: &Command) {
self.state.items.iter_mut().for_each(|(field_type, field)| {
match field_type {
FieldType::Alias => {
field.set_text(&current_command.alias);
}
FieldType::Command => {
field.set_text(&current_command.command);
}
FieldType::Namespace => {
field.set_text(&current_command.namespace);
}
FieldType::Description => {
field.set_text(current_command.description.as_ref());
}
FieldType::Tags => {
field.set_text(current_command.tags.as_ref().unwrap_or(&vec![]).join(", "));
}
FieldType::Popup(_) => todo!(),
FieldType::Info => todo!(),
let text = match field_type {
FieldType::Alias => current_command.alias.to_owned(),
FieldType::Command => current_command.command.to_owned(),
FieldType::Namespace => current_command.namespace.to_owned(),
FieldType::Description => current_command.description.clone().unwrap_or_default(),
FieldType::Tags => current_command.tags.as_ref().unwrap_or(&vec![]).join(", "),
_ => panic!("Invalid field type: {:?}", field_type),
};

field.set_text(text.to_string());
field.move_cursor_to_end_of_text();

self.original_fields
Expand Down Expand Up @@ -179,6 +168,7 @@ impl State for Fields<'_> {
self.selected_field = field_type;
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion cl-gui/src/screen/observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ impl<'d> Observer for DisplayWidget<'d> {
_ => "".to_owned(),
};

self.update_content(content);
self.highlight(event.content.query);
self.content = content;
self.should_highlight = event.content.highlight;
}
}
81 changes: 45 additions & 36 deletions cl-gui/src/widget/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ use tui::{
buffer::Buffer,
layout::{Alignment, Rect},
style::Style,
text::{Line, Text},
text::Line,
widgets::{Block, Paragraph, Widget, Wrap},
};

#[derive(PartialEq, Debug, Clone)]
pub struct DisplayWidget<'a> {
/// the content of the widget
pub content: String,
/// the raw content of the widget
raw_content: String,
/// flag indicating if the content can be highlighted
pub should_highlight: bool,
/// the highlighted content
pub highlighted_content: Option<Line<'a>>,
/// the content
pub content: Line<'a>,
/// flag indicating if the content should be trimmed
trim: bool,
block: Option<Block<'a>>,
Expand All @@ -39,9 +39,9 @@ impl<'a> DisplayWidget<'a> {
{
let content = content.into();
Self {
content,
raw_content: content.to_owned(),
should_highlight,
highlighted_content: None,
content: Line::from(content),
trim,
block: None,
style: Default::default(),
Expand All @@ -51,12 +51,24 @@ impl<'a> DisplayWidget<'a> {
}
}

pub fn content(&self) -> String {
self.content.to_owned()
pub fn raw_content(&self) -> String {
self.raw_content.to_owned()
}

pub fn set_highlighted_content(&mut self, highlight_content: Option<Line<'a>>) {
self.highlighted_content = highlight_content
pub fn update_content<C>(&mut self, updated_content: C)
where
C: Into<Line<'a>>,
{
let updated_content: Line<'a> = updated_content.into();
let raw = updated_content.spans.to_vec();

let raw_content = raw
.iter()
.map(|span| span.content.clone())
.collect::<String>();

self.raw_content = raw_content;
self.content = updated_content
}

pub fn should_highlight(&self) -> bool {
Expand All @@ -81,13 +93,6 @@ impl<'a> DisplayWidget<'a> {

impl<'a> Widget for DisplayWidget<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
// if there is no highlighted content, transforms the content in a `Text`
let content = if let Some(styled) = self.highlighted_content {
Text::from(styled)
} else {
Text::from(self.content)
};

let block = self
.block
.map(|b| {
Expand All @@ -99,6 +104,7 @@ impl<'a> Widget for DisplayWidget<'a> {
})
.unwrap_or_default();

let content = self.content;
let paragraph = Paragraph::new(content)
.style(self.style)
.alignment(self.alignment)
Expand All @@ -122,10 +128,8 @@ mod test {
let content = "sandbox\nsandbox";
let pattern = "sand";

let mut result = DisplayWidget::new(FieldType::Command, content, false, true, true);
result.highlight(pattern);

assert!(result.highlighted_content.is_some());
let mut widget = DisplayWidget::new(FieldType::Command, content, false, true, true);
widget.highlight(pattern);

let expected_line = Line::from(vec![
Span::styled("s", Style::default().add_modifier(Modifier::UNDERLINED)),
Expand All @@ -140,17 +144,15 @@ mod test {
Span::from("box"),
]);

let actual = result.highlighted_content.unwrap();
let actual = widget.content;
assert_eq!(actual, expected_line);
}

#[test]
fn should_highlight_single_line_input() {
let content = "this is a sandbox";
let pattern = "sand";

let mut d = DisplayWidget::new(FieldType::Command, content, false, true, true);

let mut widget = DisplayWidget::new(FieldType::Command, content, false, true, true);
let expected_line = Line::from(vec![
Span::from("thi"),
Span::styled("s", Style::default().add_modifier(Modifier::UNDERLINED)),
Expand All @@ -166,20 +168,17 @@ mod test {
Span::from("box"),
]);

d.highlight(pattern);

assert!(d.highlighted_content.is_some());
widget.highlight(pattern);

let actual = d.highlighted_content.unwrap();
let actual = widget.content;
assert_eq!(actual, expected_line);
}

#[test]
fn should_highlight_chars_in_content() {
let content = "change location";
let pattern = "cl";
let mut d = DisplayWidget::new(FieldType::Command, content, false, true, true);

let mut widget = DisplayWidget::new(FieldType::Command, content, false, true, true);
let expected_line = Line::from(vec![
Span::styled("c", Style::default().add_modifier(Modifier::UNDERLINED)),
Span::from("hange "),
Expand All @@ -189,11 +188,21 @@ mod test {
Span::from("ation"),
]);

d.highlight(pattern);

assert!(d.highlighted_content.is_some());
widget.highlight(pattern);

let actual = d.highlighted_content.unwrap();
let actual = widget.content;
assert_eq!(actual, expected_line);
}

#[test]
fn test() {
let string = String::from("abc");
let line = Line::from(string);

let revert = line.spans.to_vec();

let revert_line = &revert[0].content;

println!("{:?}", revert_line);
}
}
64 changes: 35 additions & 29 deletions cl-gui/src/widget/highlight.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::display::DisplayWidget;
use itertools::Itertools;
use std::collections::HashSet;
use tui::{
style::{Modifier, Style},
text::{Line, Span},
Expand All @@ -16,41 +17,43 @@ impl<'hl> DisplayWidget<'hl> {
T: Into<String>,
{
let highlight_string: String = highlight_input.into();

if !self.should_highlight() || highlight_string.is_empty() {
return;
}

if highlight_string.eq(&self.content()) {
let content = self.raw_content();

if highlight_string == content {
let line = Line::styled(
highlight_string,
Style::default().add_modifier(Modifier::UNDERLINED),
);
self.set_highlighted_content(Some(line));
self.update_content(line);
return;
}

let content = self.content();
let splitted_content = self.split_preserve_chars(&content, &highlight_string);
let grouped = self.group_by_newline(&splitted_content);

let spans = grouped
.into_iter()
.flat_map(|sv| {
sv.iter()
.map(|i| {
let style = if self.contains_ignore_case(&highlight_string, i) {
.flat_map(|line| {
line.into_iter()
.map(|segment| {
let style = if self.contains_ignore_case(&highlight_string, &segment) {
Style::default().add_modifier(Modifier::UNDERLINED)
} else {
Style::default()
};

Span::styled(i.to_owned(), style)
Span::styled(segment.to_owned(), style)
})
.collect_vec()
})
.collect_vec();

self.set_highlighted_content(Some(Line::from(spans)));
self.update_content(Line::from(spans));
}

/// Splits the given content based on the given pattern
Expand All @@ -62,27 +65,34 @@ impl<'hl> DisplayWidget<'hl> {
/// the output should be `["thi", "s", " i", "s", " ", "a", " ", "s", "a", "n", "d", "box"]`
#[inline]
fn split_preserve_chars(&self, content: &'hl str, pattern: &'hl str) -> Vec<&'hl str> {
let mut idxs = Vec::default();

for p in pattern.chars().unique() {
let matches = content.match_indices(p);
idxs.extend(matches.map(|(i, _)| i));
}
let mut idxs = pattern
.chars()
.collect::<HashSet<_>>()
.iter()
.flat_map(|p| content.match_indices(*p).map(|(i, _)| i))
.collect::<Vec<usize>>();

// must be sorted
idxs.sort();

let mut result = Vec::new();
let mut previous_index = 0;

for index in idxs {
let slice_untill_idx = &content[previous_index..index];
result.push(slice_untill_idx);
if previous_index < index {
result.push(&content[previous_index..index]);
}

let inclusive_char = &content[index..index + 1];
result.push(inclusive_char);
previous_index = index + 1;
}
result.push(&content[previous_index..]);
result.into_iter().filter(|s| !s.is_empty()).collect()

if previous_index < content.len() {
result.push(&content[previous_index..]);
}

result
}

/// Groups the content of a `Vec<&str>` in a `Vec<Vec<String>>`
Expand All @@ -99,16 +109,16 @@ impl<'hl> DisplayWidget<'hl> {
let mut sub_vec = Vec::new();

for item in input {
if item.contains('\n') {
let sub_items: Vec<&str> = item.split('\n').collect();
sub_vec.push(sub_items[0]);
if let Some((before, after)) = item.split_once('\n') {
sub_vec.push(before.to_string());
result.push(sub_vec);
sub_vec = Vec::new(); // here we finish one sub vec (representing an EOL) and starts a new empty sub vec
if sub_items.len() > 1 && !sub_items[1].is_empty() {
sub_vec.push(sub_items[1]);

if !after.is_empty() {
sub_vec.push(after.to_string());
}
} else {
sub_vec.push(item);
sub_vec.push(item.to_string());
}
}

Expand All @@ -117,10 +127,6 @@ impl<'hl> DisplayWidget<'hl> {
}

result
.iter()
.filter(|sub_vec| !sub_vec.is_empty())
.map(|sub_vec| sub_vec.iter().cloned().map(String::from).collect_vec())
.collect()
}

#[inline]
Expand Down
2 changes: 1 addition & 1 deletion cl-gui/src/widget/popup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl Popup {

let scaled_height = (SCALE_FACTOR * (content_height * 2)) / area.height;
let max_height = (area.height as f32 * MAX_SCALE_RATIO) as u16;
let final_height = std::cmp::min(scaled_height, max_height) as u16;
let final_height = std::cmp::min(scaled_height, max_height);

centered_rect!(content_width, final_height, area)
}
Expand Down

0 comments on commit 5888510

Please sign in to comment.