In this case, you should use replace widgets, not mark widgets.
Obsidian’s live preview decorations are implemented as a state field that provides decorations that display replace widgets when the cursor is not overlapping with the target positions.
Have you read Obsidian’s dev docs and CodeMirror’s system guide, as well as decoration examples? If not, they are where you start off.