-
Notifications
You must be signed in to change notification settings - Fork 24
Open
Labels
Description
Proxy tree furthur functions
-
Esc for exist expand mode
-
T for test latency of current group
-
S for switch between sorting strategies
-
/ for searching
-
Remove Enter from InterfaceEvent::ToggleHold
-
Maybe a new InterfaceEvent::Confirm correstponds to Enter
-
T,S,/in proxy event handling
So use map & expect instead of Option#and_then
| // TODO Proxy tree furthur functions |
use std::{cmp::Ordering, collections::HashMap};
use std::{fmt::Debug, marker::PhantomData};
use clashctl_interactive::{EndlessSelf, ProxySort, Sortable};
use crossterm::event::KeyCode;
use tui::{
style::{Color, Modifier, Style},
text::Span,
};
use crate::{
components::{Footer, FooterItem, MovableListManage, ProxyGroup, ProxyItem},
help_footer,
model::Proxies,
tagged_footer, Action, Coord, ListEvent, Wrap,
};
// TODO Proxy tree furthur functions
//
// - [X] Right & Enter can be used to apply selection
// - [X] Esc for exist expand mode
// - [X] T for test latency of current group
// - [X] S for switch between sorting strategies
// - [ ] / for searching
//
// In order for functions to be implemented, these are required:
// - Remove Enter from InterfaceEvent::ToggleHold
// - Maybe a new InterfaceEvent::Confirm correstponds to Enter
// - `T`, `S`, `/` in proxy event handling
#[derive(Clone, Debug, PartialEq)]
pub struct ProxyTree<'a> {
pub(super) groups: Vec<ProxyGroup<'a>>,
pub(super) expanded: bool,
pub(super) cursor: usize,
pub(super) testing: bool,
pub(super) footer: Footer<'a>,
sort_method: ProxySort,
}
impl<'a> Default for ProxyTree<'a> {
fn default() -> Self {
let mut ret = Self {
groups: Default::default(),
expanded: Default::default(),
cursor: Default::default(),
footer: Default::default(),
testing: Default::default(),
sort_method: Default::default(),
};
ret.update_footer();
ret
}
}
impl<'a> ProxyTree<'a> {
#[inline]
pub fn cursor(&self) -> usize {
self.cursor
}
#[inline]
pub fn current_group(&self) -> &ProxyGroup {
&self.groups[self.cursor]
}
#[inline]
pub fn is_testing(&self) -> bool {
self.testing
}
#[inline]
pub fn start_testing(&mut self) -> &mut Self {
self.testing = true;
self.update_footer()
}
#[inline]
pub fn end_testing(&mut self) -> &mut Self {
self.testing = false;
self.update_footer()
}
pub fn sort_groups_with_frequency(&mut self, freq: &HashMap<String, usize>) -> &mut Self {
self.groups
.sort_by(|a, b| match (freq.get(&a.name), freq.get(&b.name)) {
(Some(a_freq), Some(b_freq)) => b_freq.cmp(a_freq),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => a.name.cmp(&b.name),
});
self
}
pub fn update_footer(&mut self) -> &mut Self {
let mut footer = Footer::default();
let current_group = match self.groups.get(self.cursor) {
Some(grp) => grp,
_ => return self,
};
if !self.expanded {
let group_name = current_group.name.clone();
let style = Style::default()
.fg(Color::Blue)
.add_modifier(Modifier::REVERSED);
let highlight = style.add_modifier(Modifier::BOLD);
let sort = tagged_footer("Sort", style, self.sort_method);
let mut left = vec![
FooterItem::span(Span::styled(" FREE ", style)),
FooterItem::span(Span::styled(" SPACE to expand ", style)),
if self.testing {
FooterItem::span(Span::styled(" Testing ", highlight.fg(Color::Green)))
} else {
FooterItem::spans(help_footer("Test", style, highlight)).wrapped()
},
FooterItem::spans(sort),
];
footer.append_left(&mut left);
let name = FooterItem::span(Span::styled(group_name, style)).wrapped();
footer.push_right(name);
if let Some(now) = current_group.current {
footer.push_right(
FooterItem::span(Span::raw(current_group.members[now].name.to_owned()))
.wrapped(),
);
}
} else {
let style = Style::default()
.fg(Color::Green)
.add_modifier(Modifier::REVERSED);
let highlight = style.add_modifier(Modifier::BOLD);
footer.push_left(FooterItem::span(Span::styled(" [^] ▲ ▼ Move ", style)));
if current_group.proxy_type.is_selector() {
footer.push_left(FooterItem::span(Span::styled(" ▶ Select ", style)));
}
footer.push_left(if self.testing {
FooterItem::span(Span::styled(" Testing ", highlight.fg(Color::Blue)))
} else {
FooterItem::spans(help_footer("Test", style, highlight)).wrapped()
});
footer.push_left(tagged_footer("Sort", style, self.sort_method).into());
if let Some(ref now) = current_group.members[current_group.cursor].now {
footer.push_right(FooterItem::span(Span::raw(now.to_owned())).wrapped());
}
}
self.footer = footer;
self
}
pub fn replace_with(&mut self, mut new_tree: ProxyTree<'a>) -> &mut Self {
// let map = HashMap::<_, _, RandomState>::from_iter(self.groups.iter().map(|x| (&x.name, x)));
let old_groups = &self.groups;
let current_group = self.groups.get(self.cursor);
for (index, new_group) in new_tree.groups.iter_mut().enumerate() {
if let Some(true) = current_group.map(|x| x.name == new_group.name) {
new_tree.cursor = index;
}
if let Some(old_group) = old_groups.iter().find(|group| group.name == new_group.name) {
new_group.cursor = old_group
.members
.get(old_group.cursor)
.and_then(|old_member| {
new_group
.members
.iter()
.position(|new_member| new_member.name == old_member.name)
})
.or(new_group.current)
.unwrap_or_default()
}
}
self.groups = new_tree.groups;
let method = self.sort_method;
self.sort_with(&method);
self.update_footer()
}
}
impl<'a> From<Proxies> for ProxyTree<'a> {
fn from(val: Proxies) -> Self {
let mut ret = Self {
groups: Vec::with_capacity(val.len()),
..Default::default()
};
for (name, group) in val.groups() {
let all = group
.all
.as_ref()
.expect("ProxyGroup should have member vec");
let mut members = Vec::with_capacity(all.len());
for x in all.iter() {
let member = (
x.as_str(),
val.get(x)
.to_owned()
.expect("Group member should be in all proxies"),
)
.into();
members.push(member);
}
// if group.now.is_some then it must be in all proxies
// So use map & expect instead of Option#and_then
let current = group.now.as_ref().map(|name| {
members
.iter()
.position(|item: &ProxyItem| &item.name == name)
.expect("Group member should be in all proxies")
});
ret.groups.push(ProxyGroup {
_life: PhantomData,
name: name.to_owned(),
proxy_type: group.proxy_type,
cursor: current.unwrap_or_default(),
current,
members,
})
}
ret
}
}
impl<'a> MovableListManage for ProxyTree<'a> {
fn sort(&mut self) -> &mut Self {
let method = self.sort_method;
self.sort_with(&method);
self
}
fn next_sort(&mut self) -> &mut Self {
self.sort_method.next_self();
let method = self.sort_method;
self.sort_with(&method);
self.update_footer()
}
fn prev_sort(&mut self) -> &mut Self {
self.sort_method.prev_self();
let method = self.sort_method;
self.sort_with(&method);
self.update_footer()
}
fn current_pos(&self) -> Coord {
Default::default()
}
#[inline]
fn toggle(&mut self) -> &mut Self {
self.expanded = !self.expanded;
self.update_footer()
}
#[inline]
fn end(&mut self) -> &mut Self {
self.expanded = false;
self.update_footer()
}
#[inline]
fn len(&self) -> usize {
self.groups.len()
}
#[inline]
fn is_empty(&self) -> bool {
self.groups.is_empty()
}
fn hold(&mut self) -> &mut Self {
self.expanded = true;
self
}
fn handle(&mut self, event: ListEvent) -> Option<Action> {
if self.expanded {
let step = if event.fast { 3 } else { 1 };
let group = &mut self.groups[self.cursor];
match event.code {
KeyCode::Up => {
if group.cursor > 0 {
group.cursor = group.cursor.saturating_sub(step)
}
}
KeyCode::Down => {
let left = group.members.len().saturating_sub(group.cursor + 1);
group.cursor += left.min(step)
}
KeyCode::Right | KeyCode::Enter => {
if group.proxy_type.is_selector() {
let current = group.members[group.cursor].name.to_owned();
return Some(Action::ApplySelection {
group: group.name.to_owned(),
proxy: current,
});
}
}
_ => {}
}
} else {
match event.code {
KeyCode::Up => {
if self.cursor > 0 {
self.cursor = self.cursor.saturating_sub(1)
}
}
KeyCode::Down => {
if self.cursor < self.groups.len() - 1 {
self.cursor = self.cursor.saturating_add(1)
}
}
KeyCode::Enter => self.expanded = true,
_ => {}
}
}
self.update_footer();
None
}
fn offset(&self) -> &crate::Coord {
&Coord {
x: 0,
y: 0,
hold: false,
}
}
}2c85b9591f557c1016bafacbbb97da1098d70465
Reactions are currently unavailable