added typeaheead and introspetion

This commit is contained in:
Mark Gregory
2026-10-18 16:43:08 +01:00
parent b6123a0043
commit bcd33b3fa5
3 changed files with 34 additions and 9 deletions
+16
View File
@@ -197,6 +197,21 @@ final class DatabaseModel: ObservableObject {
let views: [String]
let columnsByTable: [String: [IntrospectedColumn]]
}
// Resolve USE statements from the editor buffer even before Run is pressed.
// The completion provider must not depend on navigator selection alone.
var completionDatabase: String {
let pattern = #"(?im)^\s*USE\s+`?([A-Za-z_][A-Za-z0-9_]*)`?\s*;?"#
if let regex = try? NSRegularExpression(pattern: pattern) {
let value = sql as NSString
let range = NSRange(location: 0, length: value.length)
if let match = regex.matches(in: sql, range: range).last,
match.numberOfRanges > 1 {
return value.substring(with: match.range(at: 1))
}
}
return selectedDatabase.isEmpty ? (selected?.database ?? "") : selectedDatabase
}
@Published private(set) var introspections: [String: SchemaIntrospection] = [:]
private let introspectionStorageKey = "DBSyn.introspection.snapshots.v2"
private func persistIntrospections() {
@@ -581,6 +596,7 @@ final class DatabaseModel: ObservableObject {
selectedDatabase = usedSchema
// Metadata is cached in memory for native SQL completion.
loadTables(database: usedSchema)
introspect(database: usedSchema)
}
}
if listingDatabases {
+9 -9
View File
@@ -972,10 +972,11 @@ struct MainView: View {
.fill(WB.border)
.frame(width: 1)
SQLTextView(text: $model.sql, selectedSQL: $selectedSQL, focusRequest: sqlFocusRequest, completions: model.schemaColumns[model.selectedDatabase] ?? [],
tables: model.introspection(for: model.selectedDatabase)?.tables ?? model.schemaTables[model.selectedDatabase] ?? [],
views: model.introspection(for: model.selectedDatabase)?.views ?? model.schemaViews[model.selectedDatabase] ?? [],
columnsByTable: model.introspection(for: model.selectedDatabase)?.columnsByTable.mapValues { $0.map(\.name) } ?? [:],
SQLTextView(text: $model.sql, selectedSQL: $selectedSQL, focusRequest: sqlFocusRequest,
completions: model.schemaColumns[model.completionDatabase] ?? [],
tables: model.introspection(for: model.completionDatabase)?.tables ?? model.schemaTables[model.completionDatabase] ?? [],
views: model.introspection(for: model.completionDatabase)?.views ?? model.schemaViews[model.completionDatabase] ?? [],
columnsByTable: model.introspection(for: model.completionDatabase)?.columnsByTable.mapValues { $0.map(\.name) } ?? [:],
databases: model.databases)
}
.background(Color(nsColor: .textBackgroundColor))
@@ -1442,7 +1443,6 @@ struct SQLTextView: NSViewRepresentable {
editor.backgroundColor = .textBackgroundColor
editor.textColor = .textColor
scroll.backgroundColor = .textBackgroundColor
context.coordinator.highlight(editor)
if context.coordinator.lastFocusRequest != focusRequest {
context.coordinator.lastFocusRequest = focusRequest
DispatchQueue.main.async { [weak editor] in
@@ -1513,7 +1513,7 @@ struct SQLTextView: NSViewRepresentable {
}
private func showSuggestions(_ editor: NSTextView) {
guard editor.window?.firstResponder === editor else { dismissSuggestions(); return }
guard editor.window != nil else { dismissSuggestions(); return }
let selection = editor.selectedRange()
guard selection.length == 0, selection.location != NSNotFound else { dismissSuggestions(); return }
let source = editor.string as NSString
@@ -1546,7 +1546,7 @@ struct SQLTextView: NSViewRepresentable {
guard !suggestionItems.isEmpty else { dismissSuggestions(); return }
dismissPopoverOnly()
let popover = NSPopover()
popover.behavior = .semitransient
popover.behavior = .applicationDefined
let items = suggestionItems
let content = NSHostingView(rootView: VStack(alignment: .leading, spacing: 0) {
ForEach(Array(items.enumerated()), id: \.offset) { index, item in
@@ -1580,7 +1580,7 @@ struct SQLTextView: NSViewRepresentable {
if keyMonitor == nil {
keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self, weak editor] event in
guard let self, let editor, self.suggestionPopover?.isShown == true,
editor.window?.firstResponder === editor else { return event }
editor.window != nil else { return event }
switch event.keyCode {
case 53: self.dismissSuggestions(); return nil // Escape
case 36, 48: self.acceptSuggestion(editor); return nil // Return / Tab
@@ -1678,7 +1678,7 @@ struct SQLTextView: NSViewRepresentable {
highlight(editor)
// Do not invoke AppKit's completion panel from textDidChange.
// That re-entrant path caused the previous editor freezes/crashes.
// Completion remains available through the standard macOS Complete command.
// Schedule outside the AppKit edit callback to avoid re-entrant completion.
scheduleSuggestions(editor)
}
+9
View File
@@ -0,0 +1,9 @@
# Phase 61.10 — SQL autocomplete wiring repair
- Resolve the active completion database from the most recent `USE schema;` line in the SQL buffer, without requiring the navigator to change or Run to be pressed.
- Feed tables, views and per-table columns from the resolved database's persisted introspection snapshot to the SQL editor.
- Following successful `USE`, load the schema and trigger introspection to populate the cache.
- Avoid requiring `firstResponder === editor` for suggestion display: macOS may make a field editor or popover responder active during the asynchronous callback.
- Use an application-defined popover to avoid semitransient auto-dismissal on focus changes.
The implementation has not been compiled or tested on macOS. Validate using `USE AAMEDIUK;` followed by `SELECT * FROM ADCU`, and inspect the app's status for introspection errors if suggestions are empty.