lib.rs

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