highlighted_workspace_location.rs

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