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