1use std::path::Path;
2
3use fuzzy::StringMatch;
4use ui::{prelude::*, HighlightedLabel};
5use util::paths::PathExt;
6use workspace::WorkspaceLocation;
7
8#[derive(Clone, 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
51#[derive(Clone)]
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 = path.compact();
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}