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