Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/jp_cli/src/cmd/conversation/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ fn build_formatter(cfg: &AppConfig, pretty: bool) -> Formatter {
None
})
.pretty_hr(pretty && cfg.style.markdown.hr_style.is_line())
.inline_code_bg(
cfg.style
.inline_code
.background
.map(crate::format::color_to_bg_param),
)
}

#[cfg(test)]
Expand Down
8 changes: 7 additions & 1 deletion crates/jp_cli/src/cmd/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,13 @@ impl Query {
} else {
None
})
.pretty_hr(pretty && cfg.style.markdown.hr_style.is_line());
.pretty_hr(pretty && cfg.style.markdown.hr_style.is_line())
.inline_code_bg(
cfg.style
.inline_code
.background
.map(crate::format::color_to_bg_param),
);

let formatted =
formatter.format_terminal(&format!("{}\n\n---\n\n", chat_request.content))?;
Expand Down
31 changes: 18 additions & 13 deletions crates/jp_cli/src/cmd/query/stream/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ use std::sync::Arc;

use jp_config::style::{
StyleConfig,
markdown::MarkdownConfig,
reasoning::{ReasoningDisplayConfig, TruncateChars},
};
use jp_conversation::event::ChatResponse;
Expand Down Expand Up @@ -68,7 +67,7 @@ pub struct ChatResponseRenderer {
impl ChatResponseRenderer {
pub fn new(printer: Arc<Printer>, config: StyleConfig) -> Self {
let pretty = printer.pretty_printing();
let formatter = formatter_from_config(&config.markdown, pretty);
let formatter = formatter_from_config(&config, pretty);
Self {
buffer: Buffer::new(),
formatter,
Expand Down Expand Up @@ -241,16 +240,16 @@ impl ChatResponseRenderer {
// so the background is continuous across paragraphs.
match opts.default_background {
Some(DefaultBackground {
color,
ref param,
fill: BackgroundFill::Terminal,
}) => {
formatted.push_str(&format!("\x1b[48;5;{color}m\x1b[K\x1b[49m\n"));
formatted.push_str(&format!("\x1b[{param}m\x1b[K\x1b[49m\n"));
}
Some(DefaultBackground {
color,
ref param,
fill: BackgroundFill::Column(width),
}) => {
formatted.push_str(&format!("\x1b[48;5;{color}m"));
formatted.push_str(&format!("\x1b[{param}m"));
for _ in 0..width {
formatted.push(' ');
}
Expand All @@ -271,7 +270,7 @@ impl ChatResponseRenderer {
.reasoning
.background
.map(|color| DefaultBackground {
color,
param: crate::format::color_to_bg_param(color),
fill: BackgroundFill::Terminal,
})
} else {
Expand Down Expand Up @@ -310,22 +309,28 @@ impl ChatResponseRenderer {
pub fn reset(&mut self) {
self.buffer = Buffer::new();
let pretty = self.printer.pretty_printing();
self.formatter = formatter_from_config(&self.config.markdown, pretty);
self.formatter = formatter_from_config(&self.config, pretty);
self.last_content_kind = None;
self.reasoning_chars_count = 0;
self.code_highlight = None;
}
}

fn formatter_from_config(config: &MarkdownConfig, pretty: bool) -> Formatter {
Formatter::with_width(config.wrap_width)
.table_max_column_width(config.table_max_column_width)
fn formatter_from_config(config: &StyleConfig, pretty: bool) -> Formatter {
Formatter::with_width(config.markdown.wrap_width)
.table_max_column_width(config.markdown.table_max_column_width)
.theme(if pretty {
config.theme.as_deref()
config.markdown.theme.as_deref()
} else {
None
})
.pretty_hr(pretty && config.hr_style.is_line())
.pretty_hr(pretty && config.markdown.hr_style.is_line())
.inline_code_bg(
config
.inline_code
.background
.map(crate::format::color_to_bg_param),
)
}

#[cfg(test)]
Expand Down
6 changes: 3 additions & 3 deletions crates/jp_cli/src/cmd/query/stream/renderer_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use jp_config::AppConfig;
use jp_config::{AppConfig, types::color::Color};
use jp_printer::{OutputFormat, SharedBuffer};

use super::*;
Expand Down Expand Up @@ -216,7 +216,7 @@ fn test_whitespace_only_block_not_printed() {
fn test_reasoning_background_color_applied() {
let mut config = AppConfig::new_test();
config.style.reasoning.display = ReasoningDisplayConfig::Full;
config.style.reasoning.background = Some(236);
config.style.reasoning.background = Some(Color::Ansi256(236));
let (mut renderer, out) = create_renderer_with_config(config);

renderer.render(&ChatResponse::Reasoning {
Expand All @@ -243,7 +243,7 @@ fn test_reasoning_background_color_applied() {
fn test_reasoning_background_not_applied_to_messages() {
let mut config = AppConfig::new_test();
config.style.reasoning.display = ReasoningDisplayConfig::Full;
config.style.reasoning.background = Some(236);
config.style.reasoning.background = Some(Color::Ansi256(236));
let (mut renderer, out) = create_renderer_with_config(config);

renderer.render(&ChatResponse::Message {
Expand Down
10 changes: 10 additions & 0 deletions crates/jp_cli/src/format.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
pub(crate) mod conversation;
pub(crate) mod datetime;

use jp_config::types::color::Color;

/// Convert a [`Color`] to an SGR background parameter string.
pub(crate) fn color_to_bg_param(color: Color) -> String {
match color {
Color::Ansi256(n) => format!("48;5;{n}"),
Color::Rgb { r, g, b } => format!("48;2;{r};{g};{b}"),
}
}
56 changes: 36 additions & 20 deletions crates/jp_config/src/assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,42 @@ impl KvAssignment {
self.try_from_str().map(Some)
}

/// Like [`Self::try_from_str`], but also accepts JSON numbers by
/// converting them to their string representation first.
///
/// Useful for types like [`Color`](crate::types::color::Color) that
/// accept both `236` (integer) and `"#504945"` (string).
pub(crate) fn try_number_or_from_str<T, E>(self) -> Result<T, KvAssignmentError>
where
T: FromStr<Err = E>,
E: Into<BoxedError>,
{
let Self { key, value, .. } = self;

match value {
KvValue::Json(Value::Number(n)) => {
let s = n.to_string();
T::from_str(&s)
.map_err(Into::into)
.or_else(|err| assignment_error(&key, Value::String(s), err))
}
KvValue::Json(Value::String(s)) | KvValue::String(s) => T::from_str(&s)
.map_err(Into::into)
.or_else(|err| assignment_error(&key, Value::String(s), err)),
KvValue::Json(_) => type_error(&key, &value, &["number", "string"]),
}
}

/// Convenience method for [`Self::try_number_or_from_str`] that wraps the
/// `Ok` value into `Some`.
pub(crate) fn try_some_number_or_from_str<T, E>(self) -> Result<Option<T>, KvAssignmentError>
where
T: FromStr<Err = E>,
E: Into<BoxedError>,
{
self.try_number_or_from_str().map(Some)
}

/// Try to parse the value as a string.
pub(crate) fn try_string(self) -> Result<String, KvAssignmentError> {
let Self { key, value, .. } = self;
Expand Down Expand Up @@ -498,26 +534,6 @@ impl KvAssignment {
self.try_u32().map(Some)
}

/// Try to parse the value as an unsigned 8-bit integer.
pub(crate) fn try_u8(self) -> Result<u8, KvAssignmentError> {
let Self { key, value, .. } = self;

match value {
#[expect(clippy::cast_possible_truncation)]
KvValue::Json(Value::Number(v)) if v.is_u64() => Ok(v.as_u64().expect("is u64") as u8),
KvValue::Json(_) => type_error(&key, &value, &["number", "string"]),
KvValue::String(v) => Ok(v
.parse()
.map_err(|err| KvAssignmentError::new(key.full_path.clone(), err))?),
}
}

/// Convenience method for [`Self::try_u8`] that wraps the `Ok` value into
/// `Some`.
pub(crate) fn try_some_u8(self) -> Result<Option<u8>, KvAssignmentError> {
self.try_u8().map(Some)
}

/// Try to parse the value as a 32-bit floating point number.
pub(crate) fn try_f32(self) -> Result<f32, KvAssignmentError> {
let Self { key, value, .. } = self;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ expression: "AppConfig::fields()"
"style.markdown.table_max_column_width",
"style.markdown.theme",
"style.markdown.wrap_width",
"style.inline_code.background",
"style.code.color",
"style.code.copy_link",
"style.code.file_link",
"style.code.line_numbers",
"style.code.theme",
"providers.mcp",
"providers.llm.aliases",
"providers.llm.openrouter.api_key_env",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,14 @@ PartialAppConfig {
},
style: PartialStyleConfig {
code: PartialCodeConfig {
theme: None,
color: None,
line_numbers: None,
file_link: None,
copy_link: None,
},
inline_code: PartialInlineCodeConfig {
background: None,
},
markdown: PartialMarkdownConfig {
wrap_width: None,
table_max_column_width: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,6 @@ Ok(
},
style: PartialStyleConfig {
code: PartialCodeConfig {
theme: Some(
"base16-mocha.dark",
),
color: Some(
true,
),
Expand All @@ -133,6 +130,9 @@ Ok(
Off,
),
},
inline_code: PartialInlineCodeConfig {
background: None,
},
markdown: PartialMarkdownConfig {
wrap_width: Some(
80,
Expand All @@ -149,7 +149,9 @@ Ok(
display: None,
summary_model: None,
background: Some(
236,
Ansi256(
236,
),
),
},
streaming: PartialStreamingConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,14 @@ PartialAppConfig {
},
style: PartialStyleConfig {
code: PartialCodeConfig {
theme: None,
color: None,
line_numbers: None,
file_link: None,
copy_link: None,
},
inline_code: PartialInlineCodeConfig {
background: None,
},
markdown: PartialMarkdownConfig {
wrap_width: None,
table_max_column_width: None,
Expand Down
11 changes: 11 additions & 0 deletions crates/jp_config/src/style.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Style configuration for output formatting.

pub mod code;
pub mod inline_code;
pub mod markdown;
pub mod reasoning;
pub mod streaming;
Expand All @@ -18,6 +19,7 @@ use crate::{
partial::ToPartial,
style::{
code::{CodeConfig, PartialCodeConfig},
inline_code::{InlineCodeConfig, PartialInlineCodeConfig},
markdown::{MarkdownConfig, PartialMarkdownConfig},
reasoning::{PartialReasoningConfig, ReasoningConfig},
streaming::{PartialStreamingConfig, StreamingConfig},
Expand All @@ -36,6 +38,12 @@ pub struct StyleConfig {
#[setting(nested)]
pub code: CodeConfig,

/// Inline code span style.
///
/// Configures how inline code (`` `like this` ``) is rendered.
#[setting(nested)]
pub inline_code: InlineCodeConfig,

/// Markdown rendering style.
///
/// Configures how markdown content is rendered in the terminal.
Expand Down Expand Up @@ -73,6 +81,7 @@ impl AssignKeyValue for PartialStyleConfig {
match kv.key_string().as_str() {
"" => *self = kv.try_object()?,
_ if kv.p("code") => self.code.assign(kv)?,
_ if kv.p("inline_code") => self.inline_code.assign(kv)?,
_ if kv.p("markdown") => self.markdown.assign(kv)?,
_ if kv.p("reasoning") => self.reasoning.assign(kv)?,
_ if kv.p("streaming") => self.streaming.assign(kv)?,
Expand All @@ -89,6 +98,7 @@ impl PartialConfigDelta for PartialStyleConfig {
fn delta(&self, next: Self) -> Self {
Self {
code: self.code.delta(next.code),
inline_code: self.inline_code.delta(next.inline_code),
markdown: self.markdown.delta(next.markdown),
reasoning: self.reasoning.delta(next.reasoning),
streaming: self.streaming.delta(next.streaming),
Expand All @@ -102,6 +112,7 @@ impl ToPartial for StyleConfig {
fn to_partial(&self) -> Self::Partial {
Self::Partial {
code: self.code.to_partial(),
inline_code: self.inline_code.to_partial(),
markdown: self.markdown.to_partial(),
reasoning: self.reasoning.to_partial(),
streaming: self.streaming.to_partial(),
Expand Down
9 changes: 0 additions & 9 deletions crates/jp_config/src/style/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ use crate::{
#[derive(Debug, Clone, PartialEq, Config)]
#[config(rename_all = "snake_case")]
pub struct CodeConfig {
/// Theme to use for code blocks.
///
/// This uses [syntect](https://github.com/trishume/syntect) theme names.
#[setting(default = "base16-mocha.dark")]
pub theme: String,

/// Whether to colorize code blocks.
#[setting(default = true)]
pub color: bool,
Expand Down Expand Up @@ -65,7 +59,6 @@ impl AssignKeyValue for PartialCodeConfig {
fn assign(&mut self, kv: KvAssignment) -> AssignResult {
match kv.key_string().as_str() {
"" => *self = kv.try_object()?,
"theme" => self.theme = kv.try_some_string()?,
"color" => self.color = kv.try_some_bool()?,
"line_numbers" => self.line_numbers = kv.try_some_bool()?,
"file_link" => self.file_link = kv.try_some_from_str()?,
Expand All @@ -80,7 +73,6 @@ impl AssignKeyValue for PartialCodeConfig {
impl PartialConfigDelta for PartialCodeConfig {
fn delta(&self, next: Self) -> Self {
Self {
theme: delta_opt(self.theme.as_ref(), next.theme),
color: delta_opt(self.color.as_ref(), next.color),
line_numbers: delta_opt(self.line_numbers.as_ref(), next.line_numbers),
file_link: delta_opt(self.file_link.as_ref(), next.file_link),
Expand All @@ -94,7 +86,6 @@ impl ToPartial for CodeConfig {
let defaults = Self::Partial::default();

Self::Partial {
theme: partial_opt(&self.theme, defaults.theme),
color: partial_opt(&self.color, defaults.color),
line_numbers: partial_opt(&self.line_numbers, defaults.line_numbers),
file_link: partial_opt(&self.file_link, defaults.file_link),
Expand Down
Loading
Loading