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