Browse Source

AvalonEdit: Copy text as HTML to clipboard.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3868 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
a3a1260100
  1. 51
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLine.cs
  2. 114
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLineTree.cs
  3. 14
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineManager.cs
  4. 7
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/NewLineFinder.cs
  5. 21
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs
  6. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs
  7. 26
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/ColorizingTransformer.cs
  8. 17
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs
  9. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/FoldingSection.cs
  10. 28
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/ITextViewConnect.cs
  11. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs
  12. 13
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs
  13. 78
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
  14. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs
  15. 38
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs
  16. 22
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingBrush.cs
  17. 9
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColor.cs
  18. 14
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColorizer.cs
  19. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  20. 21
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs
  21. 163
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/HtmlClipboard.cs

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

@ -164,11 +164,52 @@ namespace ICSharpCode.AvalonEdit.Document
} }
#endregion #endregion
#region ParserState #region Previous / Next Line
// /// <summary> /// <summary>
// /// Gets the parser state array associated with this line. /// Gets the next line in the document.
// /// </summary> /// </summary>
// public object[] ParserState { get; internal set; } /// <returns>The line following this line, or null if this is the last line.</returns>
public DocumentLine NextLine {
get {
document.DebugVerifyAccess();
if (right != null) {
return right.LeftMost;
} else {
DocumentLine node = this;
DocumentLine oldNode;
do {
oldNode = node;
node = node.parent;
// we are on the way up from the right part, don't output node again
} while (node != null && node.right == oldNode);
return node;
}
}
}
/// <summary>
/// Gets the previous line in the document.
/// </summary>
/// <returns>The line before this line, or null if this is the first line.</returns>
public DocumentLine PreviousLine {
get {
document.DebugVerifyAccess();
if (left != null) {
return left.RightMost;
} else {
DocumentLine node = this;
DocumentLine oldNode;
do {
oldNode = node;
node = node.parent;
// we are on the way up from the left part, don't output node again
} while (node != null && node.left == oldNode);
return node;
}
}
}
#endregion #endregion
#region ToString #region ToString

114
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentLineTree.cs

@ -1,3 +1,4 @@
// <file> // <file>
// <copyright see="prj:///doc/copyright.txt"/> // <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/> // <license see="prj:///doc/license.txt"/>
@ -210,7 +211,7 @@ namespace ICSharpCode.AvalonEdit.Document
} }
#endregion #endregion
#region GetLineBy / GetEnumeratorFor #region GetLineBy
public DocumentLine GetByNumber(int number) public DocumentLine GetByNumber(int number)
{ {
return GetNodeByIndex(number - 1); return GetNodeByIndex(number - 1);
@ -220,11 +221,6 @@ namespace ICSharpCode.AvalonEdit.Document
{ {
return GetNodeByOffset(offset); return GetNodeByOffset(offset);
} }
public Enumerator GetEnumeratorForOffset(int offset)
{
return new Enumerator(GetNodeByOffset(offset));
}
#endregion #endregion
#region LineCount #region LineCount
@ -625,84 +621,6 @@ namespace ICSharpCode.AvalonEdit.Document
} }
#endregion #endregion
#region Enumerator
internal struct Enumerator : IEnumerator<DocumentLine>
{
LineNode node;
internal Enumerator(LineNode node)
{
this.node = node;
}
/// <summary>
/// Gets the current value. Runs in O(1).
/// </summary>
public DocumentLine Current {
get {
if (node == null)
throw new InvalidOperationException();
return node;
}
}
object System.Collections.IEnumerator.Current {
get {
return this.Current;
}
}
void IDisposable.Dispose()
{
}
/// <summary>
/// Moves to the next index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
/// </summary>
public bool MoveNext()
{
if (node == null)
return false;
if (node.right != null) {
node = node.right.LeftMost;
} else {
LineNode oldNode;
do {
oldNode = node;
node = node.parent;
// we are on the way up from the right part, don't output node again
} while (node != null && node.right == oldNode);
}
return node != null;
}
/// <summary>
/// Moves to the previous index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
/// </summary>
public bool MoveBack()
{
if (node == null)
return false;
if (node.left != null) {
node = node.left.RightMost;
} else {
LineNode oldNode;
do {
oldNode = node;
node = node.parent;
// we are on the way up from the left part, don't output node again
} while (node != null && node.left == oldNode);
}
return node != null;
}
void System.Collections.IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
#endregion
#region IList implementation #region IList implementation
DocumentLine IList<DocumentLine>.this[int index] { DocumentLine IList<DocumentLine>.this[int index] {
get { get {
@ -781,22 +699,18 @@ namespace ICSharpCode.AvalonEdit.Document
public IEnumerator<DocumentLine> GetEnumerator() public IEnumerator<DocumentLine> GetEnumerator()
{ {
document.VerifyAccess(); document.VerifyAccess();
LineNode dummyNode = new LineNode(document); return Enumerate();
dummyNode.right = root; }
return new Enumerator(dummyNode);
} IEnumerator<DocumentLine> Enumerate()
{
// enumerator that verifies thread on each call document.VerifyAccess();
// - this is overkill, checking on the GetEnumerator call should be enough. DocumentLine line = root.LeftMost;
// IEnumerator<DocumentLine> Enumerate() while (line != null) {
// { yield return line;
// document.VerifyAccess(); line = line.NextLine;
// Enumerator e = new Enumerator(tree.GetEnumerator()); }
// while (e.MoveNext()) { }
// yield return e.Current;
// document.DebugVerifyAccess();
// }
// }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{ {

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

@ -113,8 +113,7 @@ namespace ICSharpCode.AvalonEdit.Document
{ {
Debug.Assert(length >= 0); Debug.Assert(length >= 0);
if (length == 0) return; if (length == 0) return;
DocumentLineTree.Enumerator it = documentLineTree.GetEnumeratorForOffset(offset); DocumentLine startLine = documentLineTree.GetByOffset(offset);
DocumentLine startLine = it.Current;
int startLineOffset = startLine.Offset; int startLineOffset = startLine.Offset;
Debug.Assert(offset < startLineOffset + startLine.TotalLength); Debug.Assert(offset < startLineOffset + startLine.TotalLength);
@ -155,11 +154,11 @@ namespace ICSharpCode.AvalonEdit.Document
//startLine.MergedWith(endLine, offset - startLineOffset); //startLine.MergedWith(endLine, offset - startLineOffset);
// remove all lines between startLine (excl.) and endLine (incl.) // remove all lines between startLine (excl.) and endLine (incl.)
it.MoveNext(); DocumentLine tmp = startLine.NextLine;
DocumentLine lineToRemove; DocumentLine lineToRemove;
do { do {
lineToRemove = it.Current; lineToRemove = tmp;
it.MoveNext(); tmp = tmp.NextLine;
RemoveLine(lineToRemove); RemoveLine(lineToRemove);
} while (lineToRemove != endLine); } while (lineToRemove != endLine);
@ -270,10 +269,9 @@ namespace ICSharpCode.AvalonEdit.Document
line.DelimiterLength = 2; line.DelimiterLength = 2;
} else if (newTotalLength == 1 && lineOffset > 0 && textBuffer.GetCharAt(lineOffset - 1) == '\r') { } else if (newTotalLength == 1 && lineOffset > 0 && textBuffer.GetCharAt(lineOffset - 1) == '\r') {
// we need to join this line with the previous line // we need to join this line with the previous line
DocumentLineTree.Enumerator it = new DocumentLineTree.Enumerator(line); DocumentLine previousLine = line.PreviousLine;
it.MoveBack();
RemoveLine(line); RemoveLine(line);
return SetLineLength(it.Current, it.Current.TotalLength + 1); return SetLineLength(previousLine, previousLine.TotalLength + 1);
} else { } else {
line.DelimiterLength = 1; line.DelimiterLength = 1;
} }

7
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/NewLineFinder.cs

@ -75,9 +75,10 @@ namespace ICSharpCode.AvalonEdit.Document
{ {
DocumentLine line = document.GetLineByNumber(lineNumber); DocumentLine line = document.GetLineByNumber(lineNumber);
if (line.DelimiterLength == 0) { if (line.DelimiterLength == 0) {
if (lineNumber > 1) // at the end of the document, there's no line delimiter, so use the delimiter
line = document.GetLineByNumber(lineNumber - 1); // from the previous line
else line = line.PreviousLine;
if (line == null)
return Environment.NewLine; return Environment.NewLine;
} }
return document.GetText(line.Offset + line.Length, line.DelimiterLength); return document.GetText(line.Offset + line.Length, line.DelimiterLength);

21
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs

@ -64,14 +64,12 @@ namespace ICSharpCode.AvalonEdit.Document
readonly DocumentLineTree lineTree; readonly DocumentLineTree lineTree;
readonly LineManager lineManager; readonly LineManager lineManager;
readonly TextAnchorTree anchorTree; readonly TextAnchorTree anchorTree;
//readonly ParserManager parserManager;
/// <summary> /// <summary>
/// Create an empty text document. /// Create an empty text document.
/// </summary> /// </summary>
public TextDocument() public TextDocument()
{ {
//parserManager = new ParserManager(this);
lineTree = new DocumentLineTree(this); lineTree = new DocumentLineTree(this);
lineManager = new LineManager(textBuffer, lineTree, this); lineManager = new LineManager(textBuffer, lineTree, this);
lineTrackers.CollectionChanged += delegate { lineTrackers.CollectionChanged += delegate {
@ -85,20 +83,6 @@ namespace ICSharpCode.AvalonEdit.Document
} }
#endregion #endregion
#region DocumentParsers
/*
/// <summary>
/// Gets/Sets the document parser associated with this document.
/// </summary>
public IList<IDocumentParser> DocumentParsers {
get {
VerifyAccess();
return parserManager;
}
}
*/
#endregion
#region Text #region Text
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
void VerifyRange(int offset, int length) void VerifyRange(int offset, int length)
@ -231,7 +215,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// Some events are suspended until EndUpdate is called, and the <see cref="UndoStack"/> will /// Some events are suspended until EndUpdate is called, and the <see cref="UndoStack"/> will
/// group all changes into a single action. /// group all changes into a single action.
/// Calling BeginUpdate several times increments a counter, only after the appropriate number /// Calling BeginUpdate several times increments a counter, only after the appropriate number
/// of EndUpdate calls the DocumentParsers and events resume their work. /// of EndUpdate calls the events resume their work.
/// </summary> /// </summary>
public void BeginUpdate() public void BeginUpdate()
{ {
@ -348,7 +332,7 @@ namespace ICSharpCode.AvalonEdit.Document
if (inDocumentChanging) if (inDocumentChanging)
throw new InvalidOperationException("Cannot change document within another document change."); throw new InvalidOperationException("Cannot change document within another document change.");
BeginUpdate(); BeginUpdate();
// protect document change against corruption by other changes inside the event handlers/IDocumentParser // protect document change against corruption by other changes inside the event handlers
inDocumentChanging = true; inDocumentChanging = true;
try { try {
VerifyRange(offset, length); VerifyRange(offset, length);
@ -373,7 +357,6 @@ namespace ICSharpCode.AvalonEdit.Document
anchorTree.InsertText(offset, text.Length); anchorTree.InsertText(offset, text.Length);
delayedEvents.RaiseEvents(); delayedEvents.RaiseEvents();
//parserManager.ClearParserState(lineManager.RetrieveDeletedOrChangedLines());
// fire DocumentChanged event // fire DocumentChanged event
if (Changed != null) if (Changed != null)

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs

@ -16,8 +16,8 @@ using System.Windows;
namespace ICSharpCode.AvalonEdit.Document namespace ICSharpCode.AvalonEdit.Document
{ {
/// <summary> /// <summary>
/// Interface to allow TextSegments to access the TextSegmentTree - we cannot use a direct reference /// Interface to allow TextSegments to access the TextSegmentCollection - we cannot use a direct reference
/// because TextSegmentTree is generic. /// because TextSegmentCollection is generic.
/// </summary> /// </summary>
interface ISegmentTree interface ISegmentTree
{ {

26
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/ColorizingTransformer.cs

@ -15,7 +15,7 @@ namespace ICSharpCode.AvalonEdit.Gui
/// splitting visual elements so that colors (and other text properties) can be easily assigned /// splitting visual elements so that colors (and other text properties) can be easily assigned
/// to individual words/characters. /// to individual words/characters.
/// </summary> /// </summary>
public abstract class ColorizingTransformer : IVisualLineTransformer public abstract class ColorizingTransformer : IVisualLineTransformer, ITextViewConnect
{ {
/// <summary> /// <summary>
/// Gets the list of elements currently being transformed. /// Gets the list of elements currently being transformed.
@ -76,6 +76,30 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
} }
} }
}
/// <summary>
/// Called when added to a text view.
/// </summary>
protected virtual void OnAddToTextView(TextView textView)
{
}
/// <summary>
/// Called when removed from a text view.
/// </summary>
protected virtual void OnRemoveFromTextView(TextView textView)
{
}
void ITextViewConnect.AddToTextView(TextView textView)
{
OnAddToTextView(textView);
}
void ITextViewConnect.RemoveFromTextView(TextView textView)
{
OnRemoveFromTextView(textView);
} }
} }
} }

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

@ -6,12 +6,16 @@
// </file> // </file>
using System; using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Gui namespace ICSharpCode.AvalonEdit.Gui
@ -82,7 +86,7 @@ namespace ICSharpCode.AvalonEdit.Gui
textArea.Document.Insert(offset, indentationString); textArea.Document.Insert(offset, indentationString);
if (start == end) if (start == end)
break; break;
start = textArea.Document.GetLineByNumber(start.LineNumber + 1); start = start.NextLine;
} }
} else { } else {
textArea.ReplaceSelectionWithText(indentationString); textArea.ReplaceSelectionWithText(indentationString);
@ -116,7 +120,7 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
if (start == end) if (start == end)
break; break;
start = textArea.Document.GetLineByNumber(start.LineNumber + 1); start = start.NextLine;
} }
} }
textArea.Caret.BringCaretToView(); textArea.Caret.BringCaretToView();
@ -200,8 +204,12 @@ namespace ICSharpCode.AvalonEdit.Gui
static void CopySelectedText(TextArea textArea) static void CopySelectedText(TextArea textArea)
{ {
string text = textArea.Selection.GetText(textArea.Document); string text = textArea.Selection.GetText(textArea.Document);
// ensure we use the appropriate newline sequence for the OS // Ensure we use the appropriate newline sequence for the OS
Clipboard.SetText(NewLineFinder.NormalizeNewLines(text, Environment.NewLine)); 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));
Clipboard.SetDataObject(data, true);
} }
static void CanPaste(object target, CanExecuteRoutedEventArgs args) static void CanPaste(object target, CanExecuteRoutedEventArgs args)
@ -218,6 +226,7 @@ namespace ICSharpCode.AvalonEdit.Gui
{ {
TextArea textArea = GetTextArea(target); TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) { if (textArea != null && textArea.Document != null) {
Debug.WriteLine( Clipboard.GetText(TextDataFormat.Html) );
// convert text back to correct newlines for this document // convert text back to correct newlines for this document
string newLine = NewLineFinder.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line); string newLine = NewLineFinder.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
string text = NewLineFinder.NormalizeNewLines(Clipboard.GetText(), newLine); string text = NewLineFinder.NormalizeNewLines(Clipboard.GetText(), newLine);

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/FoldingSection.cs

@ -32,7 +32,7 @@ namespace ICSharpCode.AvalonEdit.Gui
DocumentLine startLine = manager.document.GetLineByOffset(StartOffset); DocumentLine startLine = manager.document.GetLineByOffset(StartOffset);
DocumentLine endLine = manager.document.GetLineByOffset(EndOffset); DocumentLine endLine = manager.document.GetLineByOffset(EndOffset);
if (startLine != endLine) { if (startLine != endLine) {
DocumentLine startLinePlusOne = manager.document.GetLineByNumber(startLine.LineNumber + 1); DocumentLine startLinePlusOne = startLine.NextLine;
collapsedSection = manager.textView.CollapseLines(startLinePlusOne, endLine); collapsedSection = manager.textView.CollapseLines(startLinePlusOne, endLine);
} }
} else { } else {

28
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/ITextViewConnect.cs

@ -0,0 +1,28 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.AvalonEdit.Gui
{
/// <summary>
/// Allows <see cref="VisualLineElementGenerator"/>s, <see cref="IVisualLineTransformer"/>s and
/// <see cref="IBackgroundRenderer"/> to be notified when they are added or removed from a text view.
/// </summary>
public interface ITextViewConnect
{
/// <summary>
/// Called when added to a text view.
/// </summary>
void AddToTextView(TextView textView);
/// <summary>
/// Called when removed from a text view.
/// </summary>
void RemoveFromTextView(TextView textView);
}
}

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

@ -237,6 +237,9 @@ namespace ICSharpCode.AvalonEdit.Gui
// we cannot use DataObject.SetText - then we cannot drag to SciTe // we cannot use DataObject.SetText - then we cannot drag to SciTe
// (but dragging to Word works in both cases) // (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));
DragDropEffects allowedEffects = DragDropEffects.All; DragDropEffects allowedEffects = DragDropEffects.All;
var deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList(); var deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList();

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

@ -27,7 +27,7 @@ namespace ICSharpCode.AvalonEdit
/// <summary> /// <summary>
/// Control that wraps a TextView and adds support for user input and the caret. /// Control that wraps a TextView and adds support for user input and the caret.
/// </summary> /// </summary>
public class TextArea : Control, IScrollInfo, IWeakEventListener public class TextArea : Control, IScrollInfo, IWeakEventListener, IServiceProvider
{ {
#region Constructor #region Constructor
static TextArea() static TextArea()
@ -230,6 +230,8 @@ namespace ICSharpCode.AvalonEdit
selection = value; selection = value;
if (SelectionChanged != null) if (SelectionChanged != null)
SelectionChanged(this, EventArgs.Empty); SelectionChanged(this, EventArgs.Empty);
// a selection change causes commands like copy/paste/etc. to change status
CommandManager.InvalidateRequerySuggested();
} }
} }
} }
@ -578,5 +580,14 @@ namespace ICSharpCode.AvalonEdit
} }
} }
#endregion #endregion
/// <summary>
/// Gets the requested service.
/// </summary>
/// <returns>Returns the requested service instance, or null if the service cannot be found.</returns>
public virtual object GetService(Type serviceType)
{
return textView.Services.GetService(serviceType);
}
} }
} }

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

@ -8,7 +8,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@ -34,7 +36,7 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary> /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", [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.")] 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 public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener, IServiceProvider
{ {
#region Constructor #region Constructor
static TextView() static TextView()
@ -48,9 +50,9 @@ namespace ICSharpCode.AvalonEdit.Gui
public TextView() public TextView()
{ {
textLayer = new TextLayer(this); textLayer = new TextLayer(this);
elementGenerators.CollectionChanged += delegate { Redraw(); }; elementGenerators.CollectionChanged += elementGenerators_CollectionChanged;
lineTransformers.CollectionChanged += delegate { Redraw(); }; lineTransformers.CollectionChanged += lineTransformers_CollectionChanged;
backgroundRenderer.CollectionChanged += delegate { InvalidateVisual(); }; backgroundRenderer.CollectionChanged += backgroundRenderer_CollectionChanged;
layers = new UIElementCollection(this, this); layers = new UIElementCollection(this, this);
InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace); InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace);
} }
@ -135,6 +137,12 @@ namespace ICSharpCode.AvalonEdit.Gui
get { return elementGenerators; } get { return elementGenerators; }
} }
void elementGenerators_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
HandleTextViewConnect(e);
Redraw();
}
readonly ObservableCollection<IVisualLineTransformer> lineTransformers = new ObservableCollection<IVisualLineTransformer>(); readonly ObservableCollection<IVisualLineTransformer> lineTransformers = new ObservableCollection<IVisualLineTransformer>();
/// <summary> /// <summary>
@ -143,6 +151,12 @@ namespace ICSharpCode.AvalonEdit.Gui
public ObservableCollection<IVisualLineTransformer> LineTransformers { public ObservableCollection<IVisualLineTransformer> LineTransformers {
get { return lineTransformers; } get { return lineTransformers; }
} }
void lineTransformers_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
HandleTextViewConnect(e);
Redraw();
}
#endregion #endregion
#region Layers #region Layers
@ -526,11 +540,7 @@ namespace ICSharpCode.AvalonEdit.Gui
visualLine.VisualTop = scrollOffset.Y + yPos; visualLine.VisualTop = scrollOffset.Y + yPos;
int visualLineEndLineNumber = visualLine.LastDocumentLine.LineNumber; nextLine = visualLine.LastDocumentLine.NextLine;
if (visualLineEndLineNumber == document.LineCount)
nextLine = null;
else
nextLine = document.GetLineByNumber(visualLineEndLineNumber + 1);
yPos += visualLine.Height; yPos += visualLine.Height;
@ -699,6 +709,17 @@ namespace ICSharpCode.AvalonEdit.Gui
get { return backgroundRenderer; } get { return backgroundRenderer; }
} }
void backgroundRenderer_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
HandleTextViewConnect(e);
InvalidateVisual();
foreach (UIElement layer in this.Layers) {
// invalidate known layers
if (layer is Layer)
layer.InvalidateVisual();
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnRender(DrawingContext drawingContext) protected override void OnRender(DrawingContext drawingContext)
{ {
@ -1095,6 +1116,45 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
#endregion #endregion
#region Service Provider
readonly ServiceContainer services = new ServiceContainer();
/// <summary>
/// Gets a service container used to associate services with the text view.
/// </summary>
public ServiceContainer Services {
get { return services; }
}
object IServiceProvider.GetService(Type serviceType)
{
return services.GetService(serviceType);
}
void HandleTextViewConnect(NotifyCollectionChangedEventArgs e)
{
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
if (e.OldItems != null) {
foreach (ITextViewConnect c in e.OldItems.OfType<ITextViewConnect>())
c.RemoveFromTextView(this);
}
if (e.NewItems != null) {
foreach (ITextViewConnect c in e.NewItems.OfType<ITextViewConnect>())
c.AddToTextView(this);
}
break;
case NotifyCollectionChangedAction.Move:
// ignore Move
break;
default:
throw new NotSupportedException(e.Action.ToString());
}
}
#endregion
/// <summary> /// <summary>
/// Collapses lines for the purpose of scrolling. This method is meant for /// Collapses lines for the purpose of scrolling. This method is meant for
/// <see cref="VisualLineElementGenerator"/>s that cause <see cref="VisualLine"/>s to span /// <see cref="VisualLineElementGenerator"/>s that cause <see cref="VisualLine"/>s to span

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/DocumentHighlighter.cs

@ -111,7 +111,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
while (firstInvalidLine < targetLineNumber) { while (firstInvalidLine < targetLineNumber) {
HighlightLineAndUpdateTreeList(document.GetLineByNumber(firstInvalidLine), firstInvalidLine); HighlightLineAndUpdateTreeList(document.GetLineByNumber(firstInvalidLine), firstInvalidLine);
} }
highlightedLine = new HighlightedLine { DocumentLine = line }; highlightedLine = new HighlightedLine(line);
HighlightLineAndUpdateTreeList(line, targetLineNumber); HighlightLineAndUpdateTreeList(line, targetLineNumber);
return highlightedLine; return highlightedLine;
} }

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

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Text; using System.Text;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Highlighting namespace ICSharpCode.AvalonEdit.Highlighting
{ {
@ -21,15 +22,18 @@ namespace ICSharpCode.AvalonEdit.Highlighting
/// <summary> /// <summary>
/// Creates a new HighlightedLine instance. /// Creates a new HighlightedLine instance.
/// </summary> /// </summary>
public HighlightedLine() public HighlightedLine(DocumentLine documentLine)
{ {
if (documentLine == null)
throw new ArgumentNullException("documentLine");
this.DocumentLine = documentLine;
this.Sections = new List<HighlightedSection>(); this.Sections = new List<HighlightedSection>();
} }
/// <summary> /// <summary>
/// Gets/Sets the document line associated with this HighlightedLine. /// Gets/Sets the document line associated with this HighlightedLine.
/// </summary> /// </summary>
public DocumentLine DocumentLine { get; set; } public DocumentLine DocumentLine { get; private set; }
/// <summary> /// <summary>
/// Gets the highlighted sections. /// Gets the highlighted sections.
@ -79,20 +83,42 @@ namespace ICSharpCode.AvalonEdit.Highlighting
/// </summary> /// </summary>
public string ToHtml() public string ToHtml()
{ {
int startOffset = this.DocumentLine.Offset;
return ToHtml(startOffset, startOffset + this.DocumentLine.Length);
}
/// <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)
{
int documentLineStartOffset = this.DocumentLine.Offset;
int documentLineEndOffset = documentLineStartOffset + this.DocumentLine.Length;
if (startOffset < documentLineStartOffset || startOffset > documentLineEndOffset)
throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between " + documentLineStartOffset + " and " + documentLineEndOffset);
if (endOffset < startOffset || endOffset > documentLineEndOffset)
throw new ArgumentOutOfRangeException("endOffset", endOffset, "Value must be between startOffset and " + documentLineEndOffset);
ISegment requestedSegment = new SimpleSegment(startOffset, endOffset - startOffset);
List<HtmlElement> elements = new List<HtmlElement>(); List<HtmlElement> elements = new List<HtmlElement>();
for (int i = 0; i < this.Sections.Count; i++) { for (int i = 0; i < this.Sections.Count; i++) {
HighlightedSection s = this.Sections[i]; HighlightedSection s = this.Sections[i];
if (s.GetOverlap(requestedSegment).Length > 0) {
elements.Add(new HtmlElement(s.Offset, i, false, s.Color)); elements.Add(new HtmlElement(s.Offset, i, false, s.Color));
elements.Add(new HtmlElement(s.Offset + s.Length, i, true, s.Color)); elements.Add(new HtmlElement(s.Offset + s.Length, i, true, s.Color));
} }
}
elements.Sort(); elements.Sort();
TextDocument document = DocumentLine.Document; TextDocument document = DocumentLine.Document;
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
int textOffset = DocumentLine.Offset; int textOffset = startOffset;
foreach (HtmlElement e in elements) { foreach (HtmlElement e in elements) {
b.Append(document.GetText(textOffset, e.Offset - textOffset)); int newOffset = Math.Min(e.Offset, endOffset);
textOffset = e.Offset; if (newOffset > startOffset) {
HtmlClipboard.EscapeHtml(b, document.GetText(textOffset, newOffset - textOffset));
}
textOffset = newOffset;
if (e.IsEnd) { if (e.IsEnd) {
b.Append("</span>"); b.Append("</span>");
} else { } else {
@ -101,7 +127,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
b.Append("\">"); b.Append("\">");
} }
} }
b.Append(document.GetText(textOffset, DocumentLine.Offset + DocumentLine.Length - textOffset)); HtmlClipboard.EscapeHtml(b, document.GetText(textOffset, endOffset - textOffset));
return b.ToString(); return b.ToString();
} }

22
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingBrush.cs

@ -21,7 +21,21 @@ namespace ICSharpCode.AvalonEdit.Highlighting
/// <summary> /// <summary>
/// Gets the real brush. /// Gets the real brush.
/// </summary> /// </summary>
/// <param name="context">The construction context. context can be null!</param>
public abstract Brush GetBrush(ITextRunConstructionContext context); public abstract Brush GetBrush(ITextRunConstructionContext context);
/// <summary>
/// Gets the color of the brush.
/// </summary>
/// <param name="context">The construction context. context can be null!</param>
public virtual Color? GetColor(ITextRunConstructionContext context)
{
SolidColorBrush scb = GetBrush(context) as SolidColorBrush;
if (scb != null)
return scb.Color;
else
return null;
}
} }
/// <summary> /// <summary>
@ -46,14 +60,9 @@ namespace ICSharpCode.AvalonEdit.Highlighting
public override string ToString() public override string ToString()
{ {
SolidColorBrush scb = brush as SolidColorBrush;
if (scb != null) {
return scb.Color.ToString();
} else {
return brush.ToString(); return brush.ToString();
} }
} }
}
/// <summary> /// <summary>
/// HighlightingBrush implementation that finds a brush using a resource. /// HighlightingBrush implementation that finds a brush using a resource.
@ -71,7 +80,10 @@ namespace ICSharpCode.AvalonEdit.Highlighting
public override Brush GetBrush(ITextRunConstructionContext context) public override Brush GetBrush(ITextRunConstructionContext context)
{ {
if (context != null && context.TextView != null)
return (Brush)context.TextView.FindResource(resourceKey); return (Brush)context.TextView.FindResource(resourceKey);
else
return (Brush)Application.Current.FindResource(resourceKey);
} }
public override string ToString() public override string ToString()

9
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightingColor.cs

@ -6,8 +6,10 @@
// </file> // </file>
using System; using System;
using System.Globalization;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Media;
namespace ICSharpCode.AvalonEdit.Highlighting namespace ICSharpCode.AvalonEdit.Highlighting
{ {
@ -39,9 +41,10 @@ namespace ICSharpCode.AvalonEdit.Highlighting
{ {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
if (Foreground != null) { if (Foreground != null) {
b.Append("color: "); Color? c = Foreground.GetColor(null);
b.Append(Foreground.ToString()); if (c != null) {
b.Append("; "); b.AppendFormat(CultureInfo.InvariantCulture, "color: #{0:x2}{1:x2}{2:x2}; ", c.Value.R, c.Value.G, c.Value.B);
}
} }
if (FontWeight != null) { if (FontWeight != null) {
b.Append("font-weight: "); b.Append("font-weight: ");

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

@ -60,6 +60,20 @@ namespace ICSharpCode.AvalonEdit.Highlighting
highlighter = null; highlighter = null;
} }
/// <inheritdoc/>
protected override void OnAddToTextView(TextView textView)
{
base.OnAddToTextView(textView);
textView.Services.AddService(typeof(DocumentHighlighter), highlighter);
}
/// <inheritdoc/>
protected override void OnRemoveFromTextView(TextView textView)
{
base.OnRemoveFromTextView(textView);
textView.Services.RemoveService(typeof(DocumentHighlighter));
}
int currentLineEndOffset; int currentLineEndOffset;
/// <inheritdoc/> /// <inheritdoc/>

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -125,6 +125,9 @@
</Compile> </Compile>
<Compile Include="Gui\IBackgroundRenderer.cs" /> <Compile Include="Gui\IBackgroundRenderer.cs" />
<Compile Include="Gui\IReadOnlySectionProvider.cs" /> <Compile Include="Gui\IReadOnlySectionProvider.cs" />
<Compile Include="Gui\ITextViewConnect.cs">
<DependentUpon>TextView.cs</DependentUpon>
</Compile>
<Compile Include="Gui\Layer.cs"> <Compile Include="Gui\Layer.cs">
<DependentUpon>TextView.cs</DependentUpon> <DependentUpon>TextView.cs</DependentUpon>
</Compile> </Compile>
@ -258,6 +261,7 @@
<Compile Include="Utils\Empty.cs" /> <Compile Include="Utils\Empty.cs" />
<Compile Include="Utils\ExtensionMethods.cs" /> <Compile Include="Utils\ExtensionMethods.cs" />
<Compile Include="Utils\FileReader.cs" /> <Compile Include="Utils\FileReader.cs" />
<Compile Include="Utils\HtmlClipboard.cs" />
<Compile Include="Utils\ImmutableStack.cs" /> <Compile Include="Utils\ImmutableStack.cs" />
<Compile Include="Utils\NullSafeCollection.cs" /> <Compile Include="Utils\NullSafeCollection.cs" />
<Compile Include="Utils\WeakEventManagerBase.cs" /> <Compile Include="Utils\WeakEventManagerBase.cs" />

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

@ -118,11 +118,28 @@ namespace ICSharpCode.AvalonEdit.Utils
/// </returns> /// </returns>
public static bool Contains(this ISegment segment, int offset) public static bool Contains(this ISegment segment, int offset)
{ {
if (segment == null)
return false;
int start = segment.Offset; int start = segment.Offset;
int end = start + segment.Length; int end = start + segment.Length;
return offset >= start && offset <= end; return offset >= start && offset <= end;
} }
/// <summary>
/// Gets the overlapping portion of the segments.
/// Returns SimpleSegment.Invalid if the segments don't overlap.
/// </summary>
public static SimpleSegment GetOverlap(this ISegment segment, ISegment other)
{
int start1 = segment.Offset;
int end1 = start1 + segment.Length;
int start2 = other.Offset;
int end2 = start2 + other.Length;
int start = Math.Max(start1, start2);
int end = Math.Min(end1, end2);
if (end < start)
return SimpleSegment.Invalid;
else
return new SimpleSegment(start, end - start);
}
} }
} }

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

@ -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.Diagnostics;
using System.Globalization;
using System.Text;
using System.Windows;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
namespace ICSharpCode.AvalonEdit.Utils
{
/// <summary>
/// Allows copying HTML text to the clipboard.
/// </summary>
public static class HtmlClipboard
{
/// <summary>
/// Builds a header for the CF_HTML clipboard format.
/// </summary>
static string BuildHeader(int startHTML, int endHTML, int startFragment, int endFragment)
{
StringBuilder b = new StringBuilder();
b.AppendLine("Version:1.0");
b.AppendLine("StartHTML:" + startHTML.ToString("d8", CultureInfo.InvariantCulture));
b.AppendLine("EndHTML:" + endHTML.ToString("d8", CultureInfo.InvariantCulture));
b.AppendLine("StartFragment:" + startFragment.ToString("d8", CultureInfo.InvariantCulture));
b.AppendLine("EndFragment:" + endFragment.ToString("d8", CultureInfo.InvariantCulture));
return b.ToString();
}
/// <summary>
/// Sets the TextDataFormat.Html on the data object to the specified html fragment.
/// This helper methods takes care of creating the necessary CF_HTML header.
/// </summary>
public static void SetHtml(DataObject dataObject, string htmlFragment)
{
if (dataObject == null)
throw new ArgumentNullException("dataObject");
if (htmlFragment == null)
throw new ArgumentNullException("htmlFragment");
string htmlStart = @"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 4.0 Transitional//EN"">" + Environment.NewLine
+ "<HTML>" + Environment.NewLine
+ "<HEAD><TITLE>Copied from AvalonEdit</TITLE></HEAD>" + Environment.NewLine
+ "<BODY>" + Environment.NewLine
+ "<!--StartFragment-->" + Environment.NewLine;
string htmlEnd = "<!--EndFragment-->" + Environment.NewLine + "</BODY>" + Environment.NewLine + "</HTML>" + Environment.NewLine;
string dummyHeader = BuildHeader(0, 0, 0, 0);
// the offsets are stored as UTF-8 bytes (see CF_HTML documentation)
int startHTML = dummyHeader.Length;
int startFragment = startHTML + htmlStart.Length;
int endFragment = startFragment + Encoding.UTF8.GetByteCount(htmlFragment);
int endHTML = endFragment + htmlEnd.Length;
string cf_html = BuildHeader(startHTML, endHTML, startFragment, endFragment) + htmlStart + htmlFragment + htmlEnd;
Debug.WriteLine(cf_html);
dataObject.SetText(cf_html, TextDataFormat.Html);
}
/// <summary>
/// Creates a HTML fragment from a part of a document.
/// </summary>
/// <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>
/// <returns>HTML code for the document part.</returns>
public static string CreateHtmlFragment(TextDocument document, DocumentHighlighter highlighter, ISegment segment)
{
if (document == null)
throw new ArgumentNullException("document");
if (segment == null)
segment = new SimpleSegment(0, document.TextLength);
StringBuilder html = new StringBuilder();
int segmentEndOffset = segment.GetEndOffset();
DocumentLine line = document.GetLineByOffset(segment.Offset);
while (line != null && line.Offset < segmentEndOffset) {
HighlightedLine highlightedLine;
if (highlighter != null)
highlightedLine = highlighter.HighlightLine(line);
else
highlightedLine = new HighlightedLine(line);
SimpleSegment s = segment.GetOverlap(line);
if (html.Length > 0)
html.AppendLine("<br>");
html.Append(highlightedLine.ToHtml(s.Offset, s.GetEndOffset()));
line = line.NextLine;
}
return html.ToString();
}
/// <summary>
/// Creates a HTML fragment for the selected part of the document.
/// </summary>
public static string CreateHtmlFragmentForSelection(TextArea textArea)
{
if (textArea == null)
throw new ArgumentNullException("textArea");
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));
}
return html.ToString();
}
/// <summary>
/// Escapes text and writes the result to the StringBuilder.
/// </summary>
internal static void EscapeHtml(StringBuilder b, string text)
{
int spaceCount = -1;
foreach (char c in text) {
if (c == ' ') {
if (spaceCount < 0)
b.Append("&nbsp;");
else
spaceCount++;
} else if (c == '\t') {
if (spaceCount < 0)
spaceCount = 0;
// TODO: use tab width setting
spaceCount += 4;
} else {
if (spaceCount == 1) {
b.Append(' ');
} else if (spaceCount >= 1) {
for (int i = 0; i < spaceCount; i++) {
b.Append("&nbsp;");
}
}
spaceCount = 0;
switch (c) {
case '<':
b.Append("&lt;");
break;
case '>':
b.Append("&gt;");
break;
case '&':
b.Append("&amp;");
break;
case '"':
b.Append("&quot;");
break;
default:
b.Append(c);
break;
}
}
}
for (int i = 0; i < spaceCount; i++) {
b.Append("&nbsp;");
}
}
}
}
Loading…
Cancel
Save