go_to_line.rs

  1use editor::{display_map::ToDisplayPoint, Autoscroll, Editor, EditorSettings};
  2use gpui::{
  3    action, elements::*, geometry::vector::Vector2F, keymap::Binding, Axis, Entity,
  4    MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
  5};
  6use postage::watch;
  7use std::sync::Arc;
  8use text::{Bias, Point, Selection};
  9use workspace::{Settings, Workspace};
 10
 11action!(Toggle);
 12action!(Confirm);
 13
 14pub fn init(cx: &mut MutableAppContext) {
 15    cx.add_bindings([
 16        Binding::new("ctrl-g", Toggle, Some("Editor")),
 17        Binding::new("escape", Toggle, Some("GoToLine")),
 18        Binding::new("enter", Confirm, Some("GoToLine")),
 19    ]);
 20    cx.add_action(GoToLine::toggle);
 21    cx.add_action(GoToLine::confirm);
 22}
 23
 24pub struct GoToLine {
 25    settings: watch::Receiver<Settings>,
 26    line_editor: ViewHandle<Editor>,
 27    active_editor: ViewHandle<Editor>,
 28    restore_state: Option<RestoreState>,
 29    line_selection_id: Option<usize>,
 30    cursor_point: Point,
 31    max_point: Point,
 32}
 33
 34struct RestoreState {
 35    scroll_position: Vector2F,
 36    selections: Vec<Selection<usize>>,
 37}
 38
 39pub enum Event {
 40    Dismissed,
 41}
 42
 43impl GoToLine {
 44    pub fn new(
 45        active_editor: ViewHandle<Editor>,
 46        settings: watch::Receiver<Settings>,
 47        cx: &mut ViewContext<Self>,
 48    ) -> Self {
 49        let line_editor = cx.add_view(|cx| {
 50            Editor::single_line(
 51                {
 52                    let settings = settings.clone();
 53                    Arc::new(move |_| {
 54                        let settings = settings.borrow();
 55                        EditorSettings {
 56                            tab_size: settings.tab_size,
 57                            style: settings.theme.selector.input_editor.as_editor(),
 58                            soft_wrap: editor::SoftWrap::None,
 59                        }
 60                    })
 61                },
 62                cx,
 63            )
 64        });
 65        cx.subscribe(&line_editor, Self::on_line_editor_event)
 66            .detach();
 67
 68        let (restore_state, cursor_point, max_point) = active_editor.update(cx, |editor, cx| {
 69            let restore_state = Some(RestoreState {
 70                scroll_position: editor.scroll_position(cx),
 71                selections: editor.local_selections::<usize>(cx),
 72            });
 73
 74            let buffer = editor.buffer().read(cx).read(cx);
 75            (
 76                restore_state,
 77                editor.newest_selection(&buffer).head(),
 78                buffer.max_point(),
 79            )
 80        });
 81
 82        Self {
 83            settings: settings.clone(),
 84            line_editor,
 85            active_editor,
 86            restore_state,
 87            line_selection_id: None,
 88            cursor_point,
 89            max_point,
 90        }
 91    }
 92
 93    fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
 94        workspace.toggle_modal(cx, |cx, workspace| {
 95            let editor = workspace
 96                .active_item(cx)
 97                .unwrap()
 98                .to_any()
 99                .downcast::<Editor>()
100                .unwrap();
101            let view = cx.add_view(|cx| GoToLine::new(editor, workspace.settings.clone(), cx));
102            cx.subscribe(&view, Self::on_event).detach();
103            view
104        });
105    }
106
107    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
108        self.restore_state.take();
109        cx.emit(Event::Dismissed);
110    }
111
112    fn on_event(
113        workspace: &mut Workspace,
114        _: ViewHandle<Self>,
115        event: &Event,
116        cx: &mut ViewContext<Workspace>,
117    ) {
118        match event {
119            Event::Dismissed => workspace.dismiss_modal(cx),
120        }
121    }
122
123    fn on_line_editor_event(
124        &mut self,
125        _: ViewHandle<Editor>,
126        event: &editor::Event,
127        cx: &mut ViewContext<Self>,
128    ) {
129        match event {
130            editor::Event::Blurred => cx.emit(Event::Dismissed),
131            editor::Event::Edited => {
132                let line_editor = self.line_editor.read(cx).buffer().read(cx).read(cx).text();
133                let mut components = line_editor.trim().split(&[',', ':'][..]);
134                let row = components.next().and_then(|row| row.parse::<u32>().ok());
135                let column = components.next().and_then(|row| row.parse::<u32>().ok());
136                if let Some(point) = row.map(|row| {
137                    Point::new(
138                        row.saturating_sub(1),
139                        column.map(|column| column.saturating_sub(1)).unwrap_or(0),
140                    )
141                }) {
142                    self.line_selection_id = self.active_editor.update(cx, |active_editor, cx| {
143                        let snapshot = active_editor.snapshot(cx).display_snapshot;
144                        let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
145                        let display_point = point.to_display_point(&snapshot);
146                        let row = display_point.row();
147                        active_editor.select_ranges([point..point], Some(Autoscroll::Center), cx);
148                        active_editor.set_highlighted_rows(Some(row..row + 1));
149                        Some(
150                            active_editor
151                                .newest_selection::<usize>(&snapshot.buffer_snapshot)
152                                .id,
153                        )
154                    });
155                    cx.notify();
156                }
157            }
158            _ => {}
159        }
160    }
161}
162
163impl Entity for GoToLine {
164    type Event = Event;
165
166    fn release(&mut self, cx: &mut MutableAppContext) {
167        let line_selection_id = self.line_selection_id.take();
168        let restore_state = self.restore_state.take();
169        self.active_editor.update(cx, |editor, cx| {
170            editor.set_highlighted_rows(None);
171            if let Some((line_selection_id, restore_state)) = line_selection_id.zip(restore_state) {
172                let newest_selection =
173                    editor.newest_selection::<usize>(&editor.buffer().read(cx).read(cx));
174                if line_selection_id == newest_selection.id {
175                    editor.set_scroll_position(restore_state.scroll_position, cx);
176                    editor.update_selections(restore_state.selections, None, cx);
177                }
178            }
179        })
180    }
181}
182
183impl View for GoToLine {
184    fn ui_name() -> &'static str {
185        "GoToLine"
186    }
187
188    fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
189        let theme = &self.settings.borrow().theme.selector;
190
191        let label = format!(
192            "{},{} of {} lines",
193            self.cursor_point.row + 1,
194            self.cursor_point.column + 1,
195            self.max_point.row + 1
196        );
197
198        Align::new(
199            ConstrainedBox::new(
200                Container::new(
201                    Flex::new(Axis::Vertical)
202                        .with_child(
203                            Container::new(ChildView::new(self.line_editor.id()).boxed())
204                                .with_style(theme.input_editor.container)
205                                .boxed(),
206                        )
207                        .with_child(
208                            Container::new(Label::new(label, theme.empty.label.clone()).boxed())
209                                .with_style(theme.empty.container)
210                                .boxed(),
211                        )
212                        .boxed(),
213                )
214                .with_style(theme.container)
215                .boxed(),
216            )
217            .with_max_width(500.0)
218            .boxed(),
219        )
220        .top()
221        .named("go to line")
222    }
223
224    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
225        cx.focus(&self.line_editor);
226    }
227}