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::{
  8    actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, View,
  9    ViewContext, ViewHandle,
 10};
 11use language::{Buffer, LanguageRegistry};
 12use picker::{Picker, PickerDelegate};
 13use project::Project;
 14use settings::Settings;
 15use std::sync::Arc;
 16use util::ResultExt;
 17use workspace::{AppState, Workspace};
 18
 19actions!(language_selector, [Toggle]);
 20
 21pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 22    Picker::<LanguageSelector>::init(cx);
 23    cx.add_action({
 24        let language_registry = app_state.languages.clone();
 25        move |workspace, _: &Toggle, cx| {
 26            LanguageSelector::toggle(workspace, language_registry.clone(), cx)
 27        }
 28    });
 29}
 30
 31pub enum Event {
 32    Dismissed,
 33}
 34
 35pub struct LanguageSelector {
 36    buffer: ModelHandle<Buffer>,
 37    project: ModelHandle<Project>,
 38    language_registry: Arc<LanguageRegistry>,
 39    candidates: Vec<StringMatchCandidate>,
 40    matches: Vec<StringMatch>,
 41    picker: ViewHandle<Picker<Self>>,
 42    selected_index: usize,
 43}
 44
 45impl LanguageSelector {
 46    fn new(
 47        buffer: ModelHandle<Buffer>,
 48        project: ModelHandle<Project>,
 49        language_registry: Arc<LanguageRegistry>,
 50        cx: &mut ViewContext<Self>,
 51    ) -> Self {
 52        let handle = cx.weak_handle();
 53        let picker = cx.add_view(|cx| Picker::new("Select Language...", handle, cx));
 54
 55        let candidates = language_registry
 56            .language_names()
 57            .into_iter()
 58            .enumerate()
 59            .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
 60            .collect::<Vec<_>>();
 61        let mut matches = candidates
 62            .iter()
 63            .map(|candidate| StringMatch {
 64                candidate_id: candidate.id,
 65                score: 0.,
 66                positions: Default::default(),
 67                string: candidate.string.clone(),
 68            })
 69            .collect::<Vec<_>>();
 70        matches.sort_unstable_by(|mat1, mat2| mat1.string.cmp(&mat2.string));
 71
 72        Self {
 73            buffer,
 74            project,
 75            language_registry,
 76            candidates,
 77            matches,
 78            picker,
 79            selected_index: 0,
 80        }
 81    }
 82
 83    fn toggle(
 84        workspace: &mut Workspace,
 85        registry: Arc<LanguageRegistry>,
 86        cx: &mut ViewContext<Workspace>,
 87    ) {
 88        if let Some((_, buffer, _)) = workspace
 89            .active_item(cx)
 90            .and_then(|active_item| active_item.act_as::<Editor>(cx))
 91            .and_then(|editor| editor.read(cx).active_excerpt(cx))
 92        {
 93            workspace.toggle_modal(cx, |workspace, cx| {
 94                let project = workspace.project().clone();
 95                let this = cx.add_view(|cx| Self::new(buffer, project, registry, cx));
 96                cx.subscribe(&this, Self::on_event).detach();
 97                this
 98            });
 99        }
100    }
101
102    fn on_event(
103        workspace: &mut Workspace,
104        _: ViewHandle<LanguageSelector>,
105        event: &Event,
106        cx: &mut ViewContext<Workspace>,
107    ) {
108        match event {
109            Event::Dismissed => {
110                workspace.dismiss_modal(cx);
111            }
112        }
113    }
114}
115
116impl Entity for LanguageSelector {
117    type Event = Event;
118}
119
120impl View for LanguageSelector {
121    fn ui_name() -> &'static str {
122        "LanguageSelector"
123    }
124
125    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
126        ChildView::new(&self.picker, cx).boxed()
127    }
128
129    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
130        if cx.is_self_focused() {
131            cx.focus(&self.picker);
132        }
133    }
134}
135
136impl PickerDelegate for LanguageSelector {
137    fn match_count(&self) -> usize {
138        self.matches.len()
139    }
140
141    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
142        if let Some(mat) = self.matches.get(self.selected_index) {
143            let language_name = &self.candidates[mat.candidate_id].string;
144            let language = self.language_registry.language_for_name(language_name);
145            let project = self.project.downgrade();
146            let buffer = self.buffer.downgrade();
147            cx.spawn_weak(|_, mut cx| async move {
148                let language = language.await?;
149                let project = project
150                    .upgrade(&cx)
151                    .ok_or_else(|| anyhow!("project was dropped"))?;
152                let buffer = buffer
153                    .upgrade(&cx)
154                    .ok_or_else(|| anyhow!("buffer was dropped"))?;
155                project.update(&mut cx, |project, cx| {
156                    project.set_language_for_buffer(&buffer, language, cx);
157                });
158                anyhow::Ok(())
159            })
160            .detach_and_log_err(cx);
161        }
162
163        cx.emit(Event::Dismissed);
164    }
165
166    fn dismissed(&mut self, cx: &mut ViewContext<Self>) {
167        cx.emit(Event::Dismissed);
168    }
169
170    fn selected_index(&self) -> usize {
171        self.selected_index
172    }
173
174    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
175        self.selected_index = ix;
176    }
177
178    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
179        let background = cx.background().clone();
180        let candidates = self.candidates.clone();
181        cx.spawn_weak(|this, mut cx| async move {
182            let matches = if query.is_empty() {
183                candidates
184                    .into_iter()
185                    .enumerate()
186                    .map(|(index, candidate)| StringMatch {
187                        candidate_id: index,
188                        string: candidate.string,
189                        positions: Vec::new(),
190                        score: 0.0,
191                    })
192                    .collect()
193            } else {
194                match_strings(
195                    &candidates,
196                    &query,
197                    false,
198                    100,
199                    &Default::default(),
200                    background,
201                )
202                .await
203            };
204
205            if let Some(this) = this.upgrade(&cx) {
206                this.update(&mut cx, |this, cx| {
207                    this.matches = matches;
208                    this.selected_index = this
209                        .selected_index
210                        .min(this.matches.len().saturating_sub(1));
211                    cx.notify();
212                })
213                .log_err();
214            }
215        })
216    }
217
218    fn render_match(
219        &self,
220        ix: usize,
221        mouse_state: &mut MouseState,
222        selected: bool,
223        cx: &AppContext,
224    ) -> Element<Picker<Self>> {
225        let settings = cx.global::<Settings>();
226        let theme = &settings.theme;
227        let mat = &self.matches[ix];
228        let style = theme.picker.item.style_for(mouse_state, selected);
229        let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
230        let mut label = mat.string.clone();
231        if buffer_language_name.as_deref() == Some(mat.string.as_str()) {
232            label.push_str(" (current)");
233        }
234
235        Label::new(label, style.label.clone())
236            .with_highlights(mat.positions.clone())
237            .contained()
238            .with_style(style.container)
239            .boxed()
240    }
241}