1use std::sync::Arc;
2
3use editor::{display_map::ToDisplayPoint, Autoscroll, DisplayPoint, Editor};
4use gpui::{
5 actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, Axis, Entity,
6 MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
7};
8use menu::{Cancel, Confirm};
9use settings::Settings;
10use text::{Bias, Point};
11use workspace::Workspace;
12
13actions!(go_to_line, [Toggle]);
14
15pub fn init(cx: &mut MutableAppContext) {
16 cx.add_action(GoToLine::toggle);
17 cx.add_action(GoToLine::confirm);
18 cx.add_action(GoToLine::cancel);
19}
20
21pub struct GoToLine {
22 line_editor: ViewHandle<Editor>,
23 active_editor: ViewHandle<Editor>,
24 prev_scroll_position: Option<Vector2F>,
25 cursor_point: Point,
26 max_point: Point,
27}
28
29pub enum Event {
30 Dismissed,
31}
32
33impl GoToLine {
34 pub fn new(active_editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) -> Self {
35 let line_editor = cx.add_view(|cx| {
36 Editor::single_line(
37 Some(Arc::new(|theme| theme.picker.input_editor.clone())),
38 cx,
39 )
40 });
41 cx.subscribe(&line_editor, Self::on_line_editor_event)
42 .detach();
43
44 let (scroll_position, cursor_point, max_point) = active_editor.update(cx, |editor, cx| {
45 let scroll_position = editor.scroll_position(cx);
46 let buffer = editor.buffer().read(cx).snapshot(cx);
47 (
48 Some(scroll_position),
49 editor.selections.newest(cx).head(),
50 buffer.max_point(),
51 )
52 });
53
54 Self {
55 line_editor,
56 active_editor,
57 prev_scroll_position: scroll_position,
58 cursor_point,
59 max_point,
60 }
61 }
62
63 fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
64 if let Some(editor) = workspace
65 .active_item(cx)
66 .and_then(|active_item| active_item.downcast::<Editor>())
67 {
68 workspace.toggle_modal(cx, |_, cx| {
69 let view = cx.add_view(|cx| GoToLine::new(editor, cx));
70 cx.subscribe(&view, Self::on_event).detach();
71 view
72 });
73 }
74 }
75
76 fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
77 cx.emit(Event::Dismissed);
78 }
79
80 fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
81 self.prev_scroll_position.take();
82 self.active_editor.update(cx, |active_editor, cx| {
83 if let Some(rows) = active_editor.highlighted_rows() {
84 let snapshot = active_editor.snapshot(cx).display_snapshot;
85 let position = DisplayPoint::new(rows.start, 0).to_point(&snapshot);
86 active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
87 s.select_ranges([position..position])
88 });
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::BufferEdited { .. } => {
114 let line_editor = self.line_editor.read(cx).text(cx);
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.highlight_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.highlight_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, cx: &mut RenderContext<Self>) -> ElementBox {
160 let theme = &cx.global::<Settings>().theme.picker;
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 ConstrainedBox::new(
170 Container::new(
171 Flex::new(Axis::Vertical)
172 .with_child(
173 Container::new(ChildView::new(&self.line_editor, cx).boxed())
174 .with_style(theme.input_editor.container)
175 .boxed(),
176 )
177 .with_child(
178 Container::new(Label::new(label, theme.no_matches.label.clone()).boxed())
179 .with_style(theme.no_matches.container)
180 .boxed(),
181 )
182 .boxed(),
183 )
184 .with_style(theme.container)
185 .boxed(),
186 )
187 .with_max_width(500.0)
188 .named("go to line")
189 }
190
191 fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
192 cx.focus(&self.line_editor);
193 }
194}