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