#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

252 lines
13 KiB

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!------------------------------------------------------------>
<!-- INTRODUCTION
The Code Project article submission template (HTML version)
Using this template will help us post your article sooner. To use, just
follow the 3 easy steps below:
1. Fill in the article description details
2. Add links to your images and downloads
3. Include the main article text
That's all there is to it! All formatting will be done by our submission
scripts and style sheets.
-->
<!------------------------------------------------------------>
<!-- IGNORE THIS SECTION -->
<html>
<head>
<title>AvalonEdit</title>
<Style>
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt }
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; }
H2 { font-size: 13pt; }
H3 { font-size: 12pt; }
H4 { font-size: 10pt; color: black; }
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; }
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; }
</style>
<link rel="stylesheet" type="text/css" href="http://www.codeproject.com/App_Themes/NetCommunity/CodeProject.css">
</head>
<body bgcolor="#FFFFFF" color=#000000>
<div style="width:600px; margin-left: 24px;">
<!------------------------------------------------------------>
<!------------------------------------------------------------>
<!-- Fill in the details (CodeProject will reformat this section for you) -->
<!------------------------------------------------------------>
<!-- Include download and sample image information. -->
<ul class=download>
<li><a href="Article_demo.zip">Download demo project - XXX Kb </a></li>
<li><a href="Article_src.zip">Download source - XXX Kb</a></li>
</ul>
<p><img src="Article.gif" alt="Sample Image - maximum width is 600 pixels" width=400 height=200></p>
<!------------------------------------------------------------>
<!-- Add the article text. Please use simple formatting (<h2>, <p> etc) -->
<h2>Introduction</h2>
<p>ICSharpCode.AvalonEdit is the WPF-based text editor that I've written for SharpDevelop 4.0. It is meant as a replacement
for <a href="http://www.codeproject.com/KB/edit/TextEditorControl.aspx">ICSharpCode.TextEditor</a>, but should be:
<ul>
<li>Extensible</li>
<li>Easy to use</li>
<li>Better at handling large files</li>
</ul>
<p>
<b>Extensible</b> means that I wanted SharpDevelop AddIns to be able to add features to the text editor.
For example, an AddIn should be able to allow inserting images into comments - this way you could put
stuff like class diagrams right into the source code!
<p>
With, <b>Easy to use</b>, I'm referring to the programming API. It should just work&trade;.
For example, this means if you change the document text,
the editor should automatically redraw without having to call <code>Invalidate()</code>.
And if you do something wrong, you should get a meaningful exception, not corrupted state and crash later at an unrelated location.
<p>
<b>Better at handling large files</b> means that the editor should be able to handle large files (e.g.
the mscorlib XML documentation file, 7 MB, 74100 LOC), even when features like folding (code collapsing) are enabled.
<h2>Using the Code</h2>
<p>The main class of the editor is <code>ICSharpCode.AvalonEdit.TextEditor</code>.
You can use it just similar to a normal WPF <code>TextBox</code>:
<pre lang="xml">&lt;avalonEdit:TextEditor
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="textEditor"
FontFamily="Consolas"
FontSize="10pt"/&gt;</pre>
<p>To enable syntax highlighting, use:
<pre lang="cs">textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");</pre>
AvalonEdit has syntax highlighting definitions built in for:
ASP.NET, Boo, Coco/R grammars, C++, C#, HTML, Java, JavaScript, Patch files, PHP, TeX, VB, XML
<p>If you need more of AvalonEdit than a simple text box with syntax highlighting, you will first have to learn more about the architecture of AvalonEdit.
<!------------------------------------------------------------>
<h2>Architecture</h2>
TODO: overview of the namespaces, insert graph from NDepend
As you can see in this dependency graph, AvalonEdit consists of a few sub-namespaces that have cleanly separated jobs.
Most of the namespaces have a kind of 'main' class.
<ul>
<li>ICSharpCode.AvalonEdit.Utils: Various utility classes</li>
<li>ICSharpCode.AvalonEdit.Document: <code>TextDocument</code> &mdash; text model</li>
<li>ICSharpCode.AvalonEdit.Rendering: <code>TextView</code> &mdash; extensible view onto the document</li>
<li>ICSharpCode.AvalonEdit.Editing: <code>TextArea</code> &mdash; controls text editing (e.g. caret, selection, handles user input)</li>
<li>ICSharpCode.AvalonEdit.Highlighting: <code>HighlightingManager</code> &mdash; highlighting engine</li>
<li>ICSharpCode.AvalonEdit.Highlighting.Xshd: <code>HighlightingLoader</code> &mdash; XML syntax highlighting definition support (.xshd files)</li>
<li>ICSharpCode.AvalonEdit.Folding: <code>FoldingManager</code> &mdash; enables code collapsing</li>
<li>ICSharpCode.AvalonEdit: <code>TextEditor</code> &mdash; the main control that brings it all together</li>
</ul>
<p>
Here is the visual tree of the <code>TextEditor</code> control:<br>
<img src="AvalonEdit/snoop.png" alt="Visual Tree"/>
<p>
It's important to understand that AvalonEdit is a composite control with the three layers: <code>TextEditor</code> (main control), <code>TextArea</code> (editing), <code>TextView</code> (rendering).
While the main control provides some convenience methods for common tasks, for most advanced features you have to work directly with the inner controls. You can access them using <code>textEditor.TextArea</code>
or <code>textEditor.TextArea.TextView</code>.
<!------------------------------------------------------------>
<h2>Document (The Text Model)</h2>
<p>The main class of the model is <code>ICSharpCode.AvalonEdit.Document.TextDocument</code>.
Basically, the document is a <code>StringBuilder</code> with events.
However, the <code>Document</code> namespace also contains several features that are useful to applications working with the text editor.
<p>In the text editor, all three controls (<code>TextEditor</code>, <code>TextArea</code>, <code>TextView</code>) have a <code>Document</code> property pointing to the <code>TextDocument</code> instance.
You can change the <code>Document</code> property to bind the editor to another document; but please only do so on the outermost control (usually <code>TextEditor</code>), it will inform its child controls about that change.
Changing the document only on a child control would leave the outer controls confused.
<p><i>Simplified</i> definition of <code>TextDocument</code>:
<pre lang="cs">public sealed class TextDocument : ITextSource
{
public event EventHandler&lt;DocumentChangeEventArgs&gt; Changing;
public event EventHandler&lt;DocumentChangeEventArgs&gt; Changed;
public event EventHandler TextChanged;
public IList&lt;DocumentLine&gt; Lines { get; }
public DocumentLine GetLineByNumber(int number);
public DocumentLine GetLineByOffset(int offset);
public TextLocation GetLocation(int offset);
public int GetOffset(int line, int column);
public char GetCharAt(int offset);
public string GetText(int offset, int length);
public void Insert(int offset, string text);
public void Remove(int offset, int length);
public void Replace(int offset, int length, string text);
public string Text { get; set; }
public int LineCount { get; }
public int TextLength { get; }
public UndoStack UndoStack { get; }
}</pre>
<h3>Offsets and Lines</h3>
In AvalonEdit, an index into the document is called an <b>offset</b>.
<p>Offsets usually represent the position between two characters.
The first offset at the start of the document is 0; the offset after the first <code>char</code> in the document is 1.
The last valid offset is <code>document.TextLength</code>, representing the end of the document.
This is exactly the same as the 'index' parameter used by methods in the .NET <code>String</code> or <code>StringBuilder</code> classes.
<p>
Offsets are easy to use, but sometimes you need Line / Column pairs instead.
AvalonEdit defines a <code>struct</code> called <code>TextLocation</code> for those.
<p>The document provides the methods <code>GetLocation</code> and <code>GetOffset</code> to convert between offsets and <code>TextLocation</code>s.
Those are convenience methods built on top of the <code>DocumentLine</code> class.
<p>The <code>TextDocument.Lines</code> collection contains one <code>DocumentLine</code> instance for every line in the document.
This collection is read-only to user code and is automatically updated to reflect the current document content.
<!------------------------------------------------------------>
<h2>Rendering</h2>
In the whole 'Document' section, there was no mention of extensibility.
The text rendering infrastructure now has to compensate for that by being completely extensible.
<p>The <code>ICSharpCode.AvalonEdit.Rendering.TextView</code> class is the heart of AvalonEdit.
It takes care of getting the document onto the screen.
<p>To do this in an extensible way, the <code>TextView</code> uses its own kind of model: the <code>VisualLine</code>.
Visual lines are created only for the visible part of the document.
<p>The rendering process looks like this:<br>
<img src="AvalonEdit/renderingPipeline.png" alt="rendering pipeline"><br>
The last step in the pipeline is the conversion to one or more <code>System.Windows.Media.TextFormatting.TextLine</code> instances. WPF then takes care of the actual text rendering.
<p>
The "element generators", "line transformers" and "background renderers" are the extension points; it is possible to add custom implementations of
them to the <code>TextView</code> to implement additional features in the editor.
<p>
The extensibility features of the rendering namespace are discussed in detail in the article "AvalonEdit Rendering". (to be published soon)
<h2>Editing</h2>
The <code>TextArea</code> class is handling user input and executing the appropriate actions.
Both the caret and the selection are controlled by the <code>TextArea</code>.
<p>
You can customize the text area by modifying the <code>TextArea.DefaultInputHandler</code> by adding new or replacing existing
WPF input bindings in it. You can also set <code>TextArea.ActiveInputHandler</code> to something different than the default
to switch the text area into another mode. You could use this to implement an "incremental search" feature, or even a VI emulator.
<p>
The text area has the useful <code>LeftMargins</code> property - use it to add controls to the left of the text view that look like
they're inside the scroll viewer, but don't actually scroll. The <code>AbstractMargin</code> base class contains some useful code
to detect when the margin is attached/detaching from a text view; or when the active document changes. However, you're not forced to use it;
any <code>UIElement</code> can be used as margin.
<h2>Folding</h2>
Folding (code collapsing) is implemented as an extension to the editor.
It could have been implemented in a separate assembly without having to modify the AvalonEdit code.
A <code>VisualLineElementGenerator</code> takes care of the collapsed sections in the text document; and a custom margin draws the plus and minus
buttons.
<p>
You could use the relevant classes separately; but, to make it a bit easier to use, the static <code>FoldingManager.Install</code>
method will create and register the necessary parts automatically.
<p>
All that's left for you is to regularly call <code>FoldingManager.UpdateFoldings</code> with the list of foldings you want to provide.
You could calculate that list yourself, or you could use a built-in folding strategy to do it for you.
<p>
Here is the full code required to enable folding:
<pre lang="cs">foldingManager = FoldingManager.Install(textEditor.TextArea);
foldingStrategy = new XmlFoldingStrategy();
foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document);</pre>
If you want the folding markers to update when the text is changed, you have to repeat the <code>foldingStrategy.UpdateFoldings</code> call regularly.
<p>
Currently, only the <code>XmlFoldingStrategy</code> is built into AvalonEdit.
The sample application to this article also contains the <code>BraceFoldingStrategy</code> that folds using { and }.
However, it is a very simple implementation and does not handle { and } inside strings or comments correctly.
<h2>Syntax highlighting</h2>
TODO: write this section
<h2>Points of Interest</h2>
<p>Did you learn anything interesting/fun/annoying while writing the code? Did you
do anything particularly clever or wild or zany?
<h2>History</h2>
<p>Keep a running update of any changes or improvements you've made here.
<p><b>Note: although my sample code is provided under the MIT license, ICSharpCode.AvalonEdit itself is provided under the terms of the GNU LGPL.</b>
<!------------------------------- That's it! --------------------------->
</div></body>
</html>