I solved this with the handler of TSynCompletionProposal.OnExecute (TableNamesSelector is a TSynCompletionProposal, SQLEditor is a TSynEdit) :
Procedure TSQLConnectionFrame.TableNamesSelectorExecute(Kind: SynCompletionType; Sender: TObject; Var CurrentInput: String; Var x, y: Integer; Var CanExecute: Boolean);
Var
sa: TArray<String>;
Begin
If TableNamesSelector.ItemList.Count > 0 Then Exit;
sa := SQLEditor.LineText.Substring(0, SQLEditor.CaretX - 1).Split([' ']);
If Length(sa) > 0 Then sa := sa[Length(sa) - 1].Split(['.']);
If Length(sa) < 2 Then TableNamesSelector.ItemList.Assign(SQLHighlight.TableNames) // No dot, offer table names immediately
Else Begin
CanExecute := False;
If Length(sa) = 2 Then // Start a thread to collect field names of said table...
End;
End;
You have to pass sa[0], sa[1], X and Y to the thread. sa[0] is the table name, sa[1] is the field name fragment which was already typed, x and y is the position where the proposal should pop back up.
Once it finishes, you can:
TableNamesSelector.ItemList.Assign(worker.FieldNames);
TableNamesSelector.Execute(worker.FilterForText {passed to the thread as sa[1] from OnExecute}, worker.X, worker.Y);
Also handle the OnClose event of the completionproposal:
Procedure TSQLConnectionFrame.TableNamesSelectorClose(Sender: TObject);
Begin
TableNamesSelector.ItemList.Clear;
End;
The idea is that if the ItemList is empty the data still has to be collected and execution is disallowed. It will be popped up when the thread finishes.