AvaloniaEdit icon indicating copy to clipboard operation
AvaloniaEdit copied to clipboard

Crash when Toggling TextEditor.ShowLineNumbers

Open gebodal opened this issue 1 year ago • 3 comments

Unless I'm missing something, it seems that changing the ShowLineNumbers property of a loaded TextEditor from false to true will cause an ArgumentOutOfRangeException that seems to be originating from LineNumberMargin.Render(). Specifically, this error is occurring in a TextEditor hosted inside a Window which has finished loading and is visible, with the ShowLineNumbers property being changed via user interaction.

https://github.com/AvaloniaUI/AvaloniaEdit/blob/f92e5527c0f6ef1a1a6f6d2dc904af5688315949/src/AvaloniaEdit/Editing/LineNumberMargin.cs#L76-L80

It seems that the EmSize (taken from the TextBlock.FontSizeProperty) is not being set somehow, possibly because it is set in MeasureOverride(), which presumably is not being called before rendering?

https://github.com/AvaloniaUI/AvaloniaEdit/blob/f92e5527c0f6ef1a1a6f6d2dc904af5688315949/src/AvaloniaEdit/Editing/LineNumberMargin.cs#L55

The "OnChanged" method for ShowLineNumbers in TextEditor creates a brand new LineNumberMargin Control when set to true from false, so even if altered in sequence true->false->true, the error occurs.

Stack trace of error:

System.ArgumentOutOfRangeException: The parameter value must be greater than zero. (Parameter 'emSize')
   at Avalonia.Media.FormattedText.ValidateFontSize(Double emSize)
   at Avalonia.Media.FormattedText..ctor(String textToFormat, CultureInfo culture, FlowDirection flowDirection, Typeface typeface, Double emSize, IBrush foreground)
   at AvaloniaEdit.Utils.TextFormatterFactory.CreateFormattedText(Control element, String text, Typeface typeface, Nullable`1 emSize, IBrush foreground)
   at AvaloniaEdit.Editing.LineNumberMargin.Render(DrawingContext drawingContext)
   at Avalonia.Rendering.Composition.CompositingRenderer.UpdateCore()
   at Avalonia.Rendering.Composition.CompositingRenderer.Update()
   at Avalonia.Rendering.Composition.Compositor.CommitCore()
   at Avalonia.Rendering.Composition.Compositor.Commit()
   at Avalonia.Media.MediaContext.CommitCompositor(Compositor compositor)
   at Avalonia.Media.MediaContext.SyncCommit(Compositor compositor, Boolean waitFullRender)
   at Avalonia.Media.MediaContext.SyncDisposeCompositionTarget(CompositionTarget compositionTarget)
   at Avalonia.Rendering.Composition.CompositingRenderer.Dispose()
   at Avalonia.Controls.TopLevel.HandleClosed()
   at Avalonia.Controls.WindowBase.HandleClosed()
   at Avalonia.Win32.WindowImpl.AppWndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
   at Avalonia.Win32.WindowImpl.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
   at Avalonia.Win32.PopupImpl.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)

If there's a workaround, I'd love to hear about it! So far I have tried: InvalidateMeasure(), InvalidateArrange() and InvalidateVisual() after changing ShowLineNumbers on the TextEditor, the TextArea, and all Controls in textArea.LeftMargins; using Dispatcher.UIThread.Invoke() to change the value of ShowLineNumbers. None of these have solved the issue.

gebodal avatar Jun 23 '24 00:06 gebodal

OK, I realised there's a simple enough hack for this, where you just have to replace the TextEditor.ShowLineNumbers setter logic with your own. The only important point is that you have to wait until the control is loaded before disabling, otherwise the EmSize will not have been set correctly.

The below uses a modified version of the TextEditor code here: https://github.com/AvaloniaUI/AvaloniaEdit/blob/f92e5527c0f6ef1a1a6f6d2dc904af5688315949/src/AvaloniaEdit/TextEditor.cs#L549-L560

public MyControl() {
	// Other initialisation here

	//// Line numbers setup
	// Set to true initially so that internal values get initialised properly
	textEditor.ShowLineNumbers = true;

	// Collect list of line controls (following logic in TextEditor.cs#L549-L560)
	List<Control> lineNumberControlsList = new List<Control>();
	var leftMargins = textEditor.TextArea.LeftMargins;
	for (int i = 0; i < leftMargins.Count; i++) {
		if (leftMargins[i] is LineNumberMargin) {
			lineNumberControlsList.Add(leftMargins[i]);
			if (i + 1 < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i + 1])) {
				lineNumberControlsList.Add(leftMargins[i + 1]);
			}
			break;
		}
	}
	lineNumberControls = lineNumberControlsList.ToArray();

	// When finished loading, set visibility to desired value
	textEditor.Loaded += OnLoadedAdjustShowLineNumbers;
}

private readonly Control[] lineNumberControls;

// Adjust the visibility of the line number controls without actually removing them
public bool ShowLineNumbers {
	get {
		return lineNumberControls.Any(c => c.IsVisible); // Or something neater
	}
	set {
		foreach(Control c in lineNumberControls) {
			c.IsVisible = value;
		}
	}
}

private void OnLoadedAdjustShowLineNumbers(object? sender, RoutedEventArgs e) {
	ShowLineNumbers = MyDataManager.GetShowLineNumbers();
}

Or something like that, anyway. (Seems to work on my machine!)

gebodal avatar Jun 24 '24 18:06 gebodal

For me i fixed it by just resetting the grammar

public void UpdateVisuals()
    {
        Language csharpLanguage = _registryOptions.GetLanguageByExtension(".cs");
        _currentTheme = (int)App.Settings.SelectedTheme;
        _registryOptions = new RegistryOptions((ThemeName)_currentTheme);
        _textMateInstallation = _editor.InstallTextMate(_registryOptions);
        _textMateInstallation.AppliedTheme += TextMateInstallationOnAppliedTheme;
        _editor.ShowLineNumbers = App.Settings.ShowLineNumbers;
        _textMateInstallation.SetGrammar(_registryOptions.GetScopeByLanguageId(csharpLanguage.Id));
    }

SpiralDevs avatar Mar 15 '25 18:03 SpiralDevs

I can't reproduce the problem. (Tested with 11.1.0, 11.2.0 and latest master.)

@gebodal Does the problem still exist? Can you provide a test project? Otherwise, I suggest closing the issue.

mgarstenauer avatar Mar 29 '25 07:03 mgarstenauer