diff --git a/src/engine.rs b/src/engine.rs index 3c4fa2a0..1c3dd426 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use itertools::Itertools; -use nu_ansi_term::{Color, Style}; +use nu_ansi_term::Style; use crate::{enums::ReedlineRawEvent, CursorConfig}; #[cfg(feature = "bashisms")] @@ -39,6 +39,7 @@ use { cursor::{SetCursorStyle, Show}, event, event::{Event, KeyCode, KeyEvent, KeyModifiers}, + style::Color, terminal, QueueableCommand, }, std::{ @@ -132,15 +133,12 @@ pub struct Reedline { // Highlight the edit buffer highlighter: Box, - // Style used for visual selection - visual_selection_style: Style, - // Showcase hints based on various strategies (history, language-completion, spellcheck, etc) hinter: Option>, hide_hints: bool, // Use ansi coloring or not - use_ansi_coloring: bool, + theme: ReedlineTheme, // Current working directory as defined by the application. If set, it will // override the actual working directory of the process. @@ -186,6 +184,31 @@ impl Drop for Reedline { } } +pub struct ReedlineTheme { + pub visual_selection: Style, + pub use_ansi_coloring: bool, + /// The color for the prompt, indicator, and right prompt + pub prompt: Color, + pub prompt_multiline: nu_ansi_term::Color, + pub indicator: Color, + pub prompt_right: Color, +} + +impl Default for ReedlineTheme { + fn default() -> Self { + Self { + visual_selection: Style::new() + .fg(nu_ansi_term::Color::Black) + .on(nu_ansi_term::Color::LightGray), + use_ansi_coloring: true, + prompt: Color::Green, + prompt_multiline: nu_ansi_term::Color::LightBlue, + indicator: Color::Cyan, + prompt_right: Color::AnsiValue(5), + } + } +} + impl Reedline { const FILTERED_ITEM_ID: HistoryItemId = HistoryItemId(i64::MAX); @@ -195,7 +218,6 @@ impl Reedline { let history = Box::::default(); let painter = Painter::new(std::io::BufWriter::new(std::io::stderr())); let buffer_highlighter = Box::::default(); - let visual_selection_style = Style::new().on(Color::LightGray); let completer = Box::::default(); let hinter = None; let validator = None; @@ -223,11 +245,10 @@ impl Reedline { quick_completions: false, partial_completions: false, highlighter: buffer_highlighter, - visual_selection_style, hinter, hide_hints: false, validator, - use_ansi_coloring: true, + theme: ReedlineTheme::default(), cwd: None, menus: Vec::new(), buffer_editor: None, @@ -361,10 +382,37 @@ impl Reedline { /// and in the command line syntax highlighting. #[must_use] pub fn with_ansi_colors(mut self, use_ansi_coloring: bool) -> Self { - self.use_ansi_coloring = use_ansi_coloring; + self.theme.use_ansi_coloring = use_ansi_coloring; + self + } + + /// A builder which sets the color to use for the prompt. + #[must_use] + pub fn with_prompt_color(mut self, color: Color) -> Self { + self.theme.prompt = color; self } + /// A builder which sets the color to use for the multiline prompt. + #[must_use] + pub fn with_prompt_multiline_color(mut self, color: nu_ansi_term::Color) -> Self { + self.theme.prompt_multiline = color; + self + } + + /// A builder which sets the indicator color to use for the prompt. + #[must_use] + pub fn with_indicator_color(mut self, color: Color) -> Self { + self.theme.indicator = color; + self + } + + /// A builder which sets the color to use for the right side of the prompt. + #[must_use] + pub fn with_prompt_right_color(mut self, color: Color) -> Self { + self.theme.prompt_right = color; + self + } /// Update current working directory. #[must_use] pub fn with_cwd(mut self, cwd: Option) -> Self { @@ -397,7 +445,7 @@ impl Reedline { /// A builder that configures the style used for visual selection #[must_use] pub fn with_visual_selection_style(mut self, style: Style) -> Self { - self.visual_selection_style = style; + self.theme.visual_selection = style; self } @@ -1700,7 +1748,7 @@ impl Reedline { let res_string = self.history_cursor.string_at_cursor().unwrap_or_default(); // Highlight matches - let res_string = if self.use_ansi_coloring { + let res_string = if self.theme.use_ansi_coloring { let match_highlighter = SimpleMatchHighlighter::new(substring); let styled = match_highlighter.highlight(&res_string, 0); styled.render_simple() @@ -1718,11 +1766,10 @@ impl Reedline { ); self.painter.repaint_buffer( - prompt, &lines, self.prompt_edit_mode(), None, - self.use_ansi_coloring, + &self.theme, &self.cursor_shapes, )?; } @@ -1741,13 +1788,13 @@ impl Reedline { .highlighter .highlight(buffer_to_paint, cursor_position_in_buffer); if let Some((from, to)) = self.editor.get_selection() { - styled_text.style_range(from, to, self.visual_selection_style); + styled_text.style_range(from, to, self.theme.visual_selection); } let (before_cursor, after_cursor) = styled_text.render_around_insertion_point( cursor_position_in_buffer, prompt, - self.use_ansi_coloring, + &self.theme, ); let hint: String = if self.hints_active() { @@ -1756,7 +1803,7 @@ impl Reedline { buffer_to_paint, cursor_position_in_buffer, self.history.as_ref(), - self.use_ansi_coloring, + self.theme.use_ansi_coloring, &self.cwd.clone().unwrap_or_else(|| { std::env::current_dir() .unwrap_or_default() @@ -1801,11 +1848,10 @@ impl Reedline { let menu = self.menus.iter().find(|menu| menu.is_active()); self.painter.repaint_buffer( - prompt, &lines, self.prompt_edit_mode(), menu, - self.use_ansi_coloring, + &self.theme, &self.cursor_shapes, ) } diff --git a/src/painting/painter.rs b/src/painting/painter.rs index 8365f072..1718e7fe 100644 --- a/src/painting/painter.rs +++ b/src/painting/painter.rs @@ -1,11 +1,10 @@ -use crate::{CursorConfig, PromptEditMode, PromptViMode}; +use crate::{engine::ReedlineTheme, CursorConfig, PromptEditMode, PromptViMode}; use { super::utils::{coerce_crlf, line_width}, crate::{ menu::{Menu, ReedlineMenu}, painting::PromptLines, - Prompt, }, crossterm::{ cursor::{self, MoveTo, RestorePosition, SavePosition}, @@ -17,7 +16,7 @@ use { std::ops::RangeInclusive, }; #[cfg(feature = "external_printer")] -use {crate::LineBuffer, crossterm::cursor::MoveUp}; +use {crate::LineBuffer, crate::Prompt, crossterm::cursor::MoveUp}; // Returns a string that skips N number of lines with the next offset of lines // An offset of 0 would return only one line after skipping the required lines @@ -185,11 +184,10 @@ impl Painter { /// the screen. pub(crate) fn repaint_buffer( &mut self, - prompt: &dyn Prompt, lines: &PromptLines, prompt_mode: PromptEditMode, menu: Option<&ReedlineMenu>, - use_ansi_coloring: bool, + theme: &ReedlineTheme, cursor_config: &Option, ) -> Result<()> { self.stdout.queue(cursor::Hide)?; @@ -229,9 +227,9 @@ impl Painter { .queue(Clear(ClearType::FromCursorDown))?; if self.large_buffer { - self.print_large_buffer(prompt, lines, menu, use_ansi_coloring)?; + self.print_large_buffer(lines, menu, theme)?; } else { - self.print_small_buffer(prompt, lines, menu, use_ansi_coloring)?; + self.print_small_buffer(lines, menu, theme)?; } // The last_required_lines is used to calculate safe range of the current prompt. @@ -315,36 +313,32 @@ impl Painter { fn print_small_buffer( &mut self, - prompt: &dyn Prompt, lines: &PromptLines, menu: Option<&ReedlineMenu>, - use_ansi_coloring: bool, + theme: &ReedlineTheme, ) -> Result<()> { // print our prompt with color - if use_ansi_coloring { - self.stdout - .queue(SetForegroundColor(prompt.get_prompt_color()))?; + if theme.use_ansi_coloring { + self.stdout.queue(SetForegroundColor(theme.prompt))?; } self.stdout .queue(Print(&coerce_crlf(&lines.prompt_str_left)))?; - if use_ansi_coloring { - self.stdout - .queue(SetForegroundColor(prompt.get_indicator_color()))?; + if theme.use_ansi_coloring { + self.stdout.queue(SetForegroundColor(theme.indicator))?; } self.stdout .queue(Print(&coerce_crlf(&lines.prompt_indicator)))?; - if use_ansi_coloring { - self.stdout - .queue(SetForegroundColor(prompt.get_prompt_right_color()))?; + if theme.use_ansi_coloring { + self.stdout.queue(SetForegroundColor(theme.prompt_right))?; } self.print_right_prompt(lines)?; - if use_ansi_coloring { + if theme.use_ansi_coloring { self.stdout .queue(SetAttribute(Attribute::Reset))? .queue(ResetColor)?; @@ -356,7 +350,7 @@ impl Painter { .queue(Print(&lines.after_cursor))?; if let Some(menu) = menu { - self.print_menu(menu, lines, use_ansi_coloring)?; + self.print_menu(menu, lines, theme.use_ansi_coloring)?; } else { self.stdout.queue(Print(&lines.hint))?; } @@ -366,10 +360,9 @@ impl Painter { fn print_large_buffer( &mut self, - prompt: &dyn Prompt, lines: &PromptLines, menu: Option<&ReedlineMenu>, - use_ansi_coloring: bool, + theme: &ReedlineTheme, ) -> Result<()> { let screen_width = self.screen_width(); let screen_height = self.screen_height(); @@ -389,9 +382,8 @@ impl Painter { let extra_rows = (total_lines_before).saturating_sub(screen_height as usize); // print our prompt with color - if use_ansi_coloring { - self.stdout - .queue(SetForegroundColor(prompt.get_prompt_color()))?; + if theme.use_ansi_coloring { + self.stdout.queue(SetForegroundColor(theme.prompt))?; } // In case the prompt is made out of multiple lines, the prompt is split by @@ -400,9 +392,8 @@ impl Painter { self.stdout.queue(Print(&coerce_crlf(prompt_skipped)))?; if extra_rows == 0 { - if use_ansi_coloring { - self.stdout - .queue(SetForegroundColor(prompt.get_prompt_right_color()))?; + if theme.use_ansi_coloring { + self.stdout.queue(SetForegroundColor(theme.prompt_right))?; } self.print_right_prompt(lines)?; @@ -411,14 +402,13 @@ impl Painter { // Adjusting extra_rows base on the calculated prompt line size let extra_rows = extra_rows.saturating_sub(prompt_lines); - if use_ansi_coloring { - self.stdout - .queue(SetForegroundColor(prompt.get_indicator_color()))?; + if theme.use_ansi_coloring { + self.stdout.queue(SetForegroundColor(theme.indicator))?; } let indicator_skipped = skip_buffer_lines(&lines.prompt_indicator, extra_rows, None); self.stdout.queue(Print(&coerce_crlf(indicator_skipped)))?; - if use_ansi_coloring { + if theme.use_ansi_coloring { self.stdout.queue(ResetColor)?; } @@ -453,7 +443,7 @@ impl Painter { } else { self.stdout.queue(Print(&lines.after_cursor))?; } - self.print_menu(menu, lines, use_ansi_coloring)?; + self.print_menu(menu, lines, theme.use_ansi_coloring)?; } else { // Selecting lines for the hint // The -1 subtraction is done because the remaining lines consider the line where the diff --git a/src/painting/styled_text.rs b/src/painting/styled_text.rs index 82560def..020db30f 100644 --- a/src/painting/styled_text.rs +++ b/src/painting/styled_text.rs @@ -1,6 +1,6 @@ use nu_ansi_term::Style; -use crate::Prompt; +use crate::{engine::ReedlineTheme, Prompt}; use super::utils::strip_ansi; @@ -101,14 +101,14 @@ impl StyledText { insertion_point: usize, prompt: &dyn Prompt, // multiline_prompt: &str, - use_ansi_coloring: bool, + theme: &ReedlineTheme, ) -> (String, String) { let mut current_idx = 0; let mut left_string = String::new(); let mut right_string = String::new(); let multiline_prompt = prompt.render_prompt_multiline_indicator(); - let prompt_style = Style::new().fg(prompt.get_prompt_multiline_color()); + let prompt_style = Style::new().fg(theme.prompt_multiline); for pair in &self.buffer { if current_idx >= insertion_point { @@ -135,7 +135,7 @@ impl StyledText { current_idx += pair.1.len(); } - if use_ansi_coloring { + if theme.use_ansi_coloring { (left_string, right_string) } else { (strip_ansi(&left_string), strip_ansi(&right_string)) diff --git a/src/prompt/base.rs b/src/prompt/base.rs index db69f2e0..285e2ade 100644 --- a/src/prompt/base.rs +++ b/src/prompt/base.rs @@ -1,5 +1,4 @@ use { - crossterm::style::Color, serde::{Deserialize, Serialize}, std::{ borrow::Cow, @@ -8,12 +7,6 @@ use { strum_macros::EnumIter, }; -/// The default color for the prompt, indicator, and right prompt -pub static DEFAULT_PROMPT_COLOR: Color = Color::Green; -pub static DEFAULT_PROMPT_MULTILINE_COLOR: nu_ansi_term::Color = nu_ansi_term::Color::LightBlue; -pub static DEFAULT_INDICATOR_COLOR: Color = Color::Cyan; -pub static DEFAULT_PROMPT_RIGHT_COLOR: Color = Color::AnsiValue(5); - /// The current success/failure of the history search pub enum PromptHistorySearchStatus { /// Success for the search @@ -97,22 +90,6 @@ pub trait Prompt: Send { &self, history_search: PromptHistorySearch, ) -> Cow; - /// Get the default prompt color - fn get_prompt_color(&self) -> Color { - DEFAULT_PROMPT_COLOR - } - /// Get the default multiline prompt color - fn get_prompt_multiline_color(&self) -> nu_ansi_term::Color { - DEFAULT_PROMPT_MULTILINE_COLOR - } - /// Get the default indicator color - fn get_indicator_color(&self) -> Color { - DEFAULT_INDICATOR_COLOR - } - /// Get the default right prompt color - fn get_prompt_right_color(&self) -> Color { - DEFAULT_PROMPT_RIGHT_COLOR - } /// Whether to render right prompt on the last line fn right_prompt_on_last_line(&self) -> bool {