|
1 | 1 | using System; |
| 2 | +using System.Diagnostics; |
2 | 3 | using System.Threading; |
3 | 4 | using System.Threading.Tasks; |
4 | 5 | using Avalonia; |
| 6 | +using Avalonia.Controls.Primitives; |
5 | 7 | using Avalonia.Input; |
6 | 8 | using Avalonia.Media; |
7 | 9 | using Avalonia.Threading; |
@@ -195,36 +197,72 @@ await Dispatcher.UIThread.InvokeAsync(() => |
195 | 197 | data.Add(new CSharpCompletionData(item)); |
196 | 198 | } |
197 | 199 |
|
198 | | - _completionWindow.Closed += (s, e) => |
199 | | - { |
200 | | - _completionWindow = null; |
201 | | - }; |
| 200 | + var window = _completionWindow; |
| 201 | + var completionList = window.CompletionList; |
202 | 202 |
|
203 | | - _completionWindow.Show(); |
| 203 | + void ApplySelection() |
| 204 | + { |
| 205 | + if (!ReferenceEquals(_completionWindow, window)) return; |
| 206 | + |
| 207 | + var currentCaret = _editor.CaretOffset; |
| 208 | + var currentText = _editor.Text ?? string.Empty; |
| 209 | + var effectiveQuery = initialQuery; |
| 210 | + if (currentCaret > startOffset && currentCaret <= currentText.Length) |
| 211 | + { |
| 212 | + effectiveQuery = currentText.Substring(startOffset, currentCaret - startOffset); |
| 213 | + } |
| 214 | + |
| 215 | + if (!string.IsNullOrEmpty(effectiveQuery)) |
| 216 | + { |
| 217 | + completionList.SelectItem(effectiveQuery); |
| 218 | + } |
| 219 | + else if (data.Count > 0) |
| 220 | + { |
| 221 | + completionList.SelectedItem = data[0]; |
| 222 | + } |
| 223 | + } |
204 | 224 |
|
205 | | - var currentCaret = _editor.CaretOffset; |
206 | | - var currentText = _editor.Text ?? string.Empty; |
207 | | - var effectiveQuery = initialQuery; |
208 | | - if (currentCaret > startOffset && currentCaret <= currentText.Length) |
| 225 | + void OnTemplateApplied(object? s, TemplateAppliedEventArgs e) |
209 | 226 | { |
210 | | - effectiveQuery = currentText.Substring(startOffset, currentCaret - startOffset); |
| 227 | + completionList.TemplateApplied -= OnTemplateApplied; |
| 228 | + ApplySelection(); |
211 | 229 | } |
212 | 230 |
|
213 | | - if (!string.IsNullOrEmpty(effectiveQuery)) |
| 231 | + window.Closed += (s, e) => |
| 232 | + { |
| 233 | + completionList.TemplateApplied -= OnTemplateApplied; |
| 234 | + if (ReferenceEquals(_completionWindow, window)) _completionWindow = null; |
| 235 | + }; |
| 236 | + |
| 237 | + window.Show(); |
| 238 | + |
| 239 | + // AvaloniaEdit has a known upstream timing bug (AvaloniaUI/AvaloniaEdit |
| 240 | + // issues #308 and #357): TemplatedControl.ApplyTemplate() silently no-ops |
| 241 | + // if styling hasn't resolved CompletionList's ControlTemplate yet, leaving |
| 242 | + // its internal ListBox null - Show() does not guarantee it's ready. That's |
| 243 | + // harmless under light UI load (template resolves before the next frame) |
| 244 | + // but under FryPDF's heavier UI thread it can still be unresolved right |
| 245 | + // here, silently dropping the selection/highlight instead of crashing (the |
| 246 | + // crash itself is now caught by Dispatcher.UIThread.UnhandledException, but |
| 247 | + // that only stops the abort - it doesn't make the popup usable). Wait for |
| 248 | + // the template to genuinely finish applying before touching ListBox-backed |
| 249 | + // members if it isn't ready the instant Show() returns. |
| 250 | + if (completionList.ListBox != null) |
214 | 251 | { |
215 | | - _completionWindow.CompletionList.SelectItem(effectiveQuery); |
| 252 | + ApplySelection(); |
216 | 253 | } |
217 | | - else if (data.Count > 0) |
| 254 | + else |
218 | 255 | { |
219 | | - _completionWindow.CompletionList.SelectedItem = data[0]; |
| 256 | + completionList.TemplateApplied += OnTemplateApplied; |
220 | 257 | } |
221 | 258 | }); |
222 | 259 | } |
223 | 260 | catch (OperationCanceledException) |
224 | 261 | { |
225 | 262 | } |
226 | | - catch |
| 263 | + catch (Exception ex) |
227 | 264 | { |
| 265 | + Debug.WriteLine($"[CSharpEditorCompletionController] TriggerCompletion failed: {ex}"); |
228 | 266 | } |
229 | 267 | }, token); |
230 | 268 | } |
|
0 commit comments