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