pepper_plugin_remedybg/
lib.rs

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}