#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
use std::cmp::Ordering;
use std::sync::atomic::Ordering as AtomicOrdering;
use crate::ported::crt::{CRT_utf8, ColorElements, ColorScheme};
use crate::ported::object::{Object, ObjectClass};
use crate::ported::richstring::{RichString, RichString_appendWide, RichString_writeWide};
pub struct ListItem {
pub value: String,
pub key: i32,
pub moving: bool,
}
pub fn ListItem_delete(this: ListItem) {
let _ = this;
}
pub fn ListItem_display(this: &ListItem, out: &mut RichString) {
let default_color = ColorElements::DEFAULT_COLOR.packed(ColorScheme::active());
if this.moving {
let glyph: &[u8] = if CRT_utf8.load(AtomicOrdering::Relaxed) {
"↕ ".as_bytes()
} else {
"+ ".as_bytes()
};
RichString_writeWide(out, default_color, glyph);
}
RichString_appendWide(out, default_color, this.value.as_bytes());
}
static ListItem_class: ObjectClass = ObjectClass { extends: None };
impl Object for ListItem {
fn klass(&self) -> &'static ObjectClass {
&ListItem_class
}
fn display(&self, out: &mut RichString) {
ListItem_display(self, out);
}
fn compare(&self, other: &dyn Object) -> i32 {
let any: &dyn core::any::Any = other;
let o = any
.downcast_ref::<ListItem>()
.expect("ListItem_compare called across incompatible classes");
ListItem_compare(self, o)
}
}
pub fn ListItem_init(this: &mut ListItem, value: &str, key: i32) {
this.value = value.to_string();
this.key = key;
this.moving = false;
}
pub fn ListItem_new(value: &str, key: i32) -> ListItem {
let mut this = ListItem {
value: String::new(),
key: 0,
moving: false,
};
ListItem_init(&mut this, value, key);
this
}
pub fn ListItem_append(this: &mut ListItem, text: &str) {
this.value.push_str(text);
}
pub fn ListItem_compare(cast1: &ListItem, cast2: &ListItem) -> i32 {
match cast1.value.as_bytes().cmp(cast2.value.as_bytes()) {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
pub fn ListItem_getRef(this: &ListItem) -> &str {
&this.value
}
#[cfg(test)]
mod tests {
use super::*;
fn item(value: &str) -> ListItem {
ListItem {
value: value.to_string(),
key: 0,
moving: false,
}
}
#[test]
fn compare_equal_returns_zero() {
assert_eq!(ListItem_compare(&item("alpha"), &item("alpha")), 0);
assert_eq!(ListItem_compare(&item(""), &item("")), 0);
}
#[test]
fn compare_orders_lexicographically() {
assert_eq!(ListItem_compare(&item("apple"), &item("banana")), -1);
assert_eq!(ListItem_compare(&item("banana"), &item("apple")), 1);
assert_eq!(ListItem_compare(&item("app"), &item("apple")), -1);
assert_eq!(ListItem_compare(&item("apple"), &item("app")), 1);
}
#[test]
fn compare_is_case_sensitive() {
assert_eq!(ListItem_compare(&item("Apple"), &item("apple")), -1);
assert_eq!(ListItem_compare(&item("apple"), &item("Apple")), 1);
assert_ne!(ListItem_compare(&item("ABC"), &item("abc")), 0);
}
#[test]
fn init_sets_value_key_and_clears_moving() {
let mut it = ListItem {
value: "old".to_string(),
key: -1,
moving: true,
};
ListItem_init(&mut it, "new", 42);
assert_eq!(it.value, "new");
assert_eq!(it.key, 42);
assert!(!it.moving);
}
fn rendered(rs: &RichString) -> String {
rs.chptr
.iter()
.take(rs.chlen as usize)
.map(|c| c.chars)
.collect()
}
#[test]
fn display_appends_value_in_default_color() {
let it = ListItem {
value: "hello".to_string(),
key: 0,
moving: false,
};
let mut rs = RichString::new();
ListItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "hello");
assert_eq!(rs.chlen, 5);
let expect = ColorElements::DEFAULT_COLOR.packed(ColorScheme::active()) & 0xffffff;
for i in 0..5 {
assert_eq!(rs.chptr[i].attr, expect, "attr at {i}");
}
}
#[test]
fn object_display_dispatches_to_listitem_display() {
let it = ListItem {
value: "abc".to_string(),
key: 0,
moving: false,
};
let mut rs = RichString::new();
Object::display(&it, &mut rs);
assert_eq!(rendered(&rs), "abc");
}
#[test]
fn object_compare_dispatches_to_listitem_compare() {
let a = ListItem {
value: "apple".to_string(),
key: 0,
moving: false,
};
let b = ListItem {
value: "banana".to_string(),
key: 0,
moving: false,
};
assert_eq!(a.compare(&b), -1);
assert_eq!(b.compare(&a), 1);
assert_eq!(
a.compare(&ListItem {
value: "apple".to_string(),
key: 9,
moving: true
}),
0
);
}
#[test]
fn new_initializes_value_key_and_clears_moving() {
let it = ListItem_new("entry", 7);
assert_eq!(it.value, "entry");
assert_eq!(it.key, 7);
assert!(!it.moving);
let other = ListItem_new("", -3);
assert_eq!(other.value, "");
assert_eq!(other.key, -3);
assert!(!other.moving);
}
#[test]
fn display_moving_branch_selects_glyph_by_crt_utf8() {
let it = ListItem {
value: "x".to_string(),
key: 0,
moving: true,
};
CRT_utf8.store(false, AtomicOrdering::Relaxed);
let mut rs = RichString::new();
ListItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "+ x");
CRT_utf8.store(true, AtomicOrdering::Relaxed);
let mut rs = RichString::new();
ListItem_display(&it, &mut rs);
assert_eq!(rendered(&rs), "↕ x");
CRT_utf8.store(false, AtomicOrdering::Relaxed);
}
#[test]
fn get_ref_borrows_value() {
let it = item("payload");
assert_eq!(ListItem_getRef(&it), "payload");
assert_eq!(ListItem_getRef(&item("")), "");
}
#[test]
fn append_concatenates_onto_value() {
let mut it = item("foo");
ListItem_append(&mut it, "bar");
assert_eq!(it.value, "foobar");
ListItem_append(&mut it, "");
assert_eq!(it.value, "foobar");
let mut empty = item("");
ListItem_append(&mut empty, "x");
assert_eq!(empty.value, "x");
}
}