on master help sign in
DynamicPicker: Recalculate column widths for new options (#6004)

This fixes blank row text in a DynamicPicker which is initially given no options. This can happen for language servers which respond to the workspace symbol request for an empty query with an empty list of symbols, and that behavior is somewhat common since returning all symbols as the spec suggests is very expensive. For empty options, `Picker::new` calculated the widths of each column as 0. We can recalculate the column widths when the new options are set to fix this. This refactor is also a good opportunity to formalize setting new options on a picker: besides setting the new options and calculating column widths we also want to reset the cursor and rescore the options.

commit0c6d25acae86cf87356fd3b3b59a25904226e41b authorMichael Davis <mcarsondavis@gmail.com> dateTue 7 Mar 2023, 19:51 (4 years ago) parent6dc017b
1 file changed+37−25
helix-term/src/ui/picker.rs+37−25
helix-term/src/ui/picker.rs+37−25
@@ -435,26 +435,6 @@ impl<T: Item> Picker<T> {
435435 |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {},
436436 );
437437
438− let n = options
439− .first()
440− .map(|option| option.format(&editor_data).cells.len())
441− .unwrap_or_default();
442− let max_lens = options.iter().fold(vec![0; n], |mut acc, option| {
443− let row = option.format(&editor_data);
444− // maintain max for each column
445− for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
446− let width = cell.content.width();
447− if width > *acc {
448− *acc = width;
449− }
450− }
451− acc
452− });
453− let widths = max_lens
454− .into_iter()
455− .map(|len| Constraint::Length(len as u16))
456− .collect();
457−
458438 let mut picker = Self {
459439 options,
460440 editor_data,
@@ -467,10 +447,12 @@ impl<T: Item> Picker<T> {
467447 show_preview: true,
468448 callback_fn: Box::new(callback_fn),
469449 completion_height: 0,
470− widths,
450+ widths: Vec::new(),
471451 };
472452
473− // scoring on empty input:
453+ picker.calculate_column_widths();
454+
455+ // scoring on empty input
474456 // TODO: just reuse score()
475457 picker
476458 .matches
@@ -486,6 +468,38 @@ impl<T: Item> Picker<T> {
486468 picker
487469 }
488470
471+ pub fn set_options(&mut self, new_options: Vec<T>) {
472+ self.options = new_options;
473+ self.cursor = 0;
474+ self.force_score();
475+ self.calculate_column_widths();
476+ }
477+
478+ /// Calculate the width constraints using the maximum widths of each column
479+ /// for the current options.
480+ fn calculate_column_widths(&mut self) {
481+ let n = self
482+ .options
483+ .first()
484+ .map(|option| option.format(&self.editor_data).cells.len())
485+ .unwrap_or_default();
486+ let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| {
487+ let row = option.format(&self.editor_data);
488+ // maintain max for each column
489+ for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
490+ let width = cell.content.width();
491+ if width > *acc {
492+ *acc = width;
493+ }
494+ }
495+ acc
496+ });
497+ self.widths = max_lens
498+ .into_iter()
499+ .map(|len| Constraint::Length(len as u16))
500+ .collect();
501+ }
502+
489503 pub fn score(&mut self) {
490504 let pattern = self.prompt.line();
491505
@@ -931,9 +945,7 @@ impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
931945 Some(overlay) => &mut overlay.content.file_picker.picker,
932946 None => return,
933947 };
934− picker.options = new_options;
935− picker.cursor = 0;
936− picker.force_score();
948+ picker.set_options(new_options);
937949 editor.reset_idle_timer();
938950 }));
939951 anyhow::Ok(callback)