diff --git a/CHANGELOG.md b/CHANGELOG.md index 58fbc83f..2e9a028f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Sources/MarkdownEngine/Input/MarkdownListHandler.swift b/Sources/MarkdownEngine/Input/MarkdownListHandler.swift index 80d2dc65..218ae498 100644 --- a/Sources/MarkdownEngine/Input/MarkdownListHandler.swift +++ b/Sources/MarkdownEngine/Input/MarkdownListHandler.swift @@ -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 { diff --git a/Tests/MarkdownEngineTests/MarkdownListHandlerBracketAutoCloseTests.swift b/Tests/MarkdownEngineTests/MarkdownListHandlerBracketAutoCloseTests.swift new file mode 100644 index 00000000..1c74c58d --- /dev/null +++ b/Tests/MarkdownEngineTests/MarkdownListHandlerBracketAutoCloseTests.swift @@ -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 == "[[") + } +}