Skip to content

Commit

Permalink
feat: enhance lla_plugin_utils with syntax highlighting and interacti…
Browse files Browse the repository at this point in the history
…ve features

- Updated Cargo.toml to include new optional dependencies: `syntect`, `lazy_static`, and `dialoguer` for syntax highlighting and interactive selection.
- Introduced `syntax` module for code highlighting functionality using `syntect`.
- Added `CodeHighlighter` struct with methods for highlighting code and retrieving available themes.
- Implemented `InteractiveSelector` for user interaction, allowing single and multiple selections, confirmations, and custom inputs.
- Refactored UI components to support new features, including enhanced `BoxComponent` styling options.
- Improved module organization by separating text-related functionality into a new `text` module.
  • Loading branch information
chaqchase committed Dec 28, 2024
1 parent 1ff0b2d commit b54ad3b
Show file tree
Hide file tree
Showing 7 changed files with 443 additions and 107 deletions.
7 changes: 6 additions & 1 deletion lla_plugin_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ chrono = { workspace = true }
users = { workspace = true }
indicatif = { workspace = true }
console = "0.15.8"
syntect = { version = "5.1.0", optional = true }
lazy_static = { version = "1.4", optional = true }
dialoguer = { version = "0.11.0", optional = true }

[features]
default = ["config", "ui", "format"]
default = ["config", "ui", "format", "syntax", "interactive"]
config = []
ui = []
format = []
syntax = ["syntect", "lazy_static"]
interactive = ["dialoguer"]
3 changes: 3 additions & 0 deletions lla_plugin_utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
pub mod actions;
pub mod config;
pub mod format;
pub mod syntax;
pub mod ui;

pub use actions::{Action, ActionHelp, ActionRegistry};
pub use config::PluginConfig;
pub use syntax::CodeHighlighter;
pub use ui::{
components::{BoxComponent, BoxStyle, HelpFormatter, KeyValue, List, Spinner},
selector::InteractiveSelector,
TextBlock, TextStyle,
};

Expand Down
60 changes: 60 additions & 0 deletions lla_plugin_utils/src/syntax.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#[cfg(feature = "syntax")]
use lazy_static::lazy_static;
#[cfg(feature = "syntax")]
use syntect::{
easy::HighlightLines,
highlighting::{Style, ThemeSet},
parsing::SyntaxSet,
util::{as_24_bit_terminal_escaped, LinesWithEndings},
};

#[cfg(feature = "syntax")]
lazy_static! {
static ref SYNTAX_SET: SyntaxSet = SyntaxSet::load_defaults_newlines();
static ref THEME_SET: ThemeSet = ThemeSet::load_defaults();
}

pub struct CodeHighlighter;

impl CodeHighlighter {
#[cfg(feature = "syntax")]
pub fn highlight(code: &str, language: &str) -> String {
let syntax = SYNTAX_SET
.find_syntax_by_token(language)
.unwrap_or_else(|| SYNTAX_SET.find_syntax_plain_text());
let mut h = HighlightLines::new(syntax, &THEME_SET.themes["base16-ocean.dark"]);

let mut highlighted = String::new();
for line in LinesWithEndings::from(code) {
let ranges: Vec<(Style, &str)> =
h.highlight_line(line, &SYNTAX_SET).unwrap_or_default();
let escaped = as_24_bit_terminal_escaped(&ranges[..], false);
highlighted.push_str(&escaped);
}
highlighted
}

#[cfg(not(feature = "syntax"))]
pub fn highlight(code: &str, _language: &str) -> String {
code.to_string()
}

pub fn highlight_with_line_numbers(code: &str, language: &str, start_line: usize) -> String {
let highlighted = Self::highlight(code, language);
let mut result = String::new();
for (i, line) in highlighted.lines().enumerate() {
result.push_str(&format!("{:4} │ {}\n", i + start_line, line));
}
result
}
}

#[cfg(feature = "syntax")]
pub fn get_available_themes() -> Vec<String> {
THEME_SET.themes.keys().cloned().collect()
}

#[cfg(not(feature = "syntax"))]
pub fn get_available_themes() -> Vec<String> {
vec![]
}
171 changes: 161 additions & 10 deletions lla_plugin_utils/src/ui/components.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{TextBlock, TextStyle};
use indicatif::{ProgressBar, ProgressStyle};
use std::cmp;
use std::time::Duration;

pub struct Spinner {
Expand Down Expand Up @@ -212,6 +213,7 @@ impl List {
}
}

#[derive(Clone, Copy)]
pub enum BoxStyle {
Minimal,
Rounded,
Expand All @@ -220,45 +222,194 @@ pub enum BoxStyle {
Dashed,
}

impl BoxStyle {
fn get_chars(&self) -> BoxChars {
match self {
BoxStyle::Minimal => BoxChars {
top_left: '┌',
top_right: '┐',
bottom_left: '└',
bottom_right: '┘',
horizontal: '─',
vertical: '│',
left_t: '├',
right_t: '┤',
top_t: '┬',
bottom_t: '┴',
cross: '┼',
},
BoxStyle::Rounded => BoxChars {
top_left: '╭',
top_right: '╮',
bottom_left: '╰',
bottom_right: '╯',
horizontal: '─',
vertical: '│',
left_t: '├',
right_t: '┤',
top_t: '┬',
bottom_t: '┴',
cross: '┼',
},
BoxStyle::Double => BoxChars {
top_left: '╔',
top_right: '╗',
bottom_left: '╚',
bottom_right: '╝',
horizontal: '═',
vertical: '║',
left_t: '╠',
right_t: '╣',
top_t: '╦',
bottom_t: '╩',
cross: '╬',
},
BoxStyle::Heavy => BoxChars {
top_left: '┏',
top_right: '┓',
bottom_left: '┗',
bottom_right: '┛',
horizontal: '━',
vertical: '┃',
left_t: '┣',
right_t: '┫',
top_t: '┳',
bottom_t: '┻',
cross: '╋',
},
BoxStyle::Dashed => BoxChars {
top_left: '┌',
top_right: '┐',
bottom_left: '└',
bottom_right: '┘',
horizontal: '┄',
vertical: '┆',
left_t: '├',
right_t: '┤',
top_t: '┬',
bottom_t: '┴',
cross: '┼',
},
}
}
}

#[allow(dead_code)]
struct BoxChars {
top_left: char,
top_right: char,
bottom_left: char,
bottom_right: char,
horizontal: char,
vertical: char,
left_t: char,
right_t: char,
top_t: char,
bottom_t: char,
cross: char,
}

pub struct BoxComponent {
content: String,
style: BoxStyle,
width: Option<usize>,
padding: usize,
title: Option<String>,
}

impl BoxComponent {
pub fn new(content: impl Into<String>) -> Self {
Self {
content: content.into(),
style: BoxStyle::Minimal,
width: None,
padding: 0,
title: None,
}
}

pub fn style(self, _style: BoxStyle) -> Self {
pub fn style(mut self, style: BoxStyle) -> Self {
self.style = style;
self
}

pub fn width(self, _width: usize) -> Self {
pub fn width(mut self, width: usize) -> Self {
self.width = Some(width);
self
}

pub fn padding(self, _padding: usize) -> Self {
pub fn padding(mut self, padding: usize) -> Self {
self.padding = padding;
self
}

pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}

pub fn render(&self) -> String {
let chars = self.style.get_chars();
let mut output = String::new();
output.push('┌');
output.push('─');
let lines: Vec<&str> = self.content.lines().collect();

let content_width = lines
.iter()
.map(|line| console::measure_text_width(line))
.max()
.unwrap_or(0);
let title_width = self
.title
.as_ref()
.map(|t| console::measure_text_width(t))
.unwrap_or(0);
let inner_width = cmp::max(content_width, title_width) + self.padding * 2;
let total_width = self.width.unwrap_or(inner_width);

output.push(chars.top_left);
if let Some(title) = &self.title {
output.push(chars.horizontal);
output.push(' ');
output.push_str(title);
output.push(' ');
let remaining = total_width.saturating_sub(title_width + 4);
output.push_str(&chars.horizontal.to_string().repeat(remaining));
} else {
output.push_str(&chars.horizontal.to_string().repeat(total_width));
}
output.push(chars.top_right);
output.push('\n');

for line in self.content.lines() {
output.push('│');
output.push(' ');
for _ in 0..self.padding {
output.push(chars.vertical);
output.push_str(&" ".repeat(total_width));
output.push(chars.vertical);
output.push('\n');
}

for line in lines {
output.push(chars.vertical);
output.push_str(&" ".repeat(self.padding));
output.push_str(line);
let padding =
total_width.saturating_sub(console::measure_text_width(line) + self.padding);
output.push_str(&" ".repeat(padding));
output.push(chars.vertical);
output.push('\n');
}

output.push('└');
output.push('─');
for _ in 0..self.padding {
output.push(chars.vertical);
output.push_str(&" ".repeat(total_width));
output.push(chars.vertical);
output.push('\n');
}

output.push(chars.bottom_left);
output.push_str(&chars.horizontal.to_string().repeat(total_width));
output.push(chars.bottom_right);
output.push('\n');

output
}
}
Loading

0 comments on commit b54ad3b

Please sign in to comment.