Skip to main content

rusticity_term/lambda/
functions.rs

1use crate::app::App;
2use crate::common::{CyclicEnum, InputFocus};
3use crate::keymap::Mode;
4use crate::ui::lambda::{filtered_lambda_functions, DetailTab as LambdaDetailTab, FILTER_CONTROLS};
5use crate::ui::monitoring::MonitoringState;
6
7// ── Navigation ────────────────────────────────────────────────────────────────
8
9pub fn next_item(app: &mut App) {
10    if app.lambda_state.current_function.is_some()
11        && app.lambda_state.detail_tab == LambdaDetailTab::Code
12    {
13        if let Some(func_name) = &app.lambda_state.current_function.clone() {
14            if let Some(func) = app
15                .lambda_state
16                .table
17                .items
18                .iter()
19                .find(|f| f.name == *func_name)
20            {
21                let max = func.layers.len().saturating_sub(1);
22                if !func.layers.is_empty() {
23                    app.lambda_state.layer_selected =
24                        (app.lambda_state.layer_selected + 1).min(max);
25                }
26            }
27        }
28    } else if app.lambda_state.current_function.is_some()
29        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
30    {
31        let filtered: Vec<_> = app
32            .lambda_state
33            .version_table
34            .items
35            .iter()
36            .filter(|v| {
37                app.lambda_state.version_table.filter.is_empty()
38                    || v.version
39                        .to_lowercase()
40                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
41                    || v.aliases
42                        .to_lowercase()
43                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
44                    || v.description
45                        .to_lowercase()
46                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
47            })
48            .collect();
49        if !filtered.is_empty() {
50            app.lambda_state.version_table.selected =
51                (app.lambda_state.version_table.selected + 1).min(filtered.len() - 1);
52            app.lambda_state.version_table.snap_to_page();
53        }
54    } else if app.lambda_state.current_function.is_some()
55        && (app.lambda_state.detail_tab == LambdaDetailTab::Aliases
56            || (app.lambda_state.current_version.is_some()
57                && app.lambda_state.detail_tab == LambdaDetailTab::Configuration))
58    {
59        let version_filter = app.lambda_state.current_version.clone();
60        let filtered: Vec<_> = app
61            .lambda_state
62            .alias_table
63            .items
64            .iter()
65            .filter(|a| {
66                (version_filter.is_none() || a.versions.contains(version_filter.as_ref().unwrap()))
67                    && (app.lambda_state.alias_table.filter.is_empty()
68                        || a.name
69                            .to_lowercase()
70                            .contains(&app.lambda_state.alias_table.filter.to_lowercase())
71                        || a.versions
72                            .to_lowercase()
73                            .contains(&app.lambda_state.alias_table.filter.to_lowercase())
74                        || a.description
75                            .to_lowercase()
76                            .contains(&app.lambda_state.alias_table.filter.to_lowercase()))
77            })
78            .collect();
79        if !filtered.is_empty() {
80            app.lambda_state.alias_table.selected =
81                (app.lambda_state.alias_table.selected + 1).min(filtered.len() - 1);
82            app.lambda_state.alias_table.snap_to_page();
83        }
84    } else if app.lambda_state.current_function.is_none() {
85        let filtered = filtered_lambda_functions(app);
86        if !filtered.is_empty() {
87            app.lambda_state.table.next_item(filtered.len());
88            app.lambda_state.table.snap_to_page();
89        }
90    }
91}
92
93pub fn prev_item(app: &mut App) {
94    if app.lambda_state.current_function.is_some()
95        && app.lambda_state.detail_tab == LambdaDetailTab::Code
96    {
97        app.lambda_state.layer_selected = app.lambda_state.layer_selected.saturating_sub(1);
98    } else if app.lambda_state.current_function.is_some()
99        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
100    {
101        app.lambda_state.version_table.prev_item();
102    } else if app.lambda_state.current_function.is_some()
103        && (app.lambda_state.detail_tab == LambdaDetailTab::Aliases
104            || (app.lambda_state.current_version.is_some()
105                && app.lambda_state.detail_tab == LambdaDetailTab::Configuration))
106    {
107        app.lambda_state.alias_table.prev_item();
108    } else if app.lambda_state.current_function.is_none() {
109        app.lambda_state.table.prev_item();
110    }
111}
112
113pub fn page_down_filter_input(app: &mut App) {
114    if app.lambda_state.current_function.is_some()
115        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
116        && app.lambda_state.version_input_focus == InputFocus::Pagination
117    {
118        let page_size = app.lambda_state.version_table.page_size.value();
119        let filtered_count: usize = app
120            .lambda_state
121            .version_table
122            .items
123            .iter()
124            .filter(|v| {
125                app.lambda_state.version_table.filter.is_empty()
126                    || v.version
127                        .to_lowercase()
128                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
129                    || v.aliases
130                        .to_lowercase()
131                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
132                    || v.description
133                        .to_lowercase()
134                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
135            })
136            .count();
137        let target = app.lambda_state.version_table.selected + page_size;
138        app.lambda_state.version_table.selected = target.min(filtered_count.saturating_sub(1));
139    } else if app.lambda_state.current_function.is_some()
140        && (app.lambda_state.detail_tab == LambdaDetailTab::Aliases
141            || (app.lambda_state.current_version.is_some()
142                && app.lambda_state.detail_tab == LambdaDetailTab::Configuration))
143        && app.lambda_state.alias_input_focus == InputFocus::Pagination
144    {
145        let page_size = app.lambda_state.alias_table.page_size.value();
146        let version_filter = app.lambda_state.current_version.clone();
147        let filtered_count = app
148            .lambda_state
149            .alias_table
150            .items
151            .iter()
152            .filter(|a| {
153                (version_filter.is_none() || a.versions.contains(version_filter.as_ref().unwrap()))
154                    && (app.lambda_state.alias_table.filter.is_empty()
155                        || a.name
156                            .to_lowercase()
157                            .contains(&app.lambda_state.alias_table.filter.to_lowercase())
158                        || a.versions
159                            .to_lowercase()
160                            .contains(&app.lambda_state.alias_table.filter.to_lowercase())
161                        || a.description
162                            .to_lowercase()
163                            .contains(&app.lambda_state.alias_table.filter.to_lowercase()))
164            })
165            .count();
166        let target = app.lambda_state.alias_table.selected + page_size;
167        app.lambda_state.alias_table.selected = target.min(filtered_count.saturating_sub(1));
168    } else if app.lambda_state.current_function.is_none() {
169        let page_size = app.lambda_state.table.page_size.value();
170        let filtered_count = filtered_lambda_functions(app).len();
171        app.lambda_state.input_focus.handle_page_down(
172            &mut app.lambda_state.table.selected,
173            &mut app.lambda_state.table.scroll_offset,
174            page_size,
175            filtered_count,
176        );
177    }
178}
179
180pub fn page_down_normal(app: &mut App) {
181    let len = filtered_lambda_functions(app).len();
182    app.lambda_state.table.page_down(len);
183}
184
185pub fn page_up_filter_input(app: &mut App) {
186    if app.lambda_state.current_function.is_some()
187        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
188        && app.lambda_state.version_input_focus == InputFocus::Pagination
189    {
190        let page_size = app.lambda_state.version_table.page_size.value();
191        app.lambda_state.version_table.selected = app
192            .lambda_state
193            .version_table
194            .selected
195            .saturating_sub(page_size);
196    } else if app.lambda_state.current_function.is_some()
197        && (app.lambda_state.detail_tab == LambdaDetailTab::Aliases
198            || (app.lambda_state.current_version.is_some()
199                && app.lambda_state.detail_tab == LambdaDetailTab::Configuration))
200        && app.lambda_state.alias_input_focus == InputFocus::Pagination
201    {
202        let page_size = app.lambda_state.alias_table.page_size.value();
203        app.lambda_state.alias_table.selected = app
204            .lambda_state
205            .alias_table
206            .selected
207            .saturating_sub(page_size);
208    } else if app.lambda_state.current_function.is_none() {
209        let page_size = app.lambda_state.table.page_size.value();
210        app.lambda_state.input_focus.handle_page_up(
211            &mut app.lambda_state.table.selected,
212            &mut app.lambda_state.table.scroll_offset,
213            page_size,
214        );
215    }
216}
217
218pub fn page_up_normal(app: &mut App) {
219    app.lambda_state.table.page_up();
220}
221
222pub fn scroll_up(app: &mut App) {
223    if app.lambda_state.current_function.is_some()
224        && app.lambda_state.detail_tab == LambdaDetailTab::Monitor
225        && !app.lambda_state.is_metrics_loading()
226    {
227        app.lambda_state
228            .set_monitoring_scroll(app.lambda_state.monitoring_scroll().saturating_sub(1));
229    }
230}
231
232pub fn scroll_down(app: &mut App) {
233    if app.lambda_state.current_function.is_some()
234        && app.lambda_state.detail_tab == LambdaDetailTab::Monitor
235        && !app.lambda_state.is_metrics_loading()
236    {
237        app.lambda_state
238            .set_monitoring_scroll((app.lambda_state.monitoring_scroll() + 1).min(9));
239    }
240}
241
242pub fn expand_row(app: &mut App) {
243    if app.lambda_state.current_function.is_some()
244        && app.lambda_state.detail_tab == LambdaDetailTab::Code
245    {
246        if app.lambda_state.layer_expanded != Some(app.lambda_state.layer_selected) {
247            app.lambda_state.layer_expanded = Some(app.lambda_state.layer_selected);
248        } else {
249            app.lambda_state.layer_expanded = None;
250        }
251    } else if app.lambda_state.current_function.is_some()
252        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
253    {
254        app.lambda_state.version_table.toggle_expand();
255    } else if app.lambda_state.current_function.is_some()
256        && (app.lambda_state.detail_tab == LambdaDetailTab::Aliases
257            || (app.lambda_state.current_version.is_some()
258                && app.lambda_state.detail_tab == LambdaDetailTab::Configuration))
259    {
260        app.lambda_state.alias_table.toggle_expand();
261    } else if app.lambda_state.current_function.is_none() {
262        app.lambda_state.table.expand();
263    }
264}
265
266pub fn collapse_row(app: &mut App) {
267    app.lambda_state.table.collapse();
268}
269
270pub fn prev_pane(app: &mut App) {
271    if app.lambda_state.current_function.is_some()
272        && app.lambda_state.detail_tab == LambdaDetailTab::Code
273    {
274        app.lambda_state.layer_expanded = None;
275    } else if app.lambda_state.current_function.is_some()
276        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
277    {
278        app.lambda_state.version_table.collapse();
279    } else if app.lambda_state.current_function.is_some()
280        && (app.lambda_state.detail_tab == LambdaDetailTab::Aliases
281            || (app.lambda_state.current_version.is_some()
282                && app.lambda_state.detail_tab == LambdaDetailTab::Configuration))
283    {
284        app.lambda_state.alias_table.collapse();
285    } else if app.lambda_state.current_function.is_none() {
286        app.lambda_state.table.collapse();
287    }
288}
289
290pub fn go_to_page(app: &mut App, page: usize) {
291    if app.lambda_state.current_function.is_some()
292        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
293    {
294        let filtered_count = app
295            .lambda_state
296            .version_table
297            .filtered(|v| {
298                app.lambda_state.version_table.filter.is_empty()
299                    || v.version
300                        .to_lowercase()
301                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
302                    || v.aliases
303                        .to_lowercase()
304                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
305                    || v.description
306                        .to_lowercase()
307                        .contains(&app.lambda_state.version_table.filter.to_lowercase())
308            })
309            .len();
310        app.lambda_state
311            .version_table
312            .goto_page(page, filtered_count);
313    } else {
314        let filtered_count = filtered_lambda_functions(app).len();
315        app.lambda_state.table.goto_page(page, filtered_count);
316    }
317}
318
319// ── Filter ────────────────────────────────────────────────────────────────────
320
321pub fn apply_filter_reset(app: &mut App) {
322    if app.lambda_state.current_version.is_some() || app.lambda_state.current_function.is_some() {
323        app.lambda_state.version_table.reset();
324        app.lambda_state.alias_table.reset();
325    } else {
326        app.lambda_state.table.reset();
327    }
328}
329
330pub fn start_filter(app: &mut App) {
331    if app.lambda_state.current_version.is_some()
332        && app.lambda_state.detail_tab == LambdaDetailTab::Configuration
333    {
334        app.lambda_state.alias_input_focus = InputFocus::Filter;
335    } else if app.lambda_state.current_function.is_some()
336        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
337    {
338        app.lambda_state.version_input_focus = InputFocus::Filter;
339    } else if app.lambda_state.current_function.is_none() {
340        app.lambda_state.input_focus = InputFocus::Filter;
341    }
342}
343
344pub fn is_pagination_focused(app: &App) -> bool {
345    if app.lambda_state.current_function.is_some()
346        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
347    {
348        app.lambda_state.version_input_focus == InputFocus::Pagination
349    } else if app.lambda_state.current_function.is_none() {
350        app.lambda_state.input_focus == InputFocus::Pagination
351    } else {
352        false
353    }
354}
355
356pub fn next_filter_focus(app: &mut App) {
357    if app.lambda_state.current_version.is_some()
358        && app.lambda_state.detail_tab == LambdaDetailTab::Configuration
359    {
360        app.lambda_state.alias_input_focus =
361            app.lambda_state.alias_input_focus.next(&FILTER_CONTROLS);
362    } else if app.lambda_state.current_function.is_some()
363        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
364    {
365        app.lambda_state.version_input_focus =
366            app.lambda_state.version_input_focus.next(&FILTER_CONTROLS);
367    } else if app.lambda_state.current_function.is_some()
368        && app.lambda_state.detail_tab == LambdaDetailTab::Aliases
369    {
370        app.lambda_state.alias_input_focus =
371            app.lambda_state.alias_input_focus.next(&FILTER_CONTROLS);
372    } else if app.lambda_state.current_function.is_none() {
373        app.lambda_state.input_focus = app.lambda_state.input_focus.next(&FILTER_CONTROLS);
374    }
375}
376
377pub fn prev_filter_focus(app: &mut App) {
378    if app.lambda_state.current_version.is_some()
379        && app.lambda_state.detail_tab == LambdaDetailTab::Configuration
380    {
381        app.lambda_state.alias_input_focus =
382            app.lambda_state.alias_input_focus.prev(&FILTER_CONTROLS);
383    } else if app.lambda_state.current_function.is_some()
384        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
385    {
386        app.lambda_state.version_input_focus =
387            app.lambda_state.version_input_focus.prev(&FILTER_CONTROLS);
388    } else if app.lambda_state.current_function.is_some()
389        && app.lambda_state.detail_tab == LambdaDetailTab::Aliases
390    {
391        app.lambda_state.alias_input_focus =
392            app.lambda_state.alias_input_focus.prev(&FILTER_CONTROLS);
393    } else if app.lambda_state.current_function.is_none() {
394        app.lambda_state.input_focus = app.lambda_state.input_focus.prev(&FILTER_CONTROLS);
395    }
396}
397
398pub fn filter_char_allowed(app: &App) -> bool {
399    if app.lambda_state.current_function.is_none() {
400        app.lambda_state.input_focus == InputFocus::Filter
401    } else if app.lambda_state.current_version.is_some()
402        && app.lambda_state.detail_tab == LambdaDetailTab::Configuration
403    {
404        app.lambda_state.alias_input_focus == InputFocus::Filter
405    } else if app.lambda_state.current_function.is_some()
406        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
407    {
408        app.lambda_state.version_input_focus == InputFocus::Filter
409    } else if app.lambda_state.current_function.is_some()
410        && app.lambda_state.detail_tab == LambdaDetailTab::Aliases
411    {
412        app.lambda_state.alias_input_focus == InputFocus::Filter
413    } else {
414        false
415    }
416}
417
418// ── Actions ───────────────────────────────────────────────────────────────────
419
420pub fn select_item(app: &mut App) {
421    if app.lambda_state.current_function.is_some()
422        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
423    {
424        if app.mode == Mode::Normal {
425            let page_size = app.lambda_state.version_table.page_size.value();
426            let filtered: Vec<_> = app
427                .lambda_state
428                .version_table
429                .items
430                .iter()
431                .filter(|v| {
432                    app.lambda_state.version_table.filter.is_empty()
433                        || v.version
434                            .to_lowercase()
435                            .contains(&app.lambda_state.version_table.filter.to_lowercase())
436                        || v.aliases
437                            .to_lowercase()
438                            .contains(&app.lambda_state.version_table.filter.to_lowercase())
439                })
440                .collect();
441            let current_page = app.lambda_state.version_table.selected / page_size;
442            let start_idx = current_page * page_size;
443            let end_idx = (start_idx + page_size).min(filtered.len());
444            let paginated: Vec<_> = filtered[start_idx..end_idx].to_vec();
445            let page_index = app.lambda_state.version_table.selected % page_size;
446            if let Some(version) = paginated.get(page_index) {
447                app.lambda_state.current_version = Some(version.version.clone());
448                app.lambda_state.detail_tab = LambdaDetailTab::Code;
449            }
450        } else {
451            if app.lambda_state.version_table.expanded_item
452                == Some(app.lambda_state.version_table.selected)
453            {
454                app.lambda_state.version_table.collapse();
455            } else {
456                app.lambda_state.version_table.expanded_item =
457                    Some(app.lambda_state.version_table.selected);
458            }
459        }
460    } else if app.lambda_state.current_function.is_some()
461        && app.lambda_state.detail_tab == LambdaDetailTab::Aliases
462    {
463        let filtered: Vec<_> = app
464            .lambda_state
465            .alias_table
466            .items
467            .iter()
468            .filter(|a| {
469                app.lambda_state.alias_table.filter.is_empty()
470                    || a.name
471                        .to_lowercase()
472                        .contains(&app.lambda_state.alias_table.filter.to_lowercase())
473                    || a.versions
474                        .to_lowercase()
475                        .contains(&app.lambda_state.alias_table.filter.to_lowercase())
476            })
477            .collect();
478        if let Some(alias) = app.lambda_state.alias_table.get_selected(&filtered) {
479            app.lambda_state.current_alias = Some(alias.name.clone());
480        }
481    } else if app.lambda_state.current_function.is_none() {
482        let filtered = filtered_lambda_functions(app);
483        if let Some(func) = app.lambda_state.table.get_selected(&filtered) {
484            app.lambda_state.current_function = Some(func.name.clone());
485            app.lambda_state.detail_tab = LambdaDetailTab::Code;
486            app.update_current_tab_breadcrumb();
487        }
488    }
489}
490
491pub fn go_back(app: &mut App) {
492    if app.lambda_state.current_version.is_some() {
493        app.lambda_state.current_version = None;
494        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
495    } else if app.lambda_state.current_alias.is_some() {
496        app.lambda_state.current_alias = None;
497        app.lambda_state.detail_tab = LambdaDetailTab::Aliases;
498    } else if app.lambda_state.current_function.is_some() {
499        app.lambda_state.current_function = None;
500        app.update_current_tab_breadcrumb();
501    }
502}
503
504pub fn next_detail_tab(app: &mut App) {
505    if app.lambda_state.current_version.is_some() {
506        app.lambda_state.version_detail_tab = app.lambda_state.version_detail_tab.next();
507        app.lambda_state.detail_tab = app.lambda_state.version_detail_tab.to_detail_tab();
508        if app.lambda_state.detail_tab == LambdaDetailTab::Monitor {
509            app.lambda_state.set_metrics_loading(true);
510            app.lambda_state.set_monitoring_scroll(0);
511            app.lambda_state.clear_metrics();
512        }
513    } else {
514        app.lambda_state.detail_tab = app.lambda_state.detail_tab.next();
515        if app.lambda_state.detail_tab == LambdaDetailTab::Monitor {
516            app.lambda_state.set_metrics_loading(true);
517            app.lambda_state.set_monitoring_scroll(0);
518            app.lambda_state.clear_metrics();
519        }
520    }
521}
522
523pub fn prev_detail_tab(app: &mut App) {
524    if app.lambda_state.current_version.is_some() {
525        app.lambda_state.version_detail_tab = app.lambda_state.version_detail_tab.prev();
526        app.lambda_state.detail_tab = app.lambda_state.version_detail_tab.to_detail_tab();
527        if app.lambda_state.detail_tab == LambdaDetailTab::Monitor {
528            app.lambda_state.set_metrics_loading(true);
529            app.lambda_state.set_monitoring_scroll(0);
530            app.lambda_state.clear_metrics();
531        }
532    } else {
533        app.lambda_state.detail_tab = app.lambda_state.detail_tab.prev();
534        if app.lambda_state.detail_tab == LambdaDetailTab::Monitor {
535            app.lambda_state.set_metrics_loading(true);
536            app.lambda_state.set_monitoring_scroll(0);
537            app.lambda_state.clear_metrics();
538        }
539    }
540}
541
542pub fn refresh(app: &mut App) {
543    app.lambda_state.table.loading = true;
544}
545
546pub fn yank(app: &App) {
547    let filtered = filtered_lambda_functions(app);
548    if let Some(func) = app.lambda_state.table.get_selected(&filtered) {
549        app.clipboard.request_text(&func.arn);
550    }
551}
552
553// ── Column preferences ────────────────────────────────────────────────────────
554
555pub fn toggle_column(app: &mut App) {
556    crate::preferences::bindings::activate_for(app, crate::app::Service::LambdaFunctions);
557}
558
559pub fn next_preferences(app: &mut App) {
560    crate::preferences::bindings::move_section_for(app, crate::app::Service::LambdaFunctions, true);
561}
562
563pub fn prev_preferences(app: &mut App) {
564    crate::preferences::bindings::move_section_for(
565        app,
566        crate::app::Service::LambdaFunctions,
567        false,
568    );
569}
570
571pub fn column_selector_max(app: &App) -> usize {
572    crate::preferences::bindings::model_for(app, crate::app::Service::LambdaFunctions).max_index()
573}
574
575pub fn column_count(app: &App) -> usize {
576    crate::preferences::bindings::model_for(app, crate::app::Service::LambdaFunctions)
577        .column_count()
578}
579
580// ── Breadcrumb / console URL ──────────────────────────────────────────────────
581
582pub fn breadcrumb(app: &App) -> Vec<String> {
583    let mut parts = vec!["Lambda".to_string()];
584    if let Some(func) = &app.lambda_state.current_function {
585        parts.push(func.clone());
586    } else {
587        parts.push("Functions".to_string());
588    }
589    parts
590}
591
592pub fn console_url(app: &App) -> String {
593    use crate::lambda;
594    if let Some(func_name) = &app.lambda_state.current_function {
595        if let Some(version) = &app.lambda_state.current_version {
596            lambda::console_url_function_version(
597                &app.config.region,
598                func_name,
599                version,
600                &app.lambda_state.detail_tab,
601            )
602        } else {
603            lambda::console_url_function_detail(&app.config.region, func_name)
604        }
605    } else {
606        lambda::console_url_functions(&app.config.region)
607    }
608}
609
610pub fn get_active_filter_mut(app: &mut App) -> Option<&mut String> {
611    if app.lambda_state.current_version.is_some()
612        && app.lambda_state.detail_tab == LambdaDetailTab::Configuration
613    {
614        Some(&mut app.lambda_state.alias_table.filter)
615    } else if app.lambda_state.current_function.is_some()
616        && app.lambda_state.detail_tab == LambdaDetailTab::Versions
617    {
618        Some(&mut app.lambda_state.version_table.filter)
619    } else if app.lambda_state.current_function.is_some()
620        && app.lambda_state.detail_tab == LambdaDetailTab::Aliases
621    {
622        Some(&mut app.lambda_state.alias_table.filter)
623    } else {
624        Some(&mut app.lambda_state.table.filter)
625    }
626}
627
628#[cfg(test)]
629mod filter_input_regressions {
630    use super::*;
631    use crate::app::Service;
632    use crate::keymap::{handle_key, Action};
633    use crate::lambda::Function;
634    use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
635
636    fn press(app: &mut App, code: KeyCode) {
637        let action = handle_key(KeyEvent::new(code, KeyModifiers::NONE), app.mode).unwrap();
638        app.handle_action(action);
639    }
640
641    fn function(name: &str) -> Function {
642        Function {
643            name: name.into(),
644            arn: String::new(),
645            application: None,
646            description: String::new(),
647            package_type: "Zip".into(),
648            runtime: String::new(),
649            architecture: String::new(),
650            code_size: 0,
651            code_sha256: String::new(),
652            memory_mb: 128,
653            timeout_seconds: 30,
654            last_modified: String::new(),
655            layers: vec![],
656        }
657    }
658
659    #[test]
660    fn lambda_list_filter_accepts_typing_and_backspace_through_keymap() {
661        let mut app = App::new_without_client("test".into(), Some("us-east-1".into()));
662        app.current_service = Service::LambdaFunctions;
663        app.service_selected = true;
664        app.mode = Mode::Normal;
665        app.lambda_state.table.items = vec![
666            function("BETA-GrafanaWorkspace-Provisioner"),
667            function("BETA-SagemakerHyperpodEks-FsxDraKmsGrantGrantFnDEE-test"),
668        ];
669        app.lambda_state.table.selected = 1;
670        app.lambda_state.table.scroll_offset = 50;
671        app.lambda_state.table.expanded_item = Some(1);
672        app.lambda_state.input_focus = InputFocus::Pagination;
673        press(&mut app, KeyCode::Char('i'));
674        assert_eq!(app.mode, Mode::FilterInput);
675        assert_eq!(app.lambda_state.input_focus, InputFocus::Filter);
676        for c in "Fsx".chars() {
677            press(&mut app, KeyCode::Char(c));
678        }
679        assert_eq!(app.lambda_state.table.filter, "Fsx");
680        assert_eq!(filtered_lambda_functions(&app).len(), 1);
681        assert!(filtered_lambda_functions(&app)[0]
682            .name
683            .contains("FsxDraKmsGrant"));
684        assert_eq!(app.lambda_state.table.selected, 0);
685        assert_eq!(app.lambda_state.table.scroll_offset, 0);
686        assert_eq!(app.lambda_state.table.expanded_item, None);
687        press(&mut app, KeyCode::Backspace);
688        assert_eq!(app.lambda_state.table.filter, "Fs");
689        app.handle_action(Action::DeleteWord);
690        assert!(app.lambda_state.table.filter.is_empty());
691        assert_eq!(filtered_lambda_functions(&app).len(), 2);
692        press(&mut app, KeyCode::Enter);
693        assert_eq!(app.mode, Mode::Normal);
694    }
695
696    #[test]
697    fn lambda_list_pagination_focus_does_not_accept_filter_text() {
698        let mut app = App::new_without_client("test".into(), None);
699        app.current_service = Service::LambdaFunctions;
700        app.service_selected = true;
701        app.mode = Mode::Normal;
702        press(&mut app, KeyCode::Char('i'));
703        press(&mut app, KeyCode::Char('F'));
704        press(&mut app, KeyCode::Tab);
705        assert_eq!(app.lambda_state.input_focus, InputFocus::Pagination);
706        press(&mut app, KeyCode::Char('s'));
707        press(&mut app, KeyCode::Char('2'));
708        assert_eq!(app.lambda_state.table.filter, "F");
709        assert_eq!(app.page_input, "2");
710        press(&mut app, KeyCode::BackTab);
711        press(&mut app, KeyCode::Char('s'));
712        assert_eq!(app.lambda_state.table.filter, "Fs");
713    }
714
715    #[test]
716    fn lambda_detail_filter_permission_stays_scoped_to_version_and_alias_controls() {
717        let mut app = App::new_without_client("test".into(), None);
718        app.lambda_state.current_function = Some("fn".into());
719        for tab in [LambdaDetailTab::Versions, LambdaDetailTab::Aliases] {
720            app.lambda_state.detail_tab = tab;
721            app.lambda_state.version_input_focus = InputFocus::Filter;
722            app.lambda_state.alias_input_focus = InputFocus::Filter;
723            assert!(filter_char_allowed(&app));
724            app.lambda_state.version_input_focus = InputFocus::Pagination;
725            app.lambda_state.alias_input_focus = InputFocus::Pagination;
726            assert!(!filter_char_allowed(&app));
727        }
728        app.lambda_state.detail_tab = LambdaDetailTab::Code;
729        app.lambda_state.input_focus = InputFocus::Filter;
730        assert!(!filter_char_allowed(&app));
731    }
732}