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