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