language_selector.rs

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