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