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