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