1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using Tango.Scripting.Editors.Utils;
namespace Tango.Scripting.Editors.Rendering
{
/// <summary>
/// Contains weak event managers for the TextView events.
/// </summary>
public static class TextViewWeakEventManager
{
/// <summary>
/// Weak event manager for the <see cref="TextView.DocumentChanged"/> event.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public sealed class DocumentChanged : WeakEventManagerBase<DocumentChanged, TextView>
{
/// <inheritdoc/>
protected override void StartListening(TextView source)
{
source.DocumentChanged += DeliverEvent;
}
/// <inheritdoc/>
protected override void StopListeningpre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Globalization;
using System.Windows.Documents;
namespace Tango.Scripting.Editors.Document
{
/// <summary>
/// Specifies the mode for getting the next caret position.
/// </summary>
public enum CaretPositioningMode
{
/// <summary>
/// Normal positioning (stop at every caret position)
/// </summary>
Normal,
/// <summary>
/// Stop only on word borders.
/// </summary>
WordBorder,
/// <summary>
/// Stop only at the beginning of words. This is used for Ctrl+Left/Ctrl+Right.
/// </summary>
WordStart,
/// <summary>
/// Stop only at the beginning of words, and anywhere in the middle of symbols.
/// </summary>
WordStartOrSymbol,
/// <summary>
/// Stop only on word borders, and anywhere in the middle of symbols.
/// </summary>
WordBorderOrSymbol
}
/// <summary>
/// Static helper methods for working with text.
/// </summary>
public static partial 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"
};
// DEL (ASCII 127) and
// the names of the control characters in the C1 block (Unicode 128 to 159)
static readonly string[] delAndC1Table = {
"DEL",
"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 && num <= 159)
return delAndC1Table[num - 127];
else
return num.ToString("x4", CultureInfo.InvariantCulture);
}
#endregion
#region GetWhitespace
/// <summary>
/// Gets all whitespace (' ' and '\t', but no newlines) after offset.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The offset where the whitespace starts.</param>
/// <returns>The segment containing the whitespace.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetWhitespaceAfter(ITextSource textSource, int offset)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
int pos;
for (pos = offset; pos < textSource.TextLength; pos++) {
char c = textSource.GetCharAt(pos);
if (c != ' ' && c != '\t')
break;
}
return new SimpleSegment(offset, pos - offset);
}
/// <summary>
/// Gets all whitespace (' ' and '\t', but no newlines) before offset.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The offset where the whitespace ends.</param>
/// <returns>The segment containing the whitespace.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetWhitespaceBefore(ITextSource textSource, int offset)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
int pos;
for (pos = offset - 1; pos >= 0; pos--) {
char c = textSource.GetCharAt(pos);
if (c != ' ' && c != '\t')
break;
}
pos++; // go back the one character that isn't whitespace
return new SimpleSegment(pos, offset - pos);
}
/// <summary>
/// Gets the leading whitespace segment on the document line.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
Justification = "Parameter cannot be ITextSource because it must belong to the DocumentLine")]
public static ISegment GetLeadingWhitespace(TextDocument document, DocumentLine documentLine)
{
if (documentLine == null)
throw new ArgumentNullException("documentLine");
return GetWhitespaceAfter(document, documentLine.Offset);
}
/// <summary>
/// Gets the trailing whitespace segment on the document line.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
Justification = "Parameter cannot be ITextSource because it must belong to the DocumentLine")]
public static ISegment GetTrailingWhitespace(TextDocument document, DocumentLine documentLine)
{
if (documentLine == null)
throw new ArgumentNullException("documentLine");
ISegment segment = GetWhitespaceBefore(document, documentLine.EndOffset);
// If the whole line consists of whitespace, we consider all of it as leading whitespace,
// so return an empty segment as trailing whitespace.
if (segment.Offset == documentLine.Offset)
return new SimpleSegment(documentLine.EndOffset, 0);
else
return segment;
}
#endregion
#region GetSingleIndentationSegment
/// <summary>
/// Gets a single indentation segment starting at <paramref name="offset"/> - at most one tab
/// or <paramref name="indentationSize"/> spaces.
/// </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 GetSingleIndentationSegment(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')
return CharacterClass.LineTerminator;
else if (char.IsWhiteSpace(c))
return CharacterClass.Whitespace;
else if (char.IsLetterOrDigit(c) || c == '_')
return CharacterClass.IdentifierPart;
else
return CharacterClass.Other;
}
#endregion
#region GetNextCaretPosition
/// <summary>
/// Gets the next caret position.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The start offset inside the text source.</param>
/// <param name="direction">The search direction (forwards or backwards).</param>
/// <param name="mode">The mode for caret positioning.</param>
/// <returns>The offset of the next caret position, or -1 if there is no further caret position
/// in the text source.</returns>
/// <remarks>
/// This method is NOT equivalent to the actual caret movement when using VisualLine.GetNextCaretPosition.
/// In real caret movement, there are additional caret stops at line starts and ends. This method
/// treats linefeeds as simple whitespace.
/// </remarks>
public static int GetNextCaretPosition(ITextSource textSource, int offset, LogicalDirection direction, CaretPositioningMode mode)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
if (mode != CaretPositioningMode.Normal
&& mode != CaretPositioningMode.WordBorder
&& mode != CaretPositioningMode.WordStart
&& mode != CaretPositioningMode.WordBorderOrSymbol
&& mode != CaretPositioningMode.WordStartOrSymbol)
{
throw new ArgumentException("Unsupported CaretPositioningMode: " + mode, "mode");
}
if (direction != LogicalDirection.Backward
&& direction != LogicalDirection.Forward)
{
throw new ArgumentException("Invalid LogicalDirection: " + direction, "direction");
}
int textLength = textSource.TextLength;
if (textLength <= 0) {
// empty document? has a normal caret position at 0, though no word borders
if (mode == CaretPositioningMode.Normal) {
if (offset > 0 && direction == LogicalDirection.Backward) return 0;
if (offset < 0 && direction == LogicalDirection.Forward) return 0;
}
return -1;
}
while (true) {
int nextPos = (direction == LogicalDirection.Backward) ? offset - 1 : offset + 1;
// return -1 if there is no further caret position in the text source
// we also need this to handle offset values outside the valid range
if (nextPos < 0 || nextPos > textLength)
return -1;
// stop at every caret position? we can stop immediately.
if (mode == CaretPositioningMode.Normal)
return nextPos;
// not normal mode? we're looking for word borders...
// check if we've run against the textSource borders.
// a 'textSource' usually isn't the whole document, but a single VisualLineElement.
if (nextPos == 0) {
// at the document start, there's only a word border
// if the first character is not whitespace
if (!char.IsWhiteSpace(textSource.GetCharAt(0)))
return nextPos;
} else if (nextPos == textLength) {
// at the document end, there's never a word start
if (mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol) {
// at the document end, there's only a word border
// if the last character is not whitespace
if (!char.IsWhiteSpace(textSource.GetCharAt(textLength - 1)))
return nextPos;
}
} else {
CharacterClass charBefore = GetCharacterClass(textSource.GetCharAt(nextPos - 1));
CharacterClass charAfter = GetCharacterClass(textSource.GetCharAt(nextPos));
if (charBefore == charAfter) {
if (charBefore == CharacterClass.Other &&
(mode == CaretPositioningMode.WordBorderOrSymbol || mode == CaretPositioningMode.WordStartOrSymbol))
{
// With the "OrSymbol" modes, there's a word border and start between any two unknown characters
return nextPos;
}
} else {
// this looks like a possible border
// if we're looking for word starts, check that this is a word start (and not a word end)
// if we're just checking for word borders, accept unconditionally
if (!((mode == CaretPositioningMode.WordStart || mode == CaretPositioningMode.WordStartOrSymbol)
&& (charAfter == CharacterClass.Whitespace || charAfter == CharacterClass.LineTerminator)))
{
return nextPos;
}
}
}
// we'll have to continue searching...
offset = nextPos;
}
}
#endregion
}
/// <summary>
/// Classifies a character as whitespace, line terminator, part of an identifier, or other.
/// </summary>
public enum CharacterClass
{
/// <summary>
/// The character is not whitespace, line terminator or part of an identifier.
/// </summary>
Other,
/// <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 (Letter, digit or underscore).
/// </summary>
IdentifierPart,
/// <summary>
/// The character is line terminator (\r or \n).
/// </summary>
LineTerminator
}
}
|