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}