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
295#[derive(Debug, Clone, serde::Serialize)]
297pub struct LineInfo {
298 pub line_number: usize,
300 pub byte_start: usize,
302 pub byte_end: usize,
304 pub content: String,
306}
307
308#[derive(Debug, Clone, serde::Serialize)]
310pub struct LspLocation {
311 pub file: String,
313 pub line: u32,
315 pub column: u32,
317}
318
319pub type HookCallback = Box<dyn Fn(&HookArgs) -> bool + Send + Sync>;
321
322pub struct HookRegistry {
324 hooks: HashMap<String, Vec<HookCallback>>,
326}
327
328impl HookRegistry {
329 pub fn new() -> Self {
331 Self {
332 hooks: HashMap::new(),
333 }
334 }
335
336 pub fn add_hook(&mut self, name: &str, callback: HookCallback) {
338 self.hooks
339 .entry(name.to_string())
340 .or_default()
341 .push(callback);
342 }
343
344 pub fn remove_hooks(&mut self, name: &str) {
346 self.hooks.remove(name);
347 }
348
349 pub fn run_hooks(&self, name: &str, args: &HookArgs) -> bool {
351 if let Some(hooks) = self.hooks.get(name) {
352 for callback in hooks {
353 if !callback(args) {
354 return false;
355 }
356 }
357 }
358 true
359 }
360
361 pub fn hook_count(&self, name: &str) -> usize {
363 self.hooks.get(name).map(|v| v.len()).unwrap_or(0)
364 }
365
366 pub fn hook_names(&self) -> Vec<String> {
368 self.hooks.keys().cloned().collect()
369 }
370}
371
372impl Default for HookRegistry {
373 fn default() -> Self {
374 Self::new()
375 }
376}
377
378pub fn hook_args_to_json(args: &HookArgs) -> Result<serde_json::Value> {
380 let json_value = match args {
381 HookArgs::RenderStart { buffer_id } => {
382 serde_json::json!({
383 "buffer_id": buffer_id.0,
384 })
385 }
386 HookArgs::RenderLine {
387 buffer_id,
388 line_number,
389 byte_start,
390 byte_end,
391 content,
392 } => {
393 serde_json::json!({
394 "buffer_id": buffer_id.0,
395 "line_number": line_number,
396 "byte_start": byte_start,
397 "byte_end": byte_end,
398 "content": content,
399 })
400 }
401 HookArgs::BufferActivated { buffer_id } => {
402 serde_json::json!({ "buffer_id": buffer_id.0 })
403 }
404 HookArgs::BufferDeactivated { buffer_id } => {
405 serde_json::json!({ "buffer_id": buffer_id.0 })
406 }
407 HookArgs::DiagnosticsUpdated { uri, count } => {
408 serde_json::json!({
409 "uri": uri,
410 "count": count,
411 })
412 }
413 HookArgs::BufferClosed { buffer_id } => {
414 serde_json::json!({ "buffer_id": buffer_id.0 })
415 }
416 HookArgs::CursorMoved {
417 buffer_id,
418 cursor_id,
419 old_position,
420 new_position,
421 line,
422 text_properties,
423 } => {
424 serde_json::json!({
425 "buffer_id": buffer_id.0,
426 "cursor_id": cursor_id.0,
427 "old_position": old_position,
428 "new_position": new_position,
429 "line": line,
430 "text_properties": text_properties,
431 })
432 }
433 HookArgs::BeforeInsert {
434 buffer_id,
435 position,
436 text,
437 } => {
438 serde_json::json!({
439 "buffer_id": buffer_id.0,
440 "position": position,
441 "text": text,
442 })
443 }
444 HookArgs::AfterInsert {
445 buffer_id,
446 position,
447 text,
448 affected_start,
449 affected_end,
450 start_line,
451 end_line,
452 lines_added,
453 } => {
454 serde_json::json!({
455 "buffer_id": buffer_id.0,
456 "position": position,
457 "text": text,
458 "affected_start": affected_start,
459 "affected_end": affected_end,
460 "start_line": start_line,
461 "end_line": end_line,
462 "lines_added": lines_added,
463 })
464 }
465 HookArgs::BeforeDelete { buffer_id, range } => {
466 serde_json::json!({
467 "buffer_id": buffer_id.0,
468 "start": range.start,
469 "end": range.end,
470 })
471 }
472 HookArgs::AfterDelete {
473 buffer_id,
474 range,
475 deleted_text,
476 affected_start,
477 deleted_len,
478 start_line,
479 end_line,
480 lines_removed,
481 } => {
482 serde_json::json!({
483 "buffer_id": buffer_id.0,
484 "start": range.start,
485 "end": range.end,
486 "deleted_text": deleted_text,
487 "affected_start": affected_start,
488 "deleted_len": deleted_len,
489 "start_line": start_line,
490 "end_line": end_line,
491 "lines_removed": lines_removed,
492 })
493 }
494 HookArgs::BeforeFileOpen { path } => {
495 serde_json::json!({ "path": path.to_string_lossy() })
496 }
497 HookArgs::AfterFileOpen { path, buffer_id } => {
498 serde_json::json!({
499 "path": path.to_string_lossy(),
500 "buffer_id": buffer_id.0,
501 })
502 }
503 HookArgs::BeforeFileSave { path, buffer_id } => {
504 serde_json::json!({
505 "path": path.to_string_lossy(),
506 "buffer_id": buffer_id.0,
507 })
508 }
509 HookArgs::AfterFileSave { path, buffer_id } => {
510 serde_json::json!({
511 "path": path.to_string_lossy(),
512 "buffer_id": buffer_id.0,
513 })
514 }
515 HookArgs::PreCommand { action } => {
516 serde_json::json!({ "action": format!("{:?}", action) })
517 }
518 HookArgs::PostCommand { action } => {
519 serde_json::json!({ "action": format!("{:?}", action) })
520 }
521 HookArgs::Idle { milliseconds } => {
522 serde_json::json!({ "milliseconds": milliseconds })
523 }
524 HookArgs::EditorInitialized => {
525 serde_json::json!({})
526 }
527 HookArgs::PromptChanged { prompt_type, input } => {
528 serde_json::json!({
529 "prompt_type": prompt_type,
530 "input": input,
531 })
532 }
533 HookArgs::PromptConfirmed {
534 prompt_type,
535 input,
536 selected_index,
537 } => {
538 serde_json::json!({
539 "prompt_type": prompt_type,
540 "input": input,
541 "selected_index": selected_index,
542 })
543 }
544 HookArgs::PromptCancelled { prompt_type, input } => {
545 serde_json::json!({
546 "prompt_type": prompt_type,
547 "input": input,
548 })
549 }
550 HookArgs::PromptSelectionChanged {
551 prompt_type,
552 selected_index,
553 } => {
554 serde_json::json!({
555 "prompt_type": prompt_type,
556 "selected_index": selected_index,
557 })
558 }
559 HookArgs::KeyboardShortcuts { bindings } => {
560 let entries: Vec<serde_json::Value> = bindings
561 .iter()
562 .map(|(key, action)| serde_json::json!({ "key": key, "action": action }))
563 .collect();
564 serde_json::json!({ "bindings": entries })
565 }
566 HookArgs::LspReferences { symbol, locations } => {
567 let locs: Vec<serde_json::Value> = locations
568 .iter()
569 .map(|loc| {
570 serde_json::json!({
571 "file": loc.file,
572 "line": loc.line,
573 "column": loc.column,
574 })
575 })
576 .collect();
577 serde_json::json!({ "symbol": symbol, "locations": locs })
578 }
579 HookArgs::LinesChanged { buffer_id, lines } => {
580 let lines_json: Vec<serde_json::Value> = lines
581 .iter()
582 .map(|line| {
583 serde_json::json!({
584 "line_number": line.line_number,
585 "byte_start": line.byte_start,
586 "byte_end": line.byte_end,
587 "content": line.content,
588 })
589 })
590 .collect();
591 serde_json::json!({
592 "buffer_id": buffer_id.0,
593 "lines": lines_json,
594 })
595 }
596 HookArgs::ViewTransformRequest {
597 buffer_id,
598 split_id,
599 viewport_start,
600 viewport_end,
601 tokens,
602 cursor_positions,
603 } => {
604 let tokens_json: Vec<serde_json::Value> = tokens
605 .iter()
606 .map(|token| {
607 let kind_json = match &token.kind {
608 ViewTokenWireKind::Text(s) => serde_json::json!({ "Text": s }),
609 ViewTokenWireKind::Newline => serde_json::json!("Newline"),
610 ViewTokenWireKind::Space => serde_json::json!("Space"),
611 ViewTokenWireKind::Break => serde_json::json!("Break"),
612 ViewTokenWireKind::BinaryByte(b) => serde_json::json!({ "BinaryByte": b }),
613 };
614 serde_json::json!({
615 "source_offset": token.source_offset,
616 "kind": kind_json,
617 })
618 })
619 .collect();
620 serde_json::json!({
621 "buffer_id": buffer_id.0,
622 "split_id": split_id.0,
623 "viewport_start": viewport_start,
624 "viewport_end": viewport_end,
625 "tokens": tokens_json,
626 "cursor_positions": cursor_positions,
627 })
628 }
629 HookArgs::MouseClick {
630 column,
631 row,
632 button,
633 modifiers,
634 content_x,
635 content_y,
636 } => {
637 serde_json::json!({
638 "column": column,
639 "row": row,
640 "button": button,
641 "modifiers": modifiers,
642 "content_x": content_x,
643 "content_y": content_y,
644 })
645 }
646 HookArgs::MouseMove {
647 column,
648 row,
649 content_x,
650 content_y,
651 } => {
652 serde_json::json!({
653 "column": column,
654 "row": row,
655 "content_x": content_x,
656 "content_y": content_y,
657 })
658 }
659 HookArgs::LspServerRequest {
660 language,
661 method,
662 server_command,
663 params,
664 } => {
665 serde_json::json!({
666 "language": language,
667 "method": method,
668 "server_command": server_command,
669 "params": params,
670 })
671 }
672 HookArgs::ViewportChanged {
673 split_id,
674 buffer_id,
675 top_byte,
676 top_line,
677 width,
678 height,
679 } => {
680 serde_json::json!({
681 "split_id": split_id.0,
682 "buffer_id": buffer_id.0,
683 "top_byte": top_byte,
684 "top_line": top_line,
685 "width": width,
686 "height": height,
687 })
688 }
689 HookArgs::LspServerError {
690 language,
691 server_command,
692 error_type,
693 message,
694 } => {
695 serde_json::json!({
696 "language": language,
697 "server_command": server_command,
698 "error_type": error_type,
699 "message": message,
700 })
701 }
702 HookArgs::LspStatusClicked {
703 language,
704 has_error,
705 } => {
706 serde_json::json!({
707 "language": language,
708 "has_error": has_error,
709 })
710 }
711 HookArgs::ActionPopupResult {
712 popup_id,
713 action_id,
714 } => {
715 serde_json::json!({
716 "popup_id": popup_id,
717 "action_id": action_id,
718 })
719 }
720 HookArgs::ProcessOutput { process_id, data } => {
721 serde_json::json!({
722 "process_id": process_id,
723 "data": data,
724 })
725 }
726 HookArgs::LanguageChanged {
727 buffer_id,
728 language,
729 } => {
730 serde_json::json!({
731 "buffer_id": buffer_id.0,
732 "language": language,
733 })
734 }
735 HookArgs::ThemeInspectKey { theme_name, key } => {
736 serde_json::json!({
737 "theme_name": theme_name,
738 "key": key,
739 })
740 }
741 HookArgs::MouseScroll {
742 buffer_id,
743 delta,
744 col,
745 row,
746 } => {
747 serde_json::json!({
748 "buffer_id": buffer_id.0,
749 "delta": delta,
750 "col": col,
751 "row": row,
752 })
753 }
754 HookArgs::Resize { width, height } => {
755 serde_json::json!({
756 "width": width,
757 "height": height,
758 })
759 }
760 };
761
762 Ok(json_value)
763}