go_to_line.rs

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