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