1use buffer::{Bias, Point, Selection};
2use editor::{Autoscroll, Editor, EditorSettings};
3use gpui::{
4 action, elements::*, geometry::vector::Vector2F, keymap::Binding, Axis, Entity,
5 MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
6};
7use postage::watch;
8use workspace::{Settings, 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 settings: watch::Receiver<Settings>,
25 line_editor: ViewHandle<Editor>,
26 active_editor: ViewHandle<Editor>,
27 restore_state: Option<RestoreState>,
28 line_selection: Option<Selection<usize>>,
29 cursor_point: Point,
30 max_point: Point,
31}
32
33struct RestoreState {
34 scroll_position: Vector2F,
35 selections: Vec<Selection<usize>>,
36}
37
38pub enum Event {
39 Dismissed,
40}
41
42impl GoToLine {
43 pub fn new(
44 active_editor: ViewHandle<Editor>,
45 settings: watch::Receiver<Settings>,
46 cx: &mut ViewContext<Self>,
47 ) -> Self {
48 let line_editor = cx.add_view(|cx| {
49 Editor::single_line(
50 {
51 let settings = settings.clone();
52 move |_| {
53 let settings = settings.borrow();
54 EditorSettings {
55 tab_size: settings.tab_size,
56 style: settings.theme.selector.input_editor.as_editor(),
57 }
58 }
59 },
60 cx,
61 )
62 });
63 cx.subscribe(&line_editor, Self::on_line_editor_event)
64 .detach();
65
66 let (restore_state, cursor_point, max_point) = active_editor.update(cx, |editor, cx| {
67 let restore_state = Some(RestoreState {
68 scroll_position: editor.scroll_position(cx),
69 selections: editor.selections::<usize>(cx).collect(),
70 });
71
72 (
73 restore_state,
74 editor.newest_selection(cx).head(),
75 editor.buffer().read(cx).max_point(),
76 )
77 });
78
79 Self {
80 settings: settings.clone(),
81 line_editor,
82 active_editor,
83 restore_state,
84 line_selection: None,
85 cursor_point,
86 max_point,
87 }
88 }
89
90 fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
91 workspace.toggle_modal(cx, |cx, workspace| {
92 let editor = workspace
93 .active_item(cx)
94 .unwrap()
95 .to_any()
96 .downcast::<Editor>()
97 .unwrap();
98 let view = cx.add_view(|cx| GoToLine::new(editor, workspace.settings.clone(), cx));
99 cx.subscribe(&view, Self::on_event).detach();
100 view
101 });
102 }
103
104 fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
105 self.restore_state.take();
106 cx.emit(Event::Dismissed);
107 }
108
109 fn on_event(
110 workspace: &mut Workspace,
111 _: ViewHandle<Self>,
112 event: &Event,
113 cx: &mut ViewContext<Workspace>,
114 ) {
115 match event {
116 Event::Dismissed => workspace.dismiss_modal(cx),
117 }
118 }
119
120 fn on_line_editor_event(
121 &mut self,
122 _: ViewHandle<Editor>,
123 event: &editor::Event,
124 cx: &mut ViewContext<Self>,
125 ) {
126 match event {
127 editor::Event::Blurred => cx.emit(Event::Dismissed),
128 editor::Event::Edited => {
129 let line_editor = self.line_editor.read(cx).buffer().read(cx).text();
130 let mut components = line_editor.trim().split(&[',', ':'][..]);
131 let row = components.next().and_then(|row| row.parse::<u32>().ok());
132 let column = components.next().and_then(|row| row.parse::<u32>().ok());
133 if let Some(point) = row.map(|row| {
134 Point::new(
135 row.saturating_sub(1),
136 column.map(|column| column.saturating_sub(1)).unwrap_or(0),
137 )
138 }) {
139 self.line_selection = self.active_editor.update(cx, |active_editor, cx| {
140 let buffer = active_editor.buffer().read(cx);
141 let point = buffer.clip_point(point, Bias::Left);
142 active_editor.select_ranges([point..point], Some(Autoscroll::Center), cx);
143 active_editor.set_highlighted_row(Some(point.row));
144 Some(active_editor.newest_selection(cx))
145 });
146 cx.notify();
147 }
148 }
149 _ => {}
150 }
151 }
152}
153
154impl Entity for GoToLine {
155 type Event = Event;
156
157 fn release(&mut self, cx: &mut MutableAppContext) {
158 let line_selection = self.line_selection.take();
159 let restore_state = self.restore_state.take();
160 self.active_editor.update(cx, |editor, cx| {
161 editor.set_highlighted_row(None);
162 if let Some((line_selection, restore_state)) = line_selection.zip(restore_state) {
163 if line_selection.id == editor.newest_selection::<usize>(cx).id {
164 editor.set_scroll_position(restore_state.scroll_position, cx);
165 editor.update_selections(restore_state.selections, None, cx);
166 }
167 }
168 })
169 }
170}
171
172impl View for GoToLine {
173 fn ui_name() -> &'static str {
174 "GoToLine"
175 }
176
177 fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
178 let theme = &self.settings.borrow().theme.selector;
179
180 let label = format!(
181 "{},{} of {} lines",
182 self.cursor_point.row + 1,
183 self.cursor_point.column + 1,
184 self.max_point.row + 1
185 );
186
187 Align::new(
188 ConstrainedBox::new(
189 Container::new(
190 Flex::new(Axis::Vertical)
191 .with_child(
192 Container::new(ChildView::new(self.line_editor.id()).boxed())
193 .with_style(theme.input_editor.container)
194 .boxed(),
195 )
196 .with_child(
197 Container::new(Label::new(label, theme.empty.label.clone()).boxed())
198 .with_style(theme.empty.container)
199 .boxed(),
200 )
201 .boxed(),
202 )
203 .with_style(theme.container)
204 .boxed(),
205 )
206 .with_max_width(500.0)
207 .boxed(),
208 )
209 .top()
210 .named("go to line")
211 }
212
213 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
214 cx.focus(&self.line_editor);
215 }
216
217 fn on_blur(&mut self, _: &mut ViewContext<Self>) {}
218}