Browse Source

AvalonEdit: display non-printable characters using their name or hex code.

Improved dot code completion (sort completion entries, group overloaded methods).

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3907 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
2a3c49b6f5
  1. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj
  2. 40
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/IndentationStringTests.cs
  3. 46
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs
  4. 36
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionListBox.cs
  5. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindowBase.cs
  6. 14
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLine.cs
  7. 33
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs
  8. 8
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/LineNumberMargin.cs
  9. 7
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/Selection.cs
  10. 5
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs
  11. 149
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SingleCharacterElementGenerator.cs
  12. 72
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs
  13. 70
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs
  14. 29
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditorWeakEventManager.cs
  15. 69
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
  16. 14
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs
  17. 12
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  18. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Properties/CodeAnalysisDictionary.xml
  19. 43
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorComponent.cs
  20. 163
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs
  21. 24
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs
  22. 48
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/HtmlClipboard.cs
  23. 30
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/PropertyChangedWeakEventManager.cs
  24. 82
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs
  25. 95
      src/Main/Base/Project/Src/TextEditor/CodeCompletionItemProvider.cs
  26. 11
      src/Main/Base/Project/Src/TextEditor/ICompletionItem.cs
  27. 4
      src/Main/Base/Project/Src/TextEditor/ICompletionItemList.cs

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj

@ -75,6 +75,7 @@ @@ -75,6 +75,7 @@
<Compile Include="Utils\CaretNavigationTests.cs" />
<Compile Include="Utils\CompressingTreeListTests.cs" />
<Compile Include="Utils\ExtensionMethodsTests.cs" />
<Compile Include="Utils\IndentationStringTests.cs" />
<Compile Include="WeakReferenceTests.cs" />
<None Include="app.config" />
</ItemGroup>

40
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/IndentationStringTests.cs

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Tests.Utils
{
[TestFixture]
public class IndentationStringTests
{
[Test]
public void IndentWithSingleTab()
{
var options = new TextEditorOptions { IndentationSize = 4, ConvertTabsToSpaces = false };
Assert.AreEqual("\t", options.IndentationString);
Assert.AreEqual("\t", options.GetIndentationString(1));
Assert.AreEqual("\t", options.GetIndentationString(2));
Assert.AreEqual("\t", options.GetIndentationString(3));
Assert.AreEqual("\t", options.GetIndentationString(4));
Assert.AreEqual("\t", options.GetIndentationString(5));
}
[Test]
public void IndentWith4Spaces()
{
var options = new TextEditorOptions { IndentationSize = 4, ConvertTabsToSpaces = true };
Assert.AreEqual(" ", options.IndentationString);
Assert.AreEqual(" ", options.GetIndentationString(1));
Assert.AreEqual(" ", options.GetIndentationString(2));
Assert.AreEqual(" ", options.GetIndentationString(3));
Assert.AreEqual(" ", options.GetIndentationString(4));
Assert.AreEqual(" ", options.GetIndentationString(5));
}
}
}

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

@ -101,27 +101,27 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion @@ -101,27 +101,27 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
switch (e.Key) {
case Key.Down:
e.Handled = true;
SelectIndex(listBox.SelectedIndex + 1);
listBox.SelectIndex(listBox.SelectedIndex + 1);
break;
case Key.Up:
e.Handled = true;
SelectIndex(listBox.SelectedIndex - 1);
listBox.SelectIndex(listBox.SelectedIndex - 1);
break;
case Key.PageDown:
e.Handled = true;
SelectIndex(listBox.SelectedIndex + listBox.VisibleItemCount);
listBox.SelectIndex(listBox.SelectedIndex + listBox.VisibleItemCount);
break;
case Key.PageUp:
e.Handled = true;
SelectIndex(listBox.SelectedIndex - listBox.VisibleItemCount);
listBox.SelectIndex(listBox.SelectedIndex - listBox.VisibleItemCount);
break;
case Key.Home:
e.Handled = true;
SelectIndex(0);
listBox.SelectIndex(0);
break;
case Key.End:
e.Handled = true;
SelectIndex(listBox.Items.Count - 1);
listBox.SelectIndex(listBox.Items.Count - 1);
break;
case Key.Tab:
case Key.Enter:
@ -158,15 +158,15 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion @@ -158,15 +158,15 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
double bestPriority = 0;
for (int i = 0; i < completionData.Count; ++i) {
string itemText = completionData[i].Text;
if (itemText.StartsWith(startText, StringComparison.InvariantCultureIgnoreCase)) {
if (itemText.StartsWith(startText, StringComparison.OrdinalIgnoreCase)) {
double priority = 0; //completionData[i].Priority;
int quality;
if (string.Equals(itemText, startText, StringComparison.InvariantCultureIgnoreCase)) {
if (string.Equals(itemText, startText, StringComparison.OrdinalIgnoreCase)) {
if (startText == itemText)
quality = 3;
else
quality = 2;
} else if (itemText.StartsWith(startText, StringComparison.InvariantCulture)) {
} else if (itemText.StartsWith(startText, StringComparison.Ordinal)) {
quality = 1;
} else {
quality = 0;
@ -191,36 +191,16 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion @@ -191,36 +191,16 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
}
}
if (bestIndex < 0) {
ClearSelection();
listBox.ClearSelection();
} else {
int firstItem = listBox.FirstVisibleItem;
if (bestIndex < firstItem || firstItem + listBox.VisibleItemCount <= bestIndex) {
CenterViewOn(bestIndex);
SelectIndex(bestIndex);
listBox.CenterViewOn(bestIndex);
listBox.SelectIndex(bestIndex);
} else {
SelectIndex(bestIndex);
listBox.SelectIndex(bestIndex);
}
}
}
void ClearSelection()
{
listBox.SelectedIndex = -1;
}
void SelectIndex(int offset)
{
if (offset >= listBox.Items.Count)
offset = listBox.Items.Count - 1;
if (offset < 0)
offset = 0;
listBox.SelectedIndex = offset;
listBox.ScrollIntoView(listBox.SelectedItem);
}
void CenterViewOn(int offset)
{
// TODO: implement me
}
}
}

36
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionListBox.cs

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
using System;
using System.Windows;
using System.Windows.Controls;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.CodeCompletion
{
@ -43,6 +44,12 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion @@ -43,6 +44,12 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
return (int)(this.Items.Count * scrollViewer.VerticalOffset / scrollViewer.ExtentHeight);
}
}
set {
value = value.CoerceValue(0, this.Items.Count - this.VisibleItemCount);
if (scrollViewer != null) {
scrollViewer.ScrollToVerticalOffset((double)value / this.Items.Count * scrollViewer.ExtentHeight);
}
}
}
/// <summary>
@ -60,5 +67,34 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion @@ -60,5 +67,34 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
}
}
}
/// <summary>
/// Removes the selection.
/// </summary>
public void ClearSelection()
{
this.SelectedIndex = -1;
}
/// <summary>
/// Selects the item with the specified index and scrolls it into view.
/// </summary>
public void SelectIndex(int index)
{
if (index >= this.Items.Count)
index = this.Items.Count - 1;
if (index < 0)
index = 0;
this.SelectedIndex = index;
this.ScrollIntoView(this.SelectedItem);
}
/// <summary>
/// Centers the view on the item with the specified index.
/// </summary>
public void CenterViewOn(int index)
{
this.FirstVisibleItem = index - VisibleItemCount / 2;
}
}
}

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindowBase.cs

@ -127,6 +127,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion @@ -127,6 +127,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
/// <param name="event">The bubbling event.</param>
/// <param name="args">The event args to use.</param>
/// <returns>The <see cref="RoutedEventArgs.Handled"/> value of the event args.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
protected static bool RaiseEventPair(UIElement target, RoutedEvent previewEvent, RoutedEvent @event, RoutedEventArgs args)
{
if (target == null)

14
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLine.cs

@ -113,6 +113,16 @@ namespace ICSharpCode.AvalonEdit.Document @@ -113,6 +113,16 @@ namespace ICSharpCode.AvalonEdit.Document
return DocumentLineTree.GetOffsetFromNode(this);
}
}
/// <summary>
/// Gets the end offset of the line in the document's text (the offset before the newline character).
/// Runtime: O(log n)
/// </summary>
/// <exception cref="InvalidOperationException">The line was deleted.</exception>
/// <remarks>EndOffset = <see cref="Offset"/> + <see cref="Length"/>.</remarks>
public int EndOffset {
get { return this.Offset + this.Length; }
}
#endregion
#region Length
@ -131,10 +141,6 @@ namespace ICSharpCode.AvalonEdit.Document @@ -131,10 +141,6 @@ namespace ICSharpCode.AvalonEdit.Document
}
}
int ISegment.EndOffset {
get { return this.Offset + this.Length; }
}
/// <summary>
/// Gets the length of this line, including the line delimiter. O(1)
/// </summary>

33
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs

@ -81,10 +81,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -81,10 +81,6 @@ namespace ICSharpCode.AvalonEdit.Gui
#endregion
#region Tab
// TODO: make these per-textarea options
const string indentationString = "\t";
const int tabSize = 4;
static void OnTab(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
@ -96,12 +92,13 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -96,12 +92,13 @@ namespace ICSharpCode.AvalonEdit.Gui
while (true) {
int offset = start.Offset;
if (textArea.ReadOnlySectionProvider.CanInsert(offset))
textArea.Document.Insert(offset, indentationString);
textArea.Document.Insert(offset, textArea.Options.IndentationString);
if (start == end)
break;
start = start.NextLine;
}
} else {
string indentationString = textArea.Options.GetIndentationString(textArea.Caret.Position.VisualColumn);
textArea.ReplaceSelectionWithText(indentationString);
}
}
@ -124,7 +121,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -124,7 +121,7 @@ namespace ICSharpCode.AvalonEdit.Gui
}
while (true) {
int offset = start.Offset;
ISegment s = GetFirstIndentationSegment(textArea.Document, offset);
ISegment s = TextUtilities.GetIndentationSegment(textArea.Document, offset, textArea.Options.IndentationSize);
if (s.Length > 0) {
s = textArea.ReadOnlySectionProvider.GetDeletableSegments(s).FirstOrDefault();
if (s != null && s.Length > 0) {
@ -140,28 +137,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -140,28 +137,6 @@ namespace ICSharpCode.AvalonEdit.Gui
args.Handled = true;
}
}
static ISegment GetFirstIndentationSegment(TextDocument document, int offset)
{
int pos = offset;
while (pos < document.TextLength) {
char c = document.GetCharAt(pos);
if (c == '\t') {
if (pos == offset)
return new SimpleSegment(offset, 1);
else
break;
} else if (c == ' ') {
if (pos - offset >= tabSize)
break;
} else {
break;
}
// continue only if c==' ' and (pos-offset)<tabSize
pos++;
}
return new SimpleSegment(offset, pos - offset);
}
#endregion
#region Delete
@ -221,7 +196,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -221,7 +196,7 @@ namespace ICSharpCode.AvalonEdit.Gui
DataObject data = new DataObject(NewLineFinder.NormalizeNewLines(text, Environment.NewLine));
// Also copy text in HTML format to clipboard - good for pasting text into Word
// or to the SharpDevelop forums.
HtmlClipboard.SetHtml(data, HtmlClipboard.CreateHtmlFragmentForSelection(textArea));
HtmlClipboard.SetHtml(data, HtmlClipboard.CreateHtmlFragmentForSelection(textArea, new HtmlOptions(textArea.Options)));
Clipboard.SetDataObject(data, true);
}

8
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/LineNumberMargin.cs

@ -110,7 +110,8 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -110,7 +110,8 @@ namespace ICSharpCode.AvalonEdit.Gui
OnDocumentLineCountChanged();
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
/// <inheritdoc/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextDocumentWeakEventManager.LineCountChanged)) {
OnDocumentLineCountChanged();
@ -119,6 +120,11 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -119,6 +120,11 @@ namespace ICSharpCode.AvalonEdit.Gui
return false;
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return ReceiveWeakEvent(managerType, sender, e);
}
int maxLineNumberLength = 1;
void OnDocumentLineCountChanged()

7
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/Selection.cs

@ -234,10 +234,16 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -234,10 +234,16 @@ namespace ICSharpCode.AvalonEdit.Gui
get { return startOffset == endOffset; }
}
// For segments, Offset must be less than or equal to EndOffset;
// so we must use Min/Max.
int ISegment.Offset {
get { return Math.Min(startOffset, endOffset); }
}
int ISegment.EndOffset {
get { return Math.Max(startOffset, endOffset); }
}
/// <inheritdoc/>
public override int Length {
get {
@ -248,6 +254,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -248,6 +254,7 @@ namespace ICSharpCode.AvalonEdit.Gui
/// <inheritdoc/>
public override Selection SetEndpoint(int newEndOffset)
{
// the empty SimpleSelection must be immutable (it's used for the static Selection.Empty field)
if (IsEmpty)
throw new NotSupportedException();
else

5
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs

@ -244,7 +244,8 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -244,7 +244,8 @@ namespace ICSharpCode.AvalonEdit.Gui
// (but dragging to Word works in both cases)
// also copy as HTML - adds syntax highlighting when dragging to Word
HtmlClipboard.SetHtml(dataObject, HtmlClipboard.CreateHtmlFragmentForSelection(textArea));
string htmlFragment = HtmlClipboard.CreateHtmlFragmentForSelection(textArea, new HtmlOptions(textArea.Options));
HtmlClipboard.SetHtml(dataObject, htmlFragment);
DragDropEffects allowedEffects = DragDropEffects.All;
var deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList();
@ -466,7 +467,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -466,7 +467,7 @@ namespace ICSharpCode.AvalonEdit.Gui
int visualColumn;
int offset = GetOffsetFromMousePosition(e, out visualColumn);
if (allowedSegment != null) {
offset = Math.Max(allowedSegment.Offset, Math.Min(allowedSegment.EndOffset, offset));
offset = offset.CoerceValue(allowedSegment.Offset, allowedSegment.EndOffset);
}
if (offset >= 0) {
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn);

149
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/WhitespaceElementGenerator.cs → src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SingleCharacterElementGenerator.cs

@ -5,23 +5,22 @@ @@ -5,23 +5,22 @@
// <version>$Revision$</version>
// </file>
using ICSharpCode.AvalonEdit.Utils;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Document;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Gui
{
/// <summary>
/// Element generator that displays · for spaces and » for tabs.
/// Element generator that displays · for spaces and » for tabs and a box for control characeters.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace")]
public class WhitespaceElementGenerator : VisualLineElementGenerator
public class SingleCharacterElementGenerator : VisualLineElementGenerator, IWeakEventListener
{
readonly static char[] tabSpace = { ' ', '\t' };
/// <summary>
/// Gets/Sets whether to show · for spaces.
/// </summary>
@ -32,43 +31,87 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -32,43 +31,87 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary>
public bool ShowTabs { get; set; }
/// <summary>
/// Gets/Sets whether to show a box with the hex code for control characters.
/// </summary>
public bool ShowBoxForControlCharacters { get; set; }
/// <summary>
/// Creates a new WhitespaceElementGenerator instance.
/// </summary>
public WhitespaceElementGenerator()
public SingleCharacterElementGenerator()
{
this.ShowSpaces = true;
this.ShowTabs = true;
this.ShowBoxForControlCharacters = true;
}
/// <summary>
/// Fetch options from the text editor and synchronize with future option changes.
/// </summary>
public void SynchronizeOptions(ITextEditorComponent textEditor)
{
if (textEditor == null)
throw new ArgumentNullException("textEditor");
FetchOptions(textEditor.Options);
TextEditorWeakEventManager.OptionChanged.AddListener(textEditor, this);
}
/// <inheritdoc/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextEditorWeakEventManager.OptionChanged)) {
ITextEditorComponent component = (ITextEditorComponent)sender;
FetchOptions(component.Options);
return true;
}
return false;
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return ReceiveWeakEvent(managerType, sender, e);
}
void FetchOptions(TextEditorOptions options)
{
this.ShowSpaces = options.ShowSpaces;
this.ShowTabs = options.ShowTabs;
this.ShowBoxForControlCharacters = options.ShowBoxForControlCharacters;
}
/// <inheritdoc/>
public override int GetFirstInterestedOffset(int startOffset)
{
DocumentLine endLine = CurrentContext.VisualLine.LastDocumentLine;
int endOffset = endLine.Offset + endLine.Length;
string relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset);
int pos;
if (ShowTabs && ShowSpaces)
pos = relevantText.IndexOfAny(tabSpace);
else if (ShowTabs)
pos = relevantText.IndexOf('\t');
else if (ShowSpaces)
pos = relevantText.IndexOf(' ');
else
pos = -1;
if (pos >= 0)
return startOffset + pos;
else
return -1;
string relevantText = CurrentContext.Document.GetText(startOffset, endLine.EndOffset - startOffset);
for (int i = 0; i < relevantText.Length; i++) {
char c = relevantText[i];
switch (c) {
case ' ':
if (ShowSpaces)
return startOffset + i;
break;
case '\t':
if (ShowTabs)
return startOffset + i;
break;
default:
if (ShowBoxForControlCharacters && char.IsControl(c)) {
return startOffset + i;
}
break;
}
}
return -1;
}
/// <inheritdoc/>
public override VisualLineElement ConstructElement(int offset)
{
char c = CurrentContext.Document.GetCharAt(offset);
if (c == ' ') {
if (ShowSpaces && c == ' ') {
FormattedText text = new FormattedText(
"\u00B7",
CurrentContext.GlobalTextRunProperties.CultureInfo,
@ -78,7 +121,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -78,7 +121,7 @@ namespace ICSharpCode.AvalonEdit.Gui
Brushes.LightGray
);
return new SpaceTextElement(text);
} else if (c == '\t') {
} else if (ShowTabs && c == '\t') {
FormattedText text = new FormattedText(
"\u00BB",
CurrentContext.GlobalTextRunProperties.CultureInfo,
@ -88,6 +131,16 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -88,6 +131,16 @@ namespace ICSharpCode.AvalonEdit.Gui
Brushes.LightGray
);
return new TabTextElement(text);
} else if (ShowBoxForControlCharacters && char.IsControl(c)) {
FormattedText text = new FormattedText(
TextUtilities.GetControlCharacterName(c),
CurrentContext.GlobalTextRunProperties.CultureInfo,
FlowDirection.LeftToRight,
CurrentContext.GlobalTextRunProperties.Typeface,
CurrentContext.GlobalTextRunProperties.FontRenderingEmSize * 0.9,
Brushes.White
);
return new SpecialCharacterBoxElement(text);
} else {
return null;
}
@ -195,5 +248,49 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -195,5 +248,49 @@ namespace ICSharpCode.AvalonEdit.Gui
drawingContext.DrawText(element.text, origin);
}
}
sealed class SpecialCharacterBoxElement : FormattedTextElement
{
public SpecialCharacterBoxElement(FormattedText text) : base(text, 1)
{
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{
return new SpecialCharacterTextRun(this, this.TextRunProperties);
}
}
sealed class SpecialCharacterTextRun : FormattedTextRun
{
public SpecialCharacterTextRun(FormattedTextElement element, TextRunProperties properties)
: base(element, properties)
{
}
public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways)
{
Point newOrigin = new Point(origin.X + 1, origin.Y);
Rect r = base.ComputeBoundingBox(rightToLeft, sideways);
r.Offset(newOrigin.X, newOrigin.Y - this.Element.Text.Baseline);
r.Width += 1;
drawingContext.DrawRoundedRectangle(Brushes.DarkGray, null, r, 2.5, 2.5);
base.Draw(drawingContext, newOrigin, rightToLeft, sideways);
}
public override TextEmbeddedObjectMetrics Format(double remainingParagraphWidth)
{
TextEmbeddedObjectMetrics metrics = base.Format(remainingParagraphWidth);
return new TextEmbeddedObjectMetrics(metrics.Width + 3,
metrics.Height, metrics.Baseline);
}
public override Rect ComputeBoundingBox(bool rightToLeft, bool sideways)
{
Rect r = base.ComputeBoundingBox(rightToLeft, sideways);
r.Width += 3;
return r;
}
}
}
}

72
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs

@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
@ -27,7 +28,7 @@ namespace ICSharpCode.AvalonEdit @@ -27,7 +28,7 @@ namespace ICSharpCode.AvalonEdit
/// <summary>
/// Control that wraps a TextView and adds support for user input and the caret.
/// </summary>
public class TextArea : Control, IScrollInfo, IWeakEventListener, IServiceProvider
public class TextArea : Control, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider
{
#region Constructor
static TextArea()
@ -59,7 +60,9 @@ namespace ICSharpCode.AvalonEdit @@ -59,7 +60,9 @@ namespace ICSharpCode.AvalonEdit
if (textView == null)
throw new ArgumentNullException("textView");
this.textView = textView;
this.Options = textView.Options;
textView.SetBinding(TextView.DocumentProperty, new Binding("Document") { Source = this });
textView.SetBinding(TextView.OptionsProperty, new Binding("Options") { Source = this });
leftMargins.Add(new LineNumberMargin { TextView = textView, TextArea = this } );
leftMargins.Add(new Line {
@ -132,6 +135,9 @@ namespace ICSharpCode.AvalonEdit @@ -132,6 +135,9 @@ namespace ICSharpCode.AvalonEdit
set { SetValue(DocumentProperty, value); }
}
/// <inheritdoc/>
public event EventHandler DocumentChanged;
static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
((TextArea)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue);
@ -149,12 +155,62 @@ namespace ICSharpCode.AvalonEdit @@ -149,12 +155,62 @@ namespace ICSharpCode.AvalonEdit
TextDocumentWeakEventManager.Changed.AddListener(newValue, this);
TextDocumentWeakEventManager.UpdateStarted.AddListener(newValue, this);
}
if (DocumentChanged != null)
DocumentChanged(this, EventArgs.Empty);
CommandManager.InvalidateRequerySuggested();
}
#endregion
#region Caret handling on document changes
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
#region Options property
/// <summary>
/// Options property.
/// </summary>
public static readonly DependencyProperty OptionsProperty
= TextEditor.OptionsProperty.AddOwner(typeof(TextArea), new FrameworkPropertyMetadata(OnOptionsChanged));
/// <summary>
/// Gets/Sets the document displayed by the text editor.
/// </summary>
public TextEditorOptions Options {
get { return (TextEditorOptions)GetValue(OptionsProperty); }
set { SetValue(OptionsProperty, value); }
}
/// <summary>
/// Occurs when a text editor option has changed.
/// </summary>
public event PropertyChangedEventHandler OptionChanged;
/// <summary>
/// Raises the <see cref="OptionChanged"/> event.
/// </summary>
protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
{
if (OptionChanged != null) {
OptionChanged(this, e);
}
}
static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
((TextArea)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
}
void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
{
if (oldValue != null) {
PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
}
if (newValue != null) {
PropertyChangedWeakEventManager.AddListener(newValue, this);
}
OnOptionChanged(new PropertyChangedEventArgs(null));
}
#endregion
#region ReceiveWeakEvent
/// <inheritdoc/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {
caret.OnDocumentChanging();
@ -165,10 +221,20 @@ namespace ICSharpCode.AvalonEdit @@ -165,10 +221,20 @@ namespace ICSharpCode.AvalonEdit
} else if (managerType == typeof(TextDocumentWeakEventManager.UpdateStarted)) {
OnUpdateStarted();
return true;
} else if (managerType == typeof(PropertyChangedWeakEventManager)) {
OnOptionChanged((PropertyChangedEventArgs)e);
return true;
}
return false;
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return ReceiveWeakEvent(managerType, sender, e);
}
#endregion
#region Caret handling on document changes
void OnDocumentChanged(DocumentChangeEventArgs e)
{
caret.OnDocumentChanged(e);

70
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs

@ -30,7 +30,7 @@ namespace ICSharpCode.AvalonEdit @@ -30,7 +30,7 @@ namespace ICSharpCode.AvalonEdit
/// Contains a scrollable TextArea.
/// </summary>
[Localizability(LocalizationCategory.Text), ContentProperty("Text")]
public class TextEditor : Control
public class TextEditor : Control, ITextEditorComponent, IWeakEventListener
{
static TextEditor()
{
@ -53,9 +53,12 @@ namespace ICSharpCode.AvalonEdit @@ -53,9 +53,12 @@ namespace ICSharpCode.AvalonEdit
if (textArea == null)
throw new ArgumentNullException("textArea");
this.textArea = textArea;
this.Options = textArea.Options;
textArea.SetBinding(TextArea.DocumentProperty, new Binding("Document") { Source = this });
textArea.SetBinding(TextArea.OptionsProperty, new Binding("Options") { Source = this });
}
#region Document property
/// <summary>
/// Document property.
/// </summary>
@ -93,6 +96,71 @@ namespace ICSharpCode.AvalonEdit @@ -93,6 +96,71 @@ namespace ICSharpCode.AvalonEdit
if (DocumentChanged != null)
DocumentChanged(this, EventArgs.Empty);
}
#endregion
#region Options property
/// <summary>
/// Options property.
/// </summary>
public static readonly DependencyProperty OptionsProperty =
DependencyProperty.Register("Options", typeof(TextEditorOptions), typeof(TextEditor),
new FrameworkPropertyMetadata(OnOptionsChanged));
/// <summary>
/// Gets/Sets the options currently used by the text editor.
/// </summary>
public TextEditorOptions Options {
get { return (TextEditorOptions)GetValue(OptionsProperty); }
set { SetValue(OptionsProperty, value); }
}
/// <summary>
/// Occurs when a text editor option has changed.
/// </summary>
public event PropertyChangedEventHandler OptionChanged;
/// <summary>
/// Raises the <see cref="OptionChanged"/> event.
/// </summary>
protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
{
if (OptionChanged != null) {
OptionChanged(this, e);
}
}
static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
((TextEditor)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
}
void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
{
if (oldValue != null) {
PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
}
if (newValue != null) {
PropertyChangedWeakEventManager.AddListener(newValue, this);
}
OnOptionChanged(new PropertyChangedEventArgs(null));
}
/// <inheritdoc/>
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(PropertyChangedWeakEventManager)) {
OnOptionChanged((PropertyChangedEventArgs)e);
return true;
}
return false;
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return ReceiveWeakEvent(managerType, sender, e);
}
#endregion
/// <summary>
/// Gets/Sets the text of the current document.

29
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditorWeakEventManager.cs

@ -11,27 +11,46 @@ using System; @@ -11,27 +11,46 @@ using System;
namespace ICSharpCode.AvalonEdit
{
/// <summary>
/// Contains weak event managers for the TexEditor events.
/// Contains weak event managers for <see cref="ITextEditorComponent"/>.
/// </summary>
public static class TextEditorWeakEventManager
{
/// <summary>
/// Weak event manager for the <see cref="TextEditor.DocumentChanged"/> event.
/// Weak event manager for the <see cref="ITextEditorComponent.DocumentChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class DocumentChanged : WeakEventManagerBase<DocumentChanged, TextEditor>
public sealed class DocumentChanged : WeakEventManagerBase<DocumentChanged, ITextEditorComponent>
{
/// <inheritdoc/>
protected override void StartListening(TextEditor source)
protected override void StartListening(ITextEditorComponent source)
{
source.DocumentChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(TextEditor source)
protected override void StopListening(ITextEditorComponent source)
{
source.DocumentChanged -= DeliverEvent;
}
}
/// <summary>
/// Weak event manager for the <see cref="ITextEditorComponent.OptionChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class OptionChanged : WeakEventManagerBase<OptionChanged, ITextEditorComponent>
{
/// <inheritdoc/>
protected override void StartListening(ITextEditorComponent source)
{
source.OptionChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(ITextEditorComponent source)
{
source.OptionChanged -= DeliverEvent;
}
}
}
}

69
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs

@ -36,7 +36,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -36,7 +36,7 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
Justification = "The user usually doesn't work with TextView but with TextEditor; and nulling the Document property is sufficient to dispose everything.")]
public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener, IServiceProvider
public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider
{
#region Constructor
static TextView()
@ -49,10 +49,16 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -49,10 +49,16 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary>
public TextView()
{
this.Options = new TextEditorOptions();
textLayer = new TextLayer(this);
elementGenerators = new ObserveAddRemoveCollection<VisualLineElementGenerator>(ElementGenerator_Added, ElementGenerator_Removed);
lineTransformers = new ObserveAddRemoveCollection<IVisualLineTransformer>(LineTransformer_Added, LineTransformer_Removed);
backgroundRenderers = new ObserveAddRemoveCollection<IBackgroundRenderer>(BackgroundRenderer_Added, BackgroundRenderer_Removed);
SingleCharacterElementGenerator sceg = new SingleCharacterElementGenerator();
sceg.SynchronizeOptions(this);
elementGenerators.Add(sceg);
layers = new UIElementCollection(this, this);
InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace);
}
@ -115,16 +121,73 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -115,16 +121,73 @@ namespace ICSharpCode.AvalonEdit.Gui
DocumentChanged(this, EventArgs.Empty);
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
/// <inheritdoc/>
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {
// put redraw into background so that other input events can be handled before the redraw
DocumentChangeEventArgs change = (DocumentChangeEventArgs)e;
Redraw(change.Offset, change.RemovalLength, DispatcherPriority.Background);
return true;
} else if (managerType == typeof(PropertyChangedWeakEventManager)) {
OnOptionChanged((PropertyChangedEventArgs)e);
return true;
}
return false;
}
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return ReceiveWeakEvent(managerType, sender, e);
}
#endregion
#region Options property
/// <summary>
/// Options property.
/// </summary>
public static readonly DependencyProperty OptionsProperty
= TextEditor.OptionsProperty.AddOwner(typeof(TextView), new FrameworkPropertyMetadata(OnOptionsChanged));
/// <summary>
/// Gets/Sets the document displayed by the text editor.
/// </summary>
public TextEditorOptions Options {
get { return (TextEditorOptions)GetValue(OptionsProperty); }
set { SetValue(OptionsProperty, value); }
}
/// <summary>
/// Occurs when a text editor option has changed.
/// </summary>
public event PropertyChangedEventHandler OptionChanged;
/// <summary>
/// Raises the <see cref="OptionChanged"/> event.
/// </summary>
protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
{
if (OptionChanged != null) {
OptionChanged(this, e);
}
Redraw();
}
static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
((TextView)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
}
void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
{
if (oldValue != null) {
PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
}
if (newValue != null) {
PropertyChangedWeakEventManager.AddListener(newValue, this);
}
OnOptionChanged(new PropertyChangedEventArgs(null));
}
#endregion
#region ElementGenerators+LineTransformers Properties
@ -602,7 +665,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -602,7 +665,7 @@ namespace ICSharpCode.AvalonEdit.Gui
return new VisualLineTextParagraphProperties {
defaultTextRunProperties = defaultTextRunProperties,
textWrapping = canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
tabSize = 4 * WideSpaceWidth
tabSize = Options.IndentationSize * WideSpaceWidth
};
}

14
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs

@ -81,17 +81,19 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -81,17 +81,19 @@ namespace ICSharpCode.AvalonEdit.Highlighting
/// <summary>
/// Produces HTML code for the line, with &lt;span class="colorName"&gt; tags.
/// </summary>
public string ToHtml()
public string ToHtml(HtmlOptions options)
{
int startOffset = this.DocumentLine.Offset;
return ToHtml(startOffset, startOffset + this.DocumentLine.Length);
return ToHtml(startOffset, startOffset + this.DocumentLine.Length, options);
}
/// <summary>
/// Produces HTML code for a section of the line, with &lt;span class="colorName"&gt; tags.
/// </summary>
public string ToHtml(int startOffset, int endOffset)
public string ToHtml(int startOffset, int endOffset, HtmlOptions options)
{
if (options == null)
throw new ArgumentNullException("options");
int documentLineStartOffset = this.DocumentLine.Offset;
int documentLineEndOffset = documentLineStartOffset + this.DocumentLine.Length;
if (startOffset < documentLineStartOffset || startOffset > documentLineEndOffset)
@ -116,7 +118,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -116,7 +118,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
foreach (HtmlElement e in elements) {
int newOffset = Math.Min(e.Offset, endOffset);
if (newOffset > startOffset) {
HtmlClipboard.EscapeHtml(b, document.GetText(textOffset, newOffset - textOffset));
HtmlClipboard.EscapeHtml(b, document.GetText(textOffset, newOffset - textOffset), options);
}
textOffset = newOffset;
if (e.IsEnd) {
@ -127,14 +129,14 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -127,14 +129,14 @@ namespace ICSharpCode.AvalonEdit.Highlighting
b.Append("\">");
}
}
HtmlClipboard.EscapeHtml(b, document.GetText(textOffset, endOffset - textOffset));
HtmlClipboard.EscapeHtml(b, document.GetText(textOffset, endOffset - textOffset), options);
return b.ToString();
}
/// <inheritdoc/>
public override string ToString()
{
return "[" + GetType().Name + " " + ToHtml() + "]";
return "[" + GetType().Name + " " + ToHtml(new HtmlOptions()) + "]";
}
}
}

12
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
<DebugType>Full</DebugType>
<Optimize>False</Optimize>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<DefineConstants>DEBUG;TRACE;DATACONSISTENCYTEST</DefineConstants>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>false</DebugSymbols>
@ -159,7 +159,9 @@ @@ -159,7 +159,9 @@
<Compile Include="Gui\SelectionMouseHandler.cs">
<DependentUpon>Selection.cs</DependentUpon>
</Compile>
<Compile Include="Gui\TextEditorWeakEventManager.cs" />
<Compile Include="Gui\TextEditorWeakEventManager.cs">
<DependentUpon>TextEditor.cs</DependentUpon>
</Compile>
<Compile Include="Gui\TextLayer.cs">
<DependentUpon>TextView.cs</DependentUpon>
</Compile>
@ -225,7 +227,7 @@ @@ -225,7 +227,7 @@
<Compile Include="Gui\VisualYPosition.cs">
<DependentUpon>VisualLine.cs</DependentUpon>
</Compile>
<Compile Include="Gui\WhitespaceElementGenerator.cs" />
<Compile Include="Gui\SingleCharacterElementGenerator.cs" />
<Compile Include="Highlighting\DocumentHighlighter.cs" />
<Compile Include="Highlighting\HighlightedLine.cs" />
<Compile Include="Highlighting\HighlightedSection.cs" />
@ -256,6 +258,8 @@ @@ -256,6 +258,8 @@
<Compile Include="Highlighting\Xshd\XshdRuleSet.cs" />
<Compile Include="Highlighting\Xshd\XshdSpan.cs" />
<Compile Include="Highlighting\Xshd\XshdSyntaxDefinition.cs" />
<Compile Include="TextEditorComponent.cs">
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Document\DocumentChangeEventArgs.cs" />
<Compile Include="Document\GapTextBuffer.cs">
@ -269,6 +273,7 @@ @@ -269,6 +273,7 @@
<DependentUpon>DocumentLine.cs</DependentUpon>
</Compile>
<Compile Include="Document\TextDocument.cs" />
<Compile Include="TextEditorOptions.cs" />
<Compile Include="Utils\Boxes.cs" />
<Compile Include="Utils\CompressingTreeList.cs" />
<Compile Include="Utils\Constants.cs" />
@ -281,6 +286,7 @@ @@ -281,6 +286,7 @@
<Compile Include="Utils\ImmutableStack.cs" />
<Compile Include="Utils\NullSafeCollection.cs" />
<Compile Include="Utils\ObserveAddRemoveCollection.cs" />
<Compile Include="Utils\PropertyChangedWeakEventManager.cs" />
<Compile Include="Utils\TextUtilities.cs" />
<Compile Include="Utils\WeakEventManagerBase.cs" />
<Compile Include="Utils\PixelSnapHelpers.cs" />

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Properties/CodeAnalysisDictionary.xml

@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
<Word>Utils</Word>
<Word>Colorizer</Word>
<Word>Renderer</Word>
<Word>Renderers</Word>
<Word>Deletable</Word>
<!-- required so that FxCop accepts 'yPositionMode' -->
<Word>y</Word>

43
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorComponent.cs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using ICSharpCode.AvalonEdit.Utils;
using System;
using System.ComponentModel;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit
{
/// <summary>
/// Represents a text editor control (<see cref="TextEditor"/>, <see cref="TextArea"/>
/// or <see cref="Gui.TextView"/>).
/// </summary>
public interface ITextEditorComponent
{
/// <summary>
/// Gets the document being edited.
/// </summary>
TextDocument Document { get; }
/// <summary>
/// Occurs when the Document property changes (when the text editor is connected to another
/// document - not when the document content changes).
/// </summary>
event EventHandler DocumentChanged;
/// <summary>
/// Gets the options of the text editor.
/// </summary>
TextEditorOptions Options { get; }
/// <summary>
/// Occurs when the Options property changes, or when an option inside the current option list
/// changes.
/// </summary>
event PropertyChangedEventHandler OptionChanged;
}
}

163
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs

@ -0,0 +1,163 @@ @@ -0,0 +1,163 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.ComponentModel;
using System.Text;
namespace ICSharpCode.AvalonEdit
{
/// <summary>
/// A container for the text editor options.
/// </summary>
[Serializable]
public class TextEditorOptions : INotifyPropertyChanged
{
#region PropertyChanged handling
/// <inheritdoc/>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Raises the PropertyChanged event.
/// </summary>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) {
PropertyChanged(this, e);
}
}
#endregion
#region ShowSpaces / ShowTabs / ShowBoxForControlCharacters
bool showSpaces;
/// <summary>
/// Gets/Sets whether to show · for spaces.
/// </summary>
/// <remarks>The default value is <c>false</c>.</remarks>
[DefaultValue(false)]
public virtual bool ShowSpaces {
get { return showSpaces; }
set {
if (showSpaces != value) {
showSpaces = value;
OnPropertyChanged("ShowSpaces");
}
}
}
bool showTabs;
/// <summary>
/// Gets/Sets whether to show » for tabs.
/// </summary>
/// <remarks>The default value is <c>false</c>.</remarks>
[DefaultValue(false)]
public virtual bool ShowTabs {
get { return showTabs; }
set {
if (showTabs != value) {
showTabs = value;
OnPropertyChanged("ShowTabs");
}
}
}
bool showBoxForControlCharacters = true;
/// <summary>
/// Gets/Sets whether to show a box with the hex code for control characters.
/// </summary>
/// <remarks>The default value is <c>true</c>.</remarks>
[DefaultValue(true)]
public virtual bool ShowBoxForControlCharacters {
get { return showBoxForControlCharacters; }
set {
if (showBoxForControlCharacters != value) {
showBoxForControlCharacters = value;
OnPropertyChanged("ShowBoxForControlCharacters");
}
}
}
#endregion
#region TabSize / IndentationSize / ConvertTabsToSpaces / GetIndentationString
// I'm using '_' prefixes for the fields here to avoid confusion with the local variables
// in the methods below.
// The fields should be accessed only by their property - the fields might not be used
// if someone overrides the property.
int _indentationSize = 4;
/// <summary>
/// Gets/Sets the width of one indentation unit.
/// </summary>
/// <remarks>The default value is 4.</remarks>
[DefaultValue(4)]
public virtual int IndentationSize {
get { return _indentationSize; }
set {
if (value < 1)
throw new ArgumentOutOfRangeException("value", value, "value must be positive");
if (_indentationSize != value) {
_indentationSize = value;
OnPropertyChanged("IndentationSize");
}
}
}
bool _convertTabsToSpaces;
/// <summary>
/// Gets/Sets whether to use spaces for indentation instead of tabs.
/// </summary>
/// <remarks>The default value is <c>false</c>.</remarks>
[DefaultValue(false)]
public virtual bool ConvertTabsToSpaces {
get { return _convertTabsToSpaces; }
set {
if (_convertTabsToSpaces != value) {
_convertTabsToSpaces = value;
OnPropertyChanged("ConvertTabsToSpaces");
}
}
}
/// <summary>
/// Gets the text used for indentation.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
public string IndentationString {
get { return GetIndentationString(0); }
}
/// <summary>
/// Gets text required to indent from the specified <paramref name="column"/> to the next indentation level.
/// </summary>
public virtual string GetIndentationString(int column)
{
if (column < 0)
throw new ArgumentOutOfRangeException("column", column, "Value must be non-negative.");
int indentationSize = this.IndentationSize;
if (ConvertTabsToSpaces) {
return new string(' ', indentationSize - (column % indentationSize));
} else {
return "\t";
}
}
#endregion
}
}

24
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs

@ -19,7 +19,7 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -19,7 +19,7 @@ namespace ICSharpCode.AvalonEdit.Utils
{
static class ExtensionMethods
{
#region Epsilon / IsClose
#region Epsilon / IsClose / CoerceValue
public const double Epsilon = 1e-8;
/// <summary>
@ -47,6 +47,28 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -47,6 +47,28 @@ namespace ICSharpCode.AvalonEdit.Utils
{
return IsClose(d1.X, d2.X) && IsClose(d1.Y, d2.Y);
}
/// <summary>
/// Forces the value to stay between mininum and maximum.
/// </summary>
/// <returns>minimum, if value is less than minimum.
/// Maximum, if value is greater than maximum.
/// Otherwise, value.</returns>
public static double CoerceValue(this double value, double minimum, double maximum)
{
return Math.Max(Math.Min(value, maximum), minimum);
}
/// <summary>
/// Forces the value to stay between mininum and maximum.
/// </summary>
/// <returns>minimum, if value is less than minimum.
/// Maximum, if value is greater than maximum.
/// Otherwise, value.</returns>
public static int CoerceValue(this int value, int minimum, int maximum)
{
return Math.Max(Math.Min(value, maximum), minimum);
}
#endregion
#region CreateTypeface

48
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/HtmlClipboard.cs

@ -69,11 +69,14 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -69,11 +69,14 @@ namespace ICSharpCode.AvalonEdit.Utils
/// <param name="document">The document to create HTML from.</param>
/// <param name="highlighter">The highlighter used to highlight the document.</param>
/// <param name="segment">The part of the document to create HTML for. You can pass null to create HTML for the whole document.</param>
/// <param name="options">The options for the HTML creation.</param>
/// <returns>HTML code for the document part.</returns>
public static string CreateHtmlFragment(TextDocument document, DocumentHighlighter highlighter, ISegment segment)
public static string CreateHtmlFragment(TextDocument document, DocumentHighlighter highlighter, ISegment segment, HtmlOptions options)
{
if (document == null)
throw new ArgumentNullException("document");
if (options == null)
throw new ArgumentNullException("options");
if (segment == null)
segment = new SimpleSegment(0, document.TextLength);
@ -89,7 +92,7 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -89,7 +92,7 @@ namespace ICSharpCode.AvalonEdit.Utils
SimpleSegment s = segment.GetOverlap(line);
if (html.Length > 0)
html.AppendLine("<br>");
html.Append(highlightedLine.ToHtml(s.Offset, s.EndOffset));
html.Append(highlightedLine.ToHtml(s.Offset, s.EndOffset, options));
line = line.NextLine;
}
return html.ToString();
@ -98,14 +101,16 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -98,14 +101,16 @@ namespace ICSharpCode.AvalonEdit.Utils
/// <summary>
/// Creates a HTML fragment for the selected part of the document.
/// </summary>
public static string CreateHtmlFragmentForSelection(TextArea textArea)
public static string CreateHtmlFragmentForSelection(TextArea textArea, HtmlOptions options)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
if (options == null)
throw new ArgumentNullException("options");
DocumentHighlighter highlighter = textArea.GetService(typeof(DocumentHighlighter)) as DocumentHighlighter;
StringBuilder html = new StringBuilder();
foreach (ISegment selectedSegment in textArea.Selection.Segments) {
html.AppendLine(CreateHtmlFragment(textArea.Document, highlighter, selectedSegment));
html.AppendLine(CreateHtmlFragment(textArea.Document, highlighter, selectedSegment, options));
}
return html.ToString();
}
@ -113,7 +118,7 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -113,7 +118,7 @@ namespace ICSharpCode.AvalonEdit.Utils
/// <summary>
/// Escapes text and writes the result to the StringBuilder.
/// </summary>
internal static void EscapeHtml(StringBuilder b, string text)
internal static void EscapeHtml(StringBuilder b, string text, HtmlOptions options)
{
int spaceCount = -1;
foreach (char c in text) {
@ -125,8 +130,7 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -125,8 +130,7 @@ namespace ICSharpCode.AvalonEdit.Utils
} else if (c == '\t') {
if (spaceCount < 0)
spaceCount = 0;
// TODO: use tab width setting
spaceCount += 4;
spaceCount += options.TabSize;
} else {
if (spaceCount == 1) {
b.Append(' ');
@ -160,4 +164,34 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -160,4 +164,34 @@ namespace ICSharpCode.AvalonEdit.Utils
}
}
}
/// <summary>
/// Holds options for converting text to HTML.
/// </summary>
public class HtmlOptions
{
/// <summary>
/// Creates a default HtmlOptions instance.
/// </summary>
public HtmlOptions()
{
this.TabSize = 4;
}
/// <summary>
/// Creates a new HtmlOptions instance that copies applicable options from the <see cref="TextEditorOptions"/>.
/// </summary>
public HtmlOptions(TextEditorOptions options)
: this()
{
if (options == null)
throw new ArgumentNullException("options");
this.TabSize = options.IndentationSize;
}
/// <summary>
/// The amount of spaces a tab gets converted to.
/// </summary>
public int TabSize { get; set; }
}
}

30
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/PropertyChangedWeakEventManager.cs

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.ComponentModel;
namespace ICSharpCode.AvalonEdit.Utils
{
/// <summary>
/// WeakEventManager for INotifyPropertyChanged.PropertyChanged.
/// </summary>
public sealed class PropertyChangedWeakEventManager : WeakEventManagerBase<PropertyChangedWeakEventManager, INotifyPropertyChanged>
{
/// <inheritdoc/>
protected override void StartListening(INotifyPropertyChanged source)
{
source.PropertyChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListening(INotifyPropertyChanged source)
{
source.PropertyChanged -= DeliverEvent;
}
}
}

82
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs

@ -6,9 +6,10 @@ @@ -6,9 +6,10 @@
// </file>
using System;
using System.ComponentModel;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Gui;
using System.ComponentModel;
using System.Globalization;
namespace ICSharpCode.AvalonEdit.Utils
{
@ -17,9 +18,81 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -17,9 +18,81 @@ namespace ICSharpCode.AvalonEdit.Utils
/// </summary>
public static class TextUtilities
{
#region GetControlCharacterName
// the names of the first 32 ASCII characters = Unicode C0 block
static readonly string[] c0Table = {
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT",
"LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3",
"DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS",
"RS", "US"
};
// the names of the control characters in the C1 block (Unicode 128 to 159)
static readonly string[] c1Table = {
"PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA", "HTS", "HTJ",
"VTS", "PLD", "PLU", "RI", "SS2", "SS3", "DCS", "PU1", "PU2", "STS",
"CCH", "MW", "SPA", "EPA", "SOS", "SGCI", "SCI", "CSI", "ST", "OSC",
"PM", "APC"
};
/// <summary>
/// Gets the name of the control character.
/// For unknown characters, the unicode codepoint is returned as 4-digit hexadecimal value.
/// </summary>
public static string GetControlCharacterName(char controlCharacter)
{
int num = (int)controlCharacter;
if (num < c0Table.Length)
return c0Table[num];
else if (num == 127)
return "DEL";
else if (num >= 128 && num < 128 + c1Table.Length)
return c1Table[num - 128];
else
return num.ToString("x4", CultureInfo.InvariantCulture);
}
#endregion
#region GetFirstIndentationSegment
/// <summary>
/// Gets the indentation segment starting at <paramref name="offset"/>.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The offset where the indentation segment starts.</param>
/// <param name="indentationSize">The size of an indentation unit. See <see cref="TextEditorOptions.IndentationSize"/>.</param>
/// <returns>The indentation segment.
/// If there is no indentation character at the specified <paramref name="offset"/>,
/// an empty segment is returned.</returns>
public static ISegment GetIndentationSegment(ITextSource textSource, int offset, int indentationSize)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
int pos = offset;
while (pos < textSource.TextLength) {
char c = textSource.GetCharAt(pos);
if (c == '\t') {
if (pos == offset)
return new SimpleSegment(offset, 1);
else
break;
} else if (c == ' ') {
if (pos - offset >= indentationSize)
break;
} else {
break;
}
// continue only if c==' ' and (pos-offset)<tabSize
pos++;
}
return new SimpleSegment(offset, pos - offset);
}
#endregion
#region GetCharacterClass
/// <summary>
/// Gets whether the character is whitespace, part of an identifier, or line terminator.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "c")]
public static CharacterClass GetCharacterClass(char c)
{
if (c == '\r' || c == '\n')
@ -31,7 +104,9 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -31,7 +104,9 @@ namespace ICSharpCode.AvalonEdit.Utils
else
return CharacterClass.Other;
}
#endregion
#region GetNextCaretPosition
/// <summary>
/// Gets the next caret position.
/// </summary>
@ -116,6 +191,7 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -116,6 +191,7 @@ namespace ICSharpCode.AvalonEdit.Utils
offset = nextPos;
}
}
#endregion
}
/// <summary>
@ -130,9 +206,11 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -130,9 +206,11 @@ namespace ICSharpCode.AvalonEdit.Utils
/// <summary>
/// The character is whitespace (but not line terminator).
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
Whitespace,
/// <summary>
/// The character can be part of an identifier (LetterOrDigit or underscore).
/// The character can be part of an identifier (Letter, digit or underscore).
/// </summary>
IdentifierPart,
/// <summary>

95
src/Main/Base/Project/Src/TextEditor/CodeCompletionItemProvider.cs

@ -6,10 +6,11 @@ @@ -6,10 +6,11 @@
// </file>
using System;
using System.Collections;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Dom.Refactoring;
using System.Collections;
using System.Collections.Generic;
namespace ICSharpCode.SharpDevelop
{
@ -82,18 +83,61 @@ namespace ICSharpCode.SharpDevelop @@ -82,18 +83,61 @@ namespace ICSharpCode.SharpDevelop
return null;
IProjectContent callingContent = rr.CallingClass != null ? rr.CallingClass.ProjectContent : null;
ArrayList arr = rr.GetCompletionData(callingContent ?? ParserService.CurrentProjectContent);
DefaultCompletionItemList result = new DefaultCompletionItemList();
return GenerateCompletionListForCompletionData(arr, context);
}
public virtual ICompletionItemList GenerateCompletionListForCompletionData(ArrayList arr, ExpressionContext context)
{
if (arr == null)
return null;
List<ICompletionItem> resultItems = new List<ICompletionItem>();
DefaultCompletionItemList result = new DefaultCompletionItemList(resultItems);
Dictionary<string, CodeCompletionItem> methodItems = new Dictionary<string, CodeCompletionItem>();
foreach (object o in arr) {
IEntity entity = o as IEntity;
if (entity != null)
result.Items.Add(new CodeCompletionItem(entity));
if (context != null && !context.ShowEntry(o))
continue;
IMethod method = o as IMethod;
if (method != null) {
CodeCompletionItem codeItem;
if (methodItems.TryGetValue(method.Name, out codeItem)) {
codeItem.Overloads++;
continue;
}
}
ICompletionItem item = CreateCompletionItem(o, context);
if (item != null) {
resultItems.Add(item);
CodeCompletionItem codeItem = item as CodeCompletionItem;
if (method != null && codeItem != null) {
methodItems[method.Name] = codeItem;
}
if (o.Equals(context.SuggestedItem))
result.SuggestedItem = item;
}
}
resultItems.Sort((a,b) => string.Compare(a.Text, b.Text, StringComparison.OrdinalIgnoreCase));
if (context.SuggestedItem != null) {
if (result.SuggestedItem == null) {
result.SuggestedItem = CreateCompletionItem(context.SuggestedItem, context);
if (result.SuggestedItem != null) {
resultItems.Insert(0, result.SuggestedItem);
}
}
}
return result;
}
public virtual ICompletionItem CreateCompletionItem(IEntity entity)
public virtual ICompletionItem CreateCompletionItem(object o, ExpressionContext context)
{
return new CodeCompletionItem(entity);
IEntity entity = o as IEntity;
if (entity != null)
return new CodeCompletionItem(entity);
else
return new DefaultCompletionItem(o.ToString());
}
}
@ -111,18 +155,43 @@ namespace ICSharpCode.SharpDevelop @@ -111,18 +155,43 @@ namespace ICSharpCode.SharpDevelop
if (entity == null)
throw new ArgumentNullException("entity");
this.entity = entity;
IAmbience ambience = AmbienceService.GetCurrentAmbience();
ambience.ConversionFlags = entity is IClass ? ConversionFlags.ShowTypeParameterList : ConversionFlags.None;
this.Text = ambience.Convert(entity);
ambience.ConversionFlags = ConversionFlags.StandardConversionFlags;
description = ambience.Convert(entity);
this.Overloads = 1;
}
public string Text {
get {
return entity.Name;
}
}
public string Text { get; private set; }
public int Overloads { get; set; }
#region Description
string description;
bool descriptionCreated;
public string Description {
get {
return entity.Documentation;
lock (this) {
if (!descriptionCreated) {
descriptionCreated = true;
if (Overloads > 1) {
description += Environment.NewLine +
StringParser.Parse("${res:ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.CodeCompletionData.OverloadsCounter}", new string[,] {{"NumOverloads", this.Overloads.ToString()}});
}
if (!string.IsNullOrEmpty(entity.Documentation)) {
string documentation = ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.CodeCompletionData.ConvertDocumentation(entity.Documentation);
if (!string.IsNullOrEmpty(documentation)) {
description += Environment.NewLine + documentation;
}
}
}
return description;
}
}
}
#endregion
}
}

11
src/Main/Base/Project/Src/TextEditor/ICompletionItem.cs

@ -17,4 +17,15 @@ namespace ICSharpCode.SharpDevelop @@ -17,4 +17,15 @@ namespace ICSharpCode.SharpDevelop
string Text { get; }
string Description { get; }
}
public class DefaultCompletionItem : ICompletionItem
{
public string Text { get; private set; }
public string Description { get; set; }
public DefaultCompletionItem(string text)
{
this.Text = text;
}
}
}

4
src/Main/Base/Project/Src/TextEditor/ICompletionItemList.cs

@ -17,6 +17,8 @@ namespace ICSharpCode.SharpDevelop @@ -17,6 +17,8 @@ namespace ICSharpCode.SharpDevelop
{
IEnumerable<ICompletionItem> Items { get; }
ICompletionItem SuggestedItem { get; }
/// <summary>
/// Processes the specified key press.
/// </summary>
@ -59,6 +61,8 @@ namespace ICSharpCode.SharpDevelop @@ -59,6 +61,8 @@ namespace ICSharpCode.SharpDevelop
get { return items; }
}
public ICompletionItem SuggestedItem { get; set; }
IEnumerable<ICompletionItem> ICompletionItemList.Items {
get { return items; }
}

Loading…
Cancel
Save