added typeaheead and introspetion
This commit is contained in:
+1
-1
@@ -32,7 +32,7 @@ struct DBSynApp: App {
|
|||||||
NSApplication.shared.orderFrontStandardAboutPanel(options: [
|
NSApplication.shared.orderFrontStandardAboutPanel(options: [
|
||||||
.applicationName: "DBSyn",
|
.applicationName: "DBSyn",
|
||||||
.applicationIcon: icon ?? NSApplication.shared.applicationIconImage,
|
.applicationIcon: icon ?? NSApplication.shared.applicationIconImage,
|
||||||
.version: "Phase 61",
|
.version: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0",
|
||||||
.credits: NSAttributedString(string: "Native MySQL client for macOS")
|
.credits: NSAttributedString(string: "Native MySQL client for macOS")
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,7 +462,8 @@ final class DatabaseModel: ObservableObject {
|
|||||||
columns[row[0], default: []].append(column)
|
columns[row[0], default: []].append(column)
|
||||||
}
|
}
|
||||||
// Ignore results from a connection that has been removed while loading.
|
// Ignore results from a connection that has been removed while loading.
|
||||||
guard profiles.contains(where: { $0.id == profile.id }) else { return }
|
guard profiles.contains(where: { $0.id == profile.id }),
|
||||||
|
selected?.id == profile.id else { return }
|
||||||
let previous = introspections[key]
|
let previous = introspections[key]
|
||||||
let current = SchemaIntrospection(
|
let current = SchemaIntrospection(
|
||||||
connectionID: profile.id, database: database,
|
connectionID: profile.id, database: database,
|
||||||
|
|||||||
+10
-6
@@ -1553,10 +1553,9 @@ struct SQLTextView: NSViewRepresentable {
|
|||||||
let selection = editor.selectedRange()
|
let selection = editor.selectedRange()
|
||||||
editor.string = text
|
editor.string = text
|
||||||
|
|
||||||
let safeLocation = min(
|
let safeLocation = selection.location == NSNotFound
|
||||||
selection.location,
|
? 0
|
||||||
(text as NSString).length
|
: min(selection.location, (text as NSString).length)
|
||||||
)
|
|
||||||
|
|
||||||
editor.setSelectedRange(
|
editor.setSelectedRange(
|
||||||
NSRange(location: safeLocation, length: 0)
|
NSRange(location: safeLocation, length: 0)
|
||||||
@@ -1579,6 +1578,7 @@ struct SQLTextView: NSViewRepresentable {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
pendingCompletion?.cancel()
|
pendingCompletion?.cancel()
|
||||||
|
suggestionPopover?.close()
|
||||||
if let keyMonitor { NSEvent.removeMonitor(keyMonitor) }
|
if let keyMonitor { NSEvent.removeMonitor(keyMonitor) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1617,7 +1617,10 @@ struct SQLTextView: NSViewRepresentable {
|
|||||||
let source = editor.string as NSString
|
let source = editor.string as NSString
|
||||||
guard selection.location <= source.length else { dismissSuggestions(); return }
|
guard selection.location <= source.length else { dismissSuggestions(); return }
|
||||||
let before = source.substring(to: selection.location)
|
let before = source.substring(to: selection.location)
|
||||||
let expression = try! NSRegularExpression(pattern: #"[A-Za-z_][A-Za-z0-9_]*$"#)
|
guard let expression = try? NSRegularExpression(pattern: #"[A-Za-z_][A-Za-z0-9_]*$"#) else {
|
||||||
|
dismissSuggestions()
|
||||||
|
return
|
||||||
|
}
|
||||||
let beforeRange = NSRange(location: 0, length: (before as NSString).length)
|
let beforeRange = NSRange(location: 0, length: (before as NSString).length)
|
||||||
guard let match = expression.matches(in: before, range: beforeRange).last else { dismissSuggestions(); return }
|
guard let match = expression.matches(in: before, range: beforeRange).last else { dismissSuggestions(); return }
|
||||||
let prefix = (before as NSString).substring(with: match.range)
|
let prefix = (before as NSString).substring(with: match.range)
|
||||||
@@ -1678,7 +1681,8 @@ struct SQLTextView: NSViewRepresentable {
|
|||||||
if keyMonitor == nil {
|
if keyMonitor == nil {
|
||||||
keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self, weak editor] event in
|
keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self, weak editor] event in
|
||||||
guard let self, let editor, self.suggestionPopover?.isShown == true,
|
guard let self, let editor, self.suggestionPopover?.isShown == true,
|
||||||
editor.window != nil else { return event }
|
let window = editor.window, window.isKeyWindow,
|
||||||
|
window.firstResponder === editor else { return event }
|
||||||
switch event.keyCode {
|
switch event.keyCode {
|
||||||
case 53: self.dismissSuggestions(); return nil // Escape
|
case 53: self.dismissSuggestions(); return nil // Escape
|
||||||
case 36, 48: self.acceptSuggestion(editor); return nil // Return / Tab
|
case 36, 48: self.acceptSuggestion(editor); return nil // Return / Tab
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Phase 70 — UI and stability hardening
|
||||||
|
|
||||||
|
This phase deliberately avoids adding new database-changing operations.
|
||||||
|
|
||||||
|
## Editor stability
|
||||||
|
- Avoids a forced regular-expression unwrap when preparing suggestions.
|
||||||
|
- Prevents the SQL editor's local key monitor from consuming Enter, Tab, Escape or arrow keys when another control/window has focus.
|
||||||
|
- Closes suggestion popovers and removes event monitors when the editor coordinator is released.
|
||||||
|
- Handles invalid text-selection positions safely when SQL is updated programmatically.
|
||||||
|
|
||||||
|
## Introspection consistency
|
||||||
|
- Discards introspection results from a connection that is no longer selected, preventing an asynchronous response from applying a different connection's metadata to the active editor.
|
||||||
|
|
||||||
|
## About panel
|
||||||
|
- Reads the application version from its actual bundle metadata rather than displaying an obsolete phase label.
|
||||||
|
|
||||||
|
## Verification still required on macOS
|
||||||
|
- Build the Xcode scheme and test SQL editing, completion navigation and keyboard focus changes.
|
||||||
|
- Switch connections during a long introspection request and confirm stale results are not shown.
|
||||||
|
- Check System, Light and Dark appearance, and the existing graphs and administration screens.
|
||||||
|
- Run live MySQL regression tests before treating this as a release candidate.
|
||||||
Reference in New Issue
Block a user