use crate::ported::ipython::IPythonPowerline;
use serde_json::{Map, Value};
use std::collections::HashMap;
pub struct ConfigurableIPythonPowerline {
pub base: IPythonPowerline,
}
impl Default for ConfigurableIPythonPowerline {
fn default() -> Self {
Self::new()
}
}
impl ConfigurableIPythonPowerline {
pub fn new() -> Self {
Self {
base: IPythonPowerline::new(),
}
}
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_5"
}
pub fn _make_style_from_name<R>(
name: &str,
saved_msfn: R,
) -> crate::ported::renderers::ipython::since_5::PowerlinePromptStyle
where
R: FnOnce(&str) -> Value,
{
let _prev_style = saved_msfn(name);
crate::ported::renderers::ipython::since_5::PowerlinePromptStyle
}
pub fn do_setup(
&self,
_ip: &mut Map<String, Value>,
prompts: &mut Map<String, Value>,
shutdown_hook: &mut Map<String, Value>,
) {
prompts.insert(
"powerline".to_string(),
Value::String("<ConfigurableIPythonPowerline>".into()),
);
shutdown_hook.insert(
"powerline".to_string(),
Value::String("<weakref:ConfigurableIPythonPowerline>".into()),
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PromptKind {
In,
Continuation,
Rewrite,
Out,
}
impl PromptKind {
pub fn cache_key(&self) -> &'static str {
match self {
PromptKind::In => "in",
PromptKind::Continuation => "continuation",
PromptKind::Rewrite => "rewrite",
PromptKind::Out => "out",
}
}
pub fn matcher_info(&self) -> &'static str {
match self {
PromptKind::Continuation => "in2",
PromptKind::In => "in",
PromptKind::Rewrite => "rewrite",
PromptKind::Out => "out",
}
}
}
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 configurable_ipython_powerline_init_returns_renderer_module() {
let mut c = ConfigurableIPythonPowerline::new();
let cfg = Map::new();
let renderer = c.init(&cfg);
assert_eq!(renderer, ".since_5");
}
#[test]
fn init_reads_config_overrides_from_powerline_config() {
let mut c = ConfigurableIPythonPowerline::new();
let mut overrides = Map::new();
overrides.insert("foo".to_string(), Value::from(1));
let mut cfg = Map::new();
cfg.insert(
"config_overrides".to_string(),
Value::Object(overrides.clone()),
);
c.init(&cfg);
assert!(c.base.config_overrides.is_some());
assert_eq!(
c.base.config_overrides.unwrap().get("foo"),
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("default".to_string(), json!({"seg": "v"}));
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("default"), themes.get("default"));
}
#[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!(["/a", "/b"]));
c.init(&cfg);
assert_eq!(
c.base.config_paths,
vec!["/a".to_string(), "/b".to_string()]
);
}
#[test]
fn init_missing_config_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_and_shutdown_hook() {
let c = ConfigurableIPythonPowerline::new();
let mut ip = Map::new();
let mut prompts = Map::new();
let mut shutdown = Map::new();
c.do_setup(&mut ip, &mut prompts, &mut shutdown);
assert!(prompts.contains_key("powerline"));
assert!(shutdown.contains_key("powerline"));
}
#[test]
fn prompt_kind_cache_keys_match_upstream() {
assert_eq!(PromptKind::In.cache_key(), "in");
assert_eq!(PromptKind::Continuation.cache_key(), "continuation");
assert_eq!(PromptKind::Rewrite.cache_key(), "rewrite");
assert_eq!(PromptKind::Out.cache_key(), "out");
}
#[test]
fn prompt_kind_matcher_info_maps_continuation_to_in2() {
assert_eq!(PromptKind::In.matcher_info(), "in");
assert_eq!(PromptKind::Continuation.matcher_info(), "in2");
assert_eq!(PromptKind::Rewrite.matcher_info(), "rewrite");
assert_eq!(PromptKind::Out.matcher_info(), "out");
}
#[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![("Generic".to_string(), "X".to_string())]
};
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_passes_continuation_matcher_as_in2() {
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 continuation_prompt_tokens_helper_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.continuation_prompt_tokens(&mut render);
assert_eq!(last_matcher, "in2");
}
#[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_starts_with_empty_cache() {
let p = PowerlinePrompts::new(5);
assert_eq!(p.shell_execution_count, 5);
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())
});
}
}