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.active_item(cx).unwrap().downcast::<Editor>() {
 63            workspace.toggle_modal(cx, |cx, _| {
 64                let view = cx.add_view(|cx| GoToLine::new(editor, cx));
 65                cx.subscribe(&view, Self::on_event).detach();
 66                view
 67            });
 68        }
 69    }
 70
 71    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
 72        self.prev_scroll_position.take();
 73        self.active_editor.update(cx, |active_editor, cx| {
 74            if let Some(rows) = active_editor.highlighted_rows() {
 75                let snapshot = active_editor.snapshot(cx).display_snapshot;
 76                let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot);
 77                active_editor.select_ranges([position..position], Some(Autoscroll::Center), cx);
 78            }
 79        });
 80        cx.emit(Event::Dismissed);
 81    }
 82
 83    fn on_event(
 84        workspace: &mut Workspace,
 85        _: ViewHandle<Self>,
 86        event: &Event,
 87        cx: &mut ViewContext<Workspace>,
 88    ) {
 89        match event {
 90            Event::Dismissed => workspace.dismiss_modal(cx),
 91        }
 92    }
 93
 94    fn on_line_editor_event(
 95        &mut self,
 96        _: ViewHandle<Editor>,
 97        event: &editor::Event,
 98        cx: &mut ViewContext<Self>,
 99    ) {
100        match event {
101            editor::Event::Blurred => cx.emit(Event::Dismissed),
102            editor::Event::Edited => {
103                let line_editor = self.line_editor.read(cx).buffer().read(cx).read(cx).text();
104                let mut components = line_editor.trim().split(&[',', ':'][..]);
105                let row = components.next().and_then(|row| row.parse::<u32>().ok());
106                let column = components.next().and_then(|row| row.parse::<u32>().ok());
107                if let Some(point) = row.map(|row| {
108                    Point::new(
109                        row.saturating_sub(1),
110                        column.map(|column| column.saturating_sub(1)).unwrap_or(0),
111                    )
112                }) {
113                    self.active_editor.update(cx, |active_editor, cx| {
114                        let snapshot = active_editor.snapshot(cx).display_snapshot;
115                        let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
116                        let display_point = point.to_display_point(&snapshot);
117                        let row = display_point.row();
118                        active_editor.highlight_rows(Some(row..row + 1));
119                        active_editor.request_autoscroll(Autoscroll::Center, cx);
120                    });
121                    cx.notify();
122                }
123            }
124            _ => {}
125        }
126    }
127}
128
129impl Entity for GoToLine {
130    type Event = Event;
131
132    fn release(&mut self, cx: &mut MutableAppContext) {
133        let scroll_position = self.prev_scroll_position.take();
134        self.active_editor.update(cx, |editor, cx| {
135            editor.highlight_rows(None);
136            if let Some(scroll_position) = scroll_position {
137                editor.set_scroll_position(scroll_position, cx);
138            }
139        })
140    }
141}
142
143impl View for GoToLine {
144    fn ui_name() -> &'static str {
145        "GoToLine"
146    }
147
148    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
149        let theme = &cx.app_state::<Settings>().theme.selector;
150
151        let label = format!(
152            "{},{} of {} lines",
153            self.cursor_point.row + 1,
154            self.cursor_point.column + 1,
155            self.max_point.row + 1
156        );
157
158        Align::new(
159            ConstrainedBox::new(
160                Container::new(
161                    Flex::new(Axis::Vertical)
162                        .with_child(
163                            Container::new(ChildView::new(&self.line_editor).boxed())
164                                .with_style(theme.input_editor.container)
165                                .boxed(),
166                        )
167                        .with_child(
168                            Container::new(Label::new(label, theme.empty.label.clone()).boxed())
169                                .with_style(theme.empty.container)
170                                .boxed(),
171                        )
172                        .boxed(),
173                )
174                .with_style(theme.container)
175                .boxed(),
176            )
177            .with_max_width(500.0)
178            .boxed(),
179        )
180        .top()
181        .named("go to line")
182    }
183
184    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
185        cx.focus(&self.line_editor);
186    }
187}