fix: skip reformatting when pasting in codeblock (#19)

- Add isCursorInCodeBlock() helper that detects
  fenced code blocks by scanning from line 0 to
  cursor, toggling on backtick/tilde fences
- Add early-return guard in onPaste() to bypass
  reformatting when cursor is inside a code block
- Archive openspec change proposal and update
  paste-interception spec with new requirement
This commit is contained in:
Keath Milligan 2026-02-10 21:30:10 -06:00 committed by GitHub
parent 4f539dc990
commit 6be4d6ec56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 133 additions and 0 deletions

View file

@ -0,0 +1,12 @@
# Change: Skip reformatting when pasting into a code block
## Why
When a user pastes content into a fenced code block (`` ``` ``), the plugin reformats the pasted text (HTML-to-Markdown conversion, heading cascade, regex replacements, etc.). This destroys the raw content the user intended to paste as code. The plugin should detect code block context and let Obsidian's default paste proceed. (GitHub issue #14)
## What Changes
- Add a code block detection function that scans backward from the cursor to determine if the cursor is inside a fenced code block
- Add an early-return guard in `onPaste()` that skips reformatting when the cursor is inside a code block
## Impact
- Affected specs: `paste-interception`
- Affected code: `src/main.ts` (`onPaste` method, new helper function)

View file

@ -0,0 +1,39 @@
## ADDED Requirements
### Requirement: Code block paste bypass
The plugin SHALL NOT reformat pasted content when the cursor is inside a fenced code block. When a fenced code block is detected, the plugin SHALL allow Obsidian's default paste behavior to proceed unmodified.
A fenced code block is detected by scanning backward from the cursor line to find an opening fence (a line starting with `` ``` `` or `~~~`, optionally followed by a language identifier) that has no corresponding closing fence before the cursor position.
#### Scenario: Paste inside an open fenced code block
- **WHEN** `pasteOverride` is enabled
- **AND** the cursor is positioned inside a fenced code block (between an opening `` ``` `` and before a closing `` ``` ``)
- **AND** the user pastes content
- **THEN** the plugin SHALL skip all transformations
- **AND** `event.preventDefault()` SHALL NOT be called
- **AND** Obsidian's default paste behavior SHALL proceed
#### Scenario: Paste outside a code block
- **WHEN** `pasteOverride` is enabled
- **AND** the cursor is NOT inside a fenced code block
- **AND** the user pastes content
- **THEN** the plugin SHALL process the paste as normal (applying configured transformations)
#### Scenario: Paste after a closed code block
- **WHEN** `pasteOverride` is enabled
- **AND** the document contains a fenced code block that has both opening and closing fences
- **AND** the cursor is positioned after the closing fence
- **THEN** the plugin SHALL process the paste as normal (the code block is closed, cursor is not inside it)
#### Scenario: Fenced code block with language specifier
- **WHEN** `pasteOverride` is enabled
- **AND** the cursor is inside a fenced code block that has a language specifier (e.g., `` ```javascript ``)
- **AND** the user pastes content
- **THEN** the plugin SHALL skip all transformations
- **AND** Obsidian's default paste behavior SHALL proceed
#### Scenario: Tilde-fenced code block
- **WHEN** `pasteOverride` is enabled
- **AND** the cursor is inside a tilde-fenced code block (opened with `~~~`)
- **AND** the user pastes content
- **THEN** the plugin SHALL skip all transformations
- **AND** Obsidian's default paste behavior SHALL proceed

View file

@ -0,0 +1,11 @@
## 1. Implementation
- [x] 1.1 Add `isCursorInCodeBlock(editor)` helper to `src/main.ts` that scans lines backward from the cursor to detect unclosed fenced code blocks (triple-backtick or triple-tilde)
- [x] 1.2 Add early-return guard in `onPaste()` that calls `isCursorInCodeBlock()` and skips reformatting when inside a code block
- [x] 1.3 Add debug log message when paste is skipped due to code block context
## 2. Verification
- [x] 2.1 Build with `npm run build` to confirm no compilation errors
- [ ] 2.2 Manual test: paste HTML content into a fenced code block and verify raw text is pasted (no reformatting)
- [ ] 2.3 Manual test: paste HTML content outside a code block and verify reformatting still works
- [ ] 2.4 Manual test: paste into a code block with a language specifier (e.g., ```js) and verify raw text is pasted
- [ ] 2.5 Manual test: paste into an indented code block (4 spaces) -- note: this change only targets fenced blocks

View file

@ -35,3 +35,42 @@ The heading cascade logic in `transformMarkdown` SHALL accumulate the `appliedTr
- **AND** pasted content contains multiple headings where at least one is changed
- **THEN** the `appliedTransformations` flag SHALL remain `true` after processing all headings
### Requirement: Code block paste bypass
The plugin SHALL NOT reformat pasted content when the cursor is inside a fenced code block. When a fenced code block is detected, the plugin SHALL allow Obsidian's default paste behavior to proceed unmodified.
A fenced code block is detected by scanning backward from the cursor line to find an opening fence (a line starting with `` ``` `` or `~~~`, optionally followed by a language identifier) that has no corresponding closing fence before the cursor position.
#### Scenario: Paste inside an open fenced code block
- **WHEN** `pasteOverride` is enabled
- **AND** the cursor is positioned inside a fenced code block (between an opening `` ``` `` and before a closing `` ``` ``)
- **AND** the user pastes content
- **THEN** the plugin SHALL skip all transformations
- **AND** `event.preventDefault()` SHALL NOT be called
- **AND** Obsidian's default paste behavior SHALL proceed
#### Scenario: Paste outside a code block
- **WHEN** `pasteOverride` is enabled
- **AND** the cursor is NOT inside a fenced code block
- **AND** the user pastes content
- **THEN** the plugin SHALL process the paste as normal (applying configured transformations)
#### Scenario: Paste after a closed code block
- **WHEN** `pasteOverride` is enabled
- **AND** the document contains a fenced code block that has both opening and closing fences
- **AND** the cursor is positioned after the closing fence
- **THEN** the plugin SHALL process the paste as normal (the code block is closed, cursor is not inside it)
#### Scenario: Fenced code block with language specifier
- **WHEN** `pasteOverride` is enabled
- **AND** the cursor is inside a fenced code block that has a language specifier (e.g., `` ```javascript ``)
- **AND** the user pastes content
- **THEN** the plugin SHALL skip all transformations
- **AND** Obsidian's default paste behavior SHALL proceed
#### Scenario: Tilde-fenced code block
- **WHEN** `pasteOverride` is enabled
- **AND** the cursor is inside a tilde-fenced code block (opened with `~~~`)
- **AND** the user pastes content
- **THEN** the plugin SHALL skip all transformations
- **AND** Obsidian's default paste behavior SHALL proceed

View file

@ -207,6 +207,31 @@ export default class PasteReformatter extends Plugin {
}
}
/**
* Determines whether the cursor is currently inside a fenced code block.
* Scans backward from the cursor line, counting opening and closing fences
* (triple-backtick or triple-tilde lines). If there is an unclosed opening
* fence before the cursor, the cursor is inside a code block.
* @param editor The editor instance
* @returns true if the cursor is inside a fenced code block
*/
isCursorInCodeBlock(editor: any): boolean {
const cursor = editor.getCursor();
const currentLine = cursor.line;
let insideCodeBlock = false;
for (let line = 0; line <= currentLine; line++) {
const lineText = editor.getLine(line);
// Match opening/closing fences: ``` or ~~~ at the start of a line,
// optionally followed by a language identifier (only on opening fences)
if (/^(`{3,}|~{3,})/.test(lineText)) {
insideCodeBlock = !insideCodeBlock;
}
}
return insideCodeBlock;
}
onPaste(event: ClipboardEvent) {
if (this.settings.pasteOverride) {
// Check if there's content in the clipboard
@ -214,6 +239,13 @@ export default class PasteReformatter extends Plugin {
if (!clipboardData) {
return;
}
// Skip reformatting when the cursor is inside a fenced code block
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView?.editor && this.isCursorInCodeBlock(activeView.editor)) {
console.debug("Paste Reformatter plugin skipping paste: cursor is inside a code block");
return;
}
// Process the clipboard data
if (this.doPaste(clipboardData)) {