Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
line-break, programmatic, and undo/redo edits still widen ordered-list runs
when downstream display numbers can change.

### Fixed
- The Obsidian-style `[[` node-bracket completion now honors
`ListStyle.autoClosePairsEnabled`, matching the single-`[` auto-close path.
Previously an embedder that disabled bracket auto-closing (to edit plain
Markdown source without inserted pairs) still got `[[` silently expanded to
`[[]]`.

## [0.12.0] - 2026-08-10

### Added
Expand Down
5 changes: 4 additions & 1 deletion Sources/MarkdownEngine/Input/MarkdownListHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,11 @@ struct MarkdownLists {
}
}

// Autocomplete Obsidian-style node brackets and single square brackets
// Autocomplete Obsidian-style node brackets and single square brackets.
// Both behaviors are bracket auto-closing, so both honor `autoClosePairsEnabled` —
// embedders that edit plain Markdown source expect `[` to insert exactly `[`.
if replacementString == "[" {
guard autoClosePairsEnabled else { return true }
let nsText = textView.string as NSString
let insertionLocation = affectedCharRange.location
if insertionLocation > 0 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// MarkdownListHandlerBracketAutoCloseTests.swift
// MarkdownEngine
//
// Both the single-`[` auto-close and the Obsidian-style `[[` node-bracket
// completion are bracket auto-closing, so both should honor
// `ListStyle.autoClosePairsEnabled` — an embedder editing plain Markdown
// source (autoClosePairsEnabled = false) expects `[` to insert exactly `[`.
//

import AppKit
import SwiftUI
import Testing
@testable import MarkdownEngine

@MainActor
@Suite("MarkdownListHandler bracket auto-close")
struct MarkdownListHandlerBracketAutoCloseTests {

private func makeEditor(text: String, configuration: MarkdownEditorConfiguration = .default) -> NativeTextView {
_ = NSApplication.shared
let textView = NativeTextView(frame: NSRect(x: 0, y: 0, width: 800, height: 600))
textView.isEditable = true
textView.configuration = configuration
let coordinator = NativeTextViewCoordinator(
text: .constant(text),
fontName: "SF Pro Text",
fontSize: 14,
isWikiLinkActive: .constant(false),
onLinkClick: nil,
onInlineSelectionChange: nil
)
coordinator.textView = textView
textView.delegate = coordinator
textView.string = text
coordinator.lastSyncedText = text
coordinator.lastComputedStorage = text
coordinator.previousDisplayLength = (text as NSString).length
return textView
}

@Test func singleBracketAutoClosesByDefault() {
let tv = makeEditor(text: "")
tv.setSelectedRange(NSRange(location: 0, length: 0))

tv.insertText("[", replacementRange: NSRange(location: 0, length: 0))

#expect(tv.string == "[]")
}

@Test func obsidianDoubleBracketCompletesByDefault() {
let tv = makeEditor(text: "[")
tv.setSelectedRange(NSRange(location: 1, length: 0))

tv.insertText("[", replacementRange: NSRange(location: 1, length: 0))

#expect(tv.string == "[[]]")
}

@Test func singleBracketInsertsExactlyOneCharWhenAutoCloseDisabled() {
let config = MarkdownEditorConfiguration(lists: ListStyle(autoClosePairsEnabled: false))
let tv = makeEditor(text: "", configuration: config)
tv.setSelectedRange(NSRange(location: 0, length: 0))

tv.insertText("[", replacementRange: NSRange(location: 0, length: 0))

#expect(tv.string == "[")
}

@Test func obsidianDoubleBracketDoesNotAutoCompleteWhenAutoCloseDisabled() {
let config = MarkdownEditorConfiguration(lists: ListStyle(autoClosePairsEnabled: false))
let tv = makeEditor(text: "[", configuration: config)
tv.setSelectedRange(NSRange(location: 1, length: 0))

tv.insertText("[", replacementRange: NSRange(location: 1, length: 0))

#expect(tv.string == "[[")
}
}