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_id: Option<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_id: 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_id = 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 let row = display_point.row();
147 active_editor.select_ranges([point..point], Some(Autoscroll::Center), cx);
148 active_editor.set_highlighted_rows(Some(row..row + 1));
149 Some(
150 active_editor
151 .newest_selection::<usize>(&snapshot.buffer_snapshot)
152 .id,
153 )
154 });
155 cx.notify();
156 }
157 }
158 _ => {}
159 }
160 }
161}
162
163impl Entity for GoToLine {
164 type Event = Event;
165
166 fn release(&mut self, cx: &mut MutableAppContext) {
167 let line_selection_id = self.line_selection_id.take();
168 let restore_state = self.restore_state.take();
169 self.active_editor.update(cx, |editor, cx| {
170 editor.set_highlighted_rows(None);
171 if let Some((line_selection_id, restore_state)) = line_selection_id.zip(restore_state) {
172 let newest_selection =
173 editor.newest_selection::<usize>(&editor.buffer().read(cx).read(cx));
174 if line_selection_id == newest_selection.id {
175 editor.set_scroll_position(restore_state.scroll_position, cx);
176 editor.update_selections(restore_state.selections, None, cx);
177 }
178 }
179 })
180 }
181}
182
183impl View for GoToLine {
184 fn ui_name() -> &'static str {
185 "GoToLine"
186 }
187
188 fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
189 let theme = &self.settings.borrow().theme.selector;
190
191 let label = format!(
192 "{},{} of {} lines",
193 self.cursor_point.row + 1,
194 self.cursor_point.column + 1,
195 self.max_point.row + 1
196 );
197
198 Align::new(
199 ConstrainedBox::new(
200 Container::new(
201 Flex::new(Axis::Vertical)
202 .with_child(
203 Container::new(ChildView::new(self.line_editor.id()).boxed())
204 .with_style(theme.input_editor.container)
205 .boxed(),
206 )
207 .with_child(
208 Container::new(Label::new(label, theme.empty.label.clone()).boxed())
209 .with_style(theme.empty.container)
210 .boxed(),
211 )
212 .boxed(),
213 )
214 .with_style(theme.container)
215 .boxed(),
216 )
217 .with_max_width(500.0)
218 .boxed(),
219 )
220 .top()
221 .named("go to line")
222 }
223
224 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
225 cx.focus(&self.line_editor);
226 }
227}