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