1use std::{
2 collections::{HashMap, VecDeque},
3 hash::{Hash, Hasher},
4 path::Path,
5 process::{Command, Stdio},
6};
7
8use pepper::{
9 buffer::{BufferBreakpointId, BufferHandle, BufferProperties},
10 buffer_position::BufferPosition,
11 client::ClientManager,
12 command::{CommandError, CommandManager, CompletionSource},
13 cursor::Cursor,
14 editor::{Editor, EditorContext},
15 editor_utils::{to_absolute_path_string, LogKind},
16 events::{EditorEvent, EditorEventIter},
17 platform::{
18 IpcReadMode, IpcTag, Platform, PlatformIpcHandle, PlatformProcessHandle, PlatformRequest,
19 PooledBuf, ProcessTag,
20 },
21 plugin::{Plugin, PluginDefinition, PluginHandle},
22 serialization::{DeserializeError, Serialize},
23 ResourceFile,
24};
25
26mod protocol;
27
28use protocol::{
29 deserialize_remedybg_bytes, ProtocolError, RemedybgBool, RemedybgCommandKind,
30 RemedybgCommandResult, RemedybgEvent, RemedybgId, RemedybgProtocolBreakpoint,
31 RemedybgSourceLocationChangedReason, RemedybgStr,
32};
33
34pub static DEFINITION: PluginDefinition = PluginDefinition {
35 instantiate: |handle, ctx| {
36 register_commands(&mut ctx.editor.commands, handle);
37 Some(Plugin {
38 data: Box::new(RemedybgPlugin::default()),
39
40 on_editor_events,
41
42 on_process_spawned,
43 on_process_exit,
44
45 on_ipc_connected,
46 on_ipc_output,
47 on_ipc_close,
48
49 ..Default::default()
50 })
51 },
52 help_pages: &[ResourceFile {
53 name: "remedybg_help.md",
54 content: include_str!("../rc/help.md"),
55 }],
56};
57
58const SESSION_PREFIX: &str = "remedybg-";
59const IPC_BUF_SIZE: usize = 8 * 1024;
60const CONTROL_PIPE_ID: u32 = 0;
61const EVENT_PIPE_ID: u32 = 1;
62
63enum ProcessState {
64 NotRunning,
65 Spawning,
66 Running(PlatformProcessHandle),
67}
68impl Default for ProcessState {
69 fn default() -> Self {
70 Self::NotRunning
71 }
72}
73
74#[derive(PartialEq, Eq)]
75struct EditorToRemedybgBreakpointMapKey {
76 pub buffer_handle: BufferHandle,
77 pub breakpoint_id: BufferBreakpointId,
78}
79impl Hash for EditorToRemedybgBreakpointMapKey {
80 fn hash<H>(&self, state: &mut H)
81 where
82 H: Hasher,
83 {
84 state.write_u32(self.buffer_handle.0);
85 state.write_u32(self.breakpoint_id.0);
86 }
87}
88
89struct EditorToRemedybgBreakpointMapValue {
90 pub breakpoint_id: RemedybgId,
91 pub marked_for_deletion: bool,
92}
93
94struct BreakpointLocation {
95 pub buffer_handle: BufferHandle,
96 pub line_index: u32,
97}
98
99struct NewBreakpoint {
100 pub remedybg_id: RemedybgId,
101 pub buffer_handle: BufferHandle,
102 pub line_index: u32,
103}
104
105#[derive(Default)]
106pub(crate) struct RemedybgPlugin {
107 process_state: ProcessState,
108 session_name: String,
109
110 pending_commands: VecDeque<RemedybgCommandKind>,
111 control_ipc_handle: Option<PlatformIpcHandle>,
112
113 editor_to_remedybg_breakpoint_map:
114 HashMap<EditorToRemedybgBreakpointMapKey, EditorToRemedybgBreakpointMapValue>,
115 breakpoints: HashMap<RemedybgId, BreakpointLocation>,
116 new_breakpoints: Vec<NewBreakpoint>,
117 new_serialized_breakpoints: Vec<SerializedBreakpoint>,
118}
119impl RemedybgPlugin {
120 pub fn spawn(
121 &mut self,
122 platform: &mut Platform,
123 plugin_handle: PluginHandle,
124 editor_session_name: &str,
125 session_file: Option<&str>,
126 ) {
127 if !matches!(self.process_state, ProcessState::NotRunning) {
128 return;
129 }
130
131 self.process_state = ProcessState::Spawning;
132 self.session_name.clear();
133 self.session_name.push_str(SESSION_PREFIX);
134 self.session_name.push_str(editor_session_name);
135
136 let mut command = Command::new("remedybg");
137 command.arg("--servername");
138 command.arg(&self.session_name);
139 if let Some(session_file) = session_file {
140 command.arg(session_file);
141 }
142
143 command
144 .stdin(Stdio::null())
145 .stdout(Stdio::piped())
146 .stderr(Stdio::null());
147
148 platform.requests.enqueue(PlatformRequest::SpawnProcess {
149 tag: ProcessTag::Plugin {
150 plugin_handle,
151 id: 0,
152 },
153 command,
154 buf_len: 128,
155 });
156 }
157
158 fn control_ipc_handle(&self) -> Result<PlatformIpcHandle, CommandError> {
159 match self.control_ipc_handle {
160 Some(handle) => Ok(handle),
161 None => Err(CommandError::OtherStatic("remedybg is not running")),
162 }
163 }
164
165 fn begin_send_command(
166 &mut self,
167 platform: &mut Platform,
168 command_kind: RemedybgCommandKind,
169 ) -> Result<CommandSender, CommandError> {
170 let ipc_handle = self.control_ipc_handle()?;
171 let sender = begin_send_command_raw(
172 platform,
173 ipc_handle,
174 command_kind,
175 &mut self.pending_commands,
176 );
177 Ok(sender)
178 }
179
180 pub fn begin_sync_breakpoints(&mut self, platform: &mut Platform) -> Result<(), CommandError> {
181 let sender = self.begin_send_command(platform, RemedybgCommandKind::GetBreakpoints)?;
182 sender.send(platform);
183 Ok(())
184 }
185
186 fn send_buffer_breakpoint_changes(
187 &mut self,
188 editor: &mut Editor,
189 platform: &mut Platform,
190 buffer_handle: BufferHandle,
191 ) {
192 let ipc_handle = match self.control_ipc_handle() {
193 Ok(handle) => handle,
194 Err(_) => return,
195 };
196
197 for (key, value) in self.editor_to_remedybg_breakpoint_map.iter_mut() {
198 if key.buffer_handle == buffer_handle {
199 value.marked_for_deletion = true;
200 }
201 }
202
203 let buffer = editor.buffers.get(buffer_handle);
204 let current_directory = &editor.current_directory;
205 let mut file_path = editor.string_pool.acquire();
206
207 for breakpoint in buffer.breakpoints() {
208 let key = EditorToRemedybgBreakpointMapKey {
209 buffer_handle,
210 breakpoint_id: breakpoint.id,
211 };
212
213 let breakpoint_map_value = self.editor_to_remedybg_breakpoint_map.get_mut(&key);
214 let remedybg_breakpoint_location = breakpoint_map_value
215 .as_ref()
216 .and_then(|v| self.breakpoints.get(&v.breakpoint_id));
217 match (breakpoint_map_value, remedybg_breakpoint_location) {
218 (Some(breakpoint_map_value), Some(remedybg_breakpoint_location)) => {
219 breakpoint_map_value.marked_for_deletion = false;
220 if remedybg_breakpoint_location.line_index != breakpoint.line_index {
221 let line_num = (breakpoint.line_index + 1) as u32;
222
223 let mut sender = begin_send_command_raw(
224 platform,
225 ipc_handle,
226 RemedybgCommandKind::UpdateBreakpointLine,
227 &mut self.pending_commands,
228 );
229 let write = sender.write();
230 breakpoint_map_value.breakpoint_id.serialize(write);
231 line_num.serialize(write);
232 sender.send(platform);
233 }
234 }
235 _ => {
236 file_path.clear();
237 match get_absolue_file_path(current_directory, &buffer.path, &mut file_path) {
238 Ok(()) => {
239 let file_path = RemedybgStr(file_path.as_bytes());
240 let line_num = (breakpoint.line_index + 1) as u32;
241 let condition_expr = RemedybgStr(b"");
242
243 let mut sender = begin_send_command_raw(
244 platform,
245 ipc_handle,
246 RemedybgCommandKind::AddBreakpointAtFilenameLine,
247 &mut self.pending_commands,
248 );
249 let write = sender.write();
250 file_path.serialize(write);
251 line_num.serialize(write);
252 condition_expr.serialize(write);
253 sender.send(platform);
254 }
255 Err(error) => editor.logger.write(LogKind::Error).fmt(format_args!(
256 "remedybg: error while sending editor breakpoints: {}",
257 error
258 )),
259 }
260 }
261 }
262 }
263 editor.string_pool.release(file_path);
264
265 for value in self.editor_to_remedybg_breakpoint_map.values() {
266 if value.marked_for_deletion {
267 self.breakpoints.remove(&value.breakpoint_id);
268
269 let mut sender = begin_send_command_raw(
270 platform,
271 ipc_handle,
272 RemedybgCommandKind::DeleteBreakpoint,
273 &mut self.pending_commands,
274 );
275 let write = sender.write();
276 value.breakpoint_id.serialize(write);
277 sender.send(platform);
278 }
279 }
280 self.editor_to_remedybg_breakpoint_map
281 .retain(|_, v| !v.marked_for_deletion);
282 }
283}
284
285fn get_absolue_file_path(
286 current_directory: &Path,
287 buffer_path: &Path,
288 file_path: &mut String,
289) -> Result<(), CommandError> {
290 let current_directory = match current_directory.to_str() {
291 Some(path) => path,
292 None => return Err(CommandError::OtherStatic("current directory is not valid utf-8")),
293 };
294 let buffer_path = match buffer_path.to_str() {
295 Some(path) => path,
296 None => return Err(CommandError::OtherStatic("buffer path is not valid utf-8")),
297 };
298
299 to_absolute_path_string(current_directory, buffer_path, file_path);
300 Ok(())
301}
302
303struct CommandSender {
304 ipc_handle: PlatformIpcHandle,
305 buf: PooledBuf,
306}
307impl CommandSender {
308 pub fn write(&mut self) -> &mut Vec<u8> {
309 self.buf.write_no_clear()
310 }
311
312 pub fn send(self, platform: &mut Platform) {
313 platform.requests.enqueue(PlatformRequest::WriteToIpc {
314 handle: self.ipc_handle,
315 buf: self.buf,
316 })
317 }
318}
319fn begin_send_command(
320 ctx: &mut EditorContext,
321 plugin_handle: PluginHandle,
322 command_kind: RemedybgCommandKind,
323) -> Result<CommandSender, CommandError> {
324 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
325 remedybg.begin_send_command(&mut ctx.platform, command_kind)
326}
327fn begin_send_command_raw(
328 platform: &mut Platform,
329 ipc_handle: PlatformIpcHandle,
330 command_kind: RemedybgCommandKind,
331 pending_commands: &mut VecDeque<RemedybgCommandKind>,
332) -> CommandSender {
333 pending_commands.push_back(command_kind);
334
335 let mut buf = platform.buf_pool.acquire();
336 let write = buf.write();
337 command_kind.serialize(write);
338 CommandSender { ipc_handle, buf }
339}
340
341fn register_commands(commands: &mut CommandManager, plugin_handle: PluginHandle) {
342 let mut r = |name, completions, command_fn| {
343 commands.register_command(Some(plugin_handle), name, completions, command_fn);
344 };
345
346 r("remedybg-spawn", &[CompletionSource::Files], |ctx, io| {
347 let session_file = io.args.try_next();
348 io.args.assert_empty()?;
349
350 let plugin_handle = io.plugin_handle();
351 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
352 remedybg.spawn(
353 &mut ctx.platform,
354 plugin_handle,
355 &ctx.editor.session_name,
356 session_file,
357 );
358
359 Ok(())
360 });
361
362 r(
363 "remedybg-sync-breakpoints",
364 &[CompletionSource::Files],
365 |ctx, io| {
366 io.args.assert_empty()?;
367
368 let plugin_handle = io.plugin_handle();
369 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
370 remedybg.begin_sync_breakpoints(&mut ctx.platform)
371 },
372 );
373
374 static START_DEBUGGING_COMPLETIONS: &[CompletionSource] =
375 &[CompletionSource::Custom(&["paused"])];
376 r(
377 "remedybg-start-debugging",
378 START_DEBUGGING_COMPLETIONS,
379 |ctx, io| {
380 let start_paused = match io.args.try_next() {
381 Some("paused") => true,
382 Some(_) => return Err(CommandError::OtherStatic("invalid arg")),
383 None => false,
384 };
385 io.args.assert_empty()?;
386
387 let mut sender =
388 begin_send_command(ctx, io.plugin_handle(), RemedybgCommandKind::StartDebugging)?;
389 let write = sender.write();
390 RemedybgBool(start_paused).serialize(write);
391 sender.send(&mut ctx.platform);
392 Ok(())
393 },
394 );
395
396 r("remedybg-stop-debugging", &[], |ctx, io| {
397 io.args.assert_empty()?;
398 let sender =
399 begin_send_command(ctx, io.plugin_handle(), RemedybgCommandKind::StopDebugging)?;
400 sender.send(&mut ctx.platform);
401 Ok(())
402 });
403
404 r("remedybg-attach-to-process", &[], |ctx, io| {
405 let process_arg = io.args.next()?;
406 io.args.assert_empty()?;
407
408 let process = process_arg.parse::<u32>();
409 let mut sender = match process {
410 Ok(_) => begin_send_command(
411 ctx,
412 io.plugin_handle(),
413 RemedybgCommandKind::AttachToProcessByPid,
414 )?,
415 Err(_) => begin_send_command(
416 ctx,
417 io.plugin_handle(),
418 RemedybgCommandKind::AttachToProcessByName,
419 )?,
420 };
421 let write = sender.write();
422 match process {
423 Ok(pid) => pid.serialize(write),
424 Err(_) => process_arg.serialize(write),
425 }
426 RemedybgBool(false).serialize(write);
427 protocol::RDBG_IF_DEBUGGING_TARGET_STOP_DEBUGGING.serialize(write);
428 sender.send(&mut ctx.platform);
429 Ok(())
430 });
431
432 r("remedybg-step-into", &[], |ctx, io| {
433 io.args.assert_empty()?;
434 let sender =
435 begin_send_command(ctx, io.plugin_handle(), RemedybgCommandKind::StepIntoByLine)?;
436 sender.send(&mut ctx.platform);
437 Ok(())
438 });
439
440 r("remedybg-step-over", &[], |ctx, io| {
441 io.args.assert_empty()?;
442 let sender =
443 begin_send_command(ctx, io.plugin_handle(), RemedybgCommandKind::StepOverByLine)?;
444 sender.send(&mut ctx.platform);
445 Ok(())
446 });
447
448 r("remedybg-step-out", &[], |ctx, io| {
449 io.args.assert_empty()?;
450 let sender = begin_send_command(ctx, io.plugin_handle(), RemedybgCommandKind::StepOut)?;
451 sender.send(&mut ctx.platform);
452 Ok(())
453 });
454
455 r("remedybg-continue-execution", &[], |ctx, io| {
456 io.args.assert_empty()?;
457 let sender = begin_send_command(
458 ctx,
459 io.plugin_handle(),
460 RemedybgCommandKind::ContinueExecution,
461 )?;
462 sender.send(&mut ctx.platform);
463 Ok(())
464 });
465
466 r("remedybg-run-to-cursor", &[], |ctx, io| {
467 io.args.assert_empty()?;
468
469 let buffer_view_handle = io.current_buffer_view_handle(ctx)?;
470 let buffer_view = ctx.editor.buffer_views.get(buffer_view_handle);
471 let buffer_path = &ctx.editor.buffers.get(buffer_view.buffer_handle).path;
472 let current_directory = &ctx.editor.current_directory;
473
474 let mut file_path = ctx.editor.string_pool.acquire();
475 if let Err(error) = get_absolue_file_path(current_directory, buffer_path, &mut file_path) {
476 ctx.editor.string_pool.release(file_path);
477 return Err(error);
478 }
479
480 let line_number = buffer_view.cursors.main_cursor().position.line_index + 1;
481 let line_number = line_number as u32;
482
483 let mut sender = match begin_send_command(
484 ctx,
485 io.plugin_handle(),
486 RemedybgCommandKind::RunToFileAtLine,
487 ) {
488 Ok(sender) => sender,
489 Err(error) => {
490 ctx.editor.string_pool.release(file_path);
491 return Err(error);
492 }
493 };
494 let write = sender.write();
495 RemedybgStr(file_path.as_bytes()).serialize(write);
496 line_number.serialize(write);
497 sender.send(&mut ctx.platform);
498
499 ctx.editor.string_pool.release(file_path);
500 Ok(())
501 });
502
503 r("remedybg-break-execution", &[], |ctx, io| {
504 io.args.assert_empty()?;
505 let sender =
506 begin_send_command(ctx, io.plugin_handle(), RemedybgCommandKind::BreakExecution)?;
507 sender.send(&mut ctx.platform);
508 Ok(())
509 });
510}
511
512fn on_editor_events(plugin_handle: PluginHandle, ctx: &mut EditorContext) {
513 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
514
515 let mut handled_breakpoints_changed = false;
516 let mut events = EditorEventIter::new();
517 while let Some(event) = events.next(ctx.editor.events.reader()) {
518 match event {
519 &EditorEvent::BufferClose { handle } => remedybg
520 .breakpoints
521 .retain(|_, b| b.buffer_handle != handle),
522 &EditorEvent::BufferBreakpointsChanged { handle } => {
523 if !handled_breakpoints_changed {
524 handled_breakpoints_changed = true;
525 remedybg.send_buffer_breakpoint_changes(
526 &mut ctx.editor,
527 &mut ctx.platform,
528 handle,
529 );
530 }
531 }
532 _ => (),
533 }
534 }
535}
536
537fn on_process_spawned(
538 plugin_handle: PluginHandle,
539 ctx: &mut EditorContext,
540 _: u32,
541 process_handle: PlatformProcessHandle,
542) {
543 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
544 remedybg.process_state = ProcessState::Running(process_handle);
545
546 let mut control_path_buf = ctx.platform.buf_pool.acquire();
547 let path_write = control_path_buf.write();
548 path_write.extend_from_slice(remedybg.session_name.as_bytes());
549 ctx.platform
550 .requests
551 .enqueue(PlatformRequest::ConnectToIpc {
552 tag: IpcTag {
553 plugin_handle,
554 id: CONTROL_PIPE_ID,
555 },
556 path: control_path_buf,
557 read: true,
558 write: true,
559 read_mode: IpcReadMode::MessageStream,
560 buf_len: IPC_BUF_SIZE,
561 });
562
563 let mut event_path_buf = ctx.platform.buf_pool.acquire();
564 let path_write = event_path_buf.write();
565 path_write.extend_from_slice(remedybg.session_name.as_bytes());
566 path_write.extend_from_slice(b"-events");
567 ctx.platform
568 .requests
569 .enqueue(PlatformRequest::ConnectToIpc {
570 tag: IpcTag {
571 plugin_handle,
572 id: EVENT_PIPE_ID,
573 },
574 path: event_path_buf,
575 read: true,
576 write: false,
577 read_mode: IpcReadMode::MessageStream,
578 buf_len: IPC_BUF_SIZE,
579 });
580}
581
582fn on_process_exit(plugin_handle: PluginHandle, ctx: &mut EditorContext, _: u32) {
583 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
584 remedybg.process_state = ProcessState::NotRunning;
585 remedybg.breakpoints.clear();
586}
587
588fn get_ipc_name(ipc_id: u32) -> &'static str {
589 match ipc_id {
590 CONTROL_PIPE_ID => "control",
591 EVENT_PIPE_ID => "event",
592 _ => "unknown",
593 }
594}
595
596fn on_ipc_connected(
597 plugin_handle: PluginHandle,
598 ctx: &mut EditorContext,
599 id: u32,
600 ipc_handle: PlatformIpcHandle,
601) {
602 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
603 if id == CONTROL_PIPE_ID {
604 remedybg.control_ipc_handle = Some(ipc_handle);
605 let _ = remedybg.begin_sync_breakpoints(&mut ctx.platform);
606 }
607
608 let ipc_name = get_ipc_name(id);
609 ctx.editor
610 .logger
611 .write(LogKind::Diagnostic)
612 .fmt(format_args!("remedybg: connected to {} ipc", ipc_name));
613}
614
615struct SerializedBreakpoint {
616 id: RemedybgId,
617 filename_range: (u32, u32),
618 line_index: u32,
619}
620impl SerializedBreakpoint {
621 pub fn filename<'de>(&self, bytes: &'de [u8]) -> Result<&'de str, &'de [u8]> {
622 let (from, to) = self.filename_range;
623 let bytes = &bytes[from as usize..to as usize];
624 match std::str::from_utf8(bytes) {
625 Ok(str) => Ok(str),
626 Err(_) => Err(bytes),
627 }
628 }
629}
630
631fn get_all_breakpoints<'bytes>(
632 mut bytes: &'bytes [u8],
633 breakpoints: &mut Vec<SerializedBreakpoint>,
634) -> Result<&'bytes [u8], DeserializeError> {
635 breakpoints.clear();
636 let breakpoint_bytes = bytes;
637 let bytes_ptr = breakpoint_bytes.as_ptr() as usize;
638 let breakpoint_count = u16::deserialize(&mut bytes)?;
639 for _ in 0..breakpoint_count {
640 let id = RemedybgId::deserialize(&mut bytes)?;
641 let _enabled = RemedybgBool::deserialize(&mut bytes)?;
642 let _module_name = deserialize_remedybg_bytes(&mut bytes)?;
643 let _condition_expr = deserialize_remedybg_bytes(&mut bytes)?;
644 let breakpoint = RemedybgProtocolBreakpoint::deserialize(&mut bytes)?;
645 if let RemedybgProtocolBreakpoint::FilenameLine { filename, line_num } = breakpoint {
646 let filename_from = filename.0.as_ptr() as usize - bytes_ptr;
647 let filename_to = filename_from + filename.0.len();
648 let line_index = line_num.saturating_sub(1);
649 breakpoints.push(SerializedBreakpoint {
650 id,
651 filename_range: (filename_from as _, filename_to as _),
652 line_index,
653 });
654 }
655 }
656 Ok(breakpoint_bytes)
657}
658
659fn log_filename_invaid_utf8(editor: &mut Editor, bytes: &[u8]) {
660 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
661 "remedybg: serialized breakpoint has invalid utf-8 filename: {}",
662 String::from_utf8_lossy(bytes),
663 ));
664}
665
666fn on_control_response(
667 remedybg: &mut RemedybgPlugin,
668 editor: &mut Editor,
669 platform: &mut Platform,
670 command_kind: RemedybgCommandKind,
671 mut bytes: &[u8],
672) -> Result<(), ProtocolError> {
673 let ipc_handle = remedybg.control_ipc_handle()?;
674
675 match RemedybgCommandResult::deserialize(&mut bytes) {
676 Ok(RemedybgCommandResult::Ok) => (),
677 Ok(result) => return Err(ProtocolError::RemedybgCommandResult(result)),
678 Err(error) => return Err(error.into()),
679 }
680
681 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
682 "remedybg: on control response: [{:?}] bytes left: {}",
683 std::mem::discriminant(&command_kind),
684 bytes.len(),
685 ));
686
687 match command_kind {
688 RemedybgCommandKind::GetBreakpoints => {
689 let current_directory = &editor.current_directory;
690
691 let mut file_path = editor.string_pool.acquire();
692 for buffer in editor.buffers.iter_mut() {
693 let buffer_handle = buffer.handle();
694 let len = buffer.breakpoints().len();
695 if len == 0 {
696 continue;
697 }
698
699 file_path.clear();
700 let has_file_path = match get_absolue_file_path(
701 current_directory,
702 &buffer.path,
703 &mut file_path,
704 ) {
705 Ok(()) => true,
706 Err(error) => {
707 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
708 "remedybg: error when trying to get buffer ({:?} {:?}) absolute file path: {}",
709 current_directory,
710 &buffer.path,
711 error,
712 ));
713 false
714 }
715 };
716
717 let events = editor.events.writer();
718 let mut breakpoints = buffer.breakpoints_mut();
719 for i in 0..len {
720 let breakpoint = &breakpoints.as_slice()[i];
721 let key = EditorToRemedybgBreakpointMapKey {
722 buffer_handle,
723 breakpoint_id: breakpoint.id,
724 };
725
726 if remedybg
727 .editor_to_remedybg_breakpoint_map
728 .contains_key(&key)
729 || !has_file_path
730 {
731 breakpoints.remove_at(i, events);
732 } else {
733 let mut sender = begin_send_command_raw(
734 platform,
735 ipc_handle,
736 RemedybgCommandKind::AddBreakpointAtFilenameLine,
737 &mut remedybg.pending_commands,
738 );
739 let file_path = RemedybgStr(file_path.as_bytes());
740 let line_num = (breakpoint.line_index + 1) as u32;
741 let condition_expr = RemedybgStr(b"");
742
743 let write = sender.write();
744 file_path.serialize(write);
745 line_num.serialize(write);
746 condition_expr.serialize(write);
747 sender.send(platform);
748 }
749 }
750 }
751 editor.string_pool.release(file_path);
752
753 remedybg.breakpoints.clear();
754 remedybg.editor_to_remedybg_breakpoint_map.clear();
755 remedybg.new_breakpoints.clear();
756
757 let breakpoint_bytes =
758 get_all_breakpoints(bytes, &mut remedybg.new_serialized_breakpoints)?;
759 for breakpoint in &remedybg.new_serialized_breakpoints {
760 let filename = match breakpoint.filename(breakpoint_bytes) {
761 Ok(filename) => filename,
762 Err(bytes) => {
763 log_filename_invaid_utf8(editor, bytes);
764 continue;
765 }
766 };
767
768 let result =
769 editor.buffer_handle_from_path(Path::new(filename), BufferProperties::text());
770 match result.read_error {
771 Some(error) => {
772 let buffer = editor.buffers.get(result.buffer_handle);
773 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
774 "remedybg: could not open buffer {:?}: {}",
775 &buffer.path, error,
776 ));
777 let events = editor.events.writer();
778 editor.buffers.defer_remove(result.buffer_handle, events);
779 }
780 None => {
781 remedybg.breakpoints.insert(
782 breakpoint.id,
783 BreakpointLocation {
784 buffer_handle: result.buffer_handle,
785 line_index: breakpoint.line_index,
786 },
787 );
788 remedybg.new_breakpoints.push(NewBreakpoint {
789 remedybg_id: breakpoint.id,
790 buffer_handle: result.buffer_handle,
791 line_index: breakpoint.line_index,
792 });
793 }
794 }
795 }
796
797 remedybg
798 .new_breakpoints
799 .sort_unstable_by_key(|b| b.buffer_handle.0);
800
801 let mut new_breakpoints = &remedybg.new_breakpoints[..];
802 while let Some(first) = new_breakpoints.first() {
803 let buffer_handle = first.buffer_handle;
804
805 let end_index = match new_breakpoints
806 .iter()
807 .position(|b| b.buffer_handle != buffer_handle)
808 {
809 Some(i) => i,
810 None => new_breakpoints.len(),
811 };
812 let (new_buffer_breakpoints, rest) = new_breakpoints.split_at(end_index);
813 new_breakpoints = rest;
814
815 let buffer = editor.buffers.get_mut(buffer_handle);
816 let mut breakpoints = buffer.breakpoints_mut();
817 let events = editor.events.writer();
818 for new_buffer_breakpoint in new_buffer_breakpoints {
819 let breakpoint = breakpoints.add(new_buffer_breakpoint.line_index as _, events);
820 let key = EditorToRemedybgBreakpointMapKey {
821 buffer_handle,
822 breakpoint_id: breakpoint.id,
823 };
824 remedybg.editor_to_remedybg_breakpoint_map.insert(
825 key,
826 EditorToRemedybgBreakpointMapValue {
827 breakpoint_id: new_buffer_breakpoint.remedybg_id,
828 marked_for_deletion: false,
829 },
830 );
831 }
832 }
833 }
834 RemedybgCommandKind::GetBreakpoint => {
835 let id = RemedybgId::deserialize(&mut bytes)?;
836 let _enabled = RemedybgBool::deserialize(&mut bytes)?;
837 let _module_name = deserialize_remedybg_bytes(&mut bytes)?;
838 let _condition_expr = deserialize_remedybg_bytes(&mut bytes)?;
839 let breakpoint = RemedybgProtocolBreakpoint::deserialize(&mut bytes)?;
840 if let RemedybgProtocolBreakpoint::FilenameLine { filename, line_num } = breakpoint {
841 let line_index = line_num.saturating_sub(1);
842
843 if let Some(breakpoint) = remedybg.breakpoints.remove(&id) {
844 let buffer_handle = breakpoint.buffer_handle;
845 let buffer = editor.buffers.get_mut(buffer_handle);
846 let breakpoint_index = buffer
847 .breakpoints()
848 .binary_search_by_key(&breakpoint.line_index, |b| b.line_index);
849 if let Ok(breakpoint_index) = breakpoint_index {
850 let mut breakpoints = buffer.breakpoints_mut();
851 let breakpoint =
852 breakpoints.remove_at(breakpoint_index, editor.events.writer());
853 let key = EditorToRemedybgBreakpointMapKey {
854 buffer_handle,
855 breakpoint_id: breakpoint.id,
856 };
857 remedybg.editor_to_remedybg_breakpoint_map.remove(&key);
858 }
859 }
860
861 match std::str::from_utf8(filename.0) {
862 Ok(filename) => {
863 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
864 "remedybg: update breakpoint: {} to {}:{}",
865 id.0, filename, line_index,
866 ));
867
868 let buffer_handle = editor
869 .buffers
870 .find_with_path(&editor.current_directory, Path::new(filename));
871 if let Some(buffer_handle) = buffer_handle {
872 let buffer = editor.buffers.get_mut(buffer_handle);
873 let breakpoint_index = buffer
874 .breakpoints()
875 .binary_search_by_key(&line_index, |b| b.line_index);
876 let breakpoint = match breakpoint_index {
877 Ok(i) => buffer.breakpoints()[i],
878 Err(_) => buffer
879 .breakpoints_mut()
880 .add(line_index, editor.events.writer()),
881 };
882
883 remedybg.breakpoints.insert(
884 id,
885 BreakpointLocation {
886 buffer_handle,
887 line_index,
888 },
889 );
890 let key = EditorToRemedybgBreakpointMapKey {
891 buffer_handle,
892 breakpoint_id: breakpoint.id,
893 };
894 remedybg.editor_to_remedybg_breakpoint_map.insert(
895 key,
896 EditorToRemedybgBreakpointMapValue {
897 breakpoint_id: id,
898 marked_for_deletion: false,
899 },
900 );
901 }
902 }
903 Err(_) => log_filename_invaid_utf8(editor, bytes),
904 };
905 }
906 }
907 _ => (),
908 }
909
910 Ok(())
911}
912
913fn on_event(
914 remedybg: &mut RemedybgPlugin,
915 editor: &mut Editor,
916 platform: &mut Platform,
917 clients: &mut ClientManager,
918 event: &RemedybgEvent,
919 bytes: &[u8],
920) -> Result<(), ProtocolError> {
921 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
922 "remedybg: on event: {} bytes left: {}",
923 event,
924 bytes.len(),
925 ));
926
927 match event {
928 &RemedybgEvent::SourceLocationChanged {
929 filename,
930 line_num,
931 reason,
932 } => {
933 let should_focus = matches!(
934 reason,
935 RemedybgSourceLocationChangedReason::BreakpointHit
936 | RemedybgSourceLocationChangedReason::ExceptionHit
937 | RemedybgSourceLocationChangedReason::StepOver
938 | RemedybgSourceLocationChangedReason::StepIn
939 | RemedybgSourceLocationChangedReason::StepOut
940 | RemedybgSourceLocationChangedReason::NonUserBreakpoint
941 | RemedybgSourceLocationChangedReason::DebugBreak
942 );
943 if should_focus {
944 if let Some(client_handle) = clients.focused_client() {
945 let line_index = line_num.saturating_sub(1);
946 match std::str::from_utf8(filename.0) {
947 Ok(filename) => {
948 let buffer_view_handle = editor.buffer_view_handle_from_path(
949 client_handle,
950 Path::new(filename),
951 BufferProperties::text(),
952 false,
953 );
954 if let Ok(buffer_view_handle) = buffer_view_handle {
955 {
956 let position = BufferPosition::line_col(line_index, 0);
957 let buffer_view =
958 editor.buffer_views.get_mut(buffer_view_handle);
959 let mut cursors = buffer_view.cursors.mut_guard();
960 cursors.clear();
961 cursors.add(Cursor {
962 anchor: position,
963 position,
964 });
965 }
966
967 {
968 let client = clients.get_mut(client_handle);
969 client.set_buffer_view_handle(
970 Some(buffer_view_handle),
971 &editor.buffer_views,
972 );
973 }
974 }
975 }
976 Err(_) => log_filename_invaid_utf8(editor, bytes),
977 };
978 }
979 }
980 }
981 &RemedybgEvent::BreakpointAdded { breakpoint_id } => {
982 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
983 "remedybg: breakpoint added: {}",
984 breakpoint_id.0
985 ));
986
987 let mut sender =
988 remedybg.begin_send_command(platform, RemedybgCommandKind::GetBreakpoint)?;
989 let write = sender.write();
990 breakpoint_id.serialize(write);
991 sender.send(platform);
992 }
993 &RemedybgEvent::BreakpointModified { breakpoint_id, .. } => {
994 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
995 "remedybg: breakpoint modified: {}",
996 breakpoint_id.0
997 ));
998
999 let mut sender =
1000 remedybg.begin_send_command(platform, RemedybgCommandKind::GetBreakpoint)?;
1001 let write = sender.write();
1002 breakpoint_id.serialize(write);
1003 sender.send(platform);
1004 }
1005 &RemedybgEvent::BreakpointRemoved { breakpoint_id } => {
1006 editor.logger.write(LogKind::Diagnostic).fmt(format_args!(
1007 "remedybg: breakpoint removed: {}",
1008 breakpoint_id.0
1009 ));
1010
1011 if let Some(breakpoint) = remedybg.breakpoints.remove(&breakpoint_id) {
1012 let buffer = editor.buffers.get_mut(breakpoint.buffer_handle);
1013 let breakpoint_index = buffer
1014 .breakpoints()
1015 .binary_search_by_key(&breakpoint.line_index, |b| b.line_index);
1016 if let Ok(breakpoint_index) = breakpoint_index {
1017 let mut breakpoints = buffer.breakpoints_mut();
1018 breakpoints.remove_at(breakpoint_index, editor.events.writer());
1019 }
1020
1021 remedybg
1022 .editor_to_remedybg_breakpoint_map
1023 .retain(|_, v| v.breakpoint_id.0 != breakpoint_id.0);
1024 }
1025 }
1026 _ => (),
1027 }
1028
1029 Ok(())
1030}
1031
1032fn on_ipc_output(plugin_handle: PluginHandle, ctx: &mut EditorContext, id: u32, mut bytes: &[u8]) {
1033 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
1034 let message_bytes = bytes;
1035
1036 match id {
1037 CONTROL_PIPE_ID => match remedybg.pending_commands.pop_front() {
1038 Some(command_kind) => {
1039 if let Err(error) = on_control_response(
1040 remedybg,
1041 &mut ctx.editor,
1042 &mut ctx.platform,
1043 command_kind,
1044 bytes,
1045 ) {
1046 ctx.editor.logger.write(LogKind::Error).fmt(format_args!(
1047 "remedybg: error while deserializing command result {}: {}\nmessage:\n{:?}",
1048 command_kind as usize, error, message_bytes
1049 ));
1050 }
1051 }
1052 None => ctx.editor.logger.write(LogKind::Error).fmt(format_args!(
1053 "remedybg: received response with no pending command"
1054 )),
1055 },
1056 EVENT_PIPE_ID => match RemedybgEvent::deserialize(&mut bytes) {
1057 Ok(event) => {
1058 if let Err(error) = on_event(
1059 remedybg,
1060 &mut ctx.editor,
1061 &mut ctx.platform,
1062 &mut ctx.clients,
1063 &event,
1064 bytes,
1065 ) {
1066 ctx.editor.logger.write(LogKind::Error).fmt(format_args!(
1067 "remedybg: error while deserializing event {}: {}\nmessage:\n{:?}",
1068 event, error, message_bytes
1069 ));
1070 }
1071 }
1072 Err(_) => {
1073 let first_u16 = match message_bytes {
1074 [b0, b1, ..] => u16::from_le_bytes([*b0, *b1]),
1075 _ => 0,
1076 };
1077 ctx.editor.logger.write(LogKind::Error).fmt(format_args!(
1078 "remedybg: could not deserialize debug event\nmessage:\n{:?}\nfirst u16: {}",
1079 message_bytes, first_u16,
1080 ));
1081 return;
1082 }
1083 },
1084 _ => unreachable!(),
1085 }
1086}
1087
1088fn on_ipc_close(plugin_handle: PluginHandle, ctx: &mut EditorContext, id: u32) {
1089 let remedybg = ctx.plugins.get_as::<RemedybgPlugin>(plugin_handle);
1090 if id == CONTROL_PIPE_ID {
1091 remedybg.control_ipc_handle = None;
1092 }
1093
1094 let ipc_name = get_ipc_name(id);
1095 ctx.editor
1096 .logger
1097 .write(LogKind::Diagnostic)
1098 .fmt(format_args!("remedybg: {} ipc closed", ipc_name));
1099}