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