Skip to content

Commit

Permalink
label values edit support
Browse files Browse the repository at this point in the history
  • Loading branch information
lasantosr committed Jun 20, 2023
1 parent 9bc1aa0 commit f1a3f24
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 151 deletions.
48 changes: 16 additions & 32 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "intelli-shell"
description = "Like IntelliSense, but for shells"
version = "0.2.6"
version = "0.2.7"
edition = "2021"
license = "Apache-2.0"
readme = "README.md"
Expand Down Expand Up @@ -29,14 +29,14 @@ iter-flow = "0.1"
itertools = "0.10"
once_cell = "1"
parking_lot = "0.12"
ratatui = "0.21"
rayon = "1"
regex = { version = "1" }
rusqlite = { version = "0.29", features = ["bundled", "uuid"] }
rusqlite_migration = "1"
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
tempfile = { version = "3", optional = true }
tui = "0.19"
unicode-segmentation = "1"
unidecode = "0.3"
uuid = { version = "1", features = ["serde", "v4"] }
Expand Down
2 changes: 1 addition & 1 deletion src/common/process.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
use tui::{backend::Backend, layout::Rect, Frame, Terminal};
use ratatui::{backend::Backend, layout::Rect, Frame, Terminal};

use super::remove_newlines;
use crate::theme::Theme;
Expand Down
6 changes: 3 additions & 3 deletions src/common/widget/command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use tui::{
use ratatui::{
style::Style,
text::{Span, Spans},
text::{Line, Span},
widgets::ListItem,
};

Expand All @@ -17,6 +17,6 @@ impl<'a> IntoWidget<ListItem<'a>> for &'a Command {
if let Some(alias) = &self.alias {
content.insert(0, Span::styled(format!("[{alias}] "), Style::default().fg(theme.alias)))
}
ListItem::new(Spans::from(content))
ListItem::new(Line::from(content))
}
}
74 changes: 54 additions & 20 deletions src/common/widget/label.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use itertools::Itertools;
use tui::{
use ratatui::{
style::{Modifier, Style},
text::{Span, Spans, Text},
text::{Line, Span, Text},
widgets::ListItem,
};

Expand All @@ -12,30 +12,64 @@ use crate::{
theme::Theme,
};

pub const SECRET_LABEL_PREFIX: &str = "(secret) ";
pub const NEW_LABEL_PREFIX: &str = "(new) ";
const SECRET_LABEL_PREFIX: &str = "(secret) ";
const NEW_LABEL_PREFIX: &str = "(new) ";
const EDIT_LABEL_PREFIX: &str = "(edit) ";

#[cfg_attr(debug_assertions, derive(Debug))]
pub enum LabelSuggestionItem {
Secret(TextInput),
New(TextInput),
Label(String),
Persisted(LabelSuggestion),
Persisted(LabelSuggestion, Option<TextInput>),
}

impl<'a> From<&'a LabelSuggestionItem> for ListItem<'a> {
fn from(item: &'a LabelSuggestionItem) -> Self {
match item {
LabelSuggestionItem::Secret(value) => ListItem::new(Spans::from(vec![
Span::styled(SECRET_LABEL_PREFIX, Style::default().add_modifier(Modifier::ITALIC)),
Span::raw(value.as_str()),
])),
LabelSuggestionItem::New(value) => ListItem::new(Spans::from(vec![
Span::styled(NEW_LABEL_PREFIX, Style::default().add_modifier(Modifier::ITALIC)),
Span::raw(value.as_str()),
])),
LabelSuggestionItem::Label(value) => ListItem::new(value.clone()),
LabelSuggestionItem::Persisted(e) => ListItem::new(e.suggestion.clone()),
impl<'a> IntoCursorWidget<ListItem<'a>> for &'a LabelSuggestionItem {
fn into_widget_and_cursor(self, theme: Theme) -> (ListItem<'a>, Option<(Offset, Area)>) {
match self {
LabelSuggestionItem::Secret(value) => (
ListItem::new(Line::from(vec![
Span::styled(
SECRET_LABEL_PREFIX,
Style::default().fg(theme.secondary).add_modifier(Modifier::ITALIC),
),
Span::raw(value.as_str()),
])),
Some((
value.cursor() + Offset::new(SECRET_LABEL_PREFIX.len() as u16, 0),
Area::default_visible(),
)),
),
LabelSuggestionItem::New(value) => (
ListItem::new(Line::from(vec![
Span::styled(
NEW_LABEL_PREFIX,
Style::default().fg(theme.secondary).add_modifier(Modifier::ITALIC),
),
Span::raw(value.as_str()),
])),
Some((
value.cursor() + Offset::new(NEW_LABEL_PREFIX.len() as u16, 0),
Area::default_visible(),
)),
),
LabelSuggestionItem::Label(value) => (ListItem::new(value.as_str()), None),
LabelSuggestionItem::Persisted(e, input) => match input {
Some(value) => (
ListItem::new(Line::from(vec![
Span::styled(
EDIT_LABEL_PREFIX,
Style::default().fg(theme.secondary).add_modifier(Modifier::ITALIC),
),
Span::raw(value.as_str()),
])),
Some((
value.cursor() + Offset::new(EDIT_LABEL_PREFIX.len() as u16, 0),
Area::default_visible(),
)),
),
None => (ListItem::new(e.suggestion.as_str()), None),
},
}
}
}
Expand All @@ -46,7 +80,7 @@ impl<'a> IntoCursorWidget<Text<'a>> for &'a LabeledCommand {
let mut first_label_offset_x = 0;
let mut first_label_width = 0;

let text = Spans::from(
let text = Line::from(
self.parts
.iter()
.map(|p| {
Expand All @@ -58,7 +92,7 @@ impl<'a> IntoCursorWidget<Text<'a>> for &'a LabeledCommand {
let style = if !first_label_found {
first_label_found = true;
first_label_width = l.len_chars() as u16 + 4;
Style::default().fg(theme.main).add_modifier(Modifier::BOLD)
Style::default().add_modifier(Modifier::BOLD)
} else {
Style::default().fg(theme.secondary)
};
Expand Down
65 changes: 55 additions & 10 deletions src/common/widget/list.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use itertools::Itertools;
use tui::{
use ratatui::{
backend::Backend,
layout::Rect,
style::Style,
widgets::{Block, Borders, List, ListItem, ListState},
Frame,
};

use super::{Area, CustomStatefulWidget, IntoWidget};
use super::{Area, CustomStatefulWidget, IntoCursorWidget, Offset};
use crate::theme::Theme;

pub const DEFAULT_HIGHLIGHT_SYMBOL_PREFIX: &str = ">> ";

pub struct CustomStatefulList<T> {
state: ListState,
focus: bool,
items: Vec<T>,
inline: bool,
block_title: Option<&'static str>,
Expand All @@ -23,7 +25,7 @@ pub struct CustomStatefulList<T> {

impl<'s, T: 's> CustomStatefulList<T>
where
&'s T: IntoWidget<ListItem<'s>>,
&'s T: IntoCursorWidget<ListItem<'s>>,
{
/// Builds a new list from the given items
pub fn new(items: Vec<T>) -> Self {
Expand All @@ -33,6 +35,7 @@ where
}
Self {
state,
focus: false,
items,
inline: true,
block_title: None,
Expand All @@ -57,6 +60,11 @@ where
self
}

pub fn focus(mut self, focus: bool) -> Self {
self.focus = focus;
self
}

pub fn highlight_symbol(mut self, highlight_symbol: &'static str) -> Self {
self.highlight_symbol = Some(highlight_symbol);
self
Expand Down Expand Up @@ -183,7 +191,7 @@ where

impl<'s, T: 's> CustomStatefulWidget<'s> for CustomStatefulList<T>
where
&'s T: IntoWidget<ListItem<'s>>,
&'s T: IntoCursorWidget<ListItem<'s>>,
{
type Inner = List<'s>;

Expand All @@ -194,13 +202,17 @@ where
Area::new(width, height)
}

fn prepare(&'s mut self, _area: Rect, theme: Theme) -> (Self::Inner, &mut ListState) {
fn is_focused(&self) -> bool {
self.focus
}

fn prepare(&'s mut self, _area: Rect, theme: Theme) -> (Option<Offset>, Self::Inner, &mut ListState) {
// Get the widget of each item
let widget_items = self
let (widget_items, widget_cursors): (Vec<_>, Vec<_>) = self
.items
.iter()
.map(|i| IntoWidget::into_widget(i, theme))
.collect_vec();
.map(|i| IntoCursorWidget::into_widget_and_cursor(i, theme))
.unzip();

// Generate the list
let mut list = List::new(widget_items)
Expand All @@ -217,7 +229,40 @@ where
list = list.block(block);
}

let line_cursor = if let Some(selected) = self.state.selected() {
// We're returning the line cursor offset only, because we don't know where it is until we render the list
widget_cursors.get(selected).expect("Missing cursor").map(|(c, _)| c)
} else {
None
};

// Return
(list, &mut self.state)
(line_cursor, list, &mut self.state)
}

/// Renders itself in the frame
fn render_in<B: Backend>(&'s mut self, frame: &mut Frame<B>, area: Rect, theme: Theme)
where
Self: Sized,
{
let focused = self.is_focused();
let inline = self.inline;
let (line_cursor, widget, state) = self.prepare(area, theme);
frame.render_stateful_widget(widget, area, state);

if focused {
if let Some(cursor) = line_cursor {
// Recalculate global cursor offset based on line cursor and list rendered offset
if let Some(selected) = state.selected() {
let list_offset = state.offset();
let y_offset = (selected - list_offset) as u16;
// Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering
frame.set_cursor(
area.x + DEFAULT_HIGHLIGHT_SYMBOL_PREFIX.len() as u16 + cursor.x + (!inline as u16),
area.y + y_offset + cursor.y + (!inline as u16),
);
}
}
}
}
}
Loading

0 comments on commit f1a3f24

Please sign in to comment.