Skip to content

Commit c9d4dbf

Browse files
committed
fix: implement standard autocomplete dismiss behavior
Implemented VSCode/IntelliJ-style autocomplete behavior to fix duplicate windows and improve UX. **Features:** - Standard dismiss pattern: keep open when clicking sidebar/panels, dismiss only on text cursor move, invalid chars, or ESC - Fixed duplicate windows by dismissing old window before showing new one in SQLCompletionWindowController - Tab switch dismisses all autocomplete windows via NotificationCenter broadcast - Added mouseDown override in EditorTextView to detect clicks at different cursor positions **Technical changes:** - EditorTextView: Added onClickOutsideCompletion callback and mouseDown override - EditorCoordinator: Wired callbacks, added tab switch notification observer, simplified showCompletions logic - MainContentView: Post QueryTabDidChange notification on tab switch - SQLCompletionWindowController: Dismiss existing window before showing new one Autocomplete now behaves like professional code editors with no duplicate windows.
1 parent 8198db3 commit c9d4dbf

File tree

4 files changed

+52
-2
lines changed

4 files changed

+52
-2
lines changed

OpenTable/Views/Editor/EditorCoordinator.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,27 @@ final class EditorCoordinator: NSObject, NSTextViewDelegate {
8484
textView.onKeyEvent = { [weak self] event in
8585
self?.handleKeyEvent(event) ?? false
8686
}
87+
88+
textView.onClickOutsideCompletion = { [weak self] in
89+
self?.dismissCompletion()
90+
}
91+
92+
// Observe tab switch to dismiss completion (prevents duplicate windows)
93+
NotificationCenter.default.addObserver(
94+
self,
95+
selector: #selector(handleTabSwitch),
96+
name: NSNotification.Name("QueryTabDidChange"),
97+
object: nil
98+
)
99+
}
100+
101+
deinit {
102+
NotificationCenter.default.removeObserver(self)
103+
}
104+
105+
@objc private func handleTabSwitch() {
106+
// Dismiss completion when switching tabs to prevent duplicates
107+
dismissCompletion()
87108
}
88109

89110
// MARK: - NSTextViewDelegate
@@ -163,7 +184,6 @@ final class EditorCoordinator: NSObject, NSTextViewDelegate {
163184
let prevIndex = text.index(text.startIndex, offsetBy: cursorPosition - 1)
164185
let prevChar = text[prevIndex]
165186
if prevChar == ";" || prevChar == "\n" {
166-
// Check if we're at the very end or just after semicolon/newline with no new content
167187
let afterCursor = String(text[text.index(text.startIndex, offsetBy: cursorPosition)...])
168188
.trimmingCharacters(in: .whitespacesAndNewlines)
169189
if afterCursor.isEmpty || cursorPosition == text.count {
@@ -189,7 +209,7 @@ final class EditorCoordinator: NSObject, NSTextViewDelegate {
189209
return
190210
}
191211

192-
// Show completion window
212+
// Show completion window (window controller handles dismissing old window if needed)
193213
completionWindow.showCompletions(context.items, at: screenPoint, relativeTo: textView.window)
194214
}
195215

OpenTable/Views/Editor/EditorTextView.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ final class EditorTextView: NSTextView {
2121
/// Callback for handling key events (returns true if handled)
2222
var onKeyEvent: ((NSEvent) -> Bool)?
2323

24+
/// Callback when user clicks at a different position (to dismiss completion)
25+
var onClickOutsideCompletion: (() -> Void)?
26+
2427
// MARK: - Auto-Pairing Configuration
2528

2629
private let bracketPairs: [Character: Character] = [
@@ -186,6 +189,23 @@ final class EditorTextView: NSTextView {
186189
}
187190
}
188191

192+
// MARK: - Mouse Input
193+
194+
override func mouseDown(with event: NSEvent) {
195+
// Dismiss autocomplete when clicking at a different position in the text editor
196+
// This matches VSCode/IntelliJ behavior - clicking sidebar/tabs won't dismiss
197+
let clickLocation = convert(event.locationInWindow, from: nil)
198+
let characterIndex = characterIndexForInsertion(at: clickLocation)
199+
200+
// If click is far from current cursor position, dismiss completion
201+
let currentCursor = selectedRange().location
202+
if abs(characterIndex - currentCursor) > 1 {
203+
onClickOutsideCompletion?()
204+
}
205+
206+
super.mouseDown(with: event)
207+
}
208+
189209
// MARK: - Keyboard Input
190210

191211
override func keyDown(with event: NSEvent) {

OpenTable/Views/Editor/SQLCompletionWindowController.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ final class SQLCompletionWindowController: NSObject {
5555
self.items = items
5656
self.selectedIndex = 0
5757

58+
// CRITICAL: Dismiss existing window first to prevent duplicates
59+
// This ensures only one window exists at a time
60+
if window?.isVisible == true {
61+
window?.parent?.removeChildWindow(window!)
62+
window?.orderOut(nil)
63+
}
64+
5865
// Create or update window
5966
if window == nil {
6067
createWindow()

OpenTable/Views/MainContentView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ struct MainContentView: View {
198198
// Must be synchronous - save state BEFORE SwiftUI updates the view
199199
handleTabChange(oldTabId: oldTabId, newTabId: newTabId)
200200

201+
// Dismiss all autocomplete windows to prevent duplicates
202+
NotificationCenter.default.post(name: NSNotification.Name("QueryTabDidChange"), object: nil)
203+
201204
// Sync selected tab ID to session for persistence
202205
if let sessionId = DatabaseManager.shared.currentSessionId {
203206
DatabaseManager.shared.updateSession(sessionId) { session in

0 commit comments

Comments
 (0)