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.selector.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).read(cx);
44 (
45 Some(scroll_position),
46 editor.newest_selection_with_snapshot(&buffer).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.select_ranges([position..position], Some(Autoscroll::Center), cx);
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).buffer().read(cx).read(cx).text();
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.selector;
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}