Skip to main content

gpui_component/list/
list.rs

1use instant::Duration;
2use std::ops::Range;
3
4use crate::actions::{Cancel, Confirm, SelectDown, SelectUp};
5use crate::input::InputState;
6use crate::list::cache::{MeasuredEntrySize, RowEntry, RowsCache};
7use crate::{
8    ActiveTheme, IconName, Size,
9    input::{Input, InputEvent},
10    scroll::Scrollbar,
11    v_flex,
12};
13use crate::{Icon, IndexPath, Selectable, Sizable, StyledExt};
14use crate::{VirtualListScrollHandle, list::ListDelegate, v_virtual_list};
15use gpui::{
16    App, AvailableSpace, ClickEvent, Context, DefiniteLength, EdgesRefinement, EventEmitter,
17    ListSizingBehavior, RenderOnce, Role, ScrollStrategy, SharedString, StatefulInteractiveElement,
18    StyleRefinement, Subscription, px, size,
19};
20use gpui::{
21    AppContext, Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,
22    Length, MouseButton, ParentElement, Render, Styled, Task, Window, div, prelude::FluentBuilder,
23};
24use rust_i18n::t;
25
26pub(crate) fn init(cx: &mut App) {
27    let context: Option<&str> = Some("List");
28    cx.bind_keys([
29        KeyBinding::new("escape", Cancel, context),
30        KeyBinding::new("enter", Confirm { secondary: false }, context),
31        KeyBinding::new("secondary-enter", Confirm { secondary: true }, context),
32        KeyBinding::new("up", SelectUp, context),
33        KeyBinding::new("down", SelectDown, context),
34    ]);
35}
36
37#[derive(Clone)]
38pub enum ListEvent {
39    /// Move to select item.
40    Select(IndexPath),
41    /// Click on item or pressed Enter.
42    Confirm(IndexPath),
43    /// Pressed ESC to deselect the item.
44    Cancel,
45}
46
47struct ListOptions {
48    size: Size,
49    scrollbar_visible: bool,
50    search_placeholder: Option<SharedString>,
51    max_height: Option<Length>,
52    paddings: EdgesRefinement<DefiniteLength>,
53}
54
55impl Default for ListOptions {
56    fn default() -> Self {
57        Self {
58            size: Size::default(),
59            scrollbar_visible: true,
60            max_height: None,
61            search_placeholder: None,
62            paddings: EdgesRefinement::default(),
63        }
64    }
65}
66
67/// The state for List.
68///
69/// List required all items has the same height.
70pub struct ListState<D: ListDelegate> {
71    pub(crate) focus_handle: FocusHandle,
72    pub(crate) query_input: Entity<InputState>,
73    options: ListOptions,
74    delegate: D,
75    last_query: Option<String>,
76    scroll_handle: VirtualListScrollHandle,
77    rows_cache: RowsCache,
78    selected_index: Option<IndexPath>,
79    item_to_measure_index: IndexPath,
80    deferred_scroll_to_index: Option<(IndexPath, ScrollStrategy)>,
81    mouse_right_clicked_index: Option<IndexPath>,
82    reset_on_cancel: bool,
83    searchable: bool,
84    selectable: bool,
85    _search_task: Task<()>,
86    _load_more_task: Task<()>,
87    _query_input_subscription: Subscription,
88}
89
90impl<D> ListState<D>
91where
92    D: ListDelegate,
93{
94    pub fn new(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
95        let query_input =
96            cx.new(|cx| InputState::new(window, cx).placeholder(t!("List.search_placeholder")));
97
98        let _query_input_subscription =
99            cx.subscribe_in(&query_input, window, Self::on_query_input_event);
100
101        Self {
102            focus_handle: cx.focus_handle(),
103            options: ListOptions::default(),
104            delegate,
105            rows_cache: RowsCache::default(),
106            query_input,
107            last_query: None,
108            selected_index: None,
109            selectable: true,
110            searchable: false,
111            item_to_measure_index: IndexPath::default(),
112            deferred_scroll_to_index: None,
113            mouse_right_clicked_index: None,
114            scroll_handle: VirtualListScrollHandle::new(),
115            reset_on_cancel: true,
116            _search_task: Task::ready(()),
117            _load_more_task: Task::ready(()),
118            _query_input_subscription,
119        }
120    }
121
122    /// Sets whether the list is searchable, default is `false`.
123    ///
124    /// When `true`, there will be a search input at the top of the list.
125    pub fn searchable(mut self, searchable: bool) -> Self {
126        self.searchable = searchable;
127        self
128    }
129
130    pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
131        self.searchable = searchable;
132        cx.notify();
133    }
134
135    /// Sets whether the list is selectable, default is true.
136    pub fn selectable(mut self, selectable: bool) -> Self {
137        self.selectable = selectable;
138        self
139    }
140
141    /// Sets whether the list is selectable, default is true.
142    pub fn set_selectable(&mut self, selectable: bool, cx: &mut Context<Self>) {
143        self.selectable = selectable;
144        cx.notify();
145    }
146
147    pub fn delegate(&self) -> &D {
148        &self.delegate
149    }
150
151    pub fn delegate_mut(&mut self) -> &mut D {
152        &mut self.delegate
153    }
154
155    /// Focus the list, if the list is searchable, focus the search input.
156    pub fn focus(&mut self, window: &mut Window, cx: &mut App) {
157        self.focus_handle(cx).focus(window, cx);
158    }
159
160    /// Return true if either the list or the search input is focused.
161    pub(crate) fn is_focused(&self, window: &Window, cx: &App) -> bool {
162        self.focus_handle.is_focused(window) || self.query_input.focus_handle(cx).is_focused(window)
163    }
164
165    /// Set the selected index of the list,
166    /// this will also scroll to the selected item.
167    pub(crate) fn _set_selected_index(
168        &mut self,
169        ix: Option<IndexPath>,
170        window: &mut Window,
171        cx: &mut Context<Self>,
172    ) {
173        if !self.selectable {
174            return;
175        }
176
177        self.selected_index = ix;
178        self.delegate.set_selected_index(ix, window, cx);
179        self.scroll_to_selected_item(window, cx);
180    }
181
182    /// Set the selected index of the list,
183    /// this method will not scroll to the selected item.
184    pub fn set_selected_index(
185        &mut self,
186        ix: Option<IndexPath>,
187        window: &mut Window,
188        cx: &mut Context<Self>,
189    ) {
190        self.selected_index = ix;
191        self.delegate.set_selected_index(ix, window, cx);
192    }
193
194    pub fn selected_index(&self) -> Option<IndexPath> {
195        self.selected_index
196    }
197
198    /// Set the index of the item that has been right clicked.
199    pub fn set_right_clicked_index(
200        &mut self,
201        ix: Option<IndexPath>,
202        window: &mut Window,
203        cx: &mut Context<Self>,
204    ) {
205        self.mouse_right_clicked_index = ix;
206        self.delegate.set_right_clicked_index(ix, window, cx);
207    }
208
209    /// Returns the index of the item that has been right clicked.
210    pub fn right_clicked_index(&self) -> Option<IndexPath> {
211        self.mouse_right_clicked_index
212    }
213
214    /// Set the query text of the search input, this will trigger a search.
215    pub fn set_query(&mut self, query: &str, window: &mut Window, cx: &mut Context<Self>) {
216        let query = query.to_string();
217        self.query_input.update(cx, |input, cx| {
218            input.set_value(query.clone(), window, cx);
219        });
220
221        // `set_value` does not emit `InputEvent::Change`, so start the search here.
222        self.start_search(query.trim().to_string(), window, cx);
223    }
224
225    /// Set a specific list item for measurement.
226    pub fn set_item_to_measure_index(
227        &mut self,
228        ix: IndexPath,
229        _: &mut Window,
230        cx: &mut Context<Self>,
231    ) {
232        self.item_to_measure_index = ix;
233        cx.notify();
234    }
235
236    /// Scroll to the item at the given index.
237    pub fn scroll_to_item(
238        &mut self,
239        ix: IndexPath,
240        strategy: ScrollStrategy,
241        _: &mut Window,
242        cx: &mut Context<Self>,
243    ) {
244        if ix.section == 0 && ix.row == 0 {
245            // If the item is the first item, scroll to the top.
246            let mut offset = self.scroll_handle.base_handle().offset();
247            offset.y = px(0.);
248            self.scroll_handle.base_handle().set_offset(offset);
249            cx.notify();
250            return;
251        }
252        self.deferred_scroll_to_index = Some((ix, strategy));
253        cx.notify();
254    }
255
256    /// Get scroll handle
257    pub fn scroll_handle(&self) -> &VirtualListScrollHandle {
258        &self.scroll_handle
259    }
260
261    pub fn scroll_to_selected_item(&mut self, _: &mut Window, cx: &mut Context<Self>) {
262        if let Some(ix) = self.selected_index {
263            self.deferred_scroll_to_index = Some((ix, ScrollStrategy::Top));
264            cx.notify();
265        }
266    }
267
268    fn on_query_input_event(
269        &mut self,
270        state: &Entity<InputState>,
271        event: &InputEvent,
272        window: &mut Window,
273        cx: &mut Context<Self>,
274    ) {
275        match event {
276            InputEvent::Change => {
277                let text = state.read(cx).value();
278                let text = text.trim().to_string();
279                if Some(&text) == self.last_query.as_ref() {
280                    return;
281                }
282
283                self.start_search(text, window, cx);
284            }
285            _ => {}
286        }
287    }
288
289    fn start_search(&mut self, query: String, window: &mut Window, cx: &mut Context<Self>) {
290        self.set_searching(true, window, cx);
291        let search = self.delegate.perform_search(&query, window, cx);
292
293        if self.rows_cache.len() > 0 {
294            self._set_selected_index(Some(IndexPath::default()), window, cx);
295        } else {
296            self._set_selected_index(None, window, cx);
297        }
298
299        self._search_task = cx.spawn_in(window, async move |this, window| {
300            search.await;
301
302            _ = this.update_in(window, |this, _, _| {
303                this.scroll_handle.scroll_to_item(0, ScrollStrategy::Top);
304                this.last_query = Some(query);
305            });
306
307            // Always wait 100ms to avoid flicker
308            window
309                .background_executor()
310                .timer(Duration::from_millis(100))
311                .await;
312            _ = this.update_in(window, |this, window, cx| {
313                this.set_searching(false, window, cx);
314            });
315        });
316    }
317
318    fn set_searching(&mut self, searching: bool, window: &mut Window, cx: &mut Context<Self>) {
319        self.query_input
320            .update(cx, |input, cx| input.set_loading(searching, window, cx));
321    }
322
323    /// Dispatch delegate's `load_more` method when the
324    /// visible range is near the end.
325    fn load_more_if_need(
326        &mut self,
327        entities_count: usize,
328        visible_end: usize,
329        window: &mut Window,
330        cx: &mut Context<Self>,
331    ) {
332        // FIXME: Here need void sections items count.
333
334        let threshold = self.delegate.load_more_threshold();
335        // Securely handle subtract logic to prevent attempt
336        // to subtract with overflow
337        if visible_end >= entities_count.saturating_sub(threshold) {
338            if !self.delegate.has_more(cx) {
339                return;
340            }
341
342            self._load_more_task = cx.spawn_in(window, async move |view, cx| {
343                _ = view.update_in(cx, |view, window, cx| {
344                    view.delegate.load_more(window, cx);
345                });
346            });
347        }
348    }
349
350    pub(crate) fn reset_on_cancel(mut self, reset: bool) -> Self {
351        self.reset_on_cancel = reset;
352        self
353    }
354
355    fn on_action_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
356        cx.propagate();
357        if self.reset_on_cancel {
358            self._set_selected_index(None, window, cx);
359        }
360
361        self.delegate.cancel(window, cx);
362        cx.emit(ListEvent::Cancel);
363        cx.notify();
364    }
365
366    fn on_action_confirm(
367        &mut self,
368        confirm: &Confirm,
369        window: &mut Window,
370        cx: &mut Context<Self>,
371    ) {
372        if self.rows_cache.len() == 0 {
373            return;
374        }
375
376        let Some(ix) = self.selected_index else {
377            return;
378        };
379
380        self.delegate
381            .set_selected_index(self.selected_index, window, cx);
382        self.delegate.confirm(confirm.secondary, window, cx);
383        cx.emit(ListEvent::Confirm(ix));
384        cx.notify();
385    }
386
387    fn select_item(&mut self, ix: IndexPath, window: &mut Window, cx: &mut Context<Self>) {
388        if !self.selectable {
389            return;
390        }
391
392        self.selected_index = Some(ix);
393        self.delegate.set_selected_index(Some(ix), window, cx);
394        self.scroll_to_selected_item(window, cx);
395        cx.emit(ListEvent::Select(ix));
396        cx.notify();
397    }
398
399    pub(crate) fn on_action_select_prev(
400        &mut self,
401        _: &SelectUp,
402        window: &mut Window,
403        cx: &mut Context<Self>,
404    ) {
405        if self.rows_cache.len() == 0 {
406            return;
407        }
408
409        let prev_ix = self.rows_cache.prev(self.selected_index);
410        self.select_item(prev_ix, window, cx);
411    }
412
413    pub(crate) fn on_action_select_next(
414        &mut self,
415        _: &SelectDown,
416        window: &mut Window,
417        cx: &mut Context<Self>,
418    ) {
419        if self.rows_cache.len() == 0 {
420            return;
421        }
422
423        let next_ix = self.rows_cache.next(self.selected_index);
424        self.select_item(next_ix, window, cx);
425    }
426
427    fn prepare_items_if_needed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
428        let sections_count = self.delegate.sections_count(cx).max(1);
429        let mut measured_size = MeasuredEntrySize::default();
430
431        // Measure the item_height and section header/footer height.
432        let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
433        measured_size.item_size = self
434            .render_list_item(self.item_to_measure_index, window, cx)
435            .into_any_element()
436            .layout_as_root(available_space, window, cx);
437
438        if let Some(mut el) = self
439            .delegate
440            .render_section_header(0, window, cx)
441            .map(|r| r.into_any_element())
442        {
443            measured_size.section_header_size = el.layout_as_root(available_space, window, cx);
444        }
445        if let Some(mut el) = self
446            .delegate
447            .render_section_footer(0, window, cx)
448            .map(|r| r.into_any_element())
449        {
450            measured_size.section_footer_size = el.layout_as_root(available_space, window, cx);
451        }
452
453        self.rows_cache
454            .prepare_if_needed(sections_count, measured_size, cx, |section_ix, cx| {
455                self.delegate.items_count(section_ix, cx)
456            });
457    }
458
459    fn render_list_item(
460        &mut self,
461        ix: IndexPath,
462        window: &mut Window,
463        cx: &mut Context<Self>,
464    ) -> impl IntoElement {
465        let selectable = self.selectable;
466        let selected = self.selected_index.map(|s| s.eq_row(ix)).unwrap_or(false);
467        let mouse_right_clicked = self
468            .mouse_right_clicked_index
469            .map(|s| s.eq_row(ix))
470            .unwrap_or(false);
471        let id = SharedString::from(format!("list-item-{}", ix));
472
473        let total_items = self.rows_cache.items_count();
474
475        div()
476            .id(id)
477            .role(Role::ListItem)
478            .aria_position_in_set(ix.row + 1)
479            .aria_size_of_set(total_items)
480            .aria_selected(selected)
481            .w_full()
482            .relative()
483            .overflow_hidden()
484            .children(self.delegate.render_item(ix, window, cx).map(|item| {
485                item.selected(selected)
486                    .secondary_selected(mouse_right_clicked)
487            }))
488            .when(selectable, |this| {
489                this.on_click(cx.listener(move |this, e: &ClickEvent, window, cx| {
490                    this.set_right_clicked_index(None, window, cx);
491                    this.selected_index = Some(ix);
492                    this.on_action_confirm(
493                        &Confirm {
494                            secondary: e.modifiers().secondary(),
495                        },
496                        window,
497                        cx,
498                    );
499                }))
500                .on_mouse_down(
501                    MouseButton::Right,
502                    cx.listener(move |this, _, window, cx| {
503                        this.set_right_clicked_index(Some(ix), window, cx);
504                        cx.notify();
505                    }),
506                )
507            })
508    }
509
510    fn render_items(
511        &mut self,
512        items_count: usize,
513        entities_count: usize,
514        window: &mut Window,
515        cx: &mut Context<Self>,
516    ) -> impl IntoElement {
517        let rows_cache = self.rows_cache.clone();
518        let scrollbar_visible = self.options.scrollbar_visible;
519        let scroll_handle = self.scroll_handle.clone();
520        let item_to_measure_index = rows_cache
521            .position_of(&self.item_to_measure_index)
522            .or_else(|| rows_cache.first_entry_position())
523            .unwrap_or(0);
524
525        v_flex()
526            .flex_grow_1()
527            .relative()
528            .size_full()
529            .when_some(self.options.max_height, |this, h| this.max_h(h))
530            .overflow_hidden()
531            .when(items_count == 0, |this| {
532                this.child(self.delegate.render_empty(window, cx))
533            })
534            .when(items_count > 0, {
535                |this| {
536                    this.child(
537                        v_virtual_list(
538                            cx.entity(),
539                            "virtual-list",
540                            rows_cache.entries_sizes.clone(),
541                            move |list, visible_range: Range<usize>, window, cx| {
542                                list.load_more_if_need(
543                                    entities_count,
544                                    visible_range.end,
545                                    window,
546                                    cx,
547                                );
548
549                                // NOTE: Here the v_virtual_list would not able to have gap_y,
550                                // because the section header, footer is always have rendered as a empty child item,
551                                // even the delegate give a None result.
552
553                                visible_range
554                                    .map(|ix| {
555                                        let Some(entry) = rows_cache.get(ix) else {
556                                            return div();
557                                        };
558
559                                        div().children(match entry {
560                                            RowEntry::Entry(index) => Some(
561                                                list.render_list_item(index, window, cx)
562                                                    .into_any_element(),
563                                            ),
564                                            RowEntry::SectionHeader(section_ix) => list
565                                                .delegate_mut()
566                                                .render_section_header(section_ix, window, cx)
567                                                .map(|r| r.into_any_element()),
568                                            RowEntry::SectionFooter(section_ix) => list
569                                                .delegate_mut()
570                                                .render_section_footer(section_ix, window, cx)
571                                                .map(|r| r.into_any_element()),
572                                        })
573                                    })
574                                    .collect::<Vec<_>>()
575                            },
576                        )
577                        .with_item_to_measure_index(item_to_measure_index)
578                        .paddings(self.options.paddings.clone())
579                        .when(self.options.max_height.is_some(), |this| {
580                            this.with_sizing_behavior(ListSizingBehavior::Infer)
581                        })
582                        .track_scroll(&scroll_handle)
583                        .into_any_element(),
584                    )
585                }
586            })
587            .when(scrollbar_visible, |this| {
588                this.child(Scrollbar::vertical(&scroll_handle))
589            })
590    }
591}
592
593impl<D> Focusable for ListState<D>
594where
595    D: ListDelegate,
596{
597    fn focus_handle(&self, cx: &App) -> FocusHandle {
598        if self.searchable {
599            self.query_input.focus_handle(cx)
600        } else {
601            self.focus_handle.clone()
602        }
603    }
604}
605impl<D> EventEmitter<ListEvent> for ListState<D> where D: ListDelegate {}
606impl<D> Render for ListState<D>
607where
608    D: ListDelegate,
609{
610    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
611        self.prepare_items_if_needed(window, cx);
612
613        // Scroll to the selected item if it is set.
614        if let Some((ix, strategy)) = self.deferred_scroll_to_index.take() {
615            if let Some(item_ix) = self.rows_cache.position_of(&ix) {
616                self.scroll_handle.scroll_to_item(item_ix, strategy);
617            }
618        }
619
620        let loading = self.delegate().loading(cx);
621        let query_input = if self.searchable {
622            // sync placeholder
623            if let Some(placeholder) = &self.options.search_placeholder {
624                self.query_input.update(cx, |input, cx| {
625                    input.set_placeholder(placeholder.clone(), window, cx);
626                });
627            }
628            Some(self.query_input.clone())
629        } else {
630            None
631        };
632
633        let loading_view = if loading {
634            Some(self.delegate.render_loading(window, cx).into_any_element())
635        } else {
636            None
637        };
638        let initial_view = if let Some(input) = &query_input {
639            if input.read(cx).value().is_empty() {
640                self.delegate.render_initial(window, cx)
641            } else {
642                None
643            }
644        } else {
645            None
646        };
647        let items_count = self.rows_cache.items_count();
648        let entities_count = self.rows_cache.len();
649        let mouse_right_clicked_index = self.mouse_right_clicked_index;
650
651        v_flex()
652            .key_context("List")
653            .id("list-state")
654            .track_focus(&self.focus_handle)
655            .size_full()
656            .relative()
657            .overflow_hidden()
658            .when_some(query_input, |this, input| {
659                this.child(
660                    div()
661                        .map(|this| match self.options.size {
662                            Size::Small => this.px_1p5(),
663                            _ => this.px_2(),
664                        })
665                        .border_b_1()
666                        .border_color(cx.theme().border)
667                        .child(
668                            Input::new(&input)
669                                .with_size(self.options.size)
670                                .prefix(
671                                    Icon::new(IconName::Search)
672                                        .text_color(cx.theme().muted_foreground),
673                                )
674                                .cleanable(true)
675                                .p_0()
676                                .appearance(false),
677                        ),
678                )
679            })
680            .when(!loading, |this| {
681                this.on_action(cx.listener(Self::on_action_cancel))
682                    .on_action(cx.listener(Self::on_action_confirm))
683                    .on_action(cx.listener(Self::on_action_select_next))
684                    .on_action(cx.listener(Self::on_action_select_prev))
685                    .map(|this| {
686                        if let Some(view) = initial_view {
687                            this.child(view)
688                        } else {
689                            this.child(self.render_items(items_count, entities_count, window, cx))
690                        }
691                    })
692                    // Click out to cancel right clicked row
693                    .when(mouse_right_clicked_index.is_some(), |this| {
694                        this.on_mouse_down_out(cx.listener(|this, _, window, cx| {
695                            this.set_right_clicked_index(None, window, cx);
696                            cx.notify();
697                        }))
698                    })
699            })
700            .children(loading_view)
701    }
702}
703
704/// The List element.
705#[derive(IntoElement)]
706pub struct List<D: ListDelegate + 'static> {
707    state: Entity<ListState<D>>,
708    style: StyleRefinement,
709    options: ListOptions,
710}
711
712impl<D> List<D>
713where
714    D: ListDelegate + 'static,
715{
716    /// Create a new List element with the given ListState entity.
717    pub fn new(state: &Entity<ListState<D>>) -> Self {
718        Self {
719            state: state.clone(),
720            style: StyleRefinement::default(),
721            options: ListOptions::default(),
722        }
723    }
724
725    /// Set whether the scrollbar is visible, default is `true`.
726    pub fn scrollbar_visible(mut self, visible: bool) -> Self {
727        self.options.scrollbar_visible = visible;
728        self
729    }
730
731    /// Sets the placeholder text for the search input.
732    pub fn search_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
733        self.options.search_placeholder = Some(placeholder.into());
734        self
735    }
736}
737
738impl<D> Styled for List<D>
739where
740    D: ListDelegate + 'static,
741{
742    fn style(&mut self) -> &mut StyleRefinement {
743        &mut self.style
744    }
745}
746
747impl<D> Sizable for List<D>
748where
749    D: ListDelegate + 'static,
750{
751    fn with_size(mut self, size: impl Into<Size>) -> Self {
752        self.options.size = size.into();
753        self
754    }
755}
756
757impl<D> RenderOnce for List<D>
758where
759    D: ListDelegate + 'static,
760{
761    fn render(mut self, _: &mut Window, cx: &mut App) -> impl IntoElement {
762        // Take paddings, max_height to options, and clear them from style,
763        // because they would be applied to the inner virtual list.
764        self.options.paddings = self.style.padding.clone();
765        self.options.max_height = self.style.max_size.height;
766        self.style.padding = EdgesRefinement::default();
767        self.style.max_size.height = None;
768
769        self.state.update(cx, |state, _| {
770            state.options = self.options;
771        });
772
773        div()
774            .id("list")
775            .role(Role::List)
776            .size_full()
777            .refine_style(&self.style)
778            .child(self.state.clone())
779    }
780}