Browse Source

Add chapter on code completion.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5048 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
6d54b7f06a
  1. 6
      samples/AvalonEdit.Sample/AvalonEdit.Sample.csproj
  2. 40
      samples/AvalonEdit.Sample/CustomHighlighting.xshd
  3. 45
      samples/AvalonEdit.Sample/MyCompletionData.cs
  4. 2
      samples/AvalonEdit.Sample/Window1.xaml
  5. 60
      samples/AvalonEdit.Sample/Window1.xaml.cs
  6. 104
      samples/AvalonEdit.Sample/article.html
  7. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs
  8. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/Xshd/XmlHighlightingDefinition.cs

6
samples/AvalonEdit.Sample/AvalonEdit.Sample.csproj

@ -66,14 +66,13 @@
<DependentUpon>App.xaml</DependentUpon> <DependentUpon>App.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="BraceFoldingStrategy.cs" /> <Compile Include="BraceFoldingStrategy.cs" />
<Compile Include="ColorizeAvalonEdit.cs" /> <Compile Include="MyCompletionData.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\WPFAssemblyInfo.cs" /> <Compile Include="Properties\WPFAssemblyInfo.cs" />
<Compile Include="Window1.xaml.cs"> <Compile Include="Window1.xaml.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
<DependentUpon>Window1.xaml</DependentUpon> <DependentUpon>Window1.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="ImageElementGenerator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Page Include="Window1.xaml" /> <Page Include="Window1.xaml" />
@ -99,5 +98,8 @@
<Name>ICSharpCode.AvalonEdit</Name> <Name>ICSharpCode.AvalonEdit</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="CustomHighlighting.xshd" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
</Project> </Project>

40
samples/AvalonEdit.Sample/CustomHighlighting.xshd

@ -0,0 +1,40 @@
<?xml version="1.0"?>
<SyntaxDefinition name="Custom Highlighting" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color name="Comment" foreground="Green" />
<Color name="String" foreground="Blue" />
<!-- This is the main ruleset. -->
<RuleSet>
<Span color="Comment" begin="//" />
<Span color="Comment" multiline="true" begin="/\*" end="\*/" />
<Span color="String">
<Begin>"</Begin>
<End>"</End>
<RuleSet>
<!-- nested span for escape sequences -->
<Span begin="\\" end="." />
</RuleSet>
</Span>
<Keywords fontWeight="bold" foreground="Blue">
<Word>if</Word>
<Word>else</Word>
<!-- ... -->
</Keywords>
<Keywords fontWeight="bold" fontStyle="italic" foreground="Red">
<Word>AvalonEdit</Word>
</Keywords>
<!-- Digits -->
<Rule foreground="DarkBlue">
\b0[xX][0-9a-fA-F]+ # hex number
| \b
( \d+(\.[0-9]+)? #number with optional floating point
| \.[0-9]+ #or just starting with floating point
)
([eE][+-]?[0-9]+)? # optional exponent
</Rule>
</RuleSet>
</SyntaxDefinition>

45
samples/AvalonEdit.Sample/MyCompletionData.cs

@ -0,0 +1,45 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
namespace AvalonEdit.Sample
{
/// <summary>
/// Implements AvalonEdit ICompletionData interface to provide the entries in the completion drop down.
/// </summary>
public class MyCompletionData : ICompletionData
{
public MyCompletionData(string text)
{
this.Text = text;
}
public System.Windows.Media.ImageSource Image {
get { return null; }
}
public string Text { get; private set; }
// Use this property if you want to show a fancy UIElement in the drop down list.
public object Content {
get { return this.Text; }
}
public object Description {
get { return "Description for " + this.Text; }
}
public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs)
{
textArea.Document.Replace(completionSegment, this.Text);
}
}
}

2
samples/AvalonEdit.Sample/Window1.xaml

@ -69,7 +69,7 @@
Name="textEditor" Name="textEditor"
FontFamily="Consolas" FontFamily="Consolas"
FontSize="10pt" FontSize="10pt"
>Welcome to AvalonEdit! Embedded image: &lt;img src="Images/Open.png"/&gt; >Welcome to AvalonEdit!
</avalonEdit:TextEditor> </avalonEdit:TextEditor>
<GridSplitter Grid.Column="1" Width="4" HorizontalAlignment="Left"/> <GridSplitter Grid.Column="1" Width="4" HorizontalAlignment="Left"/>
<DockPanel Grid.Column="1" Margin="4 0 0 0"> <DockPanel Grid.Column="1" Margin="4 0 0 0">

60
samples/AvalonEdit.Sample/Window1.xaml.cs

@ -19,15 +19,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
using System.Xml;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting;
using Microsoft.Win32; using Microsoft.Win32;
@ -41,12 +39,28 @@ namespace AvalonEdit.Sample
{ {
public Window1() public Window1()
{ {
// Load our custom highlighting definition
IHighlightingDefinition customHighlighting;
using (Stream s = typeof(Window1).Assembly.GetManifestResourceStream("AvalonEdit.Sample.CustomHighlighting.xshd")) {
if (s == null)
throw new InvalidOperationException("Could not find embedded resource");
using (XmlReader reader = new XmlTextReader(s)) {
customHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
}
// and register it in the HighlightingManager
HighlightingManager.Instance.RegisterHighlighting("Custom Highlighting", new string[] { ".cool" }, customHighlighting);
InitializeComponent(); InitializeComponent();
propertyGridComboBox.SelectedIndex = 2; propertyGridComboBox.SelectedIndex = 2;
textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#"); //textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#");
textEditor.TextArea.TextView.ElementGenerators.Add(new ImageElementGenerator(Path.GetFullPath("../.."))); textEditor.SyntaxHighlighting = customHighlighting;
textEditor.TextArea.TextView.LineTransformers.Add(new ColorizeAvalonEdit());
textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering;
textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered;
DispatcherTimer foldingUpdateTimer = new DispatcherTimer(); DispatcherTimer foldingUpdateTimer = new DispatcherTimer();
foldingUpdateTimer.Interval = TimeSpan.FromSeconds(2); foldingUpdateTimer.Interval = TimeSpan.FromSeconds(2);
@ -98,6 +112,38 @@ namespace AvalonEdit.Sample
} }
} }
CompletionWindow completionWindow;
void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
if (e.Text == ".") {
// open code completion after the user has pressed dot:
completionWindow = new CompletionWindow(textEditor.TextArea);
// provide AvalonEdit with the data:
IList<ICompletionData> data = completionWindow.CompletionList.CompletionData;
data.Add(new MyCompletionData("Item1"));
data.Add(new MyCompletionData("Item2"));
data.Add(new MyCompletionData("Item3"));
data.Add(new MyCompletionData("Another item"));
completionWindow.Show();
completionWindow.Closed += delegate {
completionWindow = null;
};
}
}
void textEditor_TextArea_TextEntering(object sender, TextCompositionEventArgs e)
{
if (e.Text.Length > 0 && completionWindow != null) {
if (!char.IsLetterOrDigit(e.Text[0])) {
// Whenever a non-letter is typed while the completion window is open,
// insert the currently selected element.
completionWindow.CompletionList.RequestInsertion(e);
}
}
// do not set e.Handled=true - we still want to insert the character that was typed
}
#region Folding #region Folding
FoldingManager foldingManager; FoldingManager foldingManager;
AbstractFoldingStrategy foldingStrategy; AbstractFoldingStrategy foldingStrategy;

104
samples/AvalonEdit.Sample/article.html

@ -66,7 +66,7 @@ for <a href="http://www.codeproject.com/KB/edit/TextEditorControl.aspx">ICSharpC
</ul> </ul>
<p> <p>
<b>Extensible</b> means that I wanted SharpDevelop AddIns to be able to add features to the text editor. <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 For example, an AddIn should be able to allow inserting images into comments &ndash; this way you could put
stuff like class diagrams right into the source code! stuff like class diagrams right into the source code!
<p> <p>
With, <b>Easy to use</b>, I'm referring to the programming API. It should just work&trade;. With, <b>Easy to use</b>, I'm referring to the programming API. It should just work&trade;.
@ -108,9 +108,10 @@ Most of the namespaces have a kind of 'main' class.
<li>ICSharpCode.AvalonEdit.Document: <code>TextDocument</code> &mdash; text model</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.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.Editing: <code>TextArea</code> &mdash; controls text editing (e.g. caret, selection, handles user input)</li>
<li>ICSharpCode.AvalonEdit.Folding: <code>FoldingManager</code> &mdash; enables code collapsing</li>
<li>ICSharpCode.AvalonEdit.Highlighting: <code>HighlightingManager</code> &mdash; highlighting engine</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.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.CodeCompletion: <code>CompletionWindow</code> &mdash; shows a drop-down list for code completion</li>
<li>ICSharpCode.AvalonEdit: <code>TextEditor</code> &mdash; the main control that brings it all together</li> <li>ICSharpCode.AvalonEdit: <code>TextEditor</code> &mdash; the main control that brings it all together</li>
</ul> </ul>
@ -192,8 +193,10 @@ The last step in the pipeline is the conversion to one or more <code>System.Wind
<p> <p>
The "element generators", "line transformers" and "background renderers" are the extension points; it is possible to add custom implementations of 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. them to the <code>TextView</code> to implement additional features in the editor.
<!--
<p> <p>
The extensibility features of the rendering namespace are discussed in detail in the article "AvalonEdit Rendering". (to be published soon) The extensibility features of the rendering namespace are discussed in detail in the article "AvalonEdit Rendering". (to be published soon)
-->
<h2>Editing</h2> <h2>Editing</h2>
@ -204,7 +207,7 @@ You can customize the text area by modifying the <code>TextArea.DefaultInputHand
WPF input bindings in it. You can also set <code>TextArea.ActiveInputHandler</code> to something different than the default 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. to switch the text area into another mode. You could use this to implement an "incremental search" feature, or even a VI emulator.
<p> <p>
The text area has the <code>LeftMargins</code> property - use it to add controls to the left of the text view that look like The text area has the <code>LeftMargins</code> property &ndash; 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 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; 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. any <code>UIElement</code> can be used as margin.
@ -303,13 +306,13 @@ and yet usually requires only a few KB of memory even for large code files.
<p><i>On-demand</i> means that when a document is opened, only the lines initially visible will be highlighted. When the user scrolls down, highlighting will <p><i>On-demand</i> means that when a document is opened, only the lines initially visible will be highlighted. When the user scrolls down, highlighting will
continue from the point where it stopped the last time. continue from the point where it stopped the last time.
If the user scrolls quickly, so that the first visible line is far below the last highlighted line, then the highlighting engine still has to process all the If the user scrolls quickly, so that the first visible line is far below the last highlighted line, then the highlighting engine still has to process all the
lines in between - there might be comment starts in them. However, it will only scan that region for changes in the span stack; highlighting rules will not lines in between &ndash; there might be comment starts in them. However, it will only scan that region for changes in the span stack; highlighting rules will not
be tested. be tested.
<p>The stack of active spans is stored at the beginning of every line. If the user scrolls back up, the lines getting into view can be highlighted immediately <p>The stack of active spans is stored at the beginning of every line. If the user scrolls back up, the lines getting into view can be highlighted immediately
because the necessary context (the span stack) is still available. because the necessary context (the span stack) is still available.
<p><i>Incrementally</i> means that even if the document is changed, the stored span stacks will be reused as far as possible. If the user types <code>/*</code>, that would <p><i>Incrementally</i> means that even if the document is changed, the stored span stacks will be reused as far as possible. If the user types <code>/*</code>, that would
theoretically cause the whole remainder of the file to become highlighted in the comment color. However, because the engine works on-demand, it will only update the theoretically cause the whole remainder of the file to become highlighted in the comment color. However, because the engine works on-demand, it will only update the
span stacks within the currently visible region and keep a notice 'the highlighting state is not consistent between line X and X+1', where X is the last line span stacks within the currently visible region and keep a notice 'the highlighting state is not consistent between line X and line X+1', where X is the last line
in the visible region. Now, if the user would scroll down, the highlighting state would be updated and the 'not consistent' notice would be moved down. in the visible region. Now, if the user would scroll down, the highlighting state would be updated and the 'not consistent' notice would be moved down.
But usually, the user will continue typing and type <code>*/</code> only a few lines later. Now the highlighting state in the visible region will revert to the But usually, the user will continue typing and type <code>*/</code> only a few lines later. Now the highlighting state in the visible region will revert to the
normal 'only the main ruleset is on the stack of active spans'. When the user now scrolls down below the line with the 'not consistent' marker; normal 'only the main ruleset is on the stack of active spans'. When the user now scrolls down below the line with the 'not consistent' marker;
@ -323,15 +326,98 @@ The memory usage of the highlighting engine is linear to the number of span stac
This allows the highlighting engine to store the span stacks for big code files using only a tiny amount of memory, This allows the highlighting engine to store the span stacks for big code files using only a tiny amount of memory,
especially in languages like C# where sequences of <code>//</code> or <code>///</code> are more popular than <code>/* */</code> comments. especially in languages like C# where sequences of <code>//</code> or <code>///</code> are more popular than <code>/* */</code> comments.
<h2>Code Completion</h2>
<p>AvalonEdit comes with a code completion drop down window.
You only have to handle the text entering events to determine when you want to show the window; all the UI is already done for you.
<p>
Here's how you can use it:
<pre> // in the constructor:
textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering;
textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered;
}
<h2>Points of Interest</h2> CompletionWindow completionWindow;
<p>Did you learn anything interesting/fun/annoying while writing the code? Did you void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e)
do anything particularly clever or wild or zany? {
if (e.Text == ".") {
// Open code completion after the user has pressed dot:
completionWindow = new CompletionWindow(textEditor.TextArea);
IList&lt;ICompletionData> data = completionWindow.CompletionList.CompletionData;
data.Add(new MyCompletionData("Item1"));
data.Add(new MyCompletionData("Item2"));
data.Add(new MyCompletionData("Item3"));
completionWindow.Show();
completionWindow.Closed += delegate {
completionWindow = null;
};
}
}
void textEditor_TextArea_TextEntering(object sender, TextCompositionEventArgs e)
{
if (e.Text.Length > 0 &amp;&amp; completionWindow != null) {
if (!char.IsLetterOrDigit(e.Text[0])) {
// Whenever a non-letter is typed while the completion window is open,
// insert the currently selected element.
completionWindow.CompletionList.RequestInsertion(e);
}
}
// Do not set e.Handled=true.
// We still want to insert the character that was typed.
}</pre>
This code will open the code completion window whenever '.' is pressed.
By default, the <code>CompletionWindow</code> only handles key presses like Tab and Enter to insert the currently selected item. To also make
it complete when keys like '.' or ';' are pressed, we attach another handler to the <code>TextEntering</code> event and tell the
completion window to insert the selected item.
<p>
The <code>CompletionWindow</code> will actually never have focus - instead, it hijacks the WPF keyboard input events
on the text area and passes them through its <code>ListBox</code>.
This allows selecting entries in the completion list using the keyboard and normal typing in the editor at the same time.
<p>For the sake of completeness, here is the implementation of the <code>MyCompletionData</code> class used in the code above:
<pre>/// Implements AvalonEdit ICompletionData interface to provide the entries in the
/// completion drop down.
public class MyCompletionData : ICompletionData
{
public MyCompletionData(string text)
{
this.Text = text;
}
public System.Windows.Media.ImageSource Image {
get { return null; }
}
public string Text { get; private set; }
// Use this property if you want to show a fancy UIElement in the list.
public object Content {
get { return this.Text; }
}
public object Description {
get { return "Description for " + this.Text; }
}
public void Complete(TextArea textArea, ISegment completionSegment,
EventArgs insertionRequestEventArgs)
{
textArea.Document.Replace(completionSegment, this.Text);
}
}</pre>
Both the content and the description shown may be any content acceptable in WPF, including custom UIElements.
You may also implement custom logic in the <code>Complete</code> method if you want to do more than simply inserting the text.
The <code>insertionRequestEventArgs</code> can help decide which kind of insertion the user wants - depending on how the insertion
was triggered, it is an instance of <code>TextCompositionEventArgs</code>, <code>KeyEventArgs</code> or <code>MouseEventArgs</code>.
<h2>History</h2> <h2>History</h2>
<p>Keep a running update of any changes or improvements you've made here. <ul>
<li>August 13, 2008: Work on AvalonEdit started</li>
<li>November 7, 2008: First version of AvalonEdit added to SharpDevelop 4.0 trunk</li>
<li>June 14, 2009: The SharpDevelop team switches to SharpDevelop 4 as their IDE for working on SharpDevelop; AvalonEdit starts to get used for real work</li>
<li>October 4, 2009: This article first published on The Code Project</li>
</ul>
<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> <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>

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs

@ -76,7 +76,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
ObservableCollection<ICompletionData> completionData = new ObservableCollection<ICompletionData>(); ObservableCollection<ICompletionData> completionData = new ObservableCollection<ICompletionData>();
/// <summary> /// <summary>
/// Gets/Sets the completion data. /// Gets the list to which completion data can be added.
/// </summary> /// </summary>
public IList<ICompletionData> CompletionData { public IList<ICompletionData> CompletionData {
get { return completionData; } get { return completionData; }

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/Xshd/XmlHighlightingDefinition.cs

@ -111,7 +111,6 @@ namespace ICSharpCode.AvalonEdit.Highlighting.Xshd
public TranslateElementVisitor(XmlHighlightingDefinition def, IHighlightingDefinitionReferenceResolver resolver) public TranslateElementVisitor(XmlHighlightingDefinition def, IHighlightingDefinitionReferenceResolver resolver)
{ {
Debug.Assert(def != null); Debug.Assert(def != null);
Debug.Assert(resolver != null);
this.def = def; this.def = def;
this.resolver = resolver; this.resolver = resolver;
} }
@ -240,6 +239,8 @@ namespace ICSharpCode.AvalonEdit.Highlighting.Xshd
{ {
if (definitionName == null) if (definitionName == null)
return def; return def;
if (resolver == null)
throw Error(position, "Resolving references to other syntax definitions is not possible because the IHighlightingDefinitionReferenceResolver is null.");
IHighlightingDefinition d = resolver.GetDefinition(definitionName); IHighlightingDefinition d = resolver.GetDefinition(definitionName);
if (d == null) if (d == null)
throw Error(position, "Could not find definition with name '" + definitionName + "'."); throw Error(position, "Could not find definition with name '" + definitionName + "'.");

Loading…
Cancel
Save