use crate::ported::bindings::ipython::since_5::PromptKind;
use crate::ported::ipython::IPythonPowerline;
use serde_json::{Map, Value};
use std::collections::HashMap;
pub struct ConfigurableIPythonPowerline {
pub base: IPythonPowerline,
pub atexit_registered: bool,
}
impl Default for ConfigurableIPythonPowerline {
fn default() -> Self {
Self::new()
}
}
impl ConfigurableIPythonPowerline {
pub fn new() -> Self {
Self {
base: IPythonPowerline::new(),
atexit_registered: false,
}
}
pub fn init(&mut self, powerline_config: &Map<String, Value>) -> &'static str {
if let Some(overrides) = powerline_config
.get("config_overrides")
.and_then(|v| v.as_object())
{
self.base.config_overrides = Some(overrides.clone());
}
if let Some(themes) = powerline_config
.get("theme_overrides")
.and_then(|v| v.as_object())
{
self.base.theme_overrides = themes.clone();
}
if let Some(paths) = powerline_config
.get("config_paths")
.and_then(|v| v.as_array())
{
self.base.config_paths = paths
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect();
}
".since_7"
}
pub fn _make_style_from_name<R>(
name: &str,
saved_msfn: R,
) -> crate::ported::renderers::ipython::since_7::PowerlinePromptStyle
where
R: FnOnce(&str) -> Value,
{
let _prev_style = saved_msfn(name);
crate::ported::renderers::ipython::since_7::PowerlinePromptStyle::new()
}
pub fn do_setup(&mut self, _ip: &mut Map<String, Value>, prompts: &mut Map<String, Value>) {
prompts.insert(
"powerline".to_string(),
Value::String("<ConfigurableIPythonPowerline>".into()),
);
self.atexit_registered = true;
}
pub fn shutdown(&mut self) {
self.atexit_registered = false;
}
}
pub struct PowerlinePrompts {
pub shell_execution_count: u64,
pub last_output_count: Option<u64>,
pub last_output: HashMap<String, Vec<(String, String)>>,
}
impl Default for PowerlinePrompts {
fn default() -> Self {
Self::new(0)
}
}
impl PowerlinePrompts {
pub fn new(shell_execution_count: u64) -> Self {
Self {
shell_execution_count,
last_output_count: None,
last_output: HashMap::new(),
}
}
pub fn prompt_tokens<R>(&mut self, prompt: PromptKind, mut render: R) -> Vec<(String, String)>
where
R: FnMut(&str, &str, u64) -> Vec<(String, String)>,
{
if self.last_output_count != Some(self.shell_execution_count) {
self.last_output.clear();
self.last_output_count = Some(self.shell_execution_count);
}
let key = prompt.cache_key();
if !self.last_output.contains_key(key) {
let mut tokens = render("left", prompt.matcher_info(), self.shell_execution_count);
tokens.push(("Token.Generic.Prompt".to_string(), " ".to_string()));
self.last_output.insert(key.to_string(), tokens);
}
self.last_output[key].clone()
}
pub fn in_prompt_tokens<R>(&mut self, render: R) -> Vec<(String, String)>
where
R: FnMut(&str, &str, u64) -> Vec<(String, String)>,
{
self.prompt_tokens(PromptKind::In, render)
}
pub fn continuation_prompt_tokens<R>(&mut self, render: R) -> Vec<(String, String)>
where
R: FnMut(&str, &str, u64) -> Vec<(String, String)>,
{
self.prompt_tokens(PromptKind::Continuation, render)
}
pub fn rewrite_prompt_tokens<R>(&mut self, render: R) -> Vec<(String, String)>
where
R: FnMut(&str, &str, u64) -> Vec<(String, String)>,
{
self.prompt_tokens(PromptKind::Rewrite, render)
}
pub fn out_prompt_tokens<R>(&mut self, render: R) -> Vec<(String, String)>
where
R: FnMut(&str, &str, u64) -> Vec<(String, String)>,
{
self.prompt_tokens(PromptKind::Out, render)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn init_returns_since_7_renderer_module() {
let mut c = ConfigurableIPythonPowerline::new();
let cfg = Map::new();
assert_eq!(c.init(&cfg), ".since_7");
}
#[test]
fn init_reads_config_overrides_from_powerline_config() {
let mut c = ConfigurableIPythonPowerline::new();
let mut overrides = Map::new();
overrides.insert("k".to_string(), json!(1));
let mut cfg = Map::new();
cfg.insert(
"config_overrides".to_string(),
Value::Object(overrides.clone()),
);
c.init(&cfg);
assert_eq!(
c.base.config_overrides.unwrap().get("k"),
Some(&Value::from(1))
);
}
#[test]
fn init_reads_theme_overrides_from_powerline_config() {
let mut c = ConfigurableIPythonPowerline::new();
let mut themes = Map::new();
themes.insert("t".to_string(), json!({"x": 1}));
let mut cfg = Map::new();
cfg.insert("theme_overrides".to_string(), Value::Object(themes.clone()));
c.init(&cfg);
assert_eq!(c.base.theme_overrides.get("t"), themes.get("t"));
}
#[test]
fn init_reads_config_paths_from_powerline_config() {
let mut c = ConfigurableIPythonPowerline::new();
let mut cfg = Map::new();
cfg.insert("config_paths".to_string(), json!(["/x", "/y"]));
c.init(&cfg);
assert_eq!(
c.base.config_paths,
vec!["/x".to_string(), "/y".to_string()]
);
}
#[test]
fn init_missing_keys_leaves_base_attrs_empty() {
let mut c = ConfigurableIPythonPowerline::new();
let cfg = Map::new();
c.init(&cfg);
assert!(c.base.config_overrides.is_none());
assert!(c.base.theme_overrides.is_empty());
assert!(c.base.config_paths.is_empty());
}
#[test]
fn do_setup_attaches_powerline_to_prompts() {
let mut c = ConfigurableIPythonPowerline::new();
let mut ip = Map::new();
let mut prompts = Map::new();
c.do_setup(&mut ip, &mut prompts);
assert!(prompts.contains_key("powerline"));
}
#[test]
fn do_setup_marks_atexit_registered() {
let mut c = ConfigurableIPythonPowerline::new();
assert!(!c.atexit_registered);
let mut ip = Map::new();
let mut prompts = Map::new();
c.do_setup(&mut ip, &mut prompts);
assert!(c.atexit_registered);
}
#[test]
fn shutdown_clears_atexit_flag() {
let mut c = ConfigurableIPythonPowerline::new();
let mut ip = Map::new();
let mut prompts = Map::new();
c.do_setup(&mut ip, &mut prompts);
assert!(c.atexit_registered);
c.shutdown();
assert!(!c.atexit_registered);
}
#[test]
fn prompt_tokens_caches_within_same_execution_count() {
let mut p = PowerlinePrompts::new(1);
let mut render_calls = 0;
let mut render = |_side: &str, _matcher: &str, _count: u64| {
render_calls += 1;
vec![("Generic".to_string(), "X".to_string())]
};
let a = p.prompt_tokens(PromptKind::In, &mut render);
let b = p.prompt_tokens(PromptKind::In, &mut render);
assert_eq!(a, b);
assert_eq!(render_calls, 1);
}
#[test]
fn prompt_tokens_renders_again_when_execution_count_changes() {
let mut render_calls = 0;
let mut render = |_side: &str, _matcher: &str, _count: u64| {
render_calls += 1;
Vec::new()
};
let mut p = PowerlinePrompts::new(1);
let _ = p.prompt_tokens(PromptKind::In, &mut render);
p.shell_execution_count = 2;
let _ = p.prompt_tokens(PromptKind::In, &mut render);
assert_eq!(render_calls, 2);
}
#[test]
fn prompt_tokens_appends_trailing_space_token() {
let mut p = PowerlinePrompts::new(1);
let tokens = p.prompt_tokens(PromptKind::In, |_s, _m, _c| Vec::new());
assert_eq!(tokens.len(), 1);
assert_eq!(tokens[0].0, "Token.Generic.Prompt");
assert_eq!(tokens[0].1, " ");
}
#[test]
fn prompt_tokens_passes_side_left() {
let mut p = PowerlinePrompts::new(1);
let mut last_side = String::new();
let mut render = |side: &str, _matcher: &str, _count: u64| {
last_side = side.to_string();
Vec::new()
};
let _ = p.prompt_tokens(PromptKind::In, &mut render);
assert_eq!(last_side, "left");
}
#[test]
fn prompt_tokens_continuation_uses_in2_matcher() {
let mut p = PowerlinePrompts::new(1);
let mut last_matcher = String::new();
let mut render = |_side: &str, matcher: &str, _count: u64| {
last_matcher = matcher.to_string();
Vec::new()
};
let _ = p.prompt_tokens(PromptKind::Continuation, &mut render);
assert_eq!(last_matcher, "in2");
}
#[test]
fn prompt_tokens_distinct_keys_cache_separately() {
let mut render_calls = 0;
let mut render = |_side: &str, _matcher: &str, _count: u64| {
render_calls += 1;
Vec::new()
};
let mut p = PowerlinePrompts::new(1);
let _ = p.prompt_tokens(PromptKind::In, &mut render);
let _ = p.prompt_tokens(PromptKind::Out, &mut render);
let _ = p.prompt_tokens(PromptKind::Rewrite, &mut render);
let _ = p.prompt_tokens(PromptKind::Continuation, &mut render);
assert_eq!(render_calls, 4);
assert_eq!(p.last_output.len(), 4);
}
#[test]
fn in_prompt_tokens_helper_works() {
let mut p = PowerlinePrompts::new(1);
let tokens = p.in_prompt_tokens(|_s, _m, _c| Vec::new());
assert_eq!(tokens.len(), 1);
}
#[test]
fn out_prompt_tokens_helper_uses_out_matcher() {
let mut p = PowerlinePrompts::new(1);
let mut last_matcher = String::new();
let mut render = |_side: &str, matcher: &str, _count: u64| {
last_matcher = matcher.to_string();
Vec::new()
};
let _ = p.out_prompt_tokens(&mut render);
assert_eq!(last_matcher, "out");
}
#[test]
fn rewrite_prompt_tokens_helper_uses_rewrite_matcher() {
let mut p = PowerlinePrompts::new(1);
let mut last_matcher = String::new();
let mut render = |_side: &str, matcher: &str, _count: u64| {
last_matcher = matcher.to_string();
Vec::new()
};
let _ = p.rewrite_prompt_tokens(&mut render);
assert_eq!(last_matcher, "rewrite");
}
#[test]
fn powerline_prompts_init_has_empty_cache() {
let p = PowerlinePrompts::new(7);
assert_eq!(p.shell_execution_count, 7);
assert!(p.last_output_count.is_none());
assert!(p.last_output.is_empty());
}
#[test]
fn make_style_from_name_calls_saved_msfn() {
let called = std::cell::Cell::new(false);
let _r = ConfigurableIPythonPowerline::_make_style_from_name("default", |n| {
called.set(true);
assert_eq!(n, "default");
Value::String("prev_style".to_string())
});
assert!(called.get());
}
#[test]
fn make_style_from_name_returns_powerline_prompt_style() {
let _r = ConfigurableIPythonPowerline::_make_style_from_name("monokai", |_| {
Value::String("monokai_style".to_string())
});
}
}