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, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, View,
9 ViewContext, ViewHandle,
10};
11use language::{Buffer, LanguageRegistry};
12use picker::{Picker, PickerDelegate};
13use project::Project;
14use settings::Settings;
15use std::sync::Arc;
16use util::ResultExt;
17use workspace::{AppState, Workspace};
18
19actions!(language_selector, [Toggle]);
20
21pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
22 Picker::<LanguageSelector>::init(cx);
23 cx.add_action({
24 let language_registry = app_state.languages.clone();
25 move |workspace, _: &Toggle, cx| {
26 LanguageSelector::toggle(workspace, language_registry.clone(), cx)
27 }
28 });
29}
30
31pub enum Event {
32 Dismissed,
33}
34
35pub struct LanguageSelector {
36 buffer: ModelHandle<Buffer>,
37 project: ModelHandle<Project>,
38 language_registry: Arc<LanguageRegistry>,
39 candidates: Vec<StringMatchCandidate>,
40 matches: Vec<StringMatch>,
41 picker: ViewHandle<Picker<Self>>,
42 selected_index: usize,
43}
44
45impl LanguageSelector {
46 fn new(
47 buffer: ModelHandle<Buffer>,
48 project: ModelHandle<Project>,
49 language_registry: Arc<LanguageRegistry>,
50 cx: &mut ViewContext<Self>,
51 ) -> Self {
52 let handle = cx.weak_handle();
53 let picker = cx.add_view(|cx| Picker::new("Select Language...", handle, cx));
54
55 let candidates = language_registry
56 .language_names()
57 .into_iter()
58 .enumerate()
59 .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
60 .collect::<Vec<_>>();
61 let mut matches = candidates
62 .iter()
63 .map(|candidate| StringMatch {
64 candidate_id: candidate.id,
65 score: 0.,
66 positions: Default::default(),
67 string: candidate.string.clone(),
68 })
69 .collect::<Vec<_>>();
70 matches.sort_unstable_by(|mat1, mat2| mat1.string.cmp(&mat2.string));
71
72 Self {
73 buffer,
74 project,
75 language_registry,
76 candidates,
77 matches,
78 picker,
79 selected_index: 0,
80 }
81 }
82
83 fn toggle(
84 workspace: &mut Workspace,
85 registry: Arc<LanguageRegistry>,
86 cx: &mut ViewContext<Workspace>,
87 ) {
88 if let Some((_, buffer, _)) = workspace
89 .active_item(cx)
90 .and_then(|active_item| active_item.act_as::<Editor>(cx))
91 .and_then(|editor| editor.read(cx).active_excerpt(cx))
92 {
93 workspace.toggle_modal(cx, |workspace, cx| {
94 let project = workspace.project().clone();
95 let this = cx.add_view(|cx| Self::new(buffer, project, registry, cx));
96 cx.subscribe(&this, Self::on_event).detach();
97 this
98 });
99 }
100 }
101
102 fn on_event(
103 workspace: &mut Workspace,
104 _: ViewHandle<LanguageSelector>,
105 event: &Event,
106 cx: &mut ViewContext<Workspace>,
107 ) {
108 match event {
109 Event::Dismissed => {
110 workspace.dismiss_modal(cx);
111 }
112 }
113 }
114}
115
116impl Entity for LanguageSelector {
117 type Event = Event;
118}
119
120impl View for LanguageSelector {
121 fn ui_name() -> &'static str {
122 "LanguageSelector"
123 }
124
125 fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
126 ChildView::new(&self.picker, cx).boxed()
127 }
128
129 fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
130 if cx.is_self_focused() {
131 cx.focus(&self.picker);
132 }
133 }
134}
135
136impl PickerDelegate for LanguageSelector {
137 fn match_count(&self) -> usize {
138 self.matches.len()
139 }
140
141 fn confirm(&mut self, cx: &mut ViewContext<Self>) {
142 if let Some(mat) = self.matches.get(self.selected_index) {
143 let language_name = &self.candidates[mat.candidate_id].string;
144 let language = self.language_registry.language_for_name(language_name);
145 let project = self.project.downgrade();
146 let buffer = self.buffer.downgrade();
147 cx.spawn_weak(|_, mut cx| async move {
148 let language = language.await?;
149 let project = project
150 .upgrade(&cx)
151 .ok_or_else(|| anyhow!("project was dropped"))?;
152 let buffer = buffer
153 .upgrade(&cx)
154 .ok_or_else(|| anyhow!("buffer was dropped"))?;
155 project.update(&mut cx, |project, cx| {
156 project.set_language_for_buffer(&buffer, language, cx);
157 });
158 anyhow::Ok(())
159 })
160 .detach_and_log_err(cx);
161 }
162
163 cx.emit(Event::Dismissed);
164 }
165
166 fn dismissed(&mut self, cx: &mut ViewContext<Self>) {
167 cx.emit(Event::Dismissed);
168 }
169
170 fn selected_index(&self) -> usize {
171 self.selected_index
172 }
173
174 fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
175 self.selected_index = ix;
176 }
177
178 fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
179 let background = cx.background().clone();
180 let candidates = self.candidates.clone();
181 cx.spawn_weak(|this, mut cx| async move {
182 let matches = if query.is_empty() {
183 candidates
184 .into_iter()
185 .enumerate()
186 .map(|(index, candidate)| StringMatch {
187 candidate_id: index,
188 string: candidate.string,
189 positions: Vec::new(),
190 score: 0.0,
191 })
192 .collect()
193 } else {
194 match_strings(
195 &candidates,
196 &query,
197 false,
198 100,
199 &Default::default(),
200 background,
201 )
202 .await
203 };
204
205 if let Some(this) = this.upgrade(&cx) {
206 this.update(&mut cx, |this, cx| {
207 this.matches = matches;
208 this.selected_index = this
209 .selected_index
210 .min(this.matches.len().saturating_sub(1));
211 cx.notify();
212 })
213 .log_err();
214 }
215 })
216 }
217
218 fn render_match(
219 &self,
220 ix: usize,
221 mouse_state: &mut MouseState,
222 selected: bool,
223 cx: &AppContext,
224 ) -> Element<Picker<Self>> {
225 let settings = cx.global::<Settings>();
226 let theme = &settings.theme;
227 let mat = &self.matches[ix];
228 let style = theme.picker.item.style_for(mouse_state, selected);
229 let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
230 let mut label = mat.string.clone();
231 if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
232 label.push_str(" (current)");
233 }
234
235 Label::new(label, style.label.clone())
236 .with_highlights(mat.positions.clone())
237 .contained()
238 .with_style(style.container)
239 .boxed()
240 }
241}