use crate::ported::colorscheme::Colorscheme;
use serde_json::{Map, Value};
#[allow(clippy::too_many_arguments)]
pub fn list_segment_key_values(
segment: &Map<String, Value>,
theme_configs: &[&Map<String, Value>],
segment_data: Option<&Map<String, Value>>,
key: &str,
function_name: Option<&str>,
name: Option<&str>,
module: Option<&str>,
default: Option<Value>,
) -> Vec<Value> {
let mut out: Vec<Value> = Vec::new();
if let Some(v) = segment.get(key) {
out.push(v.clone());
}
let mut found_module_key = false;
for theme_config in theme_configs {
let seg_data = match theme_config.get("segment_data").and_then(|v| v.as_object()) {
Some(s) => s,
None => continue,
};
if let (Some(fname), None) = (function_name, name) {
if let Some(module) = module {
let mod_key = format!("{}.{}", module, fname);
if let Some(v) = seg_data
.get(&mod_key)
.and_then(|x| x.as_object())
.and_then(|o| o.get(key))
{
out.push(v.clone());
found_module_key = true;
}
}
if !found_module_key {
if let Some(v) = seg_data
.get(fname)
.and_then(|x| x.as_object())
.and_then(|o| o.get(key))
{
out.push(v.clone());
}
}
}
if let Some(n) = name {
if let Some(v) = seg_data
.get(n)
.and_then(|x| x.as_object())
.and_then(|o| o.get(key))
{
out.push(v.clone());
}
}
}
if let Some(sd) = segment_data {
if let Some(v) = sd.get(key) {
out.push(v.clone());
}
}
if let Some(d) = default {
out.push(d);
}
out
}
#[allow(clippy::too_many_arguments)]
pub fn get_segment_key(
merge: bool,
segment: &Map<String, Value>,
theme_configs: &[&Map<String, Value>],
segment_data: Option<&Map<String, Value>>,
key: &str,
function_name: Option<&str>,
name: Option<&str>,
module: Option<&str>,
default: Option<Value>,
) -> Option<Value> {
let candidates = list_segment_key_values(
segment,
theme_configs,
segment_data,
key,
function_name,
name,
module,
default,
);
if merge {
let mut ret: Option<Value> = None;
for value in candidates {
if ret.is_none() {
ret = Some(value);
} else if matches!(ret, Some(Value::Object(_))) && matches!(value, Value::Object(_)) {
let old_ret = ret.take().unwrap();
let mut new_ret = value.as_object().unwrap().clone();
for (k, v) in old_ret.as_object().unwrap() {
new_ret.insert(k.clone(), v.clone());
}
ret = Some(Value::Object(new_ret));
} else {
return ret;
}
}
ret
} else {
candidates.into_iter().next()
}
}
#[allow(clippy::type_complexity)]
pub fn get_string(
data: &Map<String, Value>,
segment: &Map<String, Value>,
) -> (
Option<Value>,
Option<Value>,
Option<String>,
Option<String>,
Option<String>,
) {
let name = segment
.get("name")
.and_then(|v| v.as_str())
.map(String::from);
let contents = segment.get("contents").cloned();
let _ = data;
(contents, None, None, None, name)
}
pub fn set_segment_highlighting(
_pl: &(),
colorscheme: &Colorscheme,
segment: &mut Map<String, Value>,
mode: Option<&str>,
) -> bool {
if let Some(Value::Array(lc)) = segment.get("literal_contents") {
if lc.len() == 2 && !lc[1].as_str().unwrap_or("").is_empty() {
return true;
}
}
let highlight_group_prefix = segment
.get("highlight_group_prefix")
.and_then(|v| v.as_str())
.map(String::from);
let hl_groups = |hlgs: Vec<String>| -> Vec<String> {
match &highlight_group_prefix {
None => hlgs,
Some(prefix) => {
let mut out: Vec<String> =
hlgs.iter().map(|h| format!("{}:{}", prefix, h)).collect();
out.extend(hlgs);
out
}
}
};
let hlgs_raw: Vec<String> = segment
.get("highlight_groups")
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|s| s.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let gradient_level = segment.get("gradient_level").and_then(|v| v.as_f64());
match colorscheme.get_highlighting(&hl_groups(hlgs_raw), mode, gradient_level) {
Ok(hl) => {
segment.insert("highlight".to_string(), Value::Object(hl));
}
Err(_) => {
return false;
}
}
if let Some(dhg) = segment
.get("divider_highlight_group")
.and_then(|v| v.as_str())
.map(String::from)
{
if dhg.is_empty() {
segment.insert("divider_highlight".to_string(), Value::Null);
} else {
match colorscheme.get_highlighting(&hl_groups(vec![dhg]), mode, None) {
Ok(hl) => {
segment.insert("divider_highlight".to_string(), Value::Object(hl));
}
Err(_) => {
return false;
}
}
}
} else {
segment.insert("divider_highlight".to_string(), Value::Null);
}
true
}
pub fn always_true(
_pl: &(),
_segment_info: Option<&Map<String, Value>>,
_mode: Option<&str>,
) -> bool {
true
}
#[allow(clippy::too_many_arguments)]
pub fn get_key(
merge: bool,
segment: &Map<String, Value>,
theme_configs: &[&Map<String, Value>],
segment_data: Option<&Map<String, Value>>,
module: Option<&str>,
function_name: Option<&str>,
name: Option<&str>,
key: &str,
default: Option<Value>,
) -> Option<Value> {
get_segment_key(
merge,
segment,
theme_configs,
segment_data,
key,
function_name,
name,
module,
default,
)
}
pub fn get_selector<F>(
function_name: &str,
ext: &str,
get_module_attr: F,
) -> Option<(String, String)>
where
F: Fn(&str, &str) -> bool,
{
let (module, fname) = if let Some(idx) = function_name.rfind('.') {
(
function_name[..idx].to_string(),
function_name[idx + 1..].to_string(),
)
} else {
(
format!("powerline.selectors.{}", ext),
function_name.to_string(),
)
};
if !get_module_attr(&module, &fname) {
return None;
}
Some((module, fname))
}
pub fn get_segment_selector(
segment: &Map<String, Value>,
selector_type: &str,
function: Option<Box<dyn Fn(&str) -> bool>>,
) -> Option<Box<dyn Fn(&str) -> bool>> {
let modes: Option<Vec<String>> = segment
.get(&format!("{}_modes", selector_type))
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|m| m.as_str().map(String::from))
.collect()
});
match (modes, function) {
(Some(modes), Some(func)) => {
Some(Box::new(move |mode: &str| {
modes.iter().any(|m| m == mode) || func(mode)
}))
}
(Some(modes), None) => {
Some(Box::new(move |mode: &str| modes.iter().any(|m| m == mode)))
}
(None, Some(func)) => {
Some(Box::new(move |mode: &str| func(mode)))
}
(None, None) => {
None
}
}
}
pub fn gen_display_condition(
include_function: Option<Box<dyn Fn(&str) -> bool>>,
exclude_function: Option<Box<dyn Fn(&str) -> bool>>,
) -> Box<dyn Fn(&str) -> bool> {
match (include_function, exclude_function) {
(Some(inc), Some(exc)) => {
Box::new(move |mode: &str| inc(mode) && !exc(mode))
}
(Some(inc), None) => {
Box::new(move |mode: &str| inc(mode))
}
(None, Some(exc)) => {
Box::new(move |mode: &str| !exc(mode))
}
(None, None) => {
Box::new(|_| true)
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn process_segment_lister<L, D, C>(
pl: &(),
segment_info: &Map<String, Value>,
parsed_segments: &mut Vec<Value>,
side: &str,
mode: Option<&str>,
colorscheme: &crate::ported::colorscheme::Colorscheme,
lister: L,
subsegments: &[Map<String, Value>],
patcher_args: &Map<String, Value>,
display_condition: D,
contents_func: C,
) where
L: Fn(
&(),
&Map<String, Value>,
&Map<String, Value>,
) -> Vec<(Map<String, Value>, Map<String, Value>)>,
D: Fn(&(), &Map<String, Value>, Option<&str>, &Map<String, Value>) -> bool,
C: Fn(&(), &Map<String, Value>, &Map<String, Value>) -> Option<Value>,
{
let subsegments: Vec<&Map<String, Value>> = subsegments
.iter()
.filter(|s| display_condition(pl, segment_info, mode, s))
.collect();
for (subsegment_info, mut subsegment_update) in lister(pl, segment_info, patcher_args) {
let draw_inner_divider = subsegment_update.remove("draw_inner_divider");
let old_pslen = parsed_segments.len();
for subsegment in subsegments.iter() {
let mut subsegment_owned = (*subsegment).clone();
if !subsegment_update.is_empty() {
for (k, v) in &subsegment_update {
subsegment_owned.insert(k.clone(), v.clone());
}
if let Some(mult) = subsegment_update
.get("priority_multiplier")
.and_then(|v| v.as_f64())
{
if let Some(prio) = subsegment_owned.get("priority").and_then(|v| v.as_f64()) {
subsegment_owned.insert("priority".to_string(), Value::from(prio * mult));
}
}
}
process_segment(
pl,
side,
&subsegment_info,
parsed_segments,
&subsegment_owned,
mode,
colorscheme,
&contents_func,
);
}
let mut new_pslen = parsed_segments.len();
while new_pslen > 0 {
let lit = parsed_segments[new_pslen - 1]
.get("literal_contents")
.and_then(|v| v.as_array())
.and_then(|a| a.get(1))
.and_then(|v| v.as_str())
.map(|s| !s.is_empty())
.unwrap_or(false);
if lit {
new_pslen -= 1;
} else {
break;
}
}
if new_pslen > old_pslen + 1 && draw_inner_divider.is_some() {
let r: Box<dyn Iterator<Item = usize>> = if side == "left" {
Box::new(old_pslen..new_pslen - 1)
} else {
Box::new(old_pslen + 1..new_pslen)
};
for i in r {
if let Some(obj) = parsed_segments[i].as_object_mut() {
obj.insert(
"draw_soft_divider".to_string(),
draw_inner_divider.clone().unwrap_or(Value::Null),
);
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn process_segment<C>(
pl: &(),
side: &str,
segment_info: &Map<String, Value>,
parsed_segments: &mut Vec<Value>,
segment: &Map<String, Value>,
mode: Option<&str>,
colorscheme: &crate::ported::colorscheme::Colorscheme,
contents_func: &C,
) where
C: Fn(&(), &Map<String, Value>, &Map<String, Value>) -> Option<Value>,
{
let mut segment: Map<String, Value> = segment.clone();
let _ = pl;
let seg_type = segment
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if seg_type == "function" || seg_type == "segment_list" {
let args_empty = Map::new();
let args = segment
.get("args")
.and_then(|v| v.as_object())
.unwrap_or(&args_empty);
let contents = contents_func(pl, segment_info, args);
let Some(contents) = contents else { return };
if let Some(arr) = contents.as_array().cloned() {
let mut arr = arr;
if !arr.is_empty() {
let draw_divider_position: isize = if side == "left" { -1 } else { 0 };
let keys: [(&str, isize, Value); 4] = [
("before", 0, Value::String(String::new())),
("after", -1, Value::String(String::new())),
(
"draw_soft_divider",
draw_divider_position,
Value::Bool(true),
),
(
"draw_hard_divider",
draw_divider_position,
Value::Bool(true),
),
];
for (key, i, newval) in keys {
if let Some(base_val) = segment.remove(key) {
let idx = if i < 0 {
arr.len().saturating_sub(i.unsigned_abs())
} else {
i as usize
};
if let Some(target) = arr.get_mut(idx).and_then(|v| v.as_object_mut()) {
target.insert(key.to_string(), base_val);
}
segment.insert(key.to_string(), newval);
}
}
}
let mut draw_inner_divider: Option<Value> = None;
let iter: Box<dyn Iterator<Item = Value>> = if side == "right" {
Box::new(arr.into_iter())
} else {
Box::new(arr.into_iter().rev())
};
for subsegment in iter {
let mut segment_copy = segment.clone();
if let Some(sub_obj) = subsegment.as_object() {
for (k, v) in sub_obj {
segment_copy.insert(k.clone(), v.clone());
}
}
if let Some(d) = draw_inner_divider.clone() {
segment_copy.insert("draw_soft_divider".to_string(), d);
}
draw_inner_divider = segment_copy.remove("draw_inner_divider");
if set_segment_highlighting(pl, colorscheme, &mut segment_copy, mode) {
if side == "right" {
parsed_segments.push(Value::Object(segment_copy));
} else {
let _pslen = parsed_segments.len();
parsed_segments.push(Value::Object(segment_copy));
}
}
}
} else {
segment.insert("contents".to_string(), contents);
if set_segment_highlighting(pl, colorscheme, &mut segment, mode) {
parsed_segments.push(Value::Object(segment));
}
}
} else if segment.get("width").and_then(|v| v.as_str()) == Some("auto")
|| (seg_type == "string"
&& segment
.get("contents")
.map(|v| !v.is_null())
.unwrap_or(false))
{
if set_segment_highlighting(pl, colorscheme, &mut segment, mode) {
parsed_segments.push(Value::Object(segment));
}
}
}
pub fn get<A>(
segment: &Map<String, Value>,
side: &str,
default_module: &str,
get_module_attr: A,
) -> Option<Map<String, Value>>
where
A: Fn(&str, &str) -> bool,
{
let segment_type = segment
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("function");
if !matches!(segment_type, "function" | "string" | "segment_list") {
return None;
}
if let Some(false) = segment.get("display").and_then(|v| v.as_bool()) {
return None;
}
let (function_name, module, name) = if segment_type == "string" {
let n = segment
.get("name")
.and_then(|v| v.as_str())
.map(String::from);
(String::new(), String::new(), n)
} else {
let raw = segment.get("function").and_then(|v| v.as_str())?;
let (m, fname) = match raw.rfind('.') {
Some(idx) => (raw[..idx].to_string(), raw[idx + 1..].to_string()),
None => (default_module.to_string(), raw.to_string()),
};
if !get_module_attr(&m, &fname) {
return None;
}
let n = segment
.get("name")
.and_then(|v| v.as_str())
.map(String::from);
(fname, m, n)
};
let highlight_groups: Vec<Value> = if segment_type == "function" {
vec![Value::String(function_name.clone())]
} else {
segment
.get("highlight_groups")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_else(|| {
name.clone()
.map(|n| vec![Value::String(n)])
.unwrap_or_default()
})
};
let mut out: Map<String, Value> = Map::new();
out.insert(
"name".to_string(),
Value::String(name.clone().unwrap_or_else(|| function_name.clone())),
);
out.insert("type".to_string(), Value::String(segment_type.to_string()));
out.insert(
"highlight_groups".to_string(),
Value::Array(highlight_groups),
);
out.insert("side".to_string(), Value::String(side.to_string()));
out.insert("module".to_string(), Value::String(module));
Some(out)
}
pub fn gen_segment_getter<A>(
_pl: &(),
ext: &str,
_common_config: &Map<String, Value>,
theme_configs: Vec<Map<String, Value>>,
default_module: Option<&str>,
get_module_attr: A,
_top_theme: Option<&str>,
) -> Box<dyn Fn(&Map<String, Value>, &str) -> Option<Map<String, Value>>>
where
A: Fn(&str, &str) -> bool + 'static,
{
let default_module: String = default_module
.map(String::from)
.unwrap_or_else(|| format!("powerline.segments.{}", ext));
let get_module_attr = std::sync::Arc::new(get_module_attr);
let theme_configs = std::sync::Arc::new(theme_configs);
Box::new(
move |segment: &Map<String, Value>, side: &str| -> Option<Map<String, Value>> {
let segment_type = segment
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("function")
.to_string();
if !matches!(
segment_type.as_str(),
"function" | "string" | "segment_list"
) {
return None;
}
if let Some(false) = segment.get("display").and_then(|v| v.as_bool()) {
return None;
}
let (function_name, module, name): (String, String, Option<String>) =
if segment_type == "string" {
let n = segment
.get("name")
.and_then(|v| v.as_str())
.map(String::from);
(String::new(), String::new(), n)
} else {
let raw = segment.get("function").and_then(|v| v.as_str())?;
let (m, fname) = match raw.rfind('.') {
Some(idx) => (raw[..idx].to_string(), raw[idx + 1..].to_string()),
None => (default_module.clone(), raw.to_string()),
};
if !get_module_attr(&m, &fname) {
return None;
}
let n = segment
.get("name")
.and_then(|v| v.as_str())
.map(String::from);
(fname, m, n)
};
let highlight_groups: Vec<Value> = if segment_type == "function" {
vec![Value::String(function_name.clone())]
} else {
segment
.get("highlight_groups")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_else(|| {
name.clone()
.map(|n| vec![Value::String(n)])
.unwrap_or_default()
})
};
let contents_func_id = if module.is_empty() {
String::new()
} else {
format!("{}.{}", module, function_name)
};
let mut out: Map<String, Value> = Map::new();
out.insert(
"name".to_string(),
Value::String(name.clone().unwrap_or_else(|| function_name.clone())),
);
out.insert("type".to_string(), Value::String(segment_type.clone()));
out.insert(
"highlight_groups".to_string(),
Value::Array(highlight_groups),
);
out.insert("divider_highlight_group".to_string(), Value::Null);
out.insert(
"before".to_string(),
segment
.get("before")
.cloned()
.unwrap_or_else(|| Value::String(String::new())),
);
out.insert(
"after".to_string(),
segment
.get("after")
.cloned()
.unwrap_or_else(|| Value::String(String::new())),
);
out.insert("contents_func".to_string(), Value::String(contents_func_id));
out.insert(
"contents".to_string(),
if segment_type == "string" {
segment.get("contents").cloned().unwrap_or(Value::Null)
} else {
Value::Null
},
);
out.insert(
"literal_contents".to_string(),
Value::Array(vec![Value::from(0), Value::String(String::new())]),
);
out.insert(
"priority".to_string(),
segment.get("priority").cloned().unwrap_or(Value::Null),
);
out.insert(
"draw_hard_divider".to_string(),
segment
.get("draw_hard_divider")
.cloned()
.unwrap_or(Value::Bool(true)),
);
out.insert(
"draw_soft_divider".to_string(),
segment
.get("draw_soft_divider")
.cloned()
.unwrap_or(Value::Bool(true)),
);
out.insert(
"draw_inner_divider".to_string(),
segment
.get("draw_inner_divider")
.cloned()
.unwrap_or(Value::Bool(false)),
);
out.insert("side".to_string(), Value::String(side.to_string()));
out.insert(
"width".to_string(),
segment.get("width").cloned().unwrap_or(Value::Null),
);
out.insert(
"align".to_string(),
segment
.get("align")
.cloned()
.unwrap_or_else(|| Value::String("l".to_string())),
);
out.insert("expand".to_string(), Value::Null);
out.insert("truncate".to_string(), Value::Null);
out.insert("startup".to_string(), Value::Null);
out.insert("shutdown".to_string(), Value::Null);
if let Some(v) = segment.get("include_modes") {
out.insert("include_modes".to_string(), v.clone());
}
if let Some(v) = segment.get("exclude_modes") {
out.insert("exclude_modes".to_string(), v.clone());
}
if let Some(v) = segment.get("include_function") {
out.insert("include_function".to_string(), v.clone());
}
if let Some(v) = segment.get("exclude_function") {
out.insert("exclude_function".to_string(), v.clone());
}
out.insert("_rendered_raw".to_string(), Value::String(String::new()));
out.insert("_rendered_hl".to_string(), Value::String(String::new()));
out.insert("_len".to_string(), Value::Null);
out.insert("_contents_len".to_string(), Value::Null);
let mut inline_args: Map<String, Value> = Map::new();
if let Some(a) = segment.get("args").and_then(|v| v.as_object()) {
for (k, v) in a {
inline_args.insert(k.clone(), v.clone());
}
} else {
const SPEC_KEYS: &[&str] = &[
"function",
"name",
"type",
"args",
"before",
"after",
"draw_hard_divider",
"draw_soft_divider",
"draw_inner_divider",
"priority",
"width",
"align",
"highlight_groups",
"include_function",
"exclude_function",
"include_modes",
"exclude_modes",
];
for (k, v) in segment {
if !SPEC_KEYS.contains(&k.as_str()) {
inline_args.insert(k.clone(), v.clone());
}
}
}
let mut inline_seg = segment.clone();
inline_seg.insert("args".to_string(), Value::Object(inline_args.clone()));
let theme_refs: Vec<&Map<String, Value>> = theme_configs.iter().collect();
let merged = get_segment_key(
true,
&inline_seg,
&theme_refs,
None,
"args",
Some(&function_name),
name.as_deref(),
Some(&module),
Some(Value::Object(Map::new())),
);
if let Some(Value::Object(merged_map)) = merged {
if !merged_map.is_empty() {
out.insert("args".to_string(), Value::Object(merged_map));
}
} else if !inline_args.is_empty() {
out.insert("args".to_string(), Value::Object(inline_args));
}
Some(out)
},
)
}
pub fn get_fallback_segment() -> Map<String, Value> {
let mut m = Map::new();
m.insert("name".into(), Value::String("fallback".into())); m.insert("type".into(), Value::String("string".into())); m.insert(
"highlight_groups".into(),
Value::Array(vec![Value::String("background".into())]), );
m.insert("divider_highlight_group".into(), Value::Null); m.insert("before".into(), Value::Null); m.insert("after".into(), Value::Null); m.insert("contents".into(), Value::String("".into())); m.insert(
"literal_contents".into(),
Value::Array(vec![Value::from(0), Value::String("".into())]), );
m.insert("priority".into(), Value::Null); m.insert("draw_soft_divider".into(), Value::Bool(true)); m.insert("draw_hard_divider".into(), Value::Bool(true)); m.insert("draw_inner_divider".into(), Value::Bool(true)); m.insert("width".into(), Value::Null); m.insert("align".into(), Value::Null); m.insert("expand".into(), Value::Null); m.insert("truncate".into(), Value::Null); m.insert("startup".into(), Value::Null); m.insert("shutdown".into(), Value::Null); m.insert("_rendered_raw".into(), Value::String("".into())); m.insert("_rendered_hl".into(), Value::String("".into())); m.insert("_len".into(), Value::Null); m.insert("_contents_len".into(), Value::Null); m
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SegmentGetterResult {
pub contents: Option<String>,
pub function_name: Option<String>,
pub module: Option<String>,
pub function_name_dup: Option<String>,
pub name: Option<String>,
}
pub fn get_function(
segment: &Map<String, Value>,
default_module: &str,
import_module_attr: impl FnOnce(&str, &str) -> Option<()>,
) -> Result<SegmentGetterResult, String> {
let raw_name = segment
.get("function")
.and_then(|v| v.as_str())
.ok_or_else(|| "segment has no 'function' key".to_string())?;
let (module, function_name) = match raw_name.rfind('.') {
Some(idx) => (raw_name[..idx].to_string(), raw_name[idx + 1..].to_string()),
None => (default_module.to_string(), raw_name.to_string()),
};
let imported = import_module_attr(&module, &function_name);
if imported.is_none() {
return Err("Failed to obtain segment function".to_string());
}
let name = segment
.get("name")
.and_then(|v| v.as_str())
.map(String::from);
Ok(SegmentGetterResult {
contents: None,
function_name: Some(function_name.clone()),
module: Some(module),
function_name_dup: Some(function_name),
name,
})
}
pub fn segment_getter_name(segment_type: &str) -> Option<&'static str> {
match segment_type {
"function" => Some("get_function"),
"string" => Some("get_string"),
"segment_list" => Some("get_function"),
_ => None,
}
}
pub type SpaceExpandFn = Box<dyn Fn(&(), usize, &Map<String, Value>) -> String>;
pub type StartupFn = Box<dyn Fn(&(), &std::sync::atomic::AtomicBool)>;
pub enum AttrFunc {
Space(SpaceExpandFn),
Plain(StartupFn),
None,
}
impl AttrFunc {
pub fn is_none(&self) -> bool {
matches!(self, AttrFunc::None)
}
}
pub fn expand_func<F>(pl: &(), amount: usize, segment: &Map<String, Value>, call_func: F) -> String
where
F: FnOnce() -> Result<String, String>,
{
match call_func() {
Ok(s) => s,
Err(_e) => {
let _ = pl;
let contents = segment
.get("contents")
.and_then(|v| v.as_str())
.unwrap_or("");
format!("{}{}", contents, " ".repeat(amount))
}
}
}
pub fn get_attr_func<F>(func_lookup: F, is_space_func: bool) -> AttrFunc
where
F: FnOnce() -> Option<()>,
{
if func_lookup().is_none() {
return AttrFunc::None;
}
if is_space_func {
AttrFunc::Space(Box::new(
|_pl: &(), amount: usize, segment: &Map<String, Value>| -> String {
let contents = segment
.get("contents")
.and_then(|v| v.as_str())
.unwrap_or("");
format!("{}{}", contents, " ".repeat(amount))
},
))
} else {
AttrFunc::Plain(Box::new(
|_pl: &(), _shutdown_event: &std::sync::atomic::AtomicBool| {
},
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn always_true_is_always_true() {
assert!(always_true(&(), None, None));
assert!(always_true(&(), None, Some("normal")));
}
#[test]
fn get_fallback_segment_has_expected_shape() {
let s = get_fallback_segment();
assert_eq!(s.get("name").and_then(|v| v.as_str()), Some("fallback"));
assert_eq!(s.get("type").and_then(|v| v.as_str()), Some("string"));
assert_eq!(
s.get("highlight_groups")
.and_then(|v| v.as_array())
.map(|a| a.len()),
Some(1)
);
assert_eq!(s.get("contents").and_then(|v| v.as_str()), Some(""));
let lc = s
.get("literal_contents")
.and_then(|v| v.as_array())
.unwrap();
assert_eq!(lc[0].as_u64(), Some(0));
assert_eq!(lc[1].as_str(), Some(""));
}
#[test]
fn list_segment_key_values_finds_segment_key_first() {
let mut seg = Map::new();
seg.insert("contents".into(), json!("hello"));
let theme_configs: &[&Map<String, Value>] = &[];
let vals = list_segment_key_values(
&seg,
theme_configs,
None,
"contents",
None,
None,
None,
Some(json!("DEFAULT")),
);
assert_eq!(vals[0], json!("hello"));
assert_eq!(vals[vals.len() - 1], json!("DEFAULT"));
}
#[test]
fn get_segment_key_merge_collapses_dicts_old_wins() {
let mut seg = Map::new();
seg.insert("args".into(), json!({"a": 1, "b": 2}));
let theme_config = json!({
"segment_data": {
"func_name": {"args": {"b": 99, "c": 3}}
}
})
.as_object()
.unwrap()
.clone();
let theme_configs: Vec<&Map<String, Value>> = vec![&theme_config];
let merged = get_segment_key(
true,
&seg,
&theme_configs,
None,
"args",
Some("func_name"),
None,
None,
Some(json!({})),
);
let merged = merged.unwrap();
let merged_obj = merged.as_object().unwrap();
assert_eq!(merged_obj.get("a"), Some(&json!(1)));
assert_eq!(merged_obj.get("b"), Some(&json!(2)));
assert_eq!(merged_obj.get("c"), Some(&json!(3)));
}
#[test]
fn get_segment_key_no_merge_returns_first() {
let mut seg = Map::new();
seg.insert("priority".into(), json!(10));
let theme_configs: &[&Map<String, Value>] = &[];
let v = get_segment_key(
false,
&seg,
theme_configs,
None,
"priority",
None,
None,
None,
Some(json!(0)),
);
assert_eq!(v, Some(json!(10)));
}
#[test]
fn set_segment_highlighting_basic() {
use crate::ported::colorscheme::Colorscheme;
let colorscheme_config = json!({
"groups": {"info": {"fg": "white", "bg": "blue", "attrs": []}}
})
.as_object()
.unwrap()
.clone();
let colors_config = json!({
"colors": {"white": [231, "ffffff"], "blue": [21, "0000ff"]},
"gradients": {}
})
.as_object()
.unwrap()
.clone();
let cs = Colorscheme::new(&colorscheme_config, &colors_config);
let mut segment = Map::new();
segment.insert("highlight_groups".into(), json!(["info"]));
segment.insert("literal_contents".into(), json!([0, ""]));
assert!(set_segment_highlighting(&(), &cs, &mut segment, None));
assert!(segment.contains_key("highlight"));
let hl = segment
.get("highlight")
.and_then(|v| v.as_object())
.unwrap();
assert!(hl.contains_key("fg"));
assert!(hl.contains_key("bg"));
assert!(hl.contains_key("attrs"));
}
#[test]
fn get_function_dotted_name_splits_via_rpartition() {
let mut seg = Map::new();
seg.insert(
"function".to_string(),
json!("powerline.segments.shell.uptime"),
);
seg.insert("name".to_string(), json!("custom"));
let r = get_function(&seg, "powerline.segments", |_, _| Some(())).unwrap();
assert_eq!(r.module.as_deref(), Some("powerline.segments.shell"));
assert_eq!(r.function_name.as_deref(), Some("uptime"));
assert_eq!(r.function_name_dup.as_deref(), Some("uptime"));
assert!(r.contents.is_none());
assert_eq!(r.name.as_deref(), Some("custom"));
}
#[test]
fn get_function_undotted_uses_default_module() {
let mut seg = Map::new();
seg.insert("function".to_string(), json!("uptime"));
let r = get_function(&seg, "powerline.segments.shell", |_, _| Some(())).unwrap();
assert_eq!(r.module.as_deref(), Some("powerline.segments.shell"));
assert_eq!(r.function_name.as_deref(), Some("uptime"));
}
#[test]
fn get_function_missing_function_key_returns_err() {
let seg = Map::new();
let r = get_function(&seg, "powerline.segments.shell", |_, _| Some(()));
assert!(r.is_err());
}
#[test]
fn get_function_failed_import_returns_err() {
let mut seg = Map::new();
seg.insert("function".to_string(), json!("missing_fn"));
let r = get_function(&seg, "powerline.segments.shell", |_, _| None);
let err = r.unwrap_err();
assert!(err.contains("Failed to obtain segment function"));
}
#[test]
fn get_function_passes_resolved_args_to_importer() {
let mut seg = Map::new();
seg.insert("function".to_string(), json!("my.mod.fn_name"));
use std::cell::Cell;
let captured_module: Cell<String> = Cell::new(String::new());
let captured_fn: Cell<String> = Cell::new(String::new());
let _ = get_function(&seg, "fallback", |m, n| {
captured_module.set(m.to_string());
captured_fn.set(n.to_string());
Some(())
});
assert_eq!(captured_module.into_inner(), "my.mod");
assert_eq!(captured_fn.into_inner(), "fn_name");
}
#[test]
fn segment_getter_name_dispatches_by_type() {
assert_eq!(segment_getter_name("function"), Some("get_function"));
assert_eq!(segment_getter_name("segment_list"), Some("get_function"));
assert_eq!(segment_getter_name("string"), Some("get_string"));
assert_eq!(segment_getter_name("bogus"), None);
}
#[test]
fn get_attr_func_no_attribute_returns_none() {
let r = get_attr_func(|| None, false);
assert!(r.is_none());
}
#[test]
fn get_attr_func_is_space_func_returns_expand_closure() {
let r = get_attr_func(|| Some(()), true);
match r {
AttrFunc::Space(f) => {
let mut seg = Map::new();
seg.insert("contents".to_string(), json!("hi"));
let out = f(&(), 3, &seg);
assert_eq!(out, "hi ");
}
_ => panic!("expected Space variant"),
}
}
#[test]
fn get_attr_func_not_space_func_returns_plain_closure() {
let r = get_attr_func(|| Some(()), false);
match r {
AttrFunc::Plain(_) => {} _ => panic!("expected Plain variant"),
}
}
#[test]
fn attr_func_is_none_helper() {
assert!(AttrFunc::None.is_none());
assert!(!AttrFunc::Space(Box::new(|_, _, _| String::new())).is_none());
}
#[test]
fn get_key_passes_through_to_get_segment_key() {
let mut seg = Map::new();
seg.insert("display".to_string(), json!(true));
let r = get_key(false, &seg, &[], None, None, None, None, "display", None);
assert_eq!(r, Some(json!(true)));
}
#[test]
fn get_key_falls_back_to_default_when_absent() {
let seg = Map::new();
let r = get_key(
false,
&seg,
&[],
None,
None,
None,
None,
"absent_key",
Some(json!("dflt")),
);
assert_eq!(r, Some(json!("dflt")));
}
#[test]
fn get_selector_resolves_dotted_function_name() {
let r = get_selector("foo.bar.baz", "shell", |m, f| m == "foo.bar" && f == "baz");
assert_eq!(r, Some(("foo.bar".to_string(), "baz".to_string())));
}
#[test]
fn get_selector_uses_default_selectors_module_for_undotted_name() {
let r = get_selector("some_fn", "shell", |m, f| {
m == "powerline.selectors.shell" && f == "some_fn"
});
assert_eq!(
r,
Some((
"powerline.selectors.shell".to_string(),
"some_fn".to_string()
))
);
}
#[test]
fn get_selector_returns_none_when_function_missing() {
let r = get_selector("missing_fn", "shell", |_, _| false);
assert_eq!(r, None);
}
#[test]
fn get_segment_selector_combines_modes_and_function() {
let mut seg = Map::new();
seg.insert("include_modes".to_string(), json!(["normal", "visual"]));
let func: Box<dyn Fn(&str) -> bool> = Box::new(|m| m == "insert");
let pred = get_segment_selector(&seg, "include", Some(func)).unwrap();
assert!(pred("normal"));
assert!(pred("visual"));
assert!(pred("insert"));
assert!(!pred("command"));
}
#[test]
fn get_segment_selector_modes_only_returns_membership_check() {
let mut seg = Map::new();
seg.insert("exclude_modes".to_string(), json!(["v"]));
let pred = get_segment_selector(&seg, "exclude", None).unwrap();
assert!(pred("v"));
assert!(!pred("n"));
}
#[test]
fn get_segment_selector_no_modes_no_function_returns_none() {
let seg = Map::new();
assert!(get_segment_selector(&seg, "include", None).is_none());
}
#[test]
fn gen_display_condition_no_conditions_always_displays() {
let pred = gen_display_condition(None, None);
assert!(pred("any_mode"));
}
#[test]
fn gen_display_condition_include_only_uses_include() {
let inc: Box<dyn Fn(&str) -> bool> = Box::new(|m| m == "n");
let pred = gen_display_condition(Some(inc), None);
assert!(pred("n"));
assert!(!pred("v"));
}
#[test]
fn gen_display_condition_exclude_only_negates() {
let exc: Box<dyn Fn(&str) -> bool> = Box::new(|m| m == "n");
let pred = gen_display_condition(None, Some(exc));
assert!(!pred("n"));
assert!(pred("v"));
}
#[test]
fn gen_display_condition_both_uses_and_not() {
let inc: Box<dyn Fn(&str) -> bool> = Box::new(|m| matches!(m, "n" | "v"));
let exc: Box<dyn Fn(&str) -> bool> = Box::new(|m| m == "v");
let pred = gen_display_condition(Some(inc), Some(exc));
assert!(pred("n"));
assert!(!pred("v"));
assert!(!pred("i"));
}
#[test]
fn get_dispatches_function_type_with_dotted_name() {
let mut seg = Map::new();
seg.insert("function".to_string(), json!("foo.bar.baz"));
let r = get(&seg, "left", "powerline.segments", |m, f| {
m == "foo.bar" && f == "baz"
});
assert!(r.is_some());
let out = r.unwrap();
assert_eq!(out["name"], "baz");
assert_eq!(out["type"], "function");
assert_eq!(out["side"], "left");
}
#[test]
fn get_returns_none_when_display_false() {
let mut seg = Map::new();
seg.insert(
"function".to_string(),
json!("powerline.segments.shell.mode"),
);
seg.insert("display".to_string(), json!(false));
let r = get(&seg, "right", "powerline.segments", |_, _| true);
assert!(r.is_none());
}
#[test]
fn get_returns_none_for_unknown_segment_type() {
let mut seg = Map::new();
seg.insert("type".to_string(), json!("unknown"));
let r = get(&seg, "left", "powerline.segments", |_, _| true);
assert!(r.is_none());
}
}