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