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