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