aboutsummaryrefslogtreecommitdiff
path: root/src/agent_view_eval.rs
blob: 5d3fe84490614bb9887bebc6f40990f20f1b4637 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
use std::borrow::Cow;
use std::cmp::Ordering;

use crate::api::schema::{
    AgentViewBuiltinField, AgentViewBuiltinSortField, AgentViewField, AgentViewFilter,
    AgentViewSort, AgentViewSortField, AgentViewSortOrder, AgentViewValue,
};

pub(crate) struct AgentViewContext {
    pub(crate) scope: usize,
    pub(crate) workspace_id: Option<String>,
    pub(crate) tab_id: Option<String>,
}

pub(crate) trait AgentViewEntry {
    fn scope(&self) -> usize;
    fn status(&self) -> &'static str;
    fn workspace_id(&self) -> Option<Cow<'_, str>>;
    fn tab_id(&self) -> Option<Cow<'_, str>>;
    fn pane_id(&self) -> Option<Cow<'_, str>>;
    fn agent(&self) -> Option<&str>;
    fn seen(&self) -> bool;
    fn state_change_seq(&self) -> Option<u64>;
    fn token(&self, token: &str) -> Option<&str>;
    fn workspace_order(&self) -> Option<u64>;
    fn tab_order(&self) -> Option<u64>;
    fn pane_order(&self) -> Option<u64>;
    fn attention(&self) -> u64;
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum EvalValue<'a> {
    String(Cow<'a, str>),
    ScopedId { scope: usize, value: Cow<'a, str> },
    Bool(bool),
    Number(u64),
}

pub(crate) fn matches_filter<E: AgentViewEntry + ?Sized>(
    context: &AgentViewContext,
    entry: &E,
    filter: &AgentViewFilter,
) -> bool {
    match filter {
        AgentViewFilter::All { filters } => filters
            .iter()
            .all(|filter| matches_filter(context, entry, filter)),
        AgentViewFilter::Any { filters } => filters
            .iter()
            .any(|filter| matches_filter(context, entry, filter)),
        AgentViewFilter::Not { filter } => !matches_filter(context, entry, filter),
        AgentViewFilter::Eq { field, value } => {
            field_value(entry, field) == operand_value(context, field, value)
        }
        AgentViewFilter::In { field, values } => {
            let actual = field_value(entry, field);
            values
                .iter()
                .any(|value| actual == operand_value(context, field, value))
        }
        AgentViewFilter::Exists { field } => field_value(entry, field).is_some(),
    }
}

pub(crate) fn compare_entries<E: AgentViewEntry + ?Sized>(
    left: &E,
    right: &E,
    sorts: &[AgentViewSort],
) -> Ordering {
    for sort in sorts {
        let ordering = compare_optional_values(
            sort_value(left, &sort.field),
            sort_value(right, &sort.field),
            sort.order,
        );
        if ordering != Ordering::Equal {
            return ordering;
        }
    }
    Ordering::Equal
}

fn field_value<'a, E: AgentViewEntry + ?Sized>(
    entry: &'a E,
    field: &AgentViewField,
) -> Option<EvalValue<'a>> {
    match field {
        AgentViewField::Builtin(AgentViewBuiltinField::Status) => {
            Some(EvalValue::String(Cow::Borrowed(entry.status())))
        }
        AgentViewField::Builtin(AgentViewBuiltinField::WorkspaceId) => {
            entry.workspace_id().map(|value| EvalValue::ScopedId {
                scope: entry.scope(),
                value,
            })
        }
        AgentViewField::Builtin(AgentViewBuiltinField::TabId) => {
            entry.tab_id().map(|value| EvalValue::ScopedId {
                scope: entry.scope(),
                value,
            })
        }
        AgentViewField::Builtin(AgentViewBuiltinField::PaneId) => {
            entry.pane_id().map(|value| EvalValue::ScopedId {
                scope: entry.scope(),
                value,
            })
        }
        AgentViewField::Builtin(AgentViewBuiltinField::Agent) => entry
            .agent()
            .map(|value| EvalValue::String(Cow::Borrowed(value))),
        AgentViewField::Builtin(AgentViewBuiltinField::Seen) => Some(EvalValue::Bool(entry.seen())),
        AgentViewField::Builtin(AgentViewBuiltinField::StateChangeSeq) => {
            entry.state_change_seq().map(EvalValue::Number)
        }
        AgentViewField::Token { token } => entry
            .token(token)
            .map(|value| EvalValue::String(Cow::Borrowed(value))),
    }
}

fn operand_value<'a>(
    context: &'a AgentViewContext,
    field: &AgentViewField,
    value: &'a AgentViewValue,
) -> Option<EvalValue<'a>> {
    match value {
        AgentViewValue::String(value) if is_id_field(field) => Some(EvalValue::ScopedId {
            scope: context.scope,
            value: Cow::Borrowed(value),
        }),
        AgentViewValue::String(value) => Some(EvalValue::String(Cow::Borrowed(value))),
        AgentViewValue::Bool(value) => Some(EvalValue::Bool(*value)),
        AgentViewValue::Number(value) => Some(EvalValue::Number(*value)),
        AgentViewValue::Context {
            context: view_context,
        } => match view_context {
            crate::api::schema::AgentViewContext::CurrentWorkspaceId => context
                .workspace_id
                .as_deref()
                .map(|value| EvalValue::ScopedId {
                    scope: context.scope,
                    value: Cow::Borrowed(value),
                }),
            crate::api::schema::AgentViewContext::CurrentTabId => {
                context.tab_id.as_deref().map(|value| EvalValue::ScopedId {
                    scope: context.scope,
                    value: Cow::Borrowed(value),
                })
            }
        },
    }
}

fn is_id_field(field: &AgentViewField) -> bool {
    matches!(
        field,
        AgentViewField::Builtin(
            AgentViewBuiltinField::WorkspaceId
                | AgentViewBuiltinField::TabId
                | AgentViewBuiltinField::PaneId
        )
    )
}

fn sort_value<'a, E: AgentViewEntry + ?Sized>(
    entry: &'a E,
    field: &AgentViewSortField,
) -> Option<EvalValue<'a>> {
    match field {
        AgentViewSortField::Token { token } => entry
            .token(token)
            .map(|value| EvalValue::String(Cow::Borrowed(value))),
        AgentViewSortField::Builtin(field) => match field {
            AgentViewBuiltinSortField::WorkspaceOrder => {
                entry.workspace_order().map(EvalValue::Number)
            }
            AgentViewBuiltinSortField::TabOrder => entry.tab_order().map(EvalValue::Number),
            AgentViewBuiltinSortField::PaneOrder => entry.pane_order().map(EvalValue::Number),
            AgentViewBuiltinSortField::Attention => Some(EvalValue::Number(entry.attention())),
            AgentViewBuiltinSortField::Status => {
                Some(EvalValue::String(Cow::Borrowed(entry.status())))
            }
            AgentViewBuiltinSortField::Agent => entry
                .agent()
                .map(|value| EvalValue::String(Cow::Borrowed(value))),
            AgentViewBuiltinSortField::Seen => Some(EvalValue::Bool(entry.seen())),
            AgentViewBuiltinSortField::StateChangeSeq => {
                entry.state_change_seq().map(EvalValue::Number)
            }
        },
    }
}

fn compare_optional_values(
    left: Option<EvalValue<'_>>,
    right: Option<EvalValue<'_>>,
    order: AgentViewSortOrder,
) -> Ordering {
    match (left, right) {
        (Some(left), Some(right)) => {
            let ordering = left.cmp(&right);
            if matches!(order, AgentViewSortOrder::Desc) {
                ordering.reverse()
            } else {
                ordering
            }
        }
        (Some(_), None) => Ordering::Less,
        (None, Some(_)) => Ordering::Greater,
        (None, None) => Ordering::Equal,
    }
}