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