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::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}