language_selector.rs

  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}