From d0c136bb8b3f5f570165cb54acccdfdcf1ade066 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald <daniel@danielgrunwald.de> Date: Sat, 3 Oct 2009 14:21:55 +0000 Subject: [PATCH] Split up AvalonEdit into Overview, Document, Rendering. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5045 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../AvalonEdit/renderingPipeline.png | Bin 0 -> 31718 bytes .../AvalonEdit.Sample/ColorizeAvalonEdit.cs | 3 +- samples/AvalonEdit.Sample/article.html | 308 +----------------- samples/AvalonEdit.Sample/document.html | 242 ++++++++++++++ samples/AvalonEdit.Sample/rendering.html | 301 +++++++++++++++++ 5 files changed, 561 insertions(+), 293 deletions(-) create mode 100644 samples/AvalonEdit.Sample/AvalonEdit/renderingPipeline.png create mode 100644 samples/AvalonEdit.Sample/document.html create mode 100644 samples/AvalonEdit.Sample/rendering.html diff --git a/samples/AvalonEdit.Sample/AvalonEdit/renderingPipeline.png b/samples/AvalonEdit.Sample/AvalonEdit/renderingPipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..9e7b7e308dd2c022213e8326f12a923a4e8b0753 GIT binary patch literal 31718 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 N%c;s%OFw+_e*k5)Nq+zU literal 0 HcmV?d00001 diff --git a/samples/AvalonEdit.Sample/ColorizeAvalonEdit.cs b/samples/AvalonEdit.Sample/ColorizeAvalonEdit.cs index ee0c6df71b..ebed3d2998 100644 --- a/samples/AvalonEdit.Sample/ColorizeAvalonEdit.cs +++ b/samples/AvalonEdit.Sample/ColorizeAvalonEdit.cs @@ -44,7 +44,8 @@ namespace AvalonEdit.Sample // This lambda gets called once for every VisualLineElement // between the specified offsets. Typeface tf = element.TextRunProperties.Typeface; - // Replace the typeface with a modified version of the same typeface + // Replace the typeface with a modified version of + // the same typeface element.TextRunProperties.SetTypeface(new Typeface( tf.FontFamily, FontStyles.Italic, diff --git a/samples/AvalonEdit.Sample/article.html b/samples/AvalonEdit.Sample/article.html index 5b017cd610..88219fe178 100644 --- a/samples/AvalonEdit.Sample/article.html +++ b/samples/AvalonEdit.Sample/article.html @@ -19,7 +19,7 @@ scripts and style sheets. <!-- IGNORE THIS SECTION --> <html> <head> -<title>The Code Project</title> +<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; } @@ -32,7 +32,7 @@ CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; } <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;"> +<div style="width:600px; margin-left: 24px;"> <!------------------------------------------------------------> @@ -125,13 +125,6 @@ or <code>textEditor.TextArea.TextView</code>. <!------------------------------------------------------------> <h2>Document (The Text Model)</h2> -<p>So, what is the model of a text editor that has support for complex features like syntax highlighting and folding?<br> -Would you expect to be able to access collapsed text using the document model, given that the text is folded away?<br> -Is the syntax highlighting part of the model? - -<p>In my quest for a good representation of the model, I decided on a radical strategy: -<b>if it's not a <code>char</code>, it's not in the model</b>! - <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. @@ -143,14 +136,9 @@ Changing the document only on a child control would leave the outer controls con <p><i>Simplified</i> definition of <code>TextDocument</code>: <pre lang="cs">public sealed class TextDocument : ITextSource { - public event EventHandler UpdateStarted; public event EventHandler<DocumentChangeEventArgs> Changing; public event EventHandler<DocumentChangeEventArgs> Changed; public event EventHandler TextChanged; - public event EventHandler UpdateFinished; - - public TextAnchor CreateAnchor(int offset); - public ITextSource CreateSnapshot(); public IList<DocumentLine> Lines { get; } public DocumentLine GetLineByNumber(int number); @@ -161,10 +149,6 @@ Changing the document only on a child control would leave the outer controls con public char GetCharAt(int offset); public string GetText(int offset, int length); - public void BeginUpdate(); - public bool IsInUpdate { get; } - public void EndUpdate(); - public void Insert(int offset, string text); public void Remove(int offset, int length); public void Replace(int offset, int length, string text); @@ -175,27 +159,14 @@ Changing the document only on a child control would leave the outer controls con public UndoStack UndoStack { get; } }</pre> -<h3>Offsets</h3> +<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. - -<p>This is exactly the same as the 'index' parameter used by methods in the .NET <code>String</code> or <code>StringBuilder</code> classes. -Offsets are used because they are dead simple. To all text between offset 10 and offset 30, -simply call <code>document.GetText(10, 20)</code> – just like <code>String.Substring</code>, AvalonEdit usually uses <code>Offset / Length</code> pairs to refer to text segments. - -<p>To easily pass such segments around, AvalonEdit defines the <code>ISegment</code> interface: -<pre lang="cs">public interface ISegment -{ - int Offset { get; } - int Length { get; } // must be non-negative - int EndOffset { get; } // must return Offset+Length -}</pre> -All <code>TextDocument</code> methods taking Offset/Length parameters also have an overload taking an <code>ISegment</code> instance – I have just removed those from the code listing above to make it easier to read. - -<h3>Lines</h3> +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. @@ -203,93 +174,12 @@ AvalonEdit defines a <code>struct</code> called <code>TextLocation</code> for th 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 always<sup><small>*</small></sup> reflect the current document content. - -<p>Internally, the <code>DocumentLine</code> instances are arranged in a binary tree that allows for both efficient updates and lookup. -Converting between offset and line number is possible in O(lg N) time, and the data structure also updates all offsets in O(lg N) whenever text is inserted/removed. - - -<p><small>* tiny exception: it is possible to see the line collection in an inconsistent state inside <code>ILineTracker</code> callbacks. Don't use <code>ILineTracker</code> -unless you know what you are doing!</small> - -<h3>Change Events</h3> - -Here is the order in which events are raised during a document update: - -<p><b>BeginUpdate()</b> -<ul><li><code>UpdateStarted</code> event is raised</li></ul> - -<p><b>Insert() / Remove() / Replace()</b> -<ul> -<li><code>Changing</code> event is raised</li> -<li>The document is changed</li> -<li><code>TextAnchor.Deleted</code> events are raised if anchors were in the deleted text portion</li> -<li><code>Changed</code> event is raised</li> -</ul> - -<p><b>EndUpdate()</b> -<ul><li><code>TextChanged</code> event is raised</li> -<li><code>TextLengthChanged</code> event is raised</li> -<li><code>LineCountChanged</code> event is raised</li> -<li><code>UpdateFinished</code> event is raised</li></ul> - -<p>If the insert/remove/replace methods are called without a call to <code>BeginUpdate()</code>, they will call -<code>BeginUpdate()</code> and <code>EndUpdate()</code> to ensure no change happens outside of <code>UpdateStarted</code>/<code>UpdateFinished</code>. - -<p>There can be multiple document changes between the <code>BeginUpdate()</code> and <code>EndUpdate()</code> calls. -In this case, the events associated with <code>EndUpdate</code> will be raised only once after the whole document update is done. - -<p>The <code>UndoStack</code> listens to the <code>UpdateStarted</code> and <code>UpdateFinished</code> events to group -all changes into a single undo step. - -<h3>TextAnchor</h3> -If you are working with the text editor, you will likely run into the problem that you need to store an offset, but want it to adjust -automatically whenever text is inserted prior to that offset. - -<p>Sure, you could listen to the <code>TextDocument.Changed</code> event and call <code>GetNewOffset</code> on the <code>DocumentChangeEventArgs</code> to translate -the offset, but that gets tedious; especially when your object is short-lived and you have to deal with deregistering the event handler at the correct point of time.<br> - -<p>A much simpler solution is to use the <code>TextAnchor</code> class. Usage: -<pre lang="cs">TextAnchor anchor = document.CreateAnchor(offset); -ChangeMyDocument(); -int newOffset = anchor.Offset;</pre> - -<p>The document will automatically update all text anchors; and because it uses weak references to do so, the GC can simply collect the anchor object when you don't need it anymore. - -<p>Moreover, the document is able to efficiently update a large number of anchors without having to look at each anchor object individually. Updating the offsets of all anchors -usually only takes time logarithmic to the number of anchors. Retrieving the <code>TextAnchor.Offset</code> property also runs in O(lg N). - -<p>When a piece of text containing an anchor is removed; that anchor will be deleted. First, the <code>TextAnchor.IsDeleted</code> property is set to true on all deleted anchors, then the -<code>TextAnchor.Deleted</code> events are raised. You cannot retrieve the offset from an anchor that has been deleted. - -<p>This deletion behavior might be useful when using anchors for building a bookmark feature, but in other cases you want to still be able to use the anchor. For those cases, set <code>TextAnchor.SurviveDeletion = true</code>. - -<p>Note that anchor movement is ambiguous if text is inserted exactly at the anchor's location. Does the anchor stay before the inserted text, or does it move after it? -The property <code>TextAnchor.MovementType</code> will be used to determine which of these two options the anchor will choose. The default value is <code>AnchorMovementType.BeforeInsertion</code>. - -<p>If you want to track a segment, you can use the <code>AnchorSegment</code> class which implements <code>ISegment</code> using two text anchors. - -<h3>TextSegmentCollection</h3> -<p>Sometimes it is useful to store a list of segments and be able to efficiently find all segments overlapping with some other segment.<br> -Example: you might want to store a large number of compiler warnings and render squiggly underlines only for those that are in the visible region of the document. - -<p>The <code>TextSegmentCollection</code> serves this purpose. Connected to a document, it will automatically update the offsets of all <code>TextSegment</code> instances inside the collection; -but it also has the useful methods <code>FindOverlappingSegments</code> and <code>FindFirstSegmentWithStartAfter</code>. -The underlying data structure is a hybrid between the one used for text anchors and an <a href="http://en.wikipedia.org/wiki/Interval_tree#Augmented_tree">interval tree</a>, so it is able to do both jobs quite fast. - -<h3>Thread Safety</h3> -<p>The <code>TextDocument</code> class is not thread-safe. It expects to have a single owner thread and will throw an <code>InvalidOperationException</code> when accessed from another thread. - -<p>However, there is a single method that is thread-safe: <code>CreateSnapshot()</code><br> -It returns an immutable snapshot of the document, and may be safely called even when the owner thread is concurrently modifying the document. -This is very useful for features like a background parser that is running on its own thread. -The overload <code>CreateSnapshot(out ChangeTrackingCheckpoint)</code> also returns a <code>ChangeTrackingCheckpoint</code> for the document snapshot. -Once you have two checkpoints, you can call <code>GetChangesTo</code> to retrieve the complete list of document changes that happened between those versions of the document. +This collection is read-only to user code and is automatically updated to reflect the current document content. <!------------------------------------------------------------> <h2>Rendering</h2> -Noticed how through the whole 'Document' section, there was no mention of extensibility? +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. @@ -300,180 +190,11 @@ 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. - -<h3>Lifetime of visual lines</h3> -When the <code>TextView</code> needs to construct visual lines (usually before rendering), it first -determines which <code>DocumentLine</code> is the top-most visible line in the currently viewed region. -From there, it starts to build visual lines and also immediately does the conversion to <code>TextLine</code> (word-wrapping). -The process stops once the viewed document region is filled. -<p> -The resulting visual lines (and <code>TextLine</code>s) will be cached and reused in future rendering passes. -When the user scrolls down, only the visual lines coming into view are created, the rest is reused. -<p> -The <code>TextView.Redraw</code> methods are used to remove visual lines from the cache. -AvalonEdit will redraw automatically on the affected lines when the document is changed; and will invalidate the whole cache -when any editor options are changed. You will only have to call <code>Redraw</code> manually if you write extensions to the visual line creation process -that maintain their own data source. For example, the <code>FoldingManager</code> invokes <code>Redraw</code> whenever text sections are expanded or collapsed. -<p> -Calling <code>Redraw</code> does not cause immediate recreation of the lines. -They are just removed from the cache so that the next rendering step will recreate them. -All redraw methods will enqueue a new rendering step, using the WPF Dispatcher with a low priority. - -<h3>Elements inside visual line</h3> -A visual line consists of a series of elements. These have both a <code>DocumentLength</code> measured in characters as well as a logical length called <code>VisualLength</code>. -For normal text elements, the two lengths are identical; but some elements like fold markers may have a huge document length, yet a logical length of 1. -On the other hand, some elements that are simply inserted by element generators may have a document length of 0, but still need a logical length of at least 1 to allow -addressing elements inside the visual line. -<p> -The <code>VisualColumn</code> is a position inside a visual line as measured by the logical length of elements. It is counted starting from 0 at the begin of the visual line.<br> -Also, inside visual lines, instead of normal offsets to the text document; relative offsets are used.<br> -<code>Absolute offset = relative offset + VisualLine.FirstDocumentLine.Offset</code><br> -This means that offsets inside the visual line do not have to be adjusted when text is inserted or removed in front of the visual line; we simply rely on the document -automatically updating <code>DocumentLine.Offset</code>. -<p> -The main job of a visual line element is to implement the <code>CreateTextRun</code> method. -This method should return a <code>System.Windows.Media.TextFormatting.TextRun</code> instance that can be rendered using the <code>TextLine</code> class. -<p> -Visual line elements can also handle mouse clicks and control how the caret should move. The mouse click handling might suffice as a light-weight alternative -to embedding inline <code>UIElement</code>s in the visual lines. - -<h3>Element Generators</h3> - -You can extend the text view by registering a custom class deriving from <code>VisualLineElementGenerator</code> in the <code>TextView.ElementGenerators</code> collection. -This allows you to add custom <code>VisualLineElements</code>. -Using the <code>InlineObjectElement</code> class, you can even put interactive WPF controls (anything derived from <code>UIElement</code>) into the text document. -<p> -For all document text not consumed by element generators, AvalonEdit will create <code>VisualLineText</code> elements. -<p> -Usually, the construction of the visual line will stop at the end of the <code>DocumentLine</code>. However, if some <code>VisualLineElementGenerator</code> -creates an element that's longer than the rest of the line, construction of the visual line may resume in another <code>DocumentLine</code>. -Currently, only the <code>FoldingElementGenerator</code> can cause one visual line to span multiple <code>DocumentLine</code>s. -<p> -<img src="AvalonEdit/folding.png" alt="Screenshot Folding and ImageElementGenerator"> -<p> -Here is the full source code for a class that implements embedding images into AvalonEdit: -<pre lang="cs">public class ImageElementGenerator : VisualLineElementGenerator -{ - readonly static Regex imageRegex = new Regex(@"<img src=""([\.\/\w\d]+)""/?>", - RegexOptions.IgnoreCase); - readonly string basePath; - - public ImageElementGenerator(string basePath) - { - if (basePath == null) - throw new ArgumentNullException("basePath"); - this.basePath = basePath; - } - - Match FindMatch(int startOffset) - { - // fetch the end offset of the VisualLine being generated - int endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; - TextDocument document = CurrentContext.Document; - string relevantText = document.GetText(startOffset, endOffset - startOffset); - return imageRegex.Match(relevantText); - } - - /// Gets the first offset >= startOffset where the generator wants to construct - /// an element. - /// Return -1 to signal no interest. - public override int GetFirstInterestedOffset(int startOffset) - { - Match m = FindMatch(startOffset); - return m.Success ? (startOffset + m.Index) : -1; - } - - /// Constructs an element at the specified offset. - /// May return null if no element should be constructed. - public override VisualLineElement ConstructElement(int offset) - { - Match m = FindMatch(offset); - // check whether there's a match exactly at offset - if (m.Success && m.Index == 0) { - BitmapImage bitmap = LoadBitmap(m.Groups[1].Value); - if (bitmap != null) { - Image image = new Image(); - image.Source = bitmap; - image.Width = bitmap.PixelWidth; - image.Height = bitmap.PixelHeight; - // Pass the length of the match to the 'documentLength' parameter - // of InlineObjectElement. - return new InlineObjectElement(m.Length, image); - } - } - return null; - } - - BitmapImage LoadBitmap(string fileName) - { - // TODO: add some kind of cache to avoid reloading the image whenever the - // VisualLine is reconstructed - try { - string fullFileName = Path.Combine(basePath, fileName); - if (File.Exists(fullFileName)) { - BitmapImage bitmap = new BitmapImage(new Uri(fullFileName)); - bitmap.Freeze(); - return bitmap; - } - } catch (ArgumentException) { - // invalid filename syntax - } catch (IOException) { - // other IO error - } - return null; - } -}</pre> - -<h3>Line Transformers</h3> - -Line transformers can modify the visual lines after they have been generated. The main usage of this is to colorize the text, -as done both by syntax highlighting and the selection. -<p> -The base classes <code>ColorizingTransformer</code> and <code>DocumentColorizingTransformer</code> help with this task -by providing helper methods for colorizing that split up visual line elements where necessary. The difference between -the two classes is that one works using visual columns whereas the other one uses offsets into the document. -<p> -Here is an example <code>DocumentColorizingTransformer</code> that highlights the word 'AvalonEdit' using bold font: -<pre lang="cs">public class ColorizeAvalonEdit : DocumentColorizingTransformer -{ - protected override void ColorizeLine(DocumentLine line) - { - int lineStartOffset = line.Offset; - string text = CurrentContext.Document.GetText(line); - int start = 0; - int index; - while ((index = text.IndexOf("AvalonEdit", start)) >= 0) { - base.ChangeLinePart( - lineStartOffset + index, // startOffset - lineStartOffset + index + 10, // endOffset - (VisualLineElement element) => { - // This lambda gets called once for every VisualLineElement - // between the specified offsets. - Typeface tf = element.TextRunProperties.Typeface; - // Replace the typeface with a modified version of the same typeface - element.TextRunProperties.SetTypeface(new Typeface( - tf.FontFamily, - FontStyles.Italic, - FontWeights.Bold, - tf.Stretch - )); - }); - start = index + 1; // search for next occurrence -} } }</pre> - -<h3>Background renderers</h3> - -Background renderers are simple objects that allow you to draw anything in the text view. -They can be used to draw nice-looking backgrounds behind the text. <p> -AvalonEdit contains the class <code>BackgroundGeometryBuilder</code> that helps with this task. -You can use the static <code>BackgroundGeometryBuilder.GetRectsForSegment</code> to fetch a list of rectangles that -contain text from the specified segment (you will get one rectangle per <code>TextLine</code>); -or you can use the instance methods to build a <code>PathGeometry</code> for the text's outline. -AvalonEdit also internally uses this geometry builder to create the selection with the rounded corners. +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> -Inside SharpDevelop, the first option (getting list of rectangles) is used to render the squiggly red line that for compiler errors, -while the second option is used to produce nice-looking breakpoint markers. +The extensibility features of the rendering namespace are discussed in detail in the article "AvalonEdit Rendering". (to be published soon) <h2>Editing</h2> @@ -490,14 +211,16 @@ to detect when the margin is attached/detaching from a text view; or when the ac any <code>UIElement</code> can be used as margin. <h2>Folding</h2> -Folding (code collapsing) could be implemented as an extension to the editor without having to modify the AvalonEdit code. +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> -That's exactly how folding is implemented in AvalonEdit. However, to make it a bit easier to use; the static <code>FoldingManager.Install</code> +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); @@ -505,8 +228,9 @@ 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 realize that { and } inside strings or comments are not code. +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 diff --git a/samples/AvalonEdit.Sample/document.html b/samples/AvalonEdit.Sample/document.html new file mode 100644 index 0000000000..2ba3708aca --- /dev/null +++ b/samples/AvalonEdit.Sample/document.html @@ -0,0 +1,242 @@ +<!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>Document</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. --> + +For the sample application and source code download, please see the main article: +<a href="http://www.codeproject.com/KB/edit/AvalonEdit.aspx">Using AvalonEdit (WPF Text Editor)</a> + +<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> + + + +<h2>Using the Code</h2> + + +<!------------------------------------------------------------> +<h2>Document (The Text Model)</h2> + +<p>So, what is the model of a text editor that has support for complex features like syntax highlighting and folding?<br> +Would you expect to be able to access collapsed text using the document model, given that the text is folded away?<br> +Is the syntax highlighting part of the model? + +<p>In my quest for a good representation of the model, I decided on a radical strategy: +<b>if it's not a <code>char</code>, it's not in the model</b>! + +<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 UpdateStarted; + public event EventHandler<DocumentChangeEventArgs> Changing; + public event EventHandler<DocumentChangeEventArgs> Changed; + public event EventHandler TextChanged; + public event EventHandler UpdateFinished; + + public TextAnchor CreateAnchor(int offset); + public ITextSource CreateSnapshot(); + + public IList<DocumentLine> 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 BeginUpdate(); + public bool IsInUpdate { get; } + public void EndUpdate(); + + 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</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. + +<p>This is exactly the same as the 'index' parameter used by methods in the .NET <code>String</code> or <code>StringBuilder</code> classes. +Offsets are used because they are dead simple. To all text between offset 10 and offset 30, +simply call <code>document.GetText(10, 20)</code> – just like <code>String.Substring</code>, AvalonEdit usually uses <code>Offset / Length</code> pairs to refer to text segments. + +<p>To easily pass such segments around, AvalonEdit defines the <code>ISegment</code> interface: +<pre lang="cs">public interface ISegment +{ + int Offset { get; } + int Length { get; } // must be non-negative + int EndOffset { get; } // must return Offset+Length +}</pre> +All <code>TextDocument</code> methods taking Offset/Length parameters also have an overload taking an <code>ISegment</code> instance – I have just removed those from the code listing above to make it easier to read. + +<h3>Lines</h3> +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 always<sup><small>*</small></sup> reflect the current document content. + +<p>Internally, the <code>DocumentLine</code> instances are arranged in a binary tree that allows for both efficient updates and lookup. +Converting between offset and line number is possible in O(lg N) time, and the data structure also updates all offsets in O(lg N) whenever text is inserted/removed. + + +<p><small>* tiny exception: it is possible to see the line collection in an inconsistent state inside <code>ILineTracker</code> callbacks. Don't use <code>ILineTracker</code> +unless you know what you are doing!</small> + +<h3>Change Events</h3> + +Here is the order in which events are raised during a document update: + +<p><b>BeginUpdate()</b> +<ul><li><code>UpdateStarted</code> event is raised</li></ul> + +<p><b>Insert() / Remove() / Replace()</b> +<ul> +<li><code>Changing</code> event is raised</li> +<li>The document is changed</li> +<li><code>TextAnchor.Deleted</code> events are raised if anchors were in the deleted text portion</li> +<li><code>Changed</code> event is raised</li> +</ul> + +<p><b>EndUpdate()</b> +<ul><li><code>TextChanged</code> event is raised</li> +<li><code>TextLengthChanged</code> event is raised</li> +<li><code>LineCountChanged</code> event is raised</li> +<li><code>UpdateFinished</code> event is raised</li></ul> + +<p>If the insert/remove/replace methods are called without a call to <code>BeginUpdate()</code>, they will call +<code>BeginUpdate()</code> and <code>EndUpdate()</code> to ensure no change happens outside of <code>UpdateStarted</code>/<code>UpdateFinished</code>. + +<p>There can be multiple document changes between the <code>BeginUpdate()</code> and <code>EndUpdate()</code> calls. +In this case, the events associated with <code>EndUpdate</code> will be raised only once after the whole document update is done. + +<p>The <code>UndoStack</code> listens to the <code>UpdateStarted</code> and <code>UpdateFinished</code> events to group +all changes into a single undo step. + +<h3>TextAnchor</h3> +If you are working with the text editor, you will likely run into the problem that you need to store an offset, but want it to adjust +automatically whenever text is inserted prior to that offset. + +<p>Sure, you could listen to the <code>TextDocument.Changed</code> event and call <code>GetNewOffset</code> on the <code>DocumentChangeEventArgs</code> to translate +the offset, but that gets tedious; especially when your object is short-lived and you have to deal with deregistering the event handler at the correct point of time.<br> + +<p>A much simpler solution is to use the <code>TextAnchor</code> class. Usage: +<pre lang="cs">TextAnchor anchor = document.CreateAnchor(offset); +ChangeMyDocument(); +int newOffset = anchor.Offset;</pre> + +<p>The document will automatically update all text anchors; and because it uses weak references to do so, the GC can simply collect the anchor object when you don't need it anymore. + +<p>Moreover, the document is able to efficiently update a large number of anchors without having to look at each anchor object individually. Updating the offsets of all anchors +usually only takes time logarithmic to the number of anchors. Retrieving the <code>TextAnchor.Offset</code> property also runs in O(lg N). + +<p>When a piece of text containing an anchor is removed; that anchor will be deleted. First, the <code>TextAnchor.IsDeleted</code> property is set to true on all deleted anchors, then the +<code>TextAnchor.Deleted</code> events are raised. You cannot retrieve the offset from an anchor that has been deleted. + +<p>This deletion behavior might be useful when using anchors for building a bookmark feature, but in other cases you want to still be able to use the anchor. For those cases, set <code>TextAnchor.SurviveDeletion = true</code>. + +<p>Note that anchor movement is ambiguous if text is inserted exactly at the anchor's location. Does the anchor stay before the inserted text, or does it move after it? +The property <code>TextAnchor.MovementType</code> will be used to determine which of these two options the anchor will choose. The default value is <code>AnchorMovementType.BeforeInsertion</code>. + +<p>If you want to track a segment, you can use the <code>AnchorSegment</code> class which implements <code>ISegment</code> using two text anchors. + +<h3>TextSegmentCollection</h3> +<p>Sometimes it is useful to store a list of segments and be able to efficiently find all segments overlapping with some other segment.<br> +Example: you might want to store a large number of compiler warnings and render squiggly underlines only for those that are in the visible region of the document. + +<p>The <code>TextSegmentCollection</code> serves this purpose. Connected to a document, it will automatically update the offsets of all <code>TextSegment</code> instances inside the collection; +but it also has the useful methods <code>FindOverlappingSegments</code> and <code>FindFirstSegmentWithStartAfter</code>. +The underlying data structure is a hybrid between the one used for text anchors and an <a href="http://en.wikipedia.org/wiki/Interval_tree#Augmented_tree">interval tree</a>, so it is able to do both jobs quite fast. + +<h3>Thread Safety</h3> +<p>The <code>TextDocument</code> class is not thread-safe. It expects to have a single owner thread and will throw an <code>InvalidOperationException</code> when accessed from another thread. + +<p>However, there is a single method that is thread-safe: <code>CreateSnapshot()</code><br> +It returns an immutable snapshot of the document, and may be safely called even when the owner thread is concurrently modifying the document. +This is very useful for features like a background parser that is running on its own thread. +The overload <code>CreateSnapshot(out ChangeTrackingCheckpoint)</code> also returns a <code>ChangeTrackingCheckpoint</code> for the document snapshot. +Once you have two checkpoints, you can call <code>GetChangesTo</code> to retrieve the complete list of document changes that happened between those versions of the document. + + +<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> diff --git a/samples/AvalonEdit.Sample/rendering.html b/samples/AvalonEdit.Sample/rendering.html new file mode 100644 index 0000000000..c7a6744ec1 --- /dev/null +++ b/samples/AvalonEdit.Sample/rendering.html @@ -0,0 +1,301 @@ +<!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>Rendering</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. --> + +For the sample application and source code download, please see the main article: +<a href="http://www.codeproject.com/KB/edit/AvalonEdit.aspx">Using AvalonEdit (WPF Text Editor)</a> + +<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> + +<h2>Using the Code</h2> + + +<!------------------------------------------------------------> +<h2>Rendering</h2> + +Noticed how through 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. + +<h3>Lifetime of visual lines</h3> +When the <code>TextView</code> needs to construct visual lines (usually before rendering), it first +determines which <code>DocumentLine</code> is the top-most visible line in the currently viewed region. +From there, it starts to build visual lines and also immediately does the conversion to <code>TextLine</code> (word-wrapping). +The process stops once the viewed document region is filled. +<p> +The resulting visual lines (and <code>TextLine</code>s) will be cached and reused in future rendering passes. +When the user scrolls down, only the visual lines coming into view are created, the rest is reused. +<p> +The <code>TextView.Redraw</code> methods are used to remove visual lines from the cache. +AvalonEdit will redraw automatically on the affected lines when the document is changed; and will invalidate the whole cache +when any editor options are changed. You will only have to call <code>Redraw</code> manually if you write extensions to the visual line creation process +that maintain their own data source. For example, the <code>FoldingManager</code> invokes <code>Redraw</code> whenever text sections are expanded or collapsed. +<p> +Calling <code>Redraw</code> does not cause immediate recreation of the lines. +They are just removed from the cache so that the next rendering step will recreate them. +All redraw methods will enqueue a new rendering step, using the WPF Dispatcher with a low priority. + +<h3>Elements inside visual line</h3> +A visual line consists of a series of elements. These have both a <code>DocumentLength</code> measured in characters as well as a logical length called <code>VisualLength</code>. +For normal text elements, the two lengths are identical; but some elements like fold markers may have a huge document length, yet a logical length of 1. +On the other hand, some elements that are simply inserted by element generators may have a document length of 0, but still need a logical length of at least 1 to allow +addressing elements inside the visual line. +<p> +The <code>VisualColumn</code> is a position inside a visual line as measured by the logical length of elements. It is counted starting from 0 at the begin of the visual line.<br> +Also, inside visual lines, instead of normal offsets to the text document; relative offsets are used.<br> +<code>Absolute offset = relative offset + VisualLine.FirstDocumentLine.Offset</code><br> +This means that offsets inside the visual line do not have to be adjusted when text is inserted or removed in front of the visual line; we simply rely on the document +automatically updating <code>DocumentLine.Offset</code>. +<p> +The main job of a visual line element is to implement the <code>CreateTextRun</code> method. +This method should return a <code>System.Windows.Media.TextFormatting.TextRun</code> instance that can be rendered using the <code>TextLine</code> class. +<p> +Visual line elements can also handle mouse clicks and control how the caret should move. The mouse click handling might suffice as a light-weight alternative +to embedding inline <code>UIElement</code>s in the visual lines. + +<h3>Element Generators</h3> + +You can extend the text view by registering a custom class deriving from <code>VisualLineElementGenerator</code> in the <code>TextView.ElementGenerators</code> collection. +This allows you to add custom <code>VisualLineElements</code>. +Using the <code>InlineObjectElement</code> class, you can even put interactive WPF controls (anything derived from <code>UIElement</code>) into the text document. +<p> +For all document text not consumed by element generators, AvalonEdit will create <code>VisualLineText</code> elements. +<p> +Usually, the construction of the visual line will stop at the end of the <code>DocumentLine</code>. However, if some <code>VisualLineElementGenerator</code> +creates an element that's longer than the rest of the line, construction of the visual line may resume in another <code>DocumentLine</code>. +Currently, only the <code>FoldingElementGenerator</code> can cause one visual line to span multiple <code>DocumentLine</code>s. +<p> +<img src="AvalonEdit/folding.png" alt="Screenshot Folding and ImageElementGenerator"> +<p> +Here is the full source code for a class that implements embedding images into AvalonEdit: +<pre lang="cs">public class ImageElementGenerator : VisualLineElementGenerator +{ + readonly static Regex imageRegex = new Regex(@"<img src=""([\.\/\w\d]+)""/?>", + RegexOptions.IgnoreCase); + readonly string basePath; + + public ImageElementGenerator(string basePath) + { + if (basePath == null) + throw new ArgumentNullException("basePath"); + this.basePath = basePath; + } + + Match FindMatch(int startOffset) + { + // fetch the end offset of the VisualLine being generated + int endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; + TextDocument document = CurrentContext.Document; + string relevantText = document.GetText(startOffset, endOffset - startOffset); + return imageRegex.Match(relevantText); + } + + /// Gets the first offset >= startOffset where the generator wants to construct + /// an element. + /// Return -1 to signal no interest. + public override int GetFirstInterestedOffset(int startOffset) + { + Match m = FindMatch(startOffset); + return m.Success ? (startOffset + m.Index) : -1; + } + + /// Constructs an element at the specified offset. + /// May return null if no element should be constructed. + public override VisualLineElement ConstructElement(int offset) + { + Match m = FindMatch(offset); + // check whether there's a match exactly at offset + if (m.Success && m.Index == 0) { + BitmapImage bitmap = LoadBitmap(m.Groups[1].Value); + if (bitmap != null) { + Image image = new Image(); + image.Source = bitmap; + image.Width = bitmap.PixelWidth; + image.Height = bitmap.PixelHeight; + // Pass the length of the match to the 'documentLength' parameter + // of InlineObjectElement. + return new InlineObjectElement(m.Length, image); + } + } + return null; + } + + BitmapImage LoadBitmap(string fileName) + { + // TODO: add some kind of cache to avoid reloading the image whenever the + // VisualLine is reconstructed + try { + string fullFileName = Path.Combine(basePath, fileName); + if (File.Exists(fullFileName)) { + BitmapImage bitmap = new BitmapImage(new Uri(fullFileName)); + bitmap.Freeze(); + return bitmap; + } + } catch (ArgumentException) { + // invalid filename syntax + } catch (IOException) { + // other IO error + } + return null; + } +}</pre> + +<h3>Line Transformers</h3> + +Line transformers can modify the visual lines after they have been generated. The main usage of this is to colorize the text, +as done both by syntax highlighting and the selection. +<p> +The base classes <code>ColorizingTransformer</code> and <code>DocumentColorizingTransformer</code> help with this task +by providing helper methods for colorizing that split up visual line elements where necessary. The difference between +the two classes is that one works using visual columns whereas the other one uses offsets into the document. +<p> +Here is an example <code>DocumentColorizingTransformer</code> that highlights the word 'AvalonEdit' using bold font: +<pre lang="cs">public class ColorizeAvalonEdit : DocumentColorizingTransformer +{ + protected override void ColorizeLine(DocumentLine line) + { + int lineStartOffset = line.Offset; + string text = CurrentContext.Document.GetText(line); + int start = 0; + int index; + while ((index = text.IndexOf("AvalonEdit", start)) >= 0) { + base.ChangeLinePart( + lineStartOffset + index, // startOffset + lineStartOffset + index + 10, // endOffset + (VisualLineElement element) => { + // This lambda gets called once for every VisualLineElement + // between the specified offsets. + Typeface tf = element.TextRunProperties.Typeface; + // Replace the typeface with a modified version of + // the same typeface + element.TextRunProperties.SetTypeface(new Typeface( + tf.FontFamily, + FontStyles.Italic, + FontWeights.Bold, + tf.Stretch + )); + }); + start = index + 1; // search for next occurrence +} } }</pre> + +<h3>Background renderers</h3> + +Background renderers are simple objects that allow you to draw anything in the text view. +They can be used to draw nice-looking backgrounds behind the text. +<p> +AvalonEdit contains the class <code>BackgroundGeometryBuilder</code> that helps with this task. +You can use the static <code>BackgroundGeometryBuilder.GetRectsForSegment</code> to fetch a list of rectangles that +contain text from the specified segment (you will get one rectangle per <code>TextLine</code>); +or you can use the instance methods to build a <code>PathGeometry</code> for the text's outline. +AvalonEdit also internally uses this geometry builder to create the selection with the rounded corners. +<p> +Inside SharpDevelop, the first option (getting list of rectangles) is used to render the squiggly red line that for compiler errors, +while the second option is used to produce nice-looking breakpoint markers. + +<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) could be implemented as an extension to the editor 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> +That's exactly how folding is implemented in AvalonEdit. However, 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. +<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> +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>