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