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