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}