feat: support light theme (#65)

There are two ways to enable the light theme:
- add `light_theme: true` to config.yaml
- use `AICHAT_LIGHT_THEME=true` env var
pull/67/head
sigoden 1 year ago committed by GitHub
parent dc09cb38d4
commit cfb6ce6958
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
Cargo.lock generated

@ -39,6 +39,7 @@ dependencies = [
"inquire",
"is-terminal",
"lazy_static",
"nu-ansi-term",
"parking_lot",
"reedline",
"reqwest",

@ -37,6 +37,7 @@ fancy-regex = "0.11.0"
base64 = "0.21.0"
rustc-hash = "1.1.0"
bstr = "1.3.0"
nu-ansi-term = "0.46.0"
[dependencies.reqwest]
version = "0.11.14"

@ -54,6 +54,8 @@ pub struct Config {
pub dry_run: bool,
/// If set ture, start a conversation immediately upon repl
pub conversation_first: bool,
/// Is ligth theme
pub light_theme: bool,
/// Predefined roles
#[serde(skip)]
pub roles: Vec<Role>,
@ -75,6 +77,7 @@ impl Default for Config {
proxy: None,
dry_run: false,
conversation_first: false,
light_theme: false,
roles: vec![],
role: None,
conversation: None,
@ -409,11 +412,10 @@ impl Config {
fn merge_env_vars(&mut self) {
if let Ok(value) = env::var(get_env_name("dry_run")) {
match value.as_str() {
"1" | "true" => self.dry_run = true,
"0" | "false" => self.dry_run = false,
_ => {}
}
set_bool(&mut self.dry_run, &value);
}
if let Ok(value) = env::var(get_env_name("light_theme")) {
set_bool(&mut self.light_theme, &value);
}
}
}
@ -479,3 +481,11 @@ fn get_env_name(key: &str) -> String {
key.to_ascii_uppercase(),
)
}
fn set_bool(target: &mut bool, value: &str) {
match value {
"1" | "true" => *target = true,
"0" | "false" => *target = false,
_ => {}
}
}

@ -75,10 +75,11 @@ fn start_directive(
no_stream: bool,
) -> Result<()> {
let highlight = config.lock().highlight && stdout().is_terminal();
let light_theme = config.lock().light_theme;
let output = if no_stream {
let output = client.send_message(input)?;
if highlight {
let mut markdown_render = MarkdownRender::new();
let mut markdown_render = MarkdownRender::new(light_theme);
println!("{}", markdown_render.render(&output).trim());
} else {
println!("{}", output.trim());
@ -92,7 +93,15 @@ fn start_directive(
abort_clone.set_ctrlc();
})
.expect("Error setting Ctrl-C handler");
let output = render_stream(input, &client, highlight, false, abort, wg.clone())?;
let output = render_stream(
input,
&client,
highlight,
light_theme,
false,
abort,
wg.clone(),
)?;
wg.wait();
output
};

@ -6,9 +6,13 @@ use crate::repl::{ReplyStreamEvent, SharedAbortSignal};
use anyhow::Result;
use crossbeam::channel::Receiver;
pub fn cmd_render_stream(rx: Receiver<ReplyStreamEvent>, abort: SharedAbortSignal) -> Result<()> {
pub fn cmd_render_stream(
rx: Receiver<ReplyStreamEvent>,
light_theme: bool,
abort: SharedAbortSignal,
) -> Result<()> {
let mut buffer = String::new();
let mut markdown_render = MarkdownRender::new();
let mut markdown_render = MarkdownRender::new(light_theme);
loop {
if abort.aborted() {
return Ok(());

@ -7,6 +7,7 @@ use syntect::{easy::HighlightLines, parsing::SyntaxReference};
/// Monokai Extended
const MD_THEME: &[u8] = include_bytes!("../../assets/monokai-extended.theme.bin");
const MD_THEME_LIGHT: &[u8] = include_bytes!("../../assets/monokai-extended-light.theme.bin");
/// Comes from https://github.com/sharkdp/bat/raw/5e77ca37e89c873e4490b42ff556370dc5c6ba4f/assets/syntaxes.bin
const SYNTAXES: &[u8] = include_bytes!("../../assets/syntaxes.bin");
@ -29,10 +30,14 @@ pub struct MarkdownRender {
}
impl MarkdownRender {
pub fn new() -> Self {
pub fn new(light_theme: bool) -> Self {
let syntax_set: SyntaxSet =
bincode::deserialize_from(SYNTAXES).expect("invalid syntaxes binary");
let md_theme: Theme = bincode::deserialize_from(MD_THEME).expect("invalid md_theme binary");
let md_theme: Theme = if light_theme {
bincode::deserialize_from(MD_THEME_LIGHT).expect("invalid theme binary")
} else {
bincode::deserialize_from(MD_THEME).expect("invalid theme binary")
};
let code_color = get_code_color(&md_theme);
let md_syntax = syntax_set.find_syntax_by_extension("md").unwrap().clone();
let line_type = LineType::Normal;
@ -223,7 +228,7 @@ mod tests {
#[test]
fn test_render() {
let render = MarkdownRender::new();
let render = MarkdownRender::new(true);
assert!(render.find_syntax("csharp").is_some());
}
}

@ -19,6 +19,7 @@ pub fn render_stream(
input: &str,
client: &ChatGptClient,
highlight: bool,
light_theme: bool,
repl: bool,
abort: SharedAbortSignal,
wg: WaitGroup,
@ -28,9 +29,9 @@ pub fn render_stream(
let abort_clone = abort.clone();
spawn(move || {
let err = if repl {
repl_render_stream(rx, abort)
repl_render_stream(rx, light_theme, abort)
} else {
cmd_render_stream(rx, abort)
cmd_render_stream(rx, light_theme, abort)
};
if let Err(err) = err {
let err = format!("{err:?}");

@ -16,11 +16,15 @@ use std::{
};
use unicode_width::UnicodeWidthStr;
pub fn repl_render_stream(rx: Receiver<ReplyStreamEvent>, abort: SharedAbortSignal) -> Result<()> {
pub fn repl_render_stream(
rx: Receiver<ReplyStreamEvent>,
light_theme: bool,
abort: SharedAbortSignal,
) -> Result<()> {
enable_raw_mode()?;
let mut stdout = io::stdout();
let ret = repl_render_stream_inner(rx, abort, &mut stdout);
let ret = repl_render_stream_inner(rx, light_theme, abort, &mut stdout);
disable_raw_mode()?;
@ -29,13 +33,14 @@ pub fn repl_render_stream(rx: Receiver<ReplyStreamEvent>, abort: SharedAbortSign
fn repl_render_stream_inner(
rx: Receiver<ReplyStreamEvent>,
light_theme: bool,
abort: SharedAbortSignal,
writer: &mut Stdout,
) -> Result<()> {
let mut last_tick = Instant::now();
let tick_rate = Duration::from_millis(100);
let mut buffer = String::new();
let mut markdown_render = MarkdownRender::new();
let mut markdown_render = MarkdownRender::new(light_theme);
let terminal_columns = terminal::size()?.0;
loop {
if abort.aborted() {

@ -51,11 +51,13 @@ impl ReplCmdHandler {
return Ok(());
}
let highlight = self.config.lock().highlight;
let light_theme = self.config.lock().light_theme;
let wg = WaitGroup::new();
let ret = render_stream(
&input,
&self.client,
highlight,
light_theme,
true,
self.abort.clone(),
wg.clone(),

@ -3,12 +3,17 @@ use super::REPL_COMMANDS;
use crate::config::{Config, SharedConfig};
use anyhow::{Context, Result};
use nu_ansi_term::Color;
use reedline::{
default_emacs_keybindings, ColumnarMenu, DefaultCompleter, DefaultValidator, Emacs,
FileBackedHistory, KeyCode, KeyModifiers, Keybindings, Reedline, ReedlineEvent, ReedlineMenu,
ExampleHighlighter, FileBackedHistory, KeyCode, KeyModifiers, Keybindings, Reedline,
ReedlineEvent, ReedlineMenu,
};
const MENU_NAME: &str = "completion_menu";
const MATCH_COLOR: Color = Color::Green;
const NEUTRAL_COLOR: Color = Color::White;
const NEUTRAL_COLOR_LIGHT: Color = Color::Black;
pub struct Repl {
pub editor: Reedline,
@ -16,13 +21,20 @@ pub struct Repl {
impl Repl {
pub fn init(config: SharedConfig) -> Result<Self> {
let completer = Self::create_completer(config);
let commands: Vec<String> = REPL_COMMANDS
.into_iter()
.map(|(v, _)| v.to_string())
.collect();
let completer = Self::create_completer(config.clone(), &commands);
let highlighter = Self::create_highlighter(config, &commands);
let keybindings = Self::create_keybindings();
let history = Self::create_history()?;
let menu = Self::create_menu();
let edit_mode = Box::new(Emacs::new(keybindings));
let editor = Reedline::create()
.with_completer(Box::new(completer))
.with_highlighter(Box::new(highlighter))
.with_history(history)
.with_menu(menu)
.with_edit_mode(edit_mode)
@ -33,17 +45,24 @@ impl Repl {
Ok(Self { editor })
}
fn create_completer(config: SharedConfig) -> DefaultCompleter {
let mut completion: Vec<String> = REPL_COMMANDS
.into_iter()
.map(|(v, _)| v.to_string())
.collect();
fn create_completer(config: SharedConfig, commands: &[String]) -> DefaultCompleter {
let mut completion = commands.to_vec();
completion.extend(config.lock().repl_completions());
let mut completer = DefaultCompleter::with_inclusions(&['.', '-', '_']).set_min_word_len(2);
completer.insert(completion.clone());
completer
}
fn create_highlighter(config: SharedConfig, commands: &[String]) -> ExampleHighlighter {
let mut highlighter = ExampleHighlighter::new(commands.to_vec());
if config.lock().light_theme {
highlighter.change_colors(MATCH_COLOR, NEUTRAL_COLOR_LIGHT, NEUTRAL_COLOR_LIGHT);
} else {
highlighter.change_colors(MATCH_COLOR, NEUTRAL_COLOR, NEUTRAL_COLOR);
}
highlighter
}
fn create_keybindings() -> Keybindings {
let mut keybindings = default_emacs_keybindings();
keybindings.add_binding(

Loading…
Cancel
Save