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