Skip to content

Commit c7912af

Browse files
committed
fix: add unhandled exception handlers and robustness fallbacks for code completion
1 parent d01ce04 commit c7912af

4 files changed

Lines changed: 97 additions & 18 deletions

File tree

‎Controls/CSharpCompletionData.cs‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using Avalonia;
34
using Avalonia.Controls;
45
using Avalonia.Layout;
@@ -29,9 +30,28 @@ public CSharpCompletionData(CSharpCompletionItem item)
2930

3031
public double Priority => _item.Priority;
3132

32-
public object Content => _cachedContent ??= BuildContentControl();
33+
public object Content => _cachedContent ??= SafeBuild(BuildContentControl, nameof(BuildContentControl));
3334

34-
public object Description => _cachedDescription ??= BuildDescriptionControl();
35+
public object Description => _cachedDescription ??= SafeBuild(BuildDescriptionControl, nameof(BuildDescriptionControl));
36+
37+
private Control SafeBuild(Func<Control> build, string source)
38+
{
39+
try
40+
{
41+
return build();
42+
}
43+
catch (Exception ex)
44+
{
45+
Debug.WriteLine($"[CSharpCompletionData] {source} failed for '{_item.DisplayText}': {ex}");
46+
return new TextBlock
47+
{
48+
Text = _item.DisplayText,
49+
FontSize = 12,
50+
Foreground = new SolidColorBrush(Color.Parse("#E6EDF3")),
51+
Margin = new Thickness(4, 2)
52+
};
53+
}
54+
}
3555

3656
private Control BuildContentControl()
3757
{

‎Controls/CSharpEditorCompletionController.cs‎

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Threading;
34
using System.Threading.Tasks;
45
using Avalonia;
6+
using Avalonia.Controls.Primitives;
57
using Avalonia.Input;
68
using Avalonia.Media;
79
using Avalonia.Threading;
@@ -195,36 +197,72 @@ await Dispatcher.UIThread.InvokeAsync(() =>
195197
data.Add(new CSharpCompletionData(item));
196198
}
197199

198-
_completionWindow.Closed += (s, e) =>
199-
{
200-
_completionWindow = null;
201-
};
200+
var window = _completionWindow;
201+
var completionList = window.CompletionList;
202202

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+
}
204224

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)
209226
{
210-
effectiveQuery = currentText.Substring(startOffset, currentCaret - startOffset);
227+
completionList.TemplateApplied -= OnTemplateApplied;
228+
ApplySelection();
211229
}
212230

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)
214251
{
215-
_completionWindow.CompletionList.SelectItem(effectiveQuery);
252+
ApplySelection();
216253
}
217-
else if (data.Count > 0)
254+
else
218255
{
219-
_completionWindow.CompletionList.SelectedItem = data[0];
256+
completionList.TemplateApplied += OnTemplateApplied;
220257
}
221258
});
222259
}
223260
catch (OperationCanceledException)
224261
{
225262
}
226-
catch
263+
catch (Exception ex)
227264
{
265+
Debug.WriteLine($"[CSharpEditorCompletionController] TriggerCompletion failed: {ex}");
228266
}
229267
}, token);
230268
}

‎Runner/App.axaml.cs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1+
using System.Diagnostics;
12
using Avalonia;
23
using Avalonia.Controls.ApplicationLifetimes;
34
using Avalonia.Markup.Xaml;
5+
using Avalonia.Threading;
46

57
namespace PdfEditorApp.Plugins.CSharpEditor.Runner;
68

79
public partial class App : Application
810
{
911
public override void Initialize()
1012
{
13+
// See the matching handler in the hosted FryPDF app's App.axaml.cs for why this is
14+
// needed: raw input is dispatched via Dispatcher.Send, which only offers this event
15+
// as a way to intercept an exception thrown while handling a key/pointer event before
16+
// it aborts the whole process.
17+
Dispatcher.UIThread.UnhandledException += (_, e) =>
18+
{
19+
Debug.WriteLine($"[Dispatcher.UnhandledException] {e.Exception}");
20+
e.Handled = true;
21+
};
22+
1123
AvaloniaXamlLoader.Load(this);
1224
}
1325

‎Runner/Program.cs‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System;
2+
using System.Threading.Tasks;
13
using Avalonia;
24

35
namespace PdfEditorApp.Plugins.CSharpEditor.Runner;
@@ -6,7 +8,14 @@ public static class Program
68
{
79
[STAThread]
810
public static void Main(string[] args)
9-
=> BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
11+
{
12+
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
13+
Console.Error.WriteLine($"[AppDomain.UnhandledException] {e.ExceptionObject}");
14+
TaskScheduler.UnobservedTaskException += (_, e) =>
15+
Console.Error.WriteLine($"[TaskScheduler.UnobservedTaskException] {e.Exception}");
16+
17+
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
18+
}
1019

1120
public static AppBuilder BuildAvaloniaApp()
1221
=> AppBuilder.Configure<App>()

0 commit comments

Comments
 (0)