1use anyhow::Result;
6use std::collections::HashMap;
7use std::ops::Range;
8use std::path::PathBuf;
9
10use crate::action::Action;
11use crate::api::{ViewTokenWire, ViewTokenWireKind};
12use crate::{BufferId, CursorId, SplitId};
13
14#[derive(Debug, Clone, serde::Serialize)]
16pub enum HookArgs {
17 BeforeFileOpen { path: PathBuf },
19
20 AfterFileOpen { buffer_id: BufferId, path: PathBuf },
22
23 BeforeFileSave { buffer_id: BufferId, path: PathBuf },
25
26 AfterFileSave { buffer_id: BufferId, path: PathBuf },
28
29 BufferClosed { buffer_id: BufferId },
31
32 BeforeInsert {
34 buffer_id: BufferId,
35 position: usize,
36 text: String,
37 },
38
39 AfterInsert {
41 buffer_id: BufferId,
42 position: usize,
43 text: String,
44 affected_start: usize,
46 affected_end: usize,
48 start_line: usize,
50 end_line: usize,
52 lines_added: usize,
54 },
55
56 BeforeDelete {
58 buffer_id: BufferId,
59 range: Range<usize>,
60 },
61
62 AfterDelete {
64 buffer_id: BufferId,
65 range: Range<usize>,
66 deleted_text: String,
67 affected_start: usize,
69 deleted_len: usize,
71 start_line: usize,
73 end_line: usize,
75 lines_removed: usize,
77 },
78
79 CursorMoved {
81 buffer_id: BufferId,
82 cursor_id: CursorId,
83 old_position: usize,
84 new_position: usize,
85 line: usize,
87 text_properties: Vec<std::collections::HashMap<String, serde_json::Value>>,
89 },
90
91 BufferActivated { buffer_id: BufferId },
93
94 BufferDeactivated { buffer_id: BufferId },
96
97 DiagnosticsUpdated {
99 uri: String,
101 count: usize,
103 },
104
105 PreCommand { action: Action },
107
108 PostCommand { action: Action },
110
111 Idle { milliseconds: u64 },
113
114 EditorInitialized,
116
117 RenderStart { buffer_id: BufferId },
119
120 RenderLine {
122 buffer_id: BufferId,
123 line_number: usize,
124 byte_start: usize,
125 byte_end: usize,
126 content: String,
127 },
128
129 LinesChanged {
131 buffer_id: BufferId,
132 lines: Vec<LineInfo>,
133 },
134
135 PromptChanged { prompt_type: String, input: String },
137
138 PromptConfirmed {
140 prompt_type: String,
141 input: String,
142 selected_index: Option<usize>,
143 },
144
145 PromptCancelled { prompt_type: String, input: String },
147
148 PromptSelectionChanged {
150 prompt_type: String,
151 selected_index: usize,
152 },
153
154 KeyboardShortcuts { bindings: Vec<(String, String)> },
156
157 LspReferences {
159 symbol: String,
161 locations: Vec<LspLocation>,
163 },
164
165 ViewTransformRequest {
167 buffer_id: BufferId,
168 split_id: SplitId,
169 viewport_start: usize,
171 viewport_end: usize,
173 tokens: Vec<ViewTokenWire>,
175 cursor_positions: Vec<usize>,
177 },
178
179 MouseClick {
181 column: u16,
183 row: u16,
185 button: String,
187 modifiers: String,
189 content_x: u16,
191 content_y: u16,
193 },
194
195 MouseMove {
197 column: u16,
199 row: u16,
201 content_x: u16,
203 content_y: u16,
205 },
206
207 LspServerRequest {
209 language: String,
211 method: String,
213 server_command: String,
215 params: Option<String>,
217 },
218
219 ViewportChanged {
221 split_id: SplitId,
222 buffer_id: BufferId,
223 top_byte: usize,
224 top_line: Option<usize>,
225 width: u16,
226 height: u16,
227 },
228
229 LspServerError {
231 language: String,
233 server_command: String,
235 error_type: String,
237 message: String,
239 },
240
241 LspStatusClicked {
243 language: String,
245 has_error: bool,
247 },
248
249 ActionPopupResult {
251 popup_id: String,
253 action_id: String,
255 },
256
257 ProcessOutput {
259 process_id: u64,
261 data: String,
263 },
264
265 LanguageChanged {
267 buffer_id: BufferId,
268 language: String,
270 },
271
272 ThemeInspectKey {
274 theme_name: String,
276 key: String,
278 },
279
280 MouseScroll {
282 buffer_id: BufferId,
283 delta: i32,
285 col: u16,
287 row: u16,
289 },
290
291 Resize { width: u16, height: u16 },
293
294 FocusGained,
296}
297
298#[derive(Debug, Clone, serde::Serialize)]
300pub struct LineInfo {
301 pub line_number: usize,
303 pub byte_start: usize,
305 pub byte_end: usize,
307 pub content: String,
309}
310
311#[derive(Debug, Clone, serde::Serialize)]
313pub struct LspLocation {
314 pub file: String,
316 pub line: u32,
318 pub column: u32,
320}
321
322pub type HookCallback = Box<dyn Fn(&HookArgs) -> bool + Send + Sync>;
324
325pub struct HookRegistry {
327 hooks: HashMap<String, Vec<HookCallback>>,
329}
330
331impl HookRegistry {
332 pub fn new() -> Self {
334 Self {
335 hooks: HashMap::new(),
336 }
337 }
338
339 pub fn add_hook(&mut self, name: &str, callback: HookCallback) {
341 self.hooks
342 .entry(name.to_string())
343 .or_default()
344 .push(callback);
345 }
346
347 pub fn remove_hooks(&mut self, name: &str) {
349 self.hooks.remove(name);
350 }
351
352 pub fn run_hooks(&self, name: &str, args: &HookArgs) -> bool {
354 if let Some(hooks) = self.hooks.get(name) {
355 for callback in hooks {
356 if !callback(args) {
357 return false;
358 }
359 }
360 }
361 true
362 }
363
364 pub fn hook_count(&self, name: &str) -> usize {
366 self.hooks.get(name).map(|v| v.len()).unwrap_or(0)
367 }
368
369 pub fn hook_names(&self) -> Vec<String> {
371 self.hooks.keys().cloned().collect()
372 }
373}
374
375impl Default for HookRegistry {
376 fn default() -> Self {
377 Self::new()
378 }
379}
380
381pub fn hook_args_to_json(args: &HookArgs) -> Result<serde_json::Value> {
383 let json_value = match args {
384 HookArgs::RenderStart { buffer_id } => {
385 serde_json::json!({
386 "buffer_id": buffer_id.0,
387 })
388 }
389 HookArgs::RenderLine {
390 buffer_id,
391 line_number,
392 byte_start,
393 byte_end,
394 content,
395 } => {
396 serde_json::json!({
397 "buffer_id": buffer_id.0,
398 "line_number": line_number,
399 "byte_start": byte_start,
400 "byte_end": byte_end,
401 "content": content,
402 })
403 }
404 HookArgs::BufferActivated { buffer_id } => {
405 serde_json::json!({ "buffer_id": buffer_id.0 })
406 }
407 HookArgs::BufferDeactivated { buffer_id } => {
408 serde_json::json!({ "buffer_id": buffer_id.0 })
409 }
410 HookArgs::DiagnosticsUpdated { uri, count } => {
411 serde_json::json!({
412 "uri": uri,
413 "count": count,
414 })
415 }
416 HookArgs::BufferClosed { buffer_id } => {
417 serde_json::json!({ "buffer_id": buffer_id.0 })
418 }
419 HookArgs::CursorMoved {
420 buffer_id,
421 cursor_id,
422 old_position,
423 new_position,
424 line,
425 text_properties,
426 } => {
427 serde_json::json!({
428 "buffer_id": buffer_id.0,
429 "cursor_id": cursor_id.0,
430 "old_position": old_position,
431 "new_position": new_position,
432 "line": line,
433 "text_properties": text_properties,
434 })
435 }
436 HookArgs::BeforeInsert {
437 buffer_id,
438 position,
439 text,
440 } => {
441 serde_json::json!({
442 "buffer_id": buffer_id.0,
443 "position": position,
444 "text": text,
445 })
446 }
447 HookArgs::AfterInsert {
448 buffer_id,
449 position,
450 text,
451 affected_start,
452 affected_end,
453 start_line,
454 end_line,
455 lines_added,
456 } => {
457 serde_json::json!({
458 "buffer_id": buffer_id.0,
459 "position": position,
460 "text": text,
461 "affected_start": affected_start,
462 "affected_end": affected_end,
463 "start_line": start_line,
464 "end_line": end_line,
465 "lines_added": lines_added,
466 })
467 }
468 HookArgs::BeforeDelete { buffer_id, range } => {
469 serde_json::json!({
470 "buffer_id": buffer_id.0,
471 "start": range.start,
472 "end": range.end,
473 })
474 }
475 HookArgs::AfterDelete {
476 buffer_id,
477 range,
478 deleted_text,
479 affected_start,
480 deleted_len,
481 start_line,
482 end_line,
483 lines_removed,
484 } => {
485 serde_json::json!({
486 "buffer_id": buffer_id.0,
487 "start": range.start,
488 "end": range.end,
489 "deleted_text": deleted_text,
490 "affected_start": affected_start,
491 "deleted_len": deleted_len,
492 "start_line": start_line,
493 "end_line": end_line,
494 "lines_removed": lines_removed,
495 })
496 }
497 HookArgs::BeforeFileOpen { path } => {
498 serde_json::json!({ "path": path.to_string_lossy() })
499 }
500 HookArgs::AfterFileOpen { path, buffer_id } => {
501 serde_json::json!({
502 "path": path.to_string_lossy(),
503 "buffer_id": buffer_id.0,
504 })
505 }
506 HookArgs::BeforeFileSave { path, buffer_id } => {
507 serde_json::json!({
508 "path": path.to_string_lossy(),
509 "buffer_id": buffer_id.0,
510 })
511 }
512 HookArgs::AfterFileSave { path, buffer_id } => {
513 serde_json::json!({
514 "path": path.to_string_lossy(),
515 "buffer_id": buffer_id.0,
516 })
517 }
518 HookArgs::PreCommand { action } => {
519 serde_json::json!({ "action": format!("{:?}", action) })
520 }
521 HookArgs::PostCommand { action } => {
522 serde_json::json!({ "action": format!("{:?}", action) })
523 }
524 HookArgs::Idle { milliseconds } => {
525 serde_json::json!({ "milliseconds": milliseconds })
526 }
527 HookArgs::EditorInitialized => {
528 serde_json::json!({})
529 }
530 HookArgs::PromptChanged { prompt_type, input } => {
531 serde_json::json!({
532 "prompt_type": prompt_type,
533 "input": input,
534 })
535 }
536 HookArgs::PromptConfirmed {
537 prompt_type,
538 input,
539 selected_index,
540 } => {
541 serde_json::json!({
542 "prompt_type": prompt_type,
543 "input": input,
544 "selected_index": selected_index,
545 })
546 }
547 HookArgs::PromptCancelled { prompt_type, input } => {
548 serde_json::json!({
549 "prompt_type": prompt_type,
550 "input": input,
551 })
552 }
553 HookArgs::PromptSelectionChanged {
554 prompt_type,
555 selected_index,
556 } => {
557 serde_json::json!({
558 "prompt_type": prompt_type,
559 "selected_index": selected_index,
560 })
561 }
562 HookArgs::KeyboardShortcuts { bindings } => {
563 let entries: Vec<serde_json::Value> = bindings
564 .iter()
565 .map(|(key, action)| serde_json::json!({ "key": key, "action": action }))
566 .collect();
567 serde_json::json!({ "bindings": entries })
568 }
569 HookArgs::LspReferences { symbol, locations } => {
570 let locs: Vec<serde_json::Value> = locations
571 .iter()
572 .map(|loc| {
573 serde_json::json!({
574 "file": loc.file,
575 "line": loc.line,
576 "column": loc.column,
577 })
578 })
579 .collect();
580 serde_json::json!({ "symbol": symbol, "locations": locs })
581 }
582 HookArgs::LinesChanged { buffer_id, lines } => {
583 let lines_json: Vec<serde_json::Value> = lines
584 .iter()
585 .map(|line| {
586 serde_json::json!({
587 "line_number": line.line_number,
588 "byte_start": line.byte_start,
589 "byte_end": line.byte_end,
590 "content": line.content,
591 })
592 })
593 .collect();
594 serde_json::json!({
595 "buffer_id": buffer_id.0,
596 "lines": lines_json,
597 })
598 }
599 HookArgs::ViewTransformRequest {
600 buffer_id,
601 split_id,
602 viewport_start,
603 viewport_end,
604 tokens,
605 cursor_positions,
606 } => {
607 let tokens_json: Vec<serde_json::Value> = tokens
608 .iter()
609 .map(|token| {
610 let kind_json = match &token.kind {
611 ViewTokenWireKind::Text(s) => serde_json::json!({ "Text": s }),
612 ViewTokenWireKind::Newline => serde_json::json!("Newline"),
613 ViewTokenWireKind::Space => serde_json::json!("Space"),
614 ViewTokenWireKind::Break => serde_json::json!("Break"),
615 ViewTokenWireKind::BinaryByte(b) => serde_json::json!({ "BinaryByte": b }),
616 };
617 serde_json::json!({
618 "source_offset": token.source_offset,
619 "kind": kind_json,
620 })
621 })
622 .collect();
623 serde_json::json!({
624 "buffer_id": buffer_id.0,
625 "split_id": split_id.0,
626 "viewport_start": viewport_start,
627 "viewport_end": viewport_end,
628 "tokens": tokens_json,
629 "cursor_positions": cursor_positions,
630 })
631 }
632 HookArgs::MouseClick {
633 column,
634 row,
635 button,
636 modifiers,
637 content_x,
638 content_y,
639 } => {
640 serde_json::json!({
641 "column": column,
642 "row": row,
643 "button": button,
644 "modifiers": modifiers,
645 "content_x": content_x,
646 "content_y": content_y,
647 })
648 }
649 HookArgs::MouseMove {
650 column,
651 row,
652 content_x,
653 content_y,
654 } => {
655 serde_json::json!({
656 "column": column,
657 "row": row,
658 "content_x": content_x,
659 "content_y": content_y,
660 })
661 }
662 HookArgs::LspServerRequest {
663 language,
664 method,
665 server_command,
666 params,
667 } => {
668 serde_json::json!({
669 "language": language,
670 "method": method,
671 "server_command": server_command,
672 "params": params,
673 })
674 }
675 HookArgs::ViewportChanged {
676 split_id,
677 buffer_id,
678 top_byte,
679 top_line,
680 width,
681 height,
682 } => {
683 serde_json::json!({
684 "split_id": split_id.0,
685 "buffer_id": buffer_id.0,
686 "top_byte": top_byte,
687 "top_line": top_line,
688 "width": width,
689 "height": height,
690 })
691 }
692 HookArgs::LspServerError {
693 language,
694 server_command,
695 error_type,
696 message,
697 } => {
698 serde_json::json!({
699 "language": language,
700 "server_command": server_command,
701 "error_type": error_type,
702 "message": message,
703 })
704 }
705 HookArgs::LspStatusClicked {
706 language,
707 has_error,
708 } => {
709 serde_json::json!({
710 "language": language,
711 "has_error": has_error,
712 })
713 }
714 HookArgs::ActionPopupResult {
715 popup_id,
716 action_id,
717 } => {
718 serde_json::json!({
719 "popup_id": popup_id,
720 "action_id": action_id,
721 })
722 }
723 HookArgs::ProcessOutput { process_id, data } => {
724 serde_json::json!({
725 "process_id": process_id,
726 "data": data,
727 })
728 }
729 HookArgs::LanguageChanged {
730 buffer_id,
731 language,
732 } => {
733 serde_json::json!({
734 "buffer_id": buffer_id.0,
735 "language": language,
736 })
737 }
738 HookArgs::ThemeInspectKey { theme_name, key } => {
739 serde_json::json!({
740 "theme_name": theme_name,
741 "key": key,
742 })
743 }
744 HookArgs::MouseScroll {
745 buffer_id,
746 delta,
747 col,
748 row,
749 } => {
750 serde_json::json!({
751 "buffer_id": buffer_id.0,
752 "delta": delta,
753 "col": col,
754 "row": row,
755 })
756 }
757 HookArgs::Resize { width, height } => {
758 serde_json::json!({
759 "width": width,
760 "height": height,
761 })
762 }
763 HookArgs::FocusGained => {
764 serde_json::json!({})
765 }
766 };
767
768 Ok(json_value)
769}