1use gpui::{Context, KeyDownEvent, Window};
2use twrite_core::{
3 HookContext, HookOutcome, KeyCode, KeyEvent, Modifiers, Point as BufferPoint, SearchAction,
4 Selection,
5};
6
7use super::Editor;
8
9impl Editor {
10 pub(crate) fn handle_key_down(
11 &mut self,
12 event: &KeyDownEvent,
13 window: &mut Window,
14 cx: &mut Context<Self>,
15 ) {
16 if let Some(key_event) = crate::input::translate_key_down(event) {
17 if self.context_menu.is_open() {
18 let plain = !key_event.modifiers.ctrl
19 && !key_event.modifiers.alt
20 && !key_event.modifiers.meta;
21 match &key_event.code {
22 KeyCode::Escape => {
23 self.dismiss_context_menu(cx);
24 return;
25 }
26 KeyCode::Up if plain => {
27 self.move_context_menu_selection(false, cx);
28 return;
29 }
30 KeyCode::Down if plain => {
31 self.move_context_menu_selection(true, cx);
32 return;
33 }
34 KeyCode::Enter if plain => {
35 if self.context_menu_selected.is_some() {
36 self.activate_context_menu_selected(window, cx);
37 } else {
38 self.dismiss_context_menu(cx);
39 }
40 return;
41 }
42 _ => self.dismiss_context_menu(cx),
45 }
46 }
47 self.dispatch_key(&key_event, Some(window), cx);
48 }
49 }
50
51 pub(crate) fn dispatch_key(
57 &mut self,
58 key_event: &KeyEvent,
59 window: Option<&Window>,
60 cx: &mut Context<Self>,
61 ) {
62 self.reset_blink_cursor(cx);
63 let initial_version = self.buffer.version();
64 let mut consumed = false;
65
66 let mut hook_idx = 0;
67 while hook_idx < self.hooks.len() {
68 let mut ctx = HookContext::new(
69 &mut self.buffer,
70 &mut self.selection,
71 &mut self.cursor_style,
72 &mut self.prompt,
73 &mut self.pending_effects,
74 );
75 let outcome = self.hooks[hook_idx].on_key(&mut ctx, key_event);
76 if outcome == HookOutcome::Consumed {
77 consumed = true;
78 break;
79 }
80 hook_idx += 1;
81 }
82
83 if consumed {
84 self.finish_consumed_action(window, cx, initial_version);
85 return;
86 }
87
88 if self.prompt.is_open() {
89 for hook in &mut self.hooks {
90 hook.on_selection_change(&self.buffer, self.selection.as_ref());
91 }
92 self.flush_effects();
93 self.sync_search_state();
94 cx.notify();
95 return;
96 }
97
98 let mut edited = false;
99 let select = key_event.modifiers.shift;
100
101 if key_event.modifiers.ctrl || key_event.modifiers.meta {
102 match &key_event.code {
105 KeyCode::Char('z') => {
106 if key_event.modifiers.shift {
107 self.buffer.redo();
108 } else {
109 self.buffer.undo();
110 }
111 self.selection = None;
112 edited = true;
113 }
114 KeyCode::Char('y') => {
115 self.buffer.redo();
116 self.selection = None;
117 edited = true;
118 }
119 KeyCode::Char('a') => {
120 self.selection = Some(Selection::range(0, self.buffer.len_bytes()));
121 }
122 KeyCode::Char('c') => {
123 self.copy(cx);
124 }
125 KeyCode::Char('x') => {
126 edited = self.cut(cx);
127 }
128 KeyCode::Char('v') => {
129 edited = self.paste(cx);
130 }
131 KeyCode::Backspace => {
132 if !self.delete_selection() {
133 edited = self.buffer.delete_prev_word();
134 } else {
135 edited = true;
136 }
137 self.selection = None;
138 }
139 KeyCode::Delete => {
140 if !self.delete_selection() {
141 edited = self.buffer.delete_next_word();
142 } else {
143 edited = true;
144 }
145 self.selection = None;
146 }
147 KeyCode::Left => {
148 let target = self.buffer.prev_word_offset();
149 self.move_cursor_to(target, select);
150 }
151 KeyCode::Right => {
152 let target = self.buffer.next_word_offset();
153 self.move_cursor_to(target, select);
154 }
155 KeyCode::Home => {
156 self.move_cursor_to(0, select);
157 }
158 KeyCode::End => {
159 self.move_cursor_to(self.buffer.len_bytes(), select);
160 }
161 KeyCode::Up => {
162 self.scroll_up(1);
163 self.flush_effects();
164 cx.notify();
165 return;
166 }
167 KeyCode::Down => {
168 self.scroll_down(1);
169 self.flush_effects();
170 cx.notify();
171 return;
172 }
173 _ => {}
174 }
175 } else {
176 match &key_event.code {
177 KeyCode::Backspace => {
178 if !self.delete_selection() {
179 self.buffer.backspace();
180 }
181 self.selection = None;
182 edited = true;
183 }
184 KeyCode::Delete => {
185 if !self.delete_selection() {
186 self.buffer.delete();
187 }
188 self.selection = None;
189 edited = true;
190 }
191 KeyCode::Enter => {
192 self.replace_selection_or_insert("\n");
193 self.selection = None;
194 edited = true;
195 }
196 KeyCode::Tab => {
197 self.replace_selection_or_insert(&" ".repeat(self.config.tab_size));
198 self.selection = None;
199 edited = true;
200 }
201 KeyCode::Char(' ') => {
202 self.replace_selection_or_insert(" ");
203 self.selection = None;
204 edited = true;
205 }
206 KeyCode::Left => {
207 if !select && self.selection.is_some() {
208 let sel = self.selection.take().unwrap();
209 self.buffer.set_cursor_offset(sel.byte_range().start);
210 } else {
211 let target = if self.buffer.cursor_offset() > 0 {
212 let char_idx =
213 self.buffer.text().byte_to_char(self.buffer.cursor_offset());
214 self.buffer.text().char_to_byte(char_idx - 1)
215 } else {
216 0
217 };
218 self.move_cursor_to(target, select);
219 }
220 }
221 KeyCode::Right => {
222 if !select && self.selection.is_some() {
223 let sel = self.selection.take().unwrap();
224 self.buffer.set_cursor_offset(sel.byte_range().end);
225 } else {
226 let target = if self.buffer.cursor_offset() < self.buffer.len_bytes() {
227 let char_idx =
228 self.buffer.text().byte_to_char(self.buffer.cursor_offset());
229 self.buffer
230 .text()
231 .char_to_byte((char_idx + 1).min(self.buffer.text().len_chars()))
232 } else {
233 self.buffer.len_bytes()
234 };
235 self.move_cursor_to(target, select);
236 }
237 }
238 KeyCode::Up => {
239 let point = self.buffer.cursor_point();
240 if point.row > 0 {
241 let target = self
242 .buffer
243 .point_to_offset(BufferPoint::new(point.row - 1, point.column));
244 self.move_cursor_to(target, select);
245 } else {
246 self.move_cursor_to(0, select);
247 }
248 }
249 KeyCode::Down => {
250 let point = self.buffer.cursor_point();
251 let total_lines = self.buffer.len_lines();
252 if point.row + 1 < total_lines {
253 let target = self
254 .buffer
255 .point_to_offset(BufferPoint::new(point.row + 1, point.column));
256 self.move_cursor_to(target, select);
257 } else {
258 self.move_cursor_to(self.buffer.len_bytes(), select);
259 }
260 }
261 KeyCode::Home => {
262 let target = self.buffer.line_start_offset();
263 self.move_cursor_to(target, select);
264 }
265 KeyCode::End => {
266 let target = self.buffer.line_end_offset();
267 self.move_cursor_to(target, select);
268 }
269 KeyCode::Char(c)
270 if !key_event.modifiers.alt
271 && !key_event.modifiers.ctrl
272 && !key_event.modifiers.meta =>
273 {
274 let mut insert_consumed = false;
275 let mut hook_idx = 0;
276 while hook_idx < self.hooks.len() {
277 let mut ctx = HookContext::new(
278 &mut self.buffer,
279 &mut self.selection,
280 &mut self.cursor_style,
281 &mut self.prompt,
282 &mut self.pending_effects,
283 );
284 if self.hooks[hook_idx].before_insert(&mut ctx, *c) == HookOutcome::Consumed
285 {
286 insert_consumed = true;
287 break;
288 }
289 hook_idx += 1;
290 }
291
292 if !insert_consumed {
293 let mut buf = [0u8; 4];
294 self.replace_selection_or_insert(c.encode_utf8(&mut buf));
295 self.selection = None;
296 edited = true;
297 }
298 }
299 _ => {}
300 }
301 }
302
303 if edited {
304 for hook in &mut self.hooks {
305 hook.after_edit(&mut self.buffer);
306 }
307 }
308
309 for hook in &mut self.hooks {
310 hook.on_selection_change(&self.buffer, self.selection.as_ref());
311 }
312
313 self.scroll_to_cursor(window);
314 self.flush_effects();
315 self.sync_search_state();
316 cx.notify();
317 }
318
319 pub fn press_search_key(
325 &mut self,
326 code: KeyCode,
327 ctrl: bool,
328 alt: bool,
329 shift: bool,
330 window: Option<&Window>,
331 cx: &mut Context<Self>,
332 ) {
333 let key_event = KeyEvent {
334 code,
335 modifiers: Modifiers {
336 ctrl,
337 alt,
338 shift,
339 meta: false,
340 },
341 };
342 self.dispatch_key(&key_event, window, cx);
343 }
344
345 pub fn press_search_action(
351 &mut self,
352 action: SearchAction,
353 window: Option<&Window>,
354 cx: &mut Context<Self>,
355 ) {
356 self.reset_blink_cursor(cx);
357 let initial_version = self.buffer.version();
358
359 let mut hook_idx = 0;
360 while hook_idx < self.hooks.len() {
361 let mut ctx = HookContext::new(
362 &mut self.buffer,
363 &mut self.selection,
364 &mut self.cursor_style,
365 &mut self.prompt,
366 &mut self.pending_effects,
367 );
368 if self.hooks[hook_idx].on_search_action(&mut ctx, action) == HookOutcome::Consumed {
369 break;
370 }
371 hook_idx += 1;
372 }
373
374 self.finish_consumed_action(window, cx, initial_version);
375 }
376
377 fn finish_consumed_action(
380 &mut self,
381 window: Option<&Window>,
382 cx: &mut Context<Self>,
383 initial_version: usize,
384 ) {
385 if self.buffer.version() != initial_version {
386 for hook in &mut self.hooks {
387 hook.after_edit(&mut self.buffer);
388 }
389 }
390 for hook in &mut self.hooks {
391 hook.on_selection_change(&self.buffer, self.selection.as_ref());
392 }
393 self.scroll_to_cursor(window);
394 self.flush_effects();
395 self.sync_search_state();
396 cx.notify();
397 }
398}