Preventing the caret from positioning itself between the line start and the first VisualLineElement (at VisualColumn == 0)

StackOverflow https://stackoverflow.com/questions/16720585

  •  30-05-2022
  •  | 
  •  

Question

I'm working on a project that has taken a dependency on AvalonEdit to manage a document. Certain lines in this document are flagged to use a specific indentation which cannot be modified from within AvalonEdit.

I've managed to achieve this with VisualLineElements that are injected at the start of these flagged lines. The visual elements have a DocumentLength of 0 and a VisualLength the size of the indentation. I also overrode the GetNextCaretPosition method to "push" the caret either to the left of the VisualLineElement (in the case of backwards movement) or to the right (in the case of forward movement).

Here's a version of my VisualLineElement with reduced formatting (note that the element is always injected at VisualColumn == 0) :

public class MyLineFormatElement : VisualLineElement
{
    private readonly string _elementText;

    public MyLineFormatElement (string elementText)
        : base(elementText.Length, 0)
    {
        _elementText = elementText;
    }

    public override bool CanSplit
    {
        get
        {
            return false;
        }
    }

    public override int GetNextCaretPosition(int visualColumn, System.Windows.Documents.LogicalDirection direction, ICSharpCode.AvalonEdit.Document.CaretPositioningMode mode)
    {
        if (visualColumn >= VisualColumn && visualColumn <= VisualColumn + VisualLength)
        {
            return direction == LogicalDirection.Forward ? VisualColumn + VisualLength : VisualColumn;
        }

        return base.GetNextCaretPosition(visualColumn, direction, mode);
    }

    public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
    {
        return new TextCharacters(_elementText, TextRunProperties);
    }
}

This works great except for one issue I'm having: I can still place the caret at the start of the line. From a user standpoint, this is very confusing. What's worse, pressing "delete" when at the start of the line, or pressing "backspace" when at the end of the indentation simply moves the caret without making any modification. As I understand it, the delete/backspace problem would be resolved if the caret couldn't be placed at the line start (based on the code in CaretNavigationCommandHandler.OnMoveCaret).

My best guess as to how to fix this is to override the HandlesLineBorders property so it returns true and then modify GetNextCaretPosition so that: 1. When moving forward over the element, return VisualColumn + VisualLength (this is already the case) 2. When moving backwards over the element, return the previous line's last visual column (I don't know how to do this)

Is the last part of #2 possible? If so, how do I do that? If not, are there any other approaches I should try? I'd really like to stick with VisualLineElements to keep a clean separation between the actual content of the document and the way it's rendered.

Was it helpful?

Solution

Just return -1; that signifies that there are no more caret stops within your VisualLineElement (in the search direction).

The NewLineTextElement (the symbol signifying the end of a line) in AvalonEdit 4.1 was implemented using the same approach (but the implementation was changed in 4.2 when virtual space was implemented).

You should also override IsWhitespace and return true. This is important when AvalonEdit tries to detect the indentation (when word-wrap and options.InheritWordWrapIndentation is enabled).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top