added typeaheead and introspetion
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user