1mod active_buffer_language;
2
3pub use active_buffer_language::ActiveBufferLanguage;
4use anyhow::anyhow;
5use editor::Editor;
6use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
7use gpui::{
8 actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
9 ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
10};
11use language::{Buffer, LanguageRegistry};
12use picker::{Picker, PickerDelegate};
13use project::Project;
14use std::sync::Arc;
15use ui::{v_stack, HighlightedLabel, ListItem, Selectable};
16use util::ResultExt;
17use workspace::Workspace;
18
19actions!(Toggle);
20
21pub fn init(cx: &mut AppContext) {
22 cx.observe_new_views(LanguageSelector::register).detach();
23}
24
25pub struct LanguageSelector {
26 picker: View<Picker<LanguageSelectorDelegate>>,
27}
28
29impl LanguageSelector {
30 fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
31 workspace.register_action(move |workspace, _: &Toggle, cx| {
32 Self::toggle(workspace, cx);
33 });
34 }
35
36 fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<()> {
37 let registry = workspace.app_state().languages.clone();
38 let (_, buffer, _) = workspace
39 .active_item(cx)?
40 .act_as::<Editor>(cx)?
41 .read(cx)
42 .active_excerpt(cx)?;
43 let project = workspace.project().clone();
44
45 workspace.toggle_modal(cx, move |cx| {
46 LanguageSelector::new(buffer, project, registry, cx)
47 });
48 Some(())
49 }
50
51 fn new(
52 buffer: Model<Buffer>,
53 project: Model<Project>,
54 language_registry: Arc<LanguageRegistry>,
55 cx: &mut ViewContext<Self>,
56 ) -> Self {
57 let delegate = LanguageSelectorDelegate::new(
58 cx.view().downgrade(),
59 buffer,
60 project,
61 language_registry,
62 );
63
64 let picker = cx.build_view(|cx| Picker::new(delegate, cx));
65 Self { picker }
66 }
67}
68
69impl Render for LanguageSelector {
70 type Element = Div;
71
72 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
73 v_stack().min_w_96().child(self.picker.clone())
74 }
75}
76
77impl FocusableView for LanguageSelector {
78 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
79 self.picker.focus_handle(cx)
80 }
81}
82
83impl EventEmitter<DismissEvent> for LanguageSelector {}
84
85pub struct LanguageSelectorDelegate {
86 language_selector: WeakView<LanguageSelector>,
87 buffer: Model<Buffer>,
88 project: Model<Project>,
89 language_registry: Arc<LanguageRegistry>,
90 candidates: Vec<StringMatchCandidate>,
91 matches: Vec<StringMatch>,
92 selected_index: usize,
93}
94
95impl LanguageSelectorDelegate {
96 fn new(
97 language_selector: WeakView<LanguageSelector>,
98 buffer: Model<Buffer>,
99 project: Model<Project>,
100 language_registry: Arc<LanguageRegistry>,
101 ) -> Self {
102 let candidates = language_registry
103 .language_names()
104 .into_iter()
105 .enumerate()
106 .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
107 .collect::<Vec<_>>();
108
109 Self {
110 language_selector,
111 buffer,
112 project,
113 language_registry,
114 candidates,
115 matches: vec![],
116 selected_index: 0,
117 }
118 }
119}
120
121impl PickerDelegate for LanguageSelectorDelegate {
122 type ListItem = ListItem;
123
124 fn placeholder_text(&self) -> Arc<str> {
125 "Select a language...".into()
126 }
127
128 fn match_count(&self) -> usize {
129 self.matches.len()
130 }
131
132 fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
133 if let Some(mat) = self.matches.get(self.selected_index) {
134 let language_name = &self.candidates[mat.candidate_id].string;
135 let language = self.language_registry.language_for_name(language_name);
136 let project = self.project.downgrade();
137 let buffer = self.buffer.downgrade();
138 cx.spawn(|_, mut cx| async move {
139 let language = language.await?;
140 let project = project
141 .upgrade()
142 .ok_or_else(|| anyhow!("project was dropped"))?;
143 let buffer = buffer
144 .upgrade()
145 .ok_or_else(|| anyhow!("buffer was dropped"))?;
146 project.update(&mut cx, |project, cx| {
147 project.set_language_for_buffer(&buffer, language, cx);
148 })
149 })
150 .detach_and_log_err(cx);
151 }
152 self.dismissed(cx);
153 }
154
155 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
156 self.language_selector
157 .update(cx, |_, cx| cx.emit(DismissEvent))
158 .log_err();
159 }
160
161 fn selected_index(&self) -> usize {
162 self.selected_index
163 }
164
165 fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
166 self.selected_index = ix;
167 }
168
169 fn update_matches(
170 &mut self,
171 query: String,
172 cx: &mut ViewContext<Picker<Self>>,
173 ) -> gpui::Task<()> {
174 let background = cx.background_executor().clone();
175 let candidates = self.candidates.clone();
176 cx.spawn(|this, mut cx| async move {
177 let matches = if query.is_empty() {
178 candidates
179 .into_iter()
180 .enumerate()
181 .map(|(index, candidate)| StringMatch {
182 candidate_id: index,
183 string: candidate.string,
184 positions: Vec::new(),
185 score: 0.0,
186 })
187 .collect()
188 } else {
189 match_strings(
190 &candidates,
191 &query,
192 false,
193 100,
194 &Default::default(),
195 background,
196 )
197 .await
198 };
199
200 this.update(&mut cx, |this, cx| {
201 let delegate = &mut this.delegate;
202 delegate.matches = matches;
203 delegate.selected_index = delegate
204 .selected_index
205 .min(delegate.matches.len().saturating_sub(1));
206 cx.notify();
207 })
208 .log_err();
209 })
210 }
211
212 fn render_match(
213 &self,
214 ix: usize,
215 selected: bool,
216 cx: &mut ViewContext<Picker<Self>>,
217 ) -> Option<Self::ListItem> {
218 let mat = &self.matches[ix];
219 let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
220 let mut label = mat.string.clone();
221 if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
222 label.push_str(" (current)");
223 }
224
225 Some(
226 ListItem::new(ix)
227 .inset(true)
228 .selected(selected)
229 .child(HighlightedLabel::new(label, mat.positions.clone())),
230 )
231 }
232}