From 052a540a7e18be8741d68410b09d2c866197a6fd Mon Sep 17 00:00:00 2001 From: "David W. Keith" Date: Fri, 4 Sep 2026 09:17:59 -0700 Subject: [PATCH 1/2] fix(input): honor autoClosePairsEnabled for the square-bracket paths The Obsidian-style [[ completion and single-[ auto-close are both bracket auto-closing; embedders that edit plain Markdown source (autoClosePairsEnabled = false) expect [ to insert exactly [. Co-Authored-By: Claude Sonnet 5 --- Sources/MarkdownEngine/Input/MarkdownListHandler.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 { From 4bb198d47beb9b21a88d4ed646400896e22483e8 Mon Sep 17 00:00:00 2001 From: "David W. Keith" Date: Fri, 4 Sep 2026 09:18:00 -0700 Subject: [PATCH 2/2] test: cover bracket auto-close honoring autoClosePairsEnabled Adds coverage for both the single-[ and Obsidian-style [[ paths, with autoClosePairsEnabled on (default, unchanged behavior) and off (new behavior), and a CHANGELOG entry. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 7 ++ ...downListHandlerBracketAutoCloseTests.swift | 79 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 Tests/MarkdownEngineTests/MarkdownListHandlerBracketAutoCloseTests.swift 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/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 == "[[") + } +}