go_to_line.rs

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