diff --git a/CHANGELOG.md b/CHANGELOG.md index 278bd4080..982c41389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Format Query can now be undone with Cmd+Z; the formatting is applied as a single editor edit instead of clearing the undo history. (#1645) +- Format Query now formats only the selected text when a selection is active, and the full query when nothing is selected. (#1656) - Sorting a query result no longer overwrites the SQL editor text or the contents of an opened `.sql` file; the sort runs as a separate query and the editor keeps what you wrote. (#1645) - iCloud Sync between the iPhone and Mac apps: the iOS app now uses the Production CloudKit environment, so a development build no longer syncs into a separate database the Mac never reads. - Exports no longer fail mid-table on servers that enforce a statement time limit; the export session disables the limit and restores it afterwards, the same way mysqldump does. (#1633) diff --git a/TablePro/Core/Services/Formatting/FormatScopeResolver.swift b/TablePro/Core/Services/Formatting/FormatScopeResolver.swift new file mode 100644 index 000000000..7a2d95e62 --- /dev/null +++ b/TablePro/Core/Services/Formatting/FormatScopeResolver.swift @@ -0,0 +1,47 @@ +// +// FormatScopeResolver.swift +// TablePro +// + +import Foundation + +internal enum FormatScopeResolver { + struct Scope: Equatable { + let range: NSRange + let sql: String + let cursorOffset: Int? + let isSelection: Bool + } + + static func resolve(fullText: String, selectedRange: NSRange) -> Scope { + let nsText = fullText as NSString + let fullRange = NSRange(location: 0, length: nsText.length) + let hasSelection = selectedRange.location != NSNotFound + && selectedRange.length > 0 + && NSIntersectionRange(selectedRange, fullRange).length == selectedRange.length + + guard hasSelection else { + let cursor = selectedRange.location == NSNotFound + ? 0 + : min(selectedRange.location, nsText.length) + return Scope(range: fullRange, sql: fullText, cursorOffset: cursor, isSelection: false) + } + + return Scope( + range: selectedRange, + sql: nsText.substring(with: selectedRange), + cursorOffset: nil, + isSelection: true + ) + } + + static func reapplyBoundaryWhitespace(from original: String, to formatted: String) -> String { + guard let firstNonWhitespace = original.firstIndex(where: { !$0.isWhitespace }), + let lastNonWhitespace = original.lastIndex(where: { !$0.isWhitespace }) + else { return formatted } + + let prefix = original[original.startIndex..