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}