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::{prelude::*, HighlightedLabel, ListItem};
16use util::ResultExt;
17use workspace::{ModalView, Workspace};
18
19actions!(language_selector, [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().w(rems(34.)).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 {}
84impl ModalView for LanguageSelector {}
85
86pub struct LanguageSelectorDelegate {
87 language_selector: WeakView<LanguageSelector>,
88 buffer: Model<Buffer>,
89 project: Model<Project>,
90 language_registry: Arc<LanguageRegistry>,
91 candidates: Vec<StringMatchCandidate>,
92 matches: Vec<StringMatch>,
93 selected_index: usize,
94}
95
96impl LanguageSelectorDelegate {
97 fn new(
98 language_selector: WeakView<LanguageSelector>,
99 buffer: Model<Buffer>,
100 project: Model<Project>,
101 language_registry: Arc<LanguageRegistry>,
102 ) -> Self {
103 let candidates = language_registry
104 .language_names()
105 .into_iter()
106 .enumerate()
107 .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
108 .collect::<Vec<_>>();
109
110 Self {
111 language_selector,
112 buffer,
113 project,
114 language_registry,
115 candidates,
116 matches: vec![],
117 selected_index: 0,
118 }
119 }
120}
121
122impl PickerDelegate for LanguageSelectorDelegate {
123 type ListItem = ListItem;
124
125 fn placeholder_text(&self) -> Arc<str> {
126 "Select a language...".into()
127 }
128
129 fn match_count(&self) -> usize {
130 self.matches.len()
131 }
132
133 fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
134 if let Some(mat) = self.matches.get(self.selected_index) {
135 let language_name = &self.candidates[mat.candidate_id].string;
136 let language = self.language_registry.language_for_name(language_name);
137 let project = self.project.downgrade();
138 let buffer = self.buffer.downgrade();
139 cx.spawn(|_, mut cx| async move {
140 let language = language.await?;
141 let project = project
142 .upgrade()
143 .ok_or_else(|| anyhow!("project was dropped"))?;
144 let buffer = buffer
145 .upgrade()
146 .ok_or_else(|| anyhow!("buffer was dropped"))?;
147 project.update(&mut cx, |project, cx| {
148 project.set_language_for_buffer(&buffer, language, cx);
149 })
150 })
151 .detach_and_log_err(cx);
152 }
153 self.dismissed(cx);
154 }
155
156 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
157 self.language_selector
158 .update(cx, |_, cx| cx.emit(DismissEvent))
159 .log_err();
160 }
161
162 fn selected_index(&self) -> usize {
163 self.selected_index
164 }
165
166 fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
167 self.selected_index = ix;
168 }
169
170 fn update_matches(
171 &mut self,
172 query: String,
173 cx: &mut ViewContext<Picker<Self>>,
174 ) -> gpui::Task<()> {
175 let background = cx.background_executor().clone();
176 let candidates = self.candidates.clone();
177 cx.spawn(|this, mut cx| async move {
178 let matches = if query.is_empty() {
179 candidates
180 .into_iter()
181 .enumerate()
182 .map(|(index, candidate)| StringMatch {
183 candidate_id: index,
184 string: candidate.string,
185 positions: Vec::new(),
186 score: 0.0,
187 })
188 .collect()
189 } else {
190 match_strings(
191 &candidates,
192 &query,
193 false,
194 100,
195 &Default::default(),
196 background,
197 )
198 .await
199 };
200
201 this.update(&mut cx, |this, cx| {
202 let delegate = &mut this.delegate;
203 delegate.matches = matches;
204 delegate.selected_index = delegate
205 .selected_index
206 .min(delegate.matches.len().saturating_sub(1));
207 cx.notify();
208 })
209 .log_err();
210 })
211 }
212
213 fn render_match(
214 &self,
215 ix: usize,
216 selected: bool,
217 cx: &mut ViewContext<Picker<Self>>,
218 ) -> Option<Self::ListItem> {
219 let mat = &self.matches[ix];
220 let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
221 let mut label = mat.string.clone();
222 if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
223 label.push_str(" (current)");
224 }
225
226 Some(
227 ListItem::new(ix)
228 .inset(true)
229 .selected(selected)
230 .child(HighlightedLabel::new(label, mat.positions.clone())),
231 )
232 }
233}