#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(dead_code)]
use std::io::{self, Write};
use crate::ported::crt::{
ColorElements, ColorScheme, ERR, KEY_DOWN, KEY_ENTER, KEY_F, KEY_MOUSE, KEY_RECLICK,
KEY_RESIZE, KEY_UP,
};
use crate::ported::functionbar::{
FunctionBar, FunctionBar_delete, FunctionBar_draw, FunctionBar_drawExtra, FunctionBar_getWidth,
FunctionBar_new, FunctionBar_synthesizeEvent, Ncurses,
};
use crate::ported::history::{
History, History_add, History_navigate, History_new, History_resetPosition, History_save,
};
use crate::ported::lineeditor::{
LineEditor, LineEditor_click, LineEditor_draw, LineEditor_getText, LineEditor_handleKey,
LineEditor_init, LineEditor_reset, LineEditor_setText, LineEditor_updateScroll,
};
use crate::ported::listitem::ListItem;
use crate::ported::object::Object;
use crate::ported::panel::{
Panel, PanelItem, Panel_get, Panel_getSelected, Panel_getSelectedIndex, Panel_prune,
Panel_setDefaultBar, Panel_setSelected, Panel_size, KEY_MOUSE_BAR_CLICK,
};
use crate::ported::vector::{Vector, Vector_get, Vector_size};
use crate::ported::xutils::String_contains_i;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(usize)]
pub enum IncType {
INC_SEARCH = 0,
INC_FILTER = 1,
}
pub struct IncMode {
pub editor: LineEditor,
pub bar: FunctionBar,
pub isFilter: bool,
}
impl IncMode {
fn empty() -> IncMode {
IncMode {
editor: LineEditor::default(),
bar: FunctionBar {
functions: Vec::new(),
keys: Vec::new(),
events: Vec::new(),
staticData: false,
},
isFilter: false,
}
}
}
pub struct IncSet {
pub modes: [IncMode; 2],
pub active: Option<IncType>,
pub defaultBar: Option<FunctionBar>,
pub filtering: bool,
pub found: bool,
pub history: Option<History>,
}
pub type IncMode_GetPanelValue = for<'a> fn(&'a Panel, i32) -> &'a str;
const searchFunctions: [&str; 4] = ["Next ", "Prev ", "Cancel ", " Search: "];
const searchKeys: [&str; 4] = ["F3", "S-F3", "Esc", " "];
const searchEvents: [i32; 4] = [KEY_F(3), KEY_F(15), 27, ERR];
const filterFunctions: [&str; 3] = ["Done ", "Clear ", " Filter: "];
const filterKeys: [&str; 3] = ["Enter", "Esc", " "];
const filterEvents: [i32; 3] = [13, 27, ERR];
fn IncMode_reset(mode: &mut IncMode) {
LineEditor_reset(&mut mode.editor);
}
pub fn IncSet_reset(this: &mut IncSet, type_: IncType) {
IncMode_reset(&mut this.modes[type_ as usize]);
this.found = false;
}
pub fn IncSet_setFilter(this: &mut IncSet, filter: &str) {
let mode = &mut this.modes[IncType::INC_FILTER as usize];
LineEditor_setText(&mut mode.editor, filter);
this.filtering = !filter.is_empty();
}
fn IncMode_initSearch(search: &mut IncMode) {
*search = IncMode::empty();
search.bar = FunctionBar_new(
Some(&searchFunctions[..]),
Some(&searchKeys[..]),
Some(&searchEvents[..]),
);
search.isFilter = false;
LineEditor_init(&mut search.editor);
}
fn IncMode_initFilter(filter: &mut IncMode) {
*filter = IncMode::empty();
filter.bar = FunctionBar_new(
Some(&filterFunctions[..]),
Some(&filterKeys[..]),
Some(&filterEvents[..]),
);
filter.isFilter = true;
LineEditor_init(&mut filter.editor);
}
fn IncMode_done(mode: IncMode) {
FunctionBar_delete(mode.bar);
}
pub fn IncSet_new(bar: Option<FunctionBar>) -> IncSet {
let mut this = IncSet {
modes: [IncMode::empty(), IncMode::empty()],
active: None,
defaultBar: bar,
filtering: false,
found: false,
history: None,
};
IncMode_initSearch(&mut this.modes[IncType::INC_SEARCH as usize]);
IncMode_initFilter(&mut this.modes[IncType::INC_FILTER as usize]);
this
}
pub fn IncSet_delete(this: IncSet) {
let IncSet { modes, .. } = this;
let [search, filter] = modes;
IncMode_done(search);
IncMode_done(filter);
}
pub fn IncSet_setHistoryFile(this: &mut IncSet, filename: &str) {
this.history = Some(History_new(Some(filename)));
}
pub fn IncSet_saveHistory(this: &IncSet) {
if let Some(history) = &this.history {
History_save(history);
}
}
fn updateWeakPanel(this: &IncSet, panel: &mut Panel, lines: &mut Vector) {
let selected: *const () = match Panel_getSelected(panel) {
Some(o) => o as *const dyn Object as *const (),
None => core::ptr::null(),
};
Panel_prune(panel);
if this.filtering {
let mut n: i32 = 0;
let incFilter =
LineEditor_getText(&this.modes[IncType::INC_FILTER as usize].editor).to_string();
for i in 0..Vector_size(lines) as usize {
let matches = {
let obj: &dyn core::any::Any = Vector_get(lines, i);
let value = &obj
.downcast_ref::<ListItem>()
.expect("weak panel lines are ListItem")
.value;
String_contains_i(value, &incFilter, true)
};
if matches {
let ptr: *mut dyn Object = &mut **lines.array[i].as_mut().unwrap();
let line_id = ptr as *const dyn Object as *const ();
panel.items.push(PanelItem::Borrowed(ptr));
panel.prevSelected = -1;
panel.needsRedraw = true;
if selected == line_id {
Panel_setSelected(panel, n);
}
n += 1;
}
}
} else {
for i in 0..Vector_size(lines) as usize {
let ptr: *mut dyn Object = &mut **lines.array[i].as_mut().unwrap();
let line_id = ptr as *const dyn Object as *const ();
panel.items.push(PanelItem::Borrowed(ptr));
panel.prevSelected = -1;
panel.needsRedraw = true;
if selected == line_id {
Panel_setSelected(panel, i as i32);
}
}
}
}
fn search(this: &mut IncSet, panel: &mut Panel, getPanelValue: IncMode_GetPanelValue) -> bool {
let active = this.active.unwrap();
let size = Panel_size(panel);
for i in 0..size {
if String_contains_i(
getPanelValue(&*panel, i),
LineEditor_getText(&this.modes[active as usize].editor),
true,
) {
Panel_setSelected(panel, i);
return true;
}
}
false
}
pub fn IncSet_activate(this: &mut IncSet, type_: IncType, panel: &mut Panel) {
this.active = Some(type_);
panel.currentBar = Some(this.modes[type_ as usize].bar.clone());
panel.cursorOn = true;
if let Some(history) = &mut this.history {
History_resetPosition(history);
}
IncSet_drawBar(
this,
panel,
ColorElements::FUNCTION_BAR.packed(ColorScheme::active()),
);
}
fn IncSet_deactivate(this: &mut IncSet, panel: &mut Panel) {
this.active = None;
Panel_setDefaultBar(panel);
panel.cursorOn = false;
if let Some(bar) = &this.defaultBar {
FunctionBar_draw(bar);
}
}
fn IncMode_find(
mode: &mut IncMode,
panel: &mut Panel,
getPanelValue: IncMode_GetPanelValue,
step: i32,
) -> bool {
let size = Panel_size(panel);
let here = Panel_getSelectedIndex(panel);
let mut i = here;
loop {
i += step;
if i == size {
i = 0;
}
if i == -1 {
i = size - 1;
}
if i == here {
return false;
}
if String_contains_i(
getPanelValue(&*panel, i),
LineEditor_getText(&mode.editor),
true,
) {
Panel_setSelected(panel, i);
return true;
}
}
}
pub fn IncSet_handleKey(
this: &mut IncSet,
ch: i32,
panel: &mut Panel,
getPanelValue: IncMode_GetPanelValue,
lines: &mut Vector,
) -> bool {
if ch == ERR {
return true;
}
let mode = this
.active
.expect("IncSet_handleKey called with no active mode");
let midx = mode as usize;
let size = Panel_size(panel);
let mut filterChanged = false;
let mut doSearch = true;
let functionBar = ColorElements::FUNCTION_BAR.packed(ColorScheme::active());
if ch == KEY_MOUSE_BAR_CLICK {
let fieldStartX = FunctionBar_getWidth(&this.modes[midx].bar);
LineEditor_click(
&mut this.modes[midx].editor,
panel.lastMouseBarClickX,
fieldStartX,
);
IncSet_drawBar(this, panel, functionBar);
return false;
}
if ch == KEY_F(3) || ch == KEY_F(15) {
if size == 0 {
return true;
}
IncMode_find(
&mut this.modes[midx],
panel,
getPanelValue,
if ch == KEY_F(3) { 1 } else { -1 },
);
doSearch = false;
} else if ch == KEY_UP {
if this.history.is_some() {
let entry = History_navigate(
this.history.as_mut().unwrap(),
&this.modes[midx].editor,
true,
)
.map(str::to_string);
if let Some(entry) = entry {
LineEditor_setText(&mut this.modes[midx].editor, &entry);
if this.modes[midx].isFilter {
filterChanged = true;
this.filtering = !LineEditor_getText(&this.modes[midx].editor).is_empty();
}
}
doSearch = !this.modes[midx].isFilter;
} else {
doSearch = false;
}
} else if ch == KEY_DOWN {
if this.history.is_some() {
let entry = History_navigate(
this.history.as_mut().unwrap(),
&this.modes[midx].editor,
false,
)
.map(str::to_string);
if let Some(entry) = entry {
LineEditor_setText(&mut this.modes[midx].editor, &entry);
if this.modes[midx].isFilter {
filterChanged = true;
this.filtering = !LineEditor_getText(&this.modes[midx].editor).is_empty();
}
}
doSearch = !this.modes[midx].isFilter;
} else {
doSearch = false;
}
} else if ch == KEY_RESIZE {
doSearch = !LineEditor_getText(&this.modes[midx].editor).is_empty();
} else if ch == 13 || ch == b'\r' as i32 || ch == KEY_ENTER {
if this.history.is_some() {
let text = LineEditor_getText(&this.modes[midx].editor).to_string();
if !text.is_empty() {
History_add(this.history.as_mut().unwrap(), &text);
History_save(this.history.as_ref().unwrap());
}
History_resetPosition(this.history.as_mut().unwrap());
}
if !this.modes[midx].isFilter {
IncMode_reset(&mut this.modes[midx]);
}
IncSet_deactivate(this, panel);
doSearch = false;
filterChanged = this.modes[midx].isFilter;
} else if ch == 27 || ch == KEY_MOUSE || ch == KEY_RECLICK {
if this.history.is_some() {
History_resetPosition(this.history.as_mut().unwrap());
}
if this.modes[midx].isFilter {
filterChanged = true;
this.filtering = false;
IncMode_reset(&mut this.modes[midx]);
} else {
this.found = false;
IncMode_reset(&mut this.modes[midx]);
}
IncSet_deactivate(this, panel);
doSearch = false;
} else {
let textChanged = LineEditor_handleKey(&mut this.modes[midx].editor, ch);
if textChanged {
let empty = LineEditor_getText(&this.modes[midx].editor).is_empty();
if this.modes[midx].isFilter {
filterChanged = true;
this.filtering = !empty;
} else if empty {
this.found = false;
doSearch = false;
}
} else {
doSearch = false;
}
}
if doSearch && !LineEditor_getText(&this.modes[midx].editor).is_empty() {
this.found = search(this, panel, getPanelValue);
}
if filterChanged {
updateWeakPanel(this, panel, lines);
}
if this.active.is_some() {
IncSet_drawBar(this, panel, functionBar);
}
filterChanged
}
pub fn IncSet_getListItemValue(panel: &Panel, i: i32) -> &str {
let obj: &dyn core::any::Any = Panel_get(panel, i);
match obj.downcast_ref::<ListItem>() {
Some(l) => &l.value,
None => "",
}
}
pub fn IncSet_drawBar(this: &mut IncSet, panel: &mut Panel, attr: i32) {
if let Some(active) = this.active {
let idx = active as usize;
let mut attr = attr;
if !this.modes[idx].isFilter
&& !this.found
&& !LineEditor_getText(&this.modes[idx].editor).is_empty()
{
attr = ColorElements::FAILED_SEARCH.packed(ColorScheme::active());
}
let fieldStartX = FunctionBar_drawExtra(&this.modes[idx].bar, None, -1, false);
let mut fieldWidth = Ncurses::cols() - fieldStartX;
if fieldWidth < 1 {
fieldWidth = 1;
}
LineEditor_updateScroll(&mut this.modes[idx].editor, fieldWidth);
let cursorX = LineEditor_draw(&this.modes[idx].editor, fieldStartX, fieldWidth, attr);
{
let mut out = io::stdout().lock();
Ncurses::curs_set(&mut out, true);
let _ = out.flush();
}
panel.cursorY = Ncurses::lines() - 1;
panel.cursorX = cursorX;
} else if let Some(bar) = &this.defaultBar {
FunctionBar_draw(bar);
}
}
pub fn IncSet_synthesizeEvent(this: &IncSet, panel: &mut Panel, x: i32) -> i32 {
if let Some(active) = this.active {
let bar = &this.modes[active as usize].bar;
let ev = FunctionBar_synthesizeEvent(bar, x);
if ev == ERR && x >= FunctionBar_getWidth(bar) {
panel.lastMouseBarClickX = x;
return KEY_MOUSE_BAR_CLICK;
}
ev
} else {
FunctionBar_synthesizeEvent(this.defaultBar.as_ref().unwrap(), x)
}
}
pub fn IncSet_filter(this: &IncSet) -> Option<&str> {
if this.filtering {
Some(LineEditor_getText(
&this.modes[IncType::INC_FILTER as usize].editor,
))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::panel::Panel_new;
fn li(value: &str) -> Box<dyn Object> {
Box::new(ListItem {
value: value.to_string(),
key: 0,
moving: false,
})
}
#[test]
fn new_builds_two_modes_with_flags_and_defaults() {
let set = IncSet_new(None);
assert!(set.active.is_none());
assert!(!set.filtering);
assert!(!set.found);
assert!(set.defaultBar.is_none());
assert!(!set.modes[IncType::INC_SEARCH as usize].isFilter);
assert!(set.modes[IncType::INC_FILTER as usize].isFilter);
}
#[test]
fn new_stores_default_bar() {
let bar = FunctionBar {
functions: vec!["Help".into()],
keys: vec!["F1".into()],
events: vec![1],
staticData: false,
};
let set = IncSet_new(Some(bar));
assert!(set.defaultBar.is_some());
assert_eq!(set.defaultBar.unwrap().functions, vec!["Help".to_string()]);
}
#[test]
fn init_search_bar_matches_c_tables() {
let set = IncSet_new(None);
let bar = &set.modes[IncType::INC_SEARCH as usize].bar;
assert_eq!(
bar.functions,
vec!["Next ", "Prev ", "Cancel ", " Search: "]
);
assert_eq!(bar.keys, vec!["F3", "S-F3", "Esc", " "]);
assert_eq!(bar.events, vec![KEY_F(3), KEY_F(15), 27, ERR]);
assert!(!bar.staticData);
}
#[test]
fn init_filter_bar_matches_c_tables() {
let set = IncSet_new(None);
let bar = &set.modes[IncType::INC_FILTER as usize].bar;
assert_eq!(bar.functions, vec!["Done ", "Clear ", " Filter: "]);
assert_eq!(bar.keys, vec!["Enter", "Esc", " "]);
assert_eq!(bar.events, vec![13, 27, ERR]);
}
#[test]
fn set_filter_nonempty_turns_filtering_on() {
let mut set = IncSet_new(None);
IncSet_setFilter(&mut set, "bash");
assert!(set.filtering);
}
#[test]
fn set_filter_empty_turns_filtering_off() {
let mut set = IncSet_new(None);
IncSet_setFilter(&mut set, "bash");
assert!(set.filtering);
IncSet_setFilter(&mut set, "");
assert!(!set.filtering);
}
#[test]
fn reset_clears_found() {
let mut set = IncSet_new(None);
set.found = true;
IncSet_reset(&mut set, IncType::INC_SEARCH);
assert!(!set.found);
}
#[test]
fn reset_does_not_touch_filtering_flag() {
let mut set = IncSet_new(None);
IncSet_setFilter(&mut set, "x");
assert!(set.filtering);
IncSet_reset(&mut set, IncType::INC_FILTER);
assert!(set.filtering);
assert!(!set.found);
}
#[test]
fn get_list_item_value_returns_item_strings() {
let mut p = Panel_new(0, 0, 10, 10, None);
p.items.push(PanelItem::Owned(li("systemd")));
p.items.push(PanelItem::Owned(li("bash")));
p.items.push(PanelItem::Owned(li("htop")));
assert_eq!(IncSet_getListItemValue(&p, 0), "systemd");
assert_eq!(IncSet_getListItemValue(&p, 1), "bash");
assert_eq!(IncSet_getListItemValue(&p, 2), "htop");
}
#[test]
fn get_list_item_value_usable_as_fn_pointer() {
let f: IncMode_GetPanelValue = IncSet_getListItemValue;
let mut p = Panel_new(0, 0, 10, 10, None);
p.items.push(PanelItem::Owned(li("firefox")));
assert_eq!(f(&p, 0), "firefox");
}
#[test]
fn get_list_item_value_composes_with_string_contains_i() {
use crate::ported::xutils::String_contains_i;
let mut p = Panel_new(0, 0, 10, 10, None);
for v in ["systemd", "bash", "htop", "sshd"] {
p.items.push(PanelItem::Owned(li(v)));
}
let needle = "SH"; let hits: Vec<i32> = (0..p.items.len() as i32)
.filter(|&i| String_contains_i(IncSet_getListItemValue(&p, i), needle, true))
.collect();
assert_eq!(hits, vec![1, 3]);
}
fn panel_of(values: &[&str]) -> Panel {
let mut p = Panel_new(0, 0, 10, 10, None);
for v in values {
p.items.push(PanelItem::Owned(li(v)));
}
p
}
#[test]
fn search_selects_first_forward_match() {
let mut set = IncSet_new(None);
set.active = Some(IncType::INC_SEARCH);
LineEditor_setText(&mut set.modes[IncType::INC_SEARCH as usize].editor, "sh");
let mut p = panel_of(&["systemd", "bash", "sshd"]);
assert!(search(&mut set, &mut p, IncSet_getListItemValue));
assert_eq!(p.selected, 1);
}
#[test]
fn search_no_match_returns_false_and_keeps_selection() {
let mut set = IncSet_new(None);
set.active = Some(IncType::INC_SEARCH);
LineEditor_setText(&mut set.modes[IncType::INC_SEARCH as usize].editor, "zzz");
let mut p = panel_of(&["systemd", "bash", "sshd"]);
p.selected = 2;
assert!(!search(&mut set, &mut p, IncSet_getListItemValue));
assert_eq!(p.selected, 2); }
#[test]
fn find_forward_advances_to_next_match() {
let mut set = IncSet_new(None);
let mut p = panel_of(&["bash", "zsh", "fish", "dash"]);
LineEditor_setText(&mut set.modes[IncType::INC_SEARCH as usize].editor, "sh");
p.selected = 0; assert!(IncMode_find(
&mut set.modes[IncType::INC_SEARCH as usize],
&mut p,
IncSet_getListItemValue,
1,
));
assert_eq!(p.selected, 1); }
#[test]
fn find_forward_wraps_past_end() {
let mut set = IncSet_new(None);
let mut p = panel_of(&["bash", "zsh", "fish", "dash"]);
LineEditor_setText(&mut set.modes[IncType::INC_SEARCH as usize].editor, "fish");
p.selected = 3; assert!(IncMode_find(
&mut set.modes[IncType::INC_SEARCH as usize],
&mut p,
IncSet_getListItemValue,
1,
));
assert_eq!(p.selected, 2);
}
#[test]
fn find_backward_steps_to_prev_match() {
let mut set = IncSet_new(None);
let mut p = panel_of(&["bash", "zsh", "fish", "dash"]);
LineEditor_setText(&mut set.modes[IncType::INC_SEARCH as usize].editor, "sh");
p.selected = 2; assert!(IncMode_find(
&mut set.modes[IncType::INC_SEARCH as usize],
&mut p,
IncSet_getListItemValue,
-1,
));
assert_eq!(p.selected, 1);
}
#[test]
fn find_no_match_full_loop_returns_false() {
let mut set = IncSet_new(None);
let mut p = panel_of(&["bash", "zsh", "fish", "dash"]);
LineEditor_setText(
&mut set.modes[IncType::INC_SEARCH as usize].editor,
"nomatch",
);
p.selected = 1;
assert!(!IncMode_find(
&mut set.modes[IncType::INC_SEARCH as usize],
&mut p,
IncSet_getListItemValue,
1,
));
assert_eq!(p.selected, 1); }
#[test]
fn deactivate_clears_active_restores_bar_and_hides_cursor() {
let default_bar = FunctionBar {
functions: vec!["DEFAULT".into()],
keys: vec!["F1".into()],
events: vec![1],
staticData: false,
};
let mut set = IncSet_new(Some(default_bar));
set.active = Some(IncType::INC_SEARCH);
let panel_bar = FunctionBar {
functions: vec!["DEF".into()],
keys: vec!["F1".into()],
events: vec![1],
staticData: false,
};
let mut p = Panel_new(0, 0, 10, 10, Some(panel_bar));
p.cursorOn = true;
p.currentBar = Some(set.modes[IncType::INC_SEARCH as usize].bar.clone());
IncSet_deactivate(&mut set, &mut p);
assert!(set.active.is_none());
assert!(!p.cursorOn);
assert_eq!(
p.currentBar.as_ref().unwrap().functions,
vec!["DEF".to_string()]
);
}
#[test]
fn filter_returns_text_only_when_filtering() {
let mut set = IncSet_new(None);
assert!(IncSet_filter(&set).is_none());
IncSet_setFilter(&mut set, "bash");
assert_eq!(IncSet_filter(&set), Some("bash"));
IncSet_setFilter(&mut set, "");
assert!(IncSet_filter(&set).is_none());
}
use crate::ported::listitem::ListItem_new;
use crate::ported::vector::{Vector_add, Vector_new};
fn lines_of(values: &[&str]) -> Vector {
let klass = ListItem_new("", 0).klass();
let mut lines = Vector_new(klass, true, 10);
for v in values {
Vector_add(&mut lines, Box::new(ListItem_new(v, 0)));
}
lines
}
#[test]
fn update_weak_panel_non_filtering_re_adds_all() {
let set = IncSet_new(None); let mut lines = lines_of(&["systemd", "bash", "sshd", "htop"]);
let mut panel = Panel_new(0, 0, 10, 10, None);
updateWeakPanel(&set, &mut panel, &mut lines);
assert_eq!(Panel_size(&panel), 4);
assert_eq!(IncSet_getListItemValue(&panel, 0), "systemd");
assert_eq!(IncSet_getListItemValue(&panel, 1), "bash");
assert_eq!(IncSet_getListItemValue(&panel, 3), "htop");
}
#[test]
fn update_weak_panel_filtering_keeps_only_matches() {
let mut set = IncSet_new(None);
IncSet_setFilter(&mut set, "sh"); let mut lines = lines_of(&["systemd", "bash", "sshd", "htop"]);
let mut panel = Panel_new(0, 0, 10, 10, None);
updateWeakPanel(&set, &mut panel, &mut lines);
assert_eq!(Panel_size(&panel), 2);
assert_eq!(IncSet_getListItemValue(&panel, 0), "bash");
assert_eq!(IncSet_getListItemValue(&panel, 1), "sshd");
}
#[test]
fn update_weak_panel_preserves_selection_by_pointer_identity() {
let set = IncSet_new(None);
let mut lines = lines_of(&["systemd", "bash", "sshd", "htop"]);
let mut panel = Panel_new(0, 0, 10, 10, None);
updateWeakPanel(&set, &mut panel, &mut lines);
panel.selected = 2;
updateWeakPanel(&set, &mut panel, &mut lines);
assert_eq!(panel.selected, 2);
assert_eq!(IncSet_getListItemValue(&panel, panel.selected), "sshd");
}
#[test]
fn update_weak_panel_selection_follows_object_across_filter() {
let mut set = IncSet_new(None);
let mut lines = lines_of(&["systemd", "bash", "sshd", "htop"]);
let mut panel = Panel_new(0, 0, 10, 10, None);
updateWeakPanel(&set, &mut panel, &mut lines);
panel.selected = 1;
IncSet_setFilter(&mut set, "sh");
updateWeakPanel(&set, &mut panel, &mut lines);
assert_eq!(Panel_size(&panel), 2);
assert_eq!(panel.selected, 0); assert_eq!(IncSet_getListItemValue(&panel, panel.selected), "bash");
}
}