Internal: walk to the task line at `taskIndex`, hand its * TASK_LINE_RE match to `mutate`, and splice the result back in. * Returns the markdown unchanged if `mutate` returns null/the same * line, or the index is out of range.
( markdown: string, taskIndex: number, mutate: (match: RegExpMatchArray) => string | null )
| 13 | * Returns the markdown unchanged if `mutate` returns null/the same |
| 14 | * line, or the index is out of range. */ |
| 15 | function editTaskAtIndex( |
| 16 | markdown: string, |
| 17 | taskIndex: number, |
| 18 | mutate: (match: RegExpMatchArray) => string | null |
| 19 | ): string { |
| 20 | if (taskIndex < 0) return markdown |
| 21 | |
| 22 | const lines = markdown.split('\n') |
| 23 | let currentTaskIndex = 0 |
| 24 | let inFence = false |
| 25 | let fenceMarker: string | null = null |
| 26 | |
| 27 | for (let i = 0; i < lines.length; i++) { |
| 28 | const line = lines[i] |
| 29 | const fenceMatch = line.match(FENCE_RE) |
| 30 | if (fenceMatch) { |
| 31 | const marker = fenceMatch[2] |
| 32 | if (!inFence) { |
| 33 | inFence = true |
| 34 | fenceMarker = marker |
| 35 | } else if (marker === fenceMarker) { |
| 36 | inFence = false |
| 37 | fenceMarker = null |
| 38 | } |
| 39 | continue |
| 40 | } |
| 41 | if (inFence) continue |
| 42 | |
| 43 | const taskMatch = line.match(TASK_LINE_RE) |
| 44 | if (!taskMatch) continue |
| 45 | if (currentTaskIndex !== taskIndex) { |
| 46 | currentTaskIndex += 1 |
| 47 | continue |
| 48 | } |
| 49 | |
| 50 | const next = mutate(taskMatch) |
| 51 | if (next == null || next === line) return markdown |
| 52 | lines[i] = next |
| 53 | return lines.join('\n') |
| 54 | } |
| 55 | return markdown |
| 56 | } |
| 57 | |
| 58 | /** Line numbers (0-based) of every task line, indexed by task index. Counts |
| 59 | * exactly like `editTaskAtIndex` / `parseTasksFromBody` (fence-aware) so the |
no outgoing calls
no test coverage detected