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_and_clamp_cursor(1, false, select);
163 for hook in &mut self.hooks {
164 hook.on_selection_change(&self.buffer, self.selection.as_ref());
165 }
166 self.flush_effects();
167 self.sync_search_state();
168 cx.notify();
169 return;
170 }
171 KeyCode::Down => {
172 self.scroll_and_clamp_cursor(1, true, select);
173 for hook in &mut self.hooks {
174 hook.on_selection_change(&self.buffer, self.selection.as_ref());
175 }
176 self.flush_effects();
177 self.sync_search_state();
178 cx.notify();
179 return;
180 }
181 _ => {}
182 }
183 } else {
184 match &key_event.code {
185 KeyCode::Backspace => {
186 if !self.delete_selection() {
187 self.buffer.backspace();
188 }
189 self.selection = None;
190 edited = true;
191 }
192 KeyCode::Delete => {
193 if !self.delete_selection() {
194 self.buffer.delete();
195 }
196 self.selection = None;
197 edited = true;
198 }
199 KeyCode::Enter => {
200 self.replace_selection_or_insert("\n");
201 self.selection = None;
202 edited = true;
203 }
204 KeyCode::Tab => {
205 self.replace_selection_or_insert(&" ".repeat(self.config.tab_size));
206 self.selection = None;
207 edited = true;
208 }
209 KeyCode::Char(' ') => {
210 self.replace_selection_or_insert(" ");
211 self.selection = None;
212 edited = true;
213 }
214 KeyCode::Left => {
215 if !select && self.selection.is_some() {
216 let sel = self.selection.take().unwrap();
217 self.buffer.set_cursor_offset(sel.byte_range().start);
218 } else {
219 let target = if self.buffer.cursor_offset() > 0 {
220 let char_idx =
221 self.buffer.text().byte_to_char(self.buffer.cursor_offset());
222 self.buffer.text().char_to_byte(char_idx - 1)
223 } else {
224 0
225 };
226 self.move_cursor_to(target, select);
227 }
228 }
229 KeyCode::Right => {
230 if !select && self.selection.is_some() {
231 let sel = self.selection.take().unwrap();
232 self.buffer.set_cursor_offset(sel.byte_range().end);
233 } else {
234 let target = if self.buffer.cursor_offset() < self.buffer.len_bytes() {
235 let char_idx =
236 self.buffer.text().byte_to_char(self.buffer.cursor_offset());
237 self.buffer
238 .text()
239 .char_to_byte((char_idx + 1).min(self.buffer.text().len_chars()))
240 } else {
241 self.buffer.len_bytes()
242 };
243 self.move_cursor_to(target, select);
244 }
245 }
246 KeyCode::Up => {
247 let point = self.buffer.cursor_point();
248 if point.row > 0 {
249 let target = self
250 .buffer
251 .point_to_offset(BufferPoint::new(point.row - 1, point.column));
252 self.move_cursor_to(target, select);
253 } else {
254 self.move_cursor_to(0, select);
255 }
256 }
257 KeyCode::Down => {
258 let point = self.buffer.cursor_point();
259 let total_lines = self.buffer.len_lines();
260 if point.row + 1 < total_lines {
261 let target = self
262 .buffer
263 .point_to_offset(BufferPoint::new(point.row + 1, point.column));
264 self.move_cursor_to(target, select);
265 } else {
266 self.move_cursor_to(self.buffer.len_bytes(), select);
267 }
268 }
269 KeyCode::Home => {
270 let target = self.buffer.line_start_offset();
271 self.move_cursor_to(target, select);
272 }
273 KeyCode::End => {
274 let target = self.buffer.line_end_offset();
275 self.move_cursor_to(target, select);
276 }
277 KeyCode::Char(c)
278 if !key_event.modifiers.alt
279 && !key_event.modifiers.ctrl
280 && !key_event.modifiers.meta =>
281 {
282 let mut insert_consumed = false;
283 let mut hook_idx = 0;
284 while hook_idx < self.hooks.len() {
285 let mut ctx = HookContext::new(
286 &mut self.buffer,
287 &mut self.selection,
288 &mut self.cursor_style,
289 &mut self.prompt,
290 &mut self.pending_effects,
291 );
292 if self.hooks[hook_idx].before_insert(&mut ctx, *c) == HookOutcome::Consumed
293 {
294 insert_consumed = true;
295 break;
296 }
297 hook_idx += 1;
298 }
299
300 if !insert_consumed {
301 let mut buf = [0u8; 4];
302 self.replace_selection_or_insert(c.encode_utf8(&mut buf));
303 self.selection = None;
304 edited = true;
305 }
306 }
307 _ => {}
308 }
309 }
310
311 if edited {
312 for hook in &mut self.hooks {
313 hook.after_edit(&mut self.buffer);
314 }
315 }
316
317 for hook in &mut self.hooks {
318 hook.on_selection_change(&self.buffer, self.selection.as_ref());
319 }
320
321 self.scroll_to_cursor(window);
322 self.flush_effects();
323 self.sync_search_state();
324 cx.notify();
325 }
326
327 pub fn press_search_key(
333 &mut self,
334 code: KeyCode,
335 ctrl: bool,
336 alt: bool,
337 shift: bool,
338 window: Option<&Window>,
339 cx: &mut Context<Self>,
340 ) {
341 let key_event = KeyEvent {
342 code,
343 modifiers: Modifiers {
344 ctrl,
345 alt,
346 shift,
347 meta: false,
348 },
349 };
350 self.dispatch_key(&key_event, window, cx);
351 }
352
353 pub fn press_search_action(
359 &mut self,
360 action: SearchAction,
361 window: Option<&Window>,
362 cx: &mut Context<Self>,
363 ) {
364 self.reset_blink_cursor(cx);
365 let initial_version = self.buffer.version();
366
367 let mut hook_idx = 0;
368 while hook_idx < self.hooks.len() {
369 let mut ctx = HookContext::new(
370 &mut self.buffer,
371 &mut self.selection,
372 &mut self.cursor_style,
373 &mut self.prompt,
374 &mut self.pending_effects,
375 );
376 if self.hooks[hook_idx].on_search_action(&mut ctx, action) == HookOutcome::Consumed {
377 break;
378 }
379 hook_idx += 1;
380 }
381
382 self.finish_consumed_action(window, cx, initial_version);
383 }
384
385 fn finish_consumed_action(
388 &mut self,
389 window: Option<&Window>,
390 cx: &mut Context<Self>,
391 initial_version: usize,
392 ) {
393 if self.buffer.version() != initial_version {
394 for hook in &mut self.hooks {
395 hook.after_edit(&mut self.buffer);
396 }
397 }
398 for hook in &mut self.hooks {
399 hook.on_selection_change(&self.buffer, self.selection.as_ref());
400 }
401 self.scroll_to_cursor(window);
402 self.flush_effects();
403 self.sync_search_state();
404 cx.notify();
405 }
406}