highlighted_workspace_location.rs

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