highlighted_workspace_location.rs

  1use std::path::Path;
  2
  3use fuzzy::StringMatch;
  4use gpui::{
  5    elements::{Label, LabelStyle},
  6    AnyElement, Element,
  7};
  8use util::paths::PathExt;
  9use workspace::WorkspaceLocation;
 10
 11pub struct HighlightedText {
 12    pub text: String,
 13    pub highlight_positions: Vec<usize>,
 14    char_count: usize,
 15}
 16
 17impl HighlightedText {
 18    fn join(components: impl Iterator<Item = Self>, separator: &str) -> Self {
 19        let mut char_count = 0;
 20        let separator_char_count = separator.chars().count();
 21        let mut text = String::new();
 22        let mut highlight_positions = Vec::new();
 23        for component in components {
 24            if char_count != 0 {
 25                text.push_str(separator);
 26                char_count += separator_char_count;
 27            }
 28
 29            highlight_positions.extend(
 30                component
 31                    .highlight_positions
 32                    .iter()
 33                    .map(|position| position + char_count),
 34            );
 35            text.push_str(&component.text);
 36            char_count += component.text.chars().count();
 37        }
 38
 39        Self {
 40            text,
 41            highlight_positions,
 42            char_count,
 43        }
 44    }
 45
 46    pub fn render<V: 'static>(self, style: impl Into<LabelStyle>) -> AnyElement<V> {
 47        Label::new(self.text, style)
 48            .with_highlights(self.highlight_positions)
 49            .into_any()
 50    }
 51}
 52
 53pub struct HighlightedWorkspaceLocation {
 54    pub names: HighlightedText,
 55    pub paths: Vec<HighlightedText>,
 56}
 57
 58impl HighlightedWorkspaceLocation {
 59    pub fn new(string_match: &StringMatch, location: &WorkspaceLocation) -> Self {
 60        let mut path_start_offset = 0;
 61        let (names, paths): (Vec<_>, Vec<_>) = location
 62            .paths()
 63            .iter()
 64            .map(|path| {
 65                let path = path.compact();
 66                let highlighted_text = Self::highlights_for_path(
 67                    path.as_ref(),
 68                    &string_match.positions,
 69                    path_start_offset,
 70                );
 71
 72                path_start_offset += highlighted_text.1.char_count;
 73
 74                highlighted_text
 75            })
 76            .unzip();
 77
 78        Self {
 79            names: HighlightedText::join(names.into_iter().filter_map(|name| name), ", "),
 80            paths,
 81        }
 82    }
 83
 84    // Compute the highlighted text for the name and path
 85    fn highlights_for_path(
 86        path: &Path,
 87        match_positions: &Vec<usize>,
 88        path_start_offset: usize,
 89    ) -> (Option<HighlightedText>, HighlightedText) {
 90        let path_string = path.to_string_lossy();
 91        let path_char_count = path_string.chars().count();
 92        // Get the subset of match highlight positions that line up with the given path.
 93        // Also adjusts them to start at the path start
 94        let path_positions = match_positions
 95            .iter()
 96            .copied()
 97            .skip_while(|position| *position < path_start_offset)
 98            .take_while(|position| *position < path_start_offset + path_char_count)
 99            .map(|position| position - path_start_offset)
100            .collect::<Vec<_>>();
101
102        // Again subset the highlight positions to just those that line up with the file_name
103        // again adjusted to the start of the file_name
104        let file_name_text_and_positions = path.file_name().map(|file_name| {
105            let text = file_name.to_string_lossy();
106            let char_count = text.chars().count();
107            let file_name_start = path_char_count - char_count;
108            let highlight_positions = path_positions
109                .iter()
110                .copied()
111                .skip_while(|position| *position < file_name_start)
112                .take_while(|position| *position < file_name_start + char_count)
113                .map(|position| position - file_name_start)
114                .collect::<Vec<_>>();
115            HighlightedText {
116                text: text.to_string(),
117                highlight_positions,
118                char_count,
119            }
120        });
121
122        (
123            file_name_text_and_positions,
124            HighlightedText {
125                text: path_string.to_string(),
126                highlight_positions: path_positions,
127                char_count: path_char_count,
128            },
129        )
130    }
131}