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. 42
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Highlighting/HighlightedLine.cs
  16. 26
      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 @@ -164,11 +164,52 @@ namespace ICSharpCode.AvalonEdit.Document
}
#endregion
#region ParserState
// /// <summary>
// /// Gets the parser state array associated with this line.
// /// </summary>
// public object[] ParserState { get; internal set; }
#region Previous / Next Line
/// <summary>
/// Gets the next line in the document.
/// </summary>
/// <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
#region ToString

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

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
@ -210,7 +211,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -210,7 +211,7 @@ namespace ICSharpCode.AvalonEdit.Document
}
#endregion
#region GetLineBy / GetEnumeratorFor
#region GetLineBy
public DocumentLine GetByNumber(int number)
{
return GetNodeByIndex(number - 1);
@ -220,11 +221,6 @@ namespace ICSharpCode.AvalonEdit.Document @@ -220,11 +221,6 @@ namespace ICSharpCode.AvalonEdit.Document
{
return GetNodeByOffset(offset);
}
public Enumerator GetEnumeratorForOffset(int offset)
{
return new Enumerator(GetNodeByOffset(offset));
}
#endregion
#region LineCount
@ -625,84 +621,6 @@ namespace ICSharpCode.AvalonEdit.Document @@ -625,84 +621,6 @@ namespace ICSharpCode.AvalonEdit.Document
}
#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
DocumentLine IList<DocumentLine>.this[int index] {
get {
@ -781,22 +699,18 @@ namespace ICSharpCode.AvalonEdit.Document @@ -781,22 +699,18 @@ namespace ICSharpCode.AvalonEdit.Document
public IEnumerator<DocumentLine> GetEnumerator()
{
document.VerifyAccess();
LineNode dummyNode = new LineNode(document);
dummyNode.right = root;
return new Enumerator(dummyNode);
}
// enumerator that verifies thread on each call
// - this is overkill, checking on the GetEnumerator call should be enough.
// IEnumerator<DocumentLine> Enumerate()
// {
// document.VerifyAccess();
// Enumerator e = new Enumerator(tree.GetEnumerator());
// while (e.MoveNext()) {
// yield return e.Current;
// document.DebugVerifyAccess();
// }
// }
return Enumerate();
}
IEnumerator<DocumentLine> Enumerate()
{
document.VerifyAccess();
DocumentLine line = root.LeftMost;
while (line != null) {
yield return line;
line = line.NextLine;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{

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

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

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

@ -75,9 +75,10 @@ namespace ICSharpCode.AvalonEdit.Document @@ -75,9 +75,10 @@ namespace ICSharpCode.AvalonEdit.Document
{
DocumentLine line = document.GetLineByNumber(lineNumber);
if (line.DelimiterLength == 0) {
if (lineNumber > 1)
line = document.GetLineByNumber(lineNumber - 1);
else
// at the end of the document, there's no line delimiter, so use the delimiter
// from the previous line
line = line.PreviousLine;
if (line == null)
return Environment.NewLine;
}
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 @@ -64,14 +64,12 @@ namespace ICSharpCode.AvalonEdit.Document
readonly DocumentLineTree lineTree;
readonly LineManager lineManager;
readonly TextAnchorTree anchorTree;
//readonly ParserManager parserManager;
/// <summary>
/// Create an empty text document.
/// </summary>
public TextDocument()
{
//parserManager = new ParserManager(this);
lineTree = new DocumentLineTree(this);
lineManager = new LineManager(textBuffer, lineTree, this);
lineTrackers.CollectionChanged += delegate {
@ -85,20 +83,6 @@ namespace ICSharpCode.AvalonEdit.Document @@ -85,20 +83,6 @@ namespace ICSharpCode.AvalonEdit.Document
}
#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
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
void VerifyRange(int offset, int length)
@ -231,7 +215,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -231,7 +215,7 @@ namespace ICSharpCode.AvalonEdit.Document
/// Some events are suspended until EndUpdate is called, and the <see cref="UndoStack"/> will
/// group all changes into a single action.
/// 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>
public void BeginUpdate()
{
@ -348,7 +332,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -348,7 +332,7 @@ namespace ICSharpCode.AvalonEdit.Document
if (inDocumentChanging)
throw new InvalidOperationException("Cannot change document within another document change.");
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;
try {
VerifyRange(offset, length);
@ -373,7 +357,6 @@ namespace ICSharpCode.AvalonEdit.Document @@ -373,7 +357,6 @@ namespace ICSharpCode.AvalonEdit.Document
anchorTree.InsertText(offset, text.Length);
delayedEvents.RaiseEvents();
//parserManager.ClearParserState(lineManager.RetrieveDeletedOrChangedLines());
// fire DocumentChanged event
if (Changed != null)

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

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

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

@ -15,7 +15,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -15,7 +15,7 @@ namespace ICSharpCode.AvalonEdit.Gui
/// splitting visual elements so that colors (and other text properties) can be easily assigned
/// to individual words/characters.
/// </summary>
public abstract class ColorizingTransformer : IVisualLineTransformer
public abstract class ColorizingTransformer : IVisualLineTransformer, ITextViewConnect
{
/// <summary>
/// Gets the list of elements currently being transformed.
@ -76,6 +76,30 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -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 @@ @@ -6,12 +6,16 @@
// </file>
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Gui
@ -82,7 +86,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -82,7 +86,7 @@ namespace ICSharpCode.AvalonEdit.Gui
textArea.Document.Insert(offset, indentationString);
if (start == end)
break;
start = textArea.Document.GetLineByNumber(start.LineNumber + 1);
start = start.NextLine;
}
} else {
textArea.ReplaceSelectionWithText(indentationString);
@ -116,7 +120,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -116,7 +120,7 @@ namespace ICSharpCode.AvalonEdit.Gui
}
if (start == end)
break;
start = textArea.Document.GetLineByNumber(start.LineNumber + 1);
start = start.NextLine;
}
}
textArea.Caret.BringCaretToView();
@ -200,8 +204,12 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -200,8 +204,12 @@ namespace ICSharpCode.AvalonEdit.Gui
static void CopySelectedText(TextArea textArea)
{
string text = textArea.Selection.GetText(textArea.Document);
// ensure we use the appropriate newline sequence for the OS
Clipboard.SetText(NewLineFinder.NormalizeNewLines(text, Environment.NewLine));
// Ensure we use the appropriate newline sequence for the OS
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)
@ -218,6 +226,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -218,6 +226,7 @@ namespace ICSharpCode.AvalonEdit.Gui
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
Debug.WriteLine( Clipboard.GetText(TextDataFormat.Html) );
// convert text back to correct newlines for this document
string newLine = NewLineFinder.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line);
string text = NewLineFinder.NormalizeNewLines(Clipboard.GetText(), newLine);

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

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

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

@ -0,0 +1,28 @@ @@ -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 @@ -237,6 +237,9 @@ namespace ICSharpCode.AvalonEdit.Gui
// we cannot use DataObject.SetText - then we cannot drag to SciTe
// (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;
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 @@ -27,7 +27,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
public class TextArea : Control, IScrollInfo, IWeakEventListener, IServiceProvider
{
#region Constructor
static TextArea()
@ -230,6 +230,8 @@ namespace ICSharpCode.AvalonEdit @@ -230,6 +230,8 @@ namespace ICSharpCode.AvalonEdit
selection = value;
if (SelectionChanged != null)
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 @@ -578,5 +580,14 @@ namespace ICSharpCode.AvalonEdit
}
}
#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 @@ @@ -8,7 +8,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
@ -34,7 +36,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -34,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
public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener, IServiceProvider
{
#region Constructor
static TextView()
@ -48,9 +50,9 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -48,9 +50,9 @@ namespace ICSharpCode.AvalonEdit.Gui
public TextView()
{
textLayer = new TextLayer(this);
elementGenerators.CollectionChanged += delegate { Redraw(); };
lineTransformers.CollectionChanged += delegate { Redraw(); };
backgroundRenderer.CollectionChanged += delegate { InvalidateVisual(); };
elementGenerators.CollectionChanged += elementGenerators_CollectionChanged;
lineTransformers.CollectionChanged += lineTransformers_CollectionChanged;
backgroundRenderer.CollectionChanged += backgroundRenderer_CollectionChanged;
layers = new UIElementCollection(this, this);
InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace);
}
@ -135,6 +137,12 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -135,6 +137,12 @@ namespace ICSharpCode.AvalonEdit.Gui
get { return elementGenerators; }
}
void elementGenerators_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
HandleTextViewConnect(e);
Redraw();
}
readonly ObservableCollection<IVisualLineTransformer> lineTransformers = new ObservableCollection<IVisualLineTransformer>();
/// <summary>
@ -143,6 +151,12 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -143,6 +151,12 @@ namespace ICSharpCode.AvalonEdit.Gui
public ObservableCollection<IVisualLineTransformer> LineTransformers {
get { return lineTransformers; }
}
void lineTransformers_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
HandleTextViewConnect(e);
Redraw();
}
#endregion
#region Layers
@ -526,11 +540,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -526,11 +540,7 @@ namespace ICSharpCode.AvalonEdit.Gui
visualLine.VisualTop = scrollOffset.Y + yPos;
int visualLineEndLineNumber = visualLine.LastDocumentLine.LineNumber;
if (visualLineEndLineNumber == document.LineCount)
nextLine = null;
else
nextLine = document.GetLineByNumber(visualLineEndLineNumber + 1);
nextLine = visualLine.LastDocumentLine.NextLine;
yPos += visualLine.Height;
@ -699,6 +709,17 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -699,6 +709,17 @@ namespace ICSharpCode.AvalonEdit.Gui
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/>
protected override void OnRender(DrawingContext drawingContext)
{
@ -1095,6 +1116,45 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -1095,6 +1116,45 @@ namespace ICSharpCode.AvalonEdit.Gui
}
#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>
/// Collapses lines for the purpose of scrolling. This method is meant for
/// <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 @@ -111,7 +111,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
while (firstInvalidLine < targetLineNumber) {
HighlightLineAndUpdateTreeList(document.GetLineByNumber(firstInvalidLine), firstInvalidLine);
}
highlightedLine = new HighlightedLine { DocumentLine = line };
highlightedLine = new HighlightedLine(line);
HighlightLineAndUpdateTreeList(line, targetLineNumber);
return highlightedLine;
}

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

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

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

@ -21,7 +21,21 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -21,7 +21,21 @@ namespace ICSharpCode.AvalonEdit.Highlighting
/// <summary>
/// Gets the real brush.
/// </summary>
/// <param name="context">The construction context. context can be null!</param>
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>
@ -46,12 +60,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -46,12 +60,7 @@ namespace ICSharpCode.AvalonEdit.Highlighting
public override string ToString()
{
SolidColorBrush scb = brush as SolidColorBrush;
if (scb != null) {
return scb.Color.ToString();
} else {
return brush.ToString();
}
return brush.ToString();
}
}
@ -71,7 +80,10 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -71,7 +80,10 @@ namespace ICSharpCode.AvalonEdit.Highlighting
public override Brush GetBrush(ITextRunConstructionContext context)
{
return (Brush)context.TextView.FindResource(resourceKey);
if (context != null && context.TextView != null)
return (Brush)context.TextView.FindResource(resourceKey);
else
return (Brush)Application.Current.FindResource(resourceKey);
}
public override string ToString()

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

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

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

@ -60,6 +60,20 @@ namespace ICSharpCode.AvalonEdit.Highlighting @@ -60,6 +60,20 @@ namespace ICSharpCode.AvalonEdit.Highlighting
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;
/// <inheritdoc/>

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

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

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

@ -118,11 +118,28 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -118,11 +118,28 @@ namespace ICSharpCode.AvalonEdit.Utils
/// </returns>
public static bool Contains(this ISegment segment, int offset)
{
if (segment == null)
return false;
int start = segment.Offset;
int end = start + segment.Length;
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 @@ @@ -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