use crate::ported::colorscheme::{ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE};
use crate::ported::renderers::ipython::since_7::ColorSpec;
use std::collections::HashMap;
pub const REDUCE_INITIAL: [(); 0] = [];
pub fn attrs_to_attr_names(attrs: u32) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
if attrs & ATTR_BOLD != 0 {
out.push("bold".to_string());
}
if attrs & ATTR_ITALIC != 0 {
out.push("italic".to_string());
}
if attrs & ATTR_UNDERLINE != 0 {
out.push("underline".to_string());
}
out
}
pub fn build_token_name(fg: Option<ColorSpec>, bg: Option<ColorSpec>, attrs: u32) -> String {
let guifg = fg.and_then(|f| f.truecolor);
let guibg = bg.and_then(|b| b.truecolor);
let att = attrs_to_attr_names(attrs);
let mut name = String::from("Pl");
for a in &att {
name.push_str("_a");
name.push_str(a);
}
if let Some(rgb) = guifg {
name.push_str(&format!("_f{:6x}", rgb));
}
if let Some(rgb) = guibg {
name.push_str(&format!("_b{:6x}", rgb));
}
name
}
pub fn hl(
contents: &str,
fg: Option<ColorSpec>,
bg: Option<ColorSpec>,
attrs: Option<u32>,
) -> Vec<(String, String)> {
let attrs = attrs.unwrap_or(0);
let name = build_token_name(fg, bg, attrs);
vec![(name, contents.to_string())]
}
pub fn hlstyle() -> Vec<()> {
Vec::new()
}
pub fn hl_join<T: Clone>(segments: &[Vec<T>]) -> Vec<T> {
let mut out: Vec<T> = Vec::new();
for s in segments {
out.extend_from_slice(s);
}
out
}
pub struct PowerlineStyleDict {
pub inner: HashMap<String, Vec<(String, String)>>,
}
impl Default for PowerlineStyleDict {
fn default() -> Self {
Self::new()
}
}
impl PowerlineStyleDict {
pub fn new() -> Self {
Self {
inner: HashMap::new(),
}
}
pub fn lookup<F>(&self, key: &str, missing_func: F) -> Vec<(String, String)>
where
F: FnOnce(&str) -> Vec<(String, String)>,
{
match self.inner.get(key) {
Some(v) => v.clone(),
None => missing_func(key),
}
}
pub fn insert(&mut self, key: impl Into<String>, value: Vec<(String, String)>) {
self.inner.insert(key.into(), value);
}
}
pub fn powerline_prompt_style_invalidation_hash(base: u64) -> u64 {
base + 1
}
pub fn parse_token_attrs(name: &str) -> Option<TokenAttrs> {
if !name.starts_with("Pl") || name == "Pl" {
return None;
}
let mut attrs = TokenAttrs::default();
let body = name.strip_prefix("Pl").unwrap_or(name);
for prop in body.split('_') {
if prop.is_empty() {
continue;
}
let first = prop.as_bytes()[0] as char;
let rest = &prop[1..];
match first {
'a' => match rest {
"bold" => attrs.bold = true,
"italic" => attrs.italic = true,
"underline" => attrs.underline = true,
_ => {}
},
'f' => attrs.color = Some(rest.trim().to_string()),
'b' => attrs.bgcolor = Some(rest.trim().to_string()),
_ => {}
}
}
Some(attrs)
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TokenAttrs {
pub color: Option<String>,
pub bgcolor: Option<String>,
pub bold: bool,
pub underline: bool,
pub italic: bool,
pub reverse: bool,
pub blink: bool,
}
pub struct PowerlinePromptStyle;
impl PowerlinePromptStyle {
pub fn get_attrs_for_token(token: &str) -> Option<TokenAttrs> {
parse_token_attrs(token)
}
pub fn invalidation_hash(base: u64) -> u64 {
powerline_prompt_style_invalidation_hash(base)
}
pub fn get_token_to_attributes_dict(
seed: HashMap<String, Vec<(String, String)>>,
) -> PowerlineStyleDict {
let mut d = PowerlineStyleDict::new();
d.inner = seed;
d
}
}
pub struct IPythonPygmentsRenderer {
pub id: u64,
}
impl IPythonPygmentsRenderer {
pub fn new() -> Self {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(1);
Self {
id: COUNTER.fetch_add(1, Ordering::SeqCst),
}
}
pub fn get_segment_info(
&self,
segment_info: &serde_json::Value,
) -> serde_json::Map<String, serde_json::Value> {
let mut r = serde_json::Map::new();
r.insert("ipython".to_string(), segment_info.clone());
r
}
pub fn get_client_id(&self) -> u64 {
self.id
}
}
impl Default for IPythonPygmentsRenderer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn attrs_to_attr_names_zero_is_empty() {
assert!(attrs_to_attr_names(0).is_empty());
}
#[test]
fn attrs_to_attr_names_bold() {
assert_eq!(attrs_to_attr_names(ATTR_BOLD), vec!["bold"]);
}
#[test]
fn attrs_to_attr_names_italic() {
assert_eq!(attrs_to_attr_names(ATTR_ITALIC), vec!["italic"]);
}
#[test]
fn attrs_to_attr_names_all_three() {
let r = attrs_to_attr_names(ATTR_BOLD | ATTR_ITALIC | ATTR_UNDERLINE);
assert_eq!(r, vec!["bold", "italic", "underline"]);
}
#[test]
fn build_token_name_capital_pl_prefix() {
let n = build_token_name(None, None, 0);
assert_eq!(n, "Pl");
}
#[test]
fn build_token_name_with_fg_uses_space_padded_hex() {
let n = build_token_name(
Some(ColorSpec {
cterm: 0,
truecolor: Some(0xffaabb),
}),
None,
0,
);
assert_eq!(n, "Pl_fffaabb");
}
#[test]
fn build_token_name_with_small_fg_pads_with_space() {
let n = build_token_name(
Some(ColorSpec {
cterm: 0,
truecolor: Some(0xa),
}),
None,
0,
);
assert_eq!(n, "Pl_f a");
}
#[test]
fn build_token_name_with_bg() {
let n = build_token_name(
None,
Some(ColorSpec {
cterm: 0,
truecolor: Some(0x0000ff),
}),
0,
);
assert_eq!(n, "Pl_b ff");
}
#[test]
fn build_token_name_with_attrs() {
let n = build_token_name(None, None, ATTR_BOLD);
assert_eq!(n, "Pl_abold");
}
#[test]
fn build_token_name_with_all_three() {
let n = build_token_name(
Some(ColorSpec {
cterm: 0,
truecolor: Some(0xffaabb),
}),
Some(ColorSpec {
cterm: 0,
truecolor: Some(0x0000ff),
}),
ATTR_BOLD | ATTR_UNDERLINE,
);
assert_eq!(n, "Pl_abold_aunderline_fffaabb_b ff");
}
#[test]
fn hl_returns_single_segment_pair() {
let r = hl("hello", None, None, None);
assert_eq!(r.len(), 1);
assert_eq!(r[0].0, "Pl");
assert_eq!(r[0].1, "hello");
}
#[test]
fn hl_uses_token_name_with_color() {
let r = hl(
"x",
Some(ColorSpec {
cterm: 0,
truecolor: Some(0xff0000),
}),
None,
None,
);
assert_eq!(r[0].0, "Pl_fff0000");
}
#[test]
fn hlstyle_returns_empty() {
assert!(hlstyle().is_empty());
}
#[test]
fn hl_join_flattens_segments() {
let segs = vec![vec![1, 2], vec![3], vec![4, 5, 6]];
let flat = hl_join(&segs);
assert_eq!(flat, vec![1, 2, 3, 4, 5, 6]);
}
#[test]
fn powerline_style_dict_lookup_returns_existing() {
let mut d = PowerlineStyleDict::new();
d.insert("foo", vec![("k".to_string(), "v".to_string())]);
let r = d.lookup("foo", |_| Vec::new());
assert_eq!(r, vec![("k".to_string(), "v".to_string())]);
}
#[test]
fn powerline_style_dict_lookup_calls_missing_func_on_miss() {
let d = PowerlineStyleDict::new();
let mut called_with = String::new();
let _ = d.lookup("missing", |key| {
called_with = key.to_string();
vec![("fallback".to_string(), "yes".to_string())]
});
assert_eq!(called_with, "missing");
}
#[test]
fn powerline_prompt_style_invalidation_hash_adds_one() {
assert_eq!(powerline_prompt_style_invalidation_hash(0), 1);
assert_eq!(powerline_prompt_style_invalidation_hash(42), 43);
}
#[test]
fn parse_token_attrs_plain_pl_returns_default() {
let r = parse_token_attrs("Pl");
assert!(r.is_none());
}
#[test]
fn parse_token_attrs_non_pl_returns_none() {
let r = parse_token_attrs("Generic");
assert!(r.is_none());
}
#[test]
fn parse_token_attrs_bold() {
let r = parse_token_attrs("Pl_abold").unwrap();
assert!(r.bold);
assert!(!r.italic);
assert!(!r.underline);
}
#[test]
fn parse_token_attrs_fg_color() {
let r = parse_token_attrs("Pl_fffaabb").unwrap();
assert_eq!(r.color.as_deref(), Some("ffaabb"));
assert!(r.bgcolor.is_none());
}
#[test]
fn parse_token_attrs_bg_color() {
let r = parse_token_attrs("Pl_b0000ff").unwrap();
assert_eq!(r.bgcolor.as_deref(), Some("0000ff"));
}
#[test]
fn parse_token_attrs_all_three() {
let r = parse_token_attrs("Pl_abold_aunderline_fffaabb_b0000ff").unwrap();
assert!(r.bold);
assert!(r.underline);
assert!(!r.italic);
assert_eq!(r.color.as_deref(), Some("ffaabb"));
assert_eq!(r.bgcolor.as_deref(), Some("0000ff"));
}
#[test]
fn parse_token_attrs_default_reverse_and_blink_false() {
let r = parse_token_attrs("Pl_abold").unwrap();
assert!(!r.reverse);
assert!(!r.blink);
}
#[test]
fn reduce_initial_const_is_empty_array() {
assert_eq!(REDUCE_INITIAL.len(), 0);
}
#[test]
fn powerline_prompt_style_get_attrs_for_token_pl_format() {
let r = PowerlinePromptStyle::get_attrs_for_token("Pl_abold_fff0000").unwrap();
assert!(r.bold);
assert_eq!(r.color.as_deref(), Some("ff0000"));
}
#[test]
fn powerline_prompt_style_get_attrs_for_token_non_pl_returns_none() {
assert!(PowerlinePromptStyle::get_attrs_for_token("Generic").is_none());
}
#[test]
fn powerline_prompt_style_invalidation_hash_bumps_by_one() {
assert_eq!(PowerlinePromptStyle::invalidation_hash(10), 11);
}
#[test]
fn powerline_prompt_style_get_token_to_attributes_dict_returns_dict() {
let mut seed = HashMap::new();
seed.insert(
"Pl_abold".to_string(),
vec![("k".to_string(), "v".to_string())],
);
let d = PowerlinePromptStyle::get_token_to_attributes_dict(seed);
let r = d.lookup("Pl_abold", |_| Vec::new());
assert_eq!(r, vec![("k".to_string(), "v".to_string())]);
}
#[test]
fn ipython_pygments_renderer_each_instance_gets_unique_id() {
let r1 = IPythonPygmentsRenderer::new();
let r2 = IPythonPygmentsRenderer::new();
assert_ne!(r1.id, r2.id);
assert_ne!(r1.get_client_id(), r2.get_client_id());
}
#[test]
fn ipython_pygments_renderer_get_client_id_returns_self_id() {
let r = IPythonPygmentsRenderer::new();
assert_eq!(r.get_client_id(), r.id);
}
#[test]
fn ipython_pygments_renderer_get_segment_info_wraps_payload_under_ipython_key() {
let r = IPythonPygmentsRenderer::new();
let payload = serde_json::json!({"prompt_count": 5});
let merged = r.get_segment_info(&payload);
assert_eq!(merged["ipython"]["prompt_count"], 5);
}
}