Browse Source

Improved GetNextCaretPosition - placed word borders at line starts and ends.

Implemented GetWordBeforeCaret().

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3901 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
2c2ef65f89
  1. 5
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditDocumentAdapter.cs
  2. 5
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
  3. 5
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs
  4. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/ICSharpCode.AvalonEdit.Tests.csproj
  5. 91
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/CaretNavigationTests.cs
  6. 20
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs
  7. 55
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ITextSource.cs
  8. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/Caret.cs
  9. 6
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/NewLineElementGenerator.cs
  10. 34
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLine.cs
  11. 14
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLineElement.cs
  12. 44
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLineText.cs
  13. 10
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/WhitespaceElementGenerator.cs
  14. 114
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs
  15. 15
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  16. 5
      src/Main/Base/Project/Src/Services/RefactoringService/TextEditorDocument.cs
  17. 86
      src/Main/Base/Project/Src/TextEditor/DocumentUtilitites.cs
  18. 5
      src/Main/Base/Project/Src/TextEditor/Gui/Editor/CodeCompletionBinding.cs
  19. 1
      src/Main/Base/Project/Src/TextEditor/ITextEditor.cs
  20. 1
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/IDocument.cs

5
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditDocumentAdapter.cs

@ -71,6 +71,11 @@ namespace ICSharpCode.AvalonEdit.AddIn
set { document.Text = value; } set { document.Text = value; }
} }
public event EventHandler TextChanged {
add { document.TextChanged += value; }
remove { document.TextChanged -= value; }
}
public IDocumentLine GetLine(int lineNumber) public IDocumentLine GetLine(int lineNumber)
{ {
return new LineAdapter(document.GetLineByNumber(lineNumber)); return new LineAdapter(document.GetLineByNumber(lineNumber));

5
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs

@ -52,9 +52,8 @@ namespace ICSharpCode.AvalonEdit.AddIn
if (result == CodeCompletionKeyPressResult.Completed) { if (result == CodeCompletionKeyPressResult.Completed) {
if (lastCompletionWindow != null && lastCompletionWindow != oldCompletionWindow) { if (lastCompletionWindow != null && lastCompletionWindow != oldCompletionWindow) {
// a new CompletionWindow was shown, but does not eat the input // a new CompletionWindow was shown, but does not eat the input
// increment the offsets so that they are correct after the text insertion // tell it to expect the text insertion
lastCompletionWindow.StartOffset++; lastCompletionWindow.ExpectInsertionBeforeStart = true;
lastCompletionWindow.EndOffset++;
} }
return; return;
} else if (result == CodeCompletionKeyPressResult.EatKey) { } else if (result == CodeCompletionKeyPressResult.EatKey) {

5
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs

@ -38,10 +38,7 @@ namespace ICSharpCode.AvalonEdit.AddIn
foreach (char c in e.Text) { foreach (char c in e.Text) {
switch (itemList.ProcessInput(c)) { switch (itemList.ProcessInput(c)) {
case CompletionItemListKeyResult.BeforeStartKey: case CompletionItemListKeyResult.BeforeStartKey:
if (StartOffset == EndOffset) { this.ExpectInsertionBeforeStart = true;
StartOffset++;
EndOffset++;
}
break; break;
case CompletionItemListKeyResult.NormalKey: case CompletionItemListKeyResult.NormalKey:
break; break;

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

@ -72,6 +72,7 @@
<Compile Include="Document\HeightTests.cs" /> <Compile Include="Document\HeightTests.cs" />
<Compile Include="Document\RandomizedLineManagerTest.cs" /> <Compile Include="Document\RandomizedLineManagerTest.cs" />
<Compile Include="Document\LineManagerTests.cs" /> <Compile Include="Document\LineManagerTests.cs" />
<Compile Include="Utils\CaretNavigationTests.cs" />
<Compile Include="Utils\CompressingTreeListTests.cs" /> <Compile Include="Utils\CompressingTreeListTests.cs" />
<Compile Include="Utils\ExtensionMethodsTests.cs" /> <Compile Include="Utils\ExtensionMethodsTests.cs" />
<Compile Include="WeakReferenceTests.cs" /> <Compile Include="WeakReferenceTests.cs" />

91
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit.Tests/Utils/CaretNavigationTests.cs

@ -0,0 +1,91 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using ICSharpCode.AvalonEdit.Gui;
using System;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Utils;
using NUnit.Framework;
namespace ICSharpCode.AvalonEdit.Tests.Utils
{
[TestFixture]
public class CaretNavigationTests
{
int GetNextCaretStop(string text, int offset, CaretPositioningMode mode)
{
return TextUtilities.GetNextCaretPosition(new StringTextSource(text), offset, false, mode);
}
int GetPrevCaretStop(string text, int offset, CaretPositioningMode mode)
{
return TextUtilities.GetNextCaretPosition(new StringTextSource(text), offset, true, mode);
}
[Test]
public void CaretStopInEmptyString()
{
Assert.AreEqual(0, GetNextCaretStop("", -1, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetNextCaretStop("", 0, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetPrevCaretStop("", 0, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop("", 1, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetNextCaretStop("", -1, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetNextCaretStop("", -1, CaretPositioningMode.WordBorder));
Assert.AreEqual(-1, GetPrevCaretStop("", 1, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetPrevCaretStop("", 1, CaretPositioningMode.WordBorder));
}
[Test]
public void StartOfDocumentWithWordStart()
{
Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.WordStart));
Assert.AreEqual(0, GetNextCaretStop("word", -1, CaretPositioningMode.WordBorder));
Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.WordStart));
Assert.AreEqual(0, GetPrevCaretStop("word", 1, CaretPositioningMode.WordBorder));
}
[Test]
public void StartOfDocumentNoWordStart()
{
Assert.AreEqual(0, GetNextCaretStop(" word", -1, CaretPositioningMode.Normal));
Assert.AreEqual(1, GetNextCaretStop(" word", -1, CaretPositioningMode.WordStart));
Assert.AreEqual(1, GetNextCaretStop(" word", -1, CaretPositioningMode.WordBorder));
Assert.AreEqual(0, GetPrevCaretStop(" word", 1, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetPrevCaretStop(" word", 1, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetPrevCaretStop(" word", 1, CaretPositioningMode.WordBorder));
}
[Test]
public void EndOfDocumentWordBorder()
{
Assert.AreEqual(4, GetNextCaretStop("word", 3, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetNextCaretStop("word", 3, CaretPositioningMode.WordStart));
Assert.AreEqual(4, GetNextCaretStop("word", 3, CaretPositioningMode.WordBorder));
Assert.AreEqual(4, GetPrevCaretStop("word", 5, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop("word", 5, CaretPositioningMode.WordStart));
Assert.AreEqual(4, GetPrevCaretStop("word", 5, CaretPositioningMode.WordBorder));
}
[Test]
public void EndOfDocumentNoWordBorder()
{
Assert.AreEqual(4, GetNextCaretStop("txt ", 3, CaretPositioningMode.Normal));
Assert.AreEqual(-1, GetNextCaretStop("txt ", 3, CaretPositioningMode.WordStart));
Assert.AreEqual(-1, GetNextCaretStop("txt ", 3, CaretPositioningMode.WordBorder));
Assert.AreEqual(4, GetPrevCaretStop("txt ", 5, CaretPositioningMode.Normal));
Assert.AreEqual(0, GetPrevCaretStop("txt ", 5, CaretPositioningMode.WordStart));
Assert.AreEqual(3, GetPrevCaretStop("txt ", 5, CaretPositioningMode.WordBorder));
}
}
}

20
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs

@ -163,19 +163,21 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
return completionList.ScrollViewer ?? completionList.ListBox ?? (UIElement)completionList; return completionList.ScrollViewer ?? completionList.ListBox ?? (UIElement)completionList;
} }
/// <summary>
/// Gets/sets whether the completion window should expect text insertion at the start offset,
/// which not go into the completion region, but before it.
/// </summary>
public bool ExpectInsertionBeforeStart { get; set; }
void textArea_Document_Changing(object sender, DocumentChangeEventArgs e) void textArea_Document_Changing(object sender, DocumentChangeEventArgs e)
{ {
// => startOffset test required so that this startOffset/endOffset are not incremented again if (e.Offset == startOffset && e.RemovalLength == 0 && ExpectInsertionBeforeStart) {
// for BeforeStartKey characters startOffset = e.GetNewOffset(startOffset, AnchorMovementType.AfterInsertion);
if (e.Offset >= startOffset && e.Offset <= endOffset) { this.ExpectInsertionBeforeStart = false;
endOffset += e.InsertionLength - e.RemovalLength;
} else if (e.Offset == startOffset - 1 && e.InsertionLength == 1 && e.RemovalLength == 0) {
// allow one-character insertion in front of StartOffset.
// this is necessary because for dot-completion, the CompletionWindow is shown before
// the dot is actually inserted.
} else { } else {
Close(); startOffset = e.GetNewOffset(startOffset, AnchorMovementType.BeforeInsertion);
} }
endOffset = e.GetNewOffset(endOffset, AnchorMovementType.AfterInsertion);
} }
/// <summary> /// <summary>

55
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/ITextSource.cs

@ -54,6 +54,61 @@ namespace ICSharpCode.AvalonEdit.Document
string GetText(int offset, int length); string GetText(int offset, int length);
} }
/// <summary>
/// Implements the ITextSource interface by wrapping another TextSource
/// and viewing only a part of the text.
/// </summary>
public sealed class TextSourceView : ITextSource
{
readonly ITextSource baseTextSource;
readonly ISegment viewedSegment;
/// <summary>
/// Creates a new TextSourceView object.
/// </summary>
/// <param name="baseTextSource">The base text source.</param>
/// <param name="viewedSegment">A text segment from the base text source</param>
public TextSourceView(ITextSource baseTextSource, ISegment viewedSegment)
{
if (baseTextSource == null)
throw new ArgumentNullException("baseTextSource");
if (viewedSegment == null)
throw new ArgumentNullException("viewedSegment");
this.baseTextSource = baseTextSource;
this.viewedSegment = viewedSegment;
}
/// <inheritdoc/>
public event EventHandler TextChanged {
add { baseTextSource.TextChanged += value; }
remove { baseTextSource.TextChanged -= value; }
}
/// <inheritdoc/>
public string Text {
get {
return baseTextSource.GetText(viewedSegment.Offset, viewedSegment.Length);
}
}
/// <inheritdoc/>
public int TextLength {
get { return viewedSegment.Length; }
}
/// <inheritdoc/>
public char GetCharAt(int offset)
{
return baseTextSource.GetCharAt(viewedSegment.Offset + offset);
}
/// <inheritdoc/>
public string GetText(int offset, int length)
{
return baseTextSource.GetText(viewedSegment.Offset + offset, length);
}
}
/// <summary> /// <summary>
/// Implements the ITextSource interface using a string. /// Implements the ITextSource interface using a string.
/// </summary> /// </summary>

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/Caret.cs

@ -242,7 +242,9 @@ namespace ICSharpCode.AvalonEdit.Gui
Rect CalcCaretRectangle(VisualLine visualLine) Rect CalcCaretRectangle(VisualLine visualLine)
{ {
RevalidateVisualColumn(visualLine); if (!visualColumnValid) {
RevalidateVisualColumn(visualLine);
}
TextLine textLine = visualLine.GetTextLine(position.VisualColumn); TextLine textLine = visualLine.GetTextLine(position.VisualColumn);
double xPos = textLine.GetDistanceFromCharacterHit(new CharacterHit(position.VisualColumn, 0)); double xPos = textLine.GetDistanceFromCharacterHit(new CharacterHit(position.VisualColumn, 0));

6
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/NewLineElementGenerator.cs

@ -57,7 +57,7 @@ namespace ICSharpCode.AvalonEdit.Gui
return new NewLineTextElement(text); return new NewLineTextElement(text);
} }
class NewLineTextElement : FormattedTextElement sealed class NewLineTextElement : FormattedTextElement
{ {
public NewLineTextElement(FormattedText text) : base(text, 0) public NewLineTextElement(FormattedText text) : base(text, 0)
{ {
@ -76,6 +76,10 @@ namespace ICSharpCode.AvalonEdit.Gui
return -1; return -1;
} }
} }
public override bool HandlesLineBorders {
get { return true; }
}
} }
} }
} }

34
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLine.cs

@ -317,12 +317,31 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary> /// </summary>
public int GetNextCaretPosition(int visualColumn, bool backwards, CaretPositioningMode mode) public int GetNextCaretPosition(int visualColumn, bool backwards, CaretPositioningMode mode)
{ {
if (elements.Count == 0) {
// special handling for empty visual lines:
// even though we don't have any elements,
// there's a single caret stop at visualColumn 0
if (visualColumn < 0 && !backwards)
return 0;
else if (visualColumn > 0 && backwards)
return 0;
else
return -1;
}
int i; int i;
if (backwards) { if (backwards) {
// Search Backwards:
// If the last element doesn't handle line borders, return the line end as caret stop
if (visualColumn > this.VisualLength && !elements[elements.Count-1].HandlesLineBorders) {
return this.VisualLength;
}
// skip elements that start after or at visualColumn
for (i = elements.Count - 1; i >= 0; i--) { for (i = elements.Count - 1; i >= 0; i--) {
if (elements[i].VisualColumn < visualColumn) if (elements[i].VisualColumn < visualColumn)
break; break;
} }
// search last element that has a caret stop
for (; i >= 0; i--) { for (; i >= 0; i--) {
int pos = elements[i].GetNextCaretPosition( int pos = elements[i].GetNextCaretPosition(
Math.Min(visualColumn, elements[i].VisualColumn + elements[i].VisualLength + 1), Math.Min(visualColumn, elements[i].VisualColumn + elements[i].VisualLength + 1),
@ -330,11 +349,21 @@ namespace ICSharpCode.AvalonEdit.Gui
if (pos >= 0) if (pos >= 0)
return pos; return pos;
} }
// if we've found nothing, and the first element doesn't handle line borders,
// return the line start as caret stop
if (visualColumn > 0 && !elements[0].HandlesLineBorders)
return 0;
} else { } else {
// Search Forwards:
// If the first element doesn't handle line borders, return the line start as caret stop
if (visualColumn < 0 && !elements[0].HandlesLineBorders)
return 0;
// skip elements that end before or at visualColumn
for (i = 0; i < elements.Count; i++) { for (i = 0; i < elements.Count; i++) {
if (elements[i].VisualColumn + elements[i].VisualLength > visualColumn) if (elements[i].VisualColumn + elements[i].VisualLength > visualColumn)
break; break;
} }
// search first element that has a caret stop
for (; i < elements.Count; i++) { for (; i < elements.Count; i++) {
int pos = elements[i].GetNextCaretPosition( int pos = elements[i].GetNextCaretPosition(
Math.Max(visualColumn, elements[i].VisualColumn - 1), Math.Max(visualColumn, elements[i].VisualColumn - 1),
@ -342,7 +371,12 @@ namespace ICSharpCode.AvalonEdit.Gui
if (pos >= 0) if (pos >= 0)
return pos; return pos;
} }
// if we've found nothing, and the last element doesn't handle line borders,
// return the line end as caret stop
if (visualColumn < this.VisualLength && !elements[elements.Count-1].HandlesLineBorders)
return this.VisualLength;
} }
// we've found nothing, return -1 and let the caret search continue in the next line
return -1; return -1;
} }
} }

14
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLineElement.cs

@ -171,7 +171,8 @@ namespace ICSharpCode.AvalonEdit.Gui
/// <param name="mode">Whether to stop only at word borders.</param> /// <param name="mode">Whether to stop only at word borders.</param>
/// <returns>The visual column of the next caret position, or -1 if there is no next caret position.</returns> /// <returns>The visual column of the next caret position, or -1 if there is no next caret position.</returns>
/// <remarks> /// <remarks>
/// In the space between two line elements, usually both of them contain a caret position. /// In the space between two line elements, it is sufficient that one of them contains a caret position;
/// though in many cases, both of them contain one.
/// </remarks> /// </remarks>
public virtual int GetNextCaretPosition(int visualColumn, bool backwards, CaretPositioningMode mode) public virtual int GetNextCaretPosition(int visualColumn, bool backwards, CaretPositioningMode mode)
{ {
@ -191,6 +192,17 @@ namespace ICSharpCode.AvalonEdit.Gui
return -1; return -1;
} }
/// <summary>
/// Gets whether the <see cref="GetNextCaretPosition"/> implementation handles line borders.
/// If this property returns false, the caller of GetNextCaretPosition should handle the line
/// borders (i.e. place caret stops at the start and end of the line).
/// This property has an effect only for VisualLineElements that are at the start or end of a
/// <see cref="VisualLine"/>.
/// </summary>
public virtual bool HandlesLineBorders {
get { return false; }
}
/// <summary> /// <summary>
/// Queries the cursor over the visual line element. /// Queries the cursor over the visual line element.
/// </summary> /// </summary>

44
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLineText.cs

@ -5,10 +5,10 @@
// <version>$Revision$</version> // <version>$Revision$</version>
// </file> // </file>
using ICSharpCode.AvalonEdit.Utils;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows.Media.TextFormatting; using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Gui namespace ICSharpCode.AvalonEdit.Gui
@ -94,41 +94,15 @@ namespace ICSharpCode.AvalonEdit.Gui
/// <inheritdoc/> /// <inheritdoc/>
public override int GetNextCaretPosition(int visualColumn, bool backwards, CaretPositioningMode mode) public override int GetNextCaretPosition(int visualColumn, bool backwards, CaretPositioningMode mode)
{ {
int nextPos = backwards ? visualColumn - 1 : visualColumn + 1; int textOffset = parentVisualLine.FirstDocumentLine.Offset + this.RelativeTextOffset;
if (nextPos >= this.VisualColumn && nextPos <= this.VisualColumn + this.VisualLength) { TextSourceView view = new TextSourceView(
if (mode == CaretPositioningMode.WordBorder || mode == CaretPositioningMode.WordStart) { parentVisualLine.FirstDocumentLine.Document,
TextDocument document = parentVisualLine.FirstDocumentLine.Document; new SimpleSegment(textOffset, this.DocumentLength));
int textOffset = parentVisualLine.FirstDocumentLine.Offset + GetRelativeOffset(nextPos); int pos = TextUtilities.GetNextCaretPosition(view, visualColumn - this.VisualColumn, backwards, mode);
if (textOffset > 0 && textOffset < document.TextLength) { if (pos < 0)
CharClass charBefore = GetCharClass(document.GetCharAt(textOffset - 1)); return pos;
CharClass charAfter = GetCharClass(document.GetCharAt(textOffset));
if (charBefore == charAfter || (charAfter == CharClass.Whitespace && mode == CaretPositioningMode.WordStart))
return GetNextCaretPosition(nextPos, backwards, mode);
}
}
return nextPos;
}
return -1;
}
enum CharClass
{
Whitespace,
IdentifierPart,
LineTerminator,
Other
}
static CharClass GetCharClass(char c)
{
if (c == '\r' || c == '\n')
return CharClass.LineTerminator;
else if (char.IsWhiteSpace(c))
return CharClass.Whitespace;
else if (char.IsLetterOrDigit(c) || c == '_')
return CharClass.IdentifierPart;
else else
return CharClass.Other; return this.VisualColumn + pos;
} }
} }
} }

10
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/WhitespaceElementGenerator.cs

@ -93,7 +93,7 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
} }
class SpaceTextElement : FormattedTextElement sealed class SpaceTextElement : FormattedTextElement
{ {
public SpaceTextElement(FormattedText text) : base(text, 1) public SpaceTextElement(FormattedText text) : base(text, 1)
{ {
@ -110,7 +110,7 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
} }
class TabTextElement : VisualLineElement sealed class TabTextElement : VisualLineElement
{ {
internal readonly FormattedText text; internal readonly FormattedText text;
@ -121,6 +121,8 @@ namespace ICSharpCode.AvalonEdit.Gui
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
{ {
// the TabTextElement consists of two TextRuns:
// first a TabGlyphRun, then TextCharacters '\t' to let WPF handle the tab indentation
if (startVisualColumn == this.VisualColumn) if (startVisualColumn == this.VisualColumn)
return new TabGlyphRun(this, this.TextRunProperties); return new TabGlyphRun(this, this.TextRunProperties);
else if (startVisualColumn == this.VisualColumn + 1) else if (startVisualColumn == this.VisualColumn + 1)
@ -138,9 +140,9 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
} }
class TabGlyphRun : TextEmbeddedObject sealed class TabGlyphRun : TextEmbeddedObject
{ {
protected readonly TabTextElement element; readonly TabTextElement element;
TextRunProperties properties; TextRunProperties properties;
public TabGlyphRun(TabTextElement element, TextRunProperties properties) public TabGlyphRun(TabTextElement element, TextRunProperties properties)

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

@ -6,6 +6,9 @@
// </file> // </file>
using System; using System;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Gui;
using System.ComponentModel;
namespace ICSharpCode.AvalonEdit.Utils namespace ICSharpCode.AvalonEdit.Utils
{ {
@ -14,5 +17,116 @@ namespace ICSharpCode.AvalonEdit.Utils
/// </summary> /// </summary>
public static class TextUtilities public static class TextUtilities
{ {
/// <summary>
/// Gets whether the character is whitespace, part of an identifier, or line terminator.
/// </summary>
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;
}
/// <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="backwards">True to look backwards, false to look forwards.</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>
public static int GetNextCaretPosition(ITextSource textSource, int offset, bool backwards, CaretPositioningMode mode)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
if (mode != CaretPositioningMode.Normal
&& mode != CaretPositioningMode.WordBorder
&& mode != CaretPositioningMode.WordStart)
{
throw new ArgumentException("Unsupported CaretPositioningMode: " + mode, "mode");
}
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 && backwards) return 0;
if (offset < 0 && !backwards) return 0;
}
return -1;
}
while (true) {
int nextPos = backwards ? 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) {
// 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) {
// 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 && (charAfter == CharacterClass.Whitespace || charAfter == CharacterClass.LineTerminator))) {
return nextPos;
}
}
}
// we'll have to continue searching...
offset = nextPos;
}
}
}
/// <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>
Whitespace,
/// <summary>
/// The character can be part of an identifier (LetterOrDigit or underscore).
/// </summary>
IdentifierPart,
/// <summary>
/// The character is line terminator (\r or \n).
/// </summary>
LineTerminator
} }
} }

15
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -512,6 +512,7 @@
<Compile Include="Src\TextEditor\Commands\TextAreaContextmenuCommands.cs" /> <Compile Include="Src\TextEditor\Commands\TextAreaContextmenuCommands.cs" />
<Compile Include="Src\TextEditor\Commands\ToolCommands.cs" /> <Compile Include="Src\TextEditor\Commands\ToolCommands.cs" />
<Compile Include="Src\TextEditor\Conditions\TextContentCondition.cs" /> <Compile Include="Src\TextEditor\Conditions\TextContentCondition.cs" />
<Compile Include="Src\TextEditor\DocumentUtilitites.cs" />
<Compile Include="Src\TextEditor\Gui\Dialogs\GotoDialog.cs"> <Compile Include="Src\TextEditor\Gui\Dialogs\GotoDialog.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@ -750,9 +751,15 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\Libraries\AvalonEdit\ICSharpCode.AvalonEdit\ICSharpCode.AvalonEdit.csproj">
<Project>{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}</Project>
<Name>ICSharpCode.AvalonEdit</Name>
<Private>True</Private>
</ProjectReference>
<ProjectReference Include="..\..\..\Libraries\ICSharpCode.TextEditor\Project\ICSharpCode.TextEditor.csproj"> <ProjectReference Include="..\..\..\Libraries\ICSharpCode.TextEditor\Project\ICSharpCode.TextEditor.csproj">
<Project>{2D18BE89-D210-49EB-A9DD-2246FBB3DF6D}</Project> <Project>{2D18BE89-D210-49EB-A9DD-2246FBB3DF6D}</Project>
<Name>ICSharpCode.TextEditor</Name> <Name>ICSharpCode.TextEditor</Name>
<Private>False</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\..\Libraries\NRefactory\Project\NRefactory.csproj"> <ProjectReference Include="..\..\..\Libraries\NRefactory\Project\NRefactory.csproj">
<Project>{3A9AE6AA-BC07-4A2F-972C-581E3AE2F195}</Project> <Project>{3A9AE6AA-BC07-4A2F-972C-581E3AE2F195}</Project>
@ -761,6 +768,7 @@
<ProjectReference Include="..\..\Core\Project\ICSharpCode.Core.csproj"> <ProjectReference Include="..\..\Core\Project\ICSharpCode.Core.csproj">
<Project>{35cef10f-2d4c-45f2-9dd1-161e0fec583c}</Project> <Project>{35cef10f-2d4c-45f2-9dd1-161e0fec583c}</Project>
<Name>ICSharpCode.Core</Name> <Name>ICSharpCode.Core</Name>
<Private>False</Private>
</ProjectReference> </ProjectReference>
<Folder Include="Src\Gui\Pads\SearchResultPad" /> <Folder Include="Src\Gui\Pads\SearchResultPad" />
<Folder Include="Src\Gui\Pads\SearchResultPad\Nodes" /> <Folder Include="Src\Gui\Pads\SearchResultPad\Nodes" />
@ -774,25 +782,28 @@
<ProjectReference Include="..\..\ICSharpCode.Core.Presentation\ICSharpCode.Core.Presentation.csproj"> <ProjectReference Include="..\..\ICSharpCode.Core.Presentation\ICSharpCode.Core.Presentation.csproj">
<Project>{7E4A7172-7FF5-48D0-B719-7CD959DD1AC9}</Project> <Project>{7E4A7172-7FF5-48D0-B719-7CD959DD1AC9}</Project>
<Name>ICSharpCode.Core.Presentation</Name> <Name>ICSharpCode.Core.Presentation</Name>
<Private>True</Private> <Private>False</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\ICSharpCode.Core.WinForms\ICSharpCode.Core.WinForms.csproj"> <ProjectReference Include="..\..\ICSharpCode.Core.WinForms\ICSharpCode.Core.WinForms.csproj">
<Project>{857CA1A3-FC88-4BE0-AB6A-D1EE772AB288}</Project> <Project>{857CA1A3-FC88-4BE0-AB6A-D1EE772AB288}</Project>
<Name>ICSharpCode.Core.WinForms</Name> <Name>ICSharpCode.Core.WinForms</Name>
<Private>False</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\ICSharpCode.SharpDevelop.BuildWorker\ICSharpCode.SharpDevelop.BuildWorker.csproj"> <ProjectReference Include="..\..\ICSharpCode.SharpDevelop.BuildWorker\ICSharpCode.SharpDevelop.BuildWorker.csproj">
<Project>{C3CBC8E3-81D8-4C5B-9941-DCCD12D50B1F}</Project> <Project>{C3CBC8E3-81D8-4C5B-9941-DCCD12D50B1F}</Project>
<Name>ICSharpCode.SharpDevelop.BuildWorker</Name> <Name>ICSharpCode.SharpDevelop.BuildWorker</Name>
<Private>False</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\ICSharpCode.SharpDevelop.Dom\Project\ICSharpCode.SharpDevelop.Dom.csproj"> <ProjectReference Include="..\..\ICSharpCode.SharpDevelop.Dom\Project\ICSharpCode.SharpDevelop.Dom.csproj">
<Project>{924EE450-603D-49C1-A8E5-4AFAA31CE6F3}</Project> <Project>{924EE450-603D-49C1-A8E5-4AFAA31CE6F3}</Project>
<Name>ICSharpCode.SharpDevelop.Dom</Name> <Name>ICSharpCode.SharpDevelop.Dom</Name>
<Private>False</Private>
</ProjectReference> </ProjectReference>
<Folder Include="Src\Gui\Dialogs\SolutionConfiguration" /> <Folder Include="Src\Gui\Dialogs\SolutionConfiguration" />
<ProjectReference Include="..\..\ICSharpCode.SharpDevelop.Widgets\Project\ICSharpCode.SharpDevelop.Widgets.csproj"> <ProjectReference Include="..\..\ICSharpCode.SharpDevelop.Widgets\Project\ICSharpCode.SharpDevelop.Widgets.csproj">
<Project>{8035765F-D51F-4A0C-A746-2FD100E19419}</Project> <Project>{8035765F-D51F-4A0C-A746-2FD100E19419}</Project>
<Name>ICSharpCode.SharpDevelop.Widgets</Name> <Name>ICSharpCode.SharpDevelop.Widgets</Name>
<Private>True</Private> <Private>False</Private>
</ProjectReference> </ProjectReference>
<Page Include="Src\Gui\App.xaml" /> <Page Include="Src\Gui\App.xaml" />
<Page Include="Src\Gui\Dialogs\TabbedOptionsDialog.xaml" /> <Page Include="Src\Gui\Dialogs\TabbedOptionsDialog.xaml" />

5
src/Main/Base/Project/Src/Services/RefactoringService/TextEditorDocument.cs

@ -72,6 +72,11 @@ namespace ICSharpCode.SharpDevelop.Refactoring
set { doc.TextContent = value; } set { doc.TextContent = value; }
} }
public event EventHandler TextChanged {
add { doc.TextContentChanged += value; }
remove { doc.TextContentChanged -= value; }
}
public string GetText(int offset, int length) public string GetText(int offset, int length)
{ {
return doc.GetText(offset, length); return doc.GetText(offset, length);

86
src/Main/Base/Project/Src/TextEditor/DocumentUtilitites.cs

@ -0,0 +1,86 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using ICSharpCode.AvalonEdit.Gui;
using System;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.SharpDevelop.Dom.Refactoring;
namespace ICSharpCode.SharpDevelop
{
/// <summary>
/// Extension methods for ITextEditor and IDocument.
/// </summary>
public static class DocumentUtilitites
{
/// <summary>
/// Gets the word in front of the caret.
/// </summary>
public static string GetWordBeforeCaret(this ITextEditor editor)
{
if (editor == null)
throw new ArgumentNullException("editor");
int endOffset = editor.Caret.Offset;
int startOffset = FindPrevWordStart(editor.Document, endOffset);
if (startOffset < 0)
return string.Empty;
else
return editor.Document.GetText(startOffset, endOffset - startOffset);
}
/// <summary>
/// Finds the first word start in the document before offset.
/// </summary>
/// <returns>The offset of the word start, or -1 if there is no word start before the specified offset.</returns>
public static int FindPrevWordStart(IDocument document, int offset)
{
return TextUtilities.GetNextCaretPosition(GetTextSource(document), offset, true, CaretPositioningMode.WordStart);
}
#region ITextSource implementation
public static ICSharpCode.AvalonEdit.Document.ITextSource GetTextSource(IDocument document)
{
if (document == null)
throw new ArgumentNullException("document");
return new DocumentTextSource(document);
}
sealed class DocumentTextSource : ICSharpCode.AvalonEdit.Document.ITextSource
{
readonly IDocument document;
public DocumentTextSource(IDocument document)
{
this.document = document;
}
public event EventHandler TextChanged {
add { document.TextChanged += value; }
remove { document.TextChanged -= value; }
}
public string Text {
get { return document.Text; }
}
public int TextLength {
get { return document.TextLength; }
}
public char GetCharAt(int offset)
{
return document.GetCharAt(offset);
}
public string GetText(int offset, int length)
{
return document.GetText(offset, length);
}
}
#endregion
}
}

5
src/Main/Base/Project/Src/TextEditor/Gui/Editor/CodeCompletionBinding.cs

@ -189,8 +189,9 @@ namespace ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor
case ' ': case ' ':
if (CodeCompletionOptions.KeywordCompletionEnabled) { if (CodeCompletionOptions.KeywordCompletionEnabled) {
string word = editor.GetWordBeforeCaret(); string word = editor.GetWordBeforeCaret();
if (word != null) { if (!string.IsNullOrEmpty(word)) {
return HandleKeyword(editor, word) ? CodeCompletionKeyPressResult.Completed : CodeCompletionKeyPressResult.None; if (HandleKeyword(editor, word))
return CodeCompletionKeyPressResult.Completed;
} }
} }
break; break;

1
src/Main/Base/Project/Src/TextEditor/ITextEditor.cs

@ -43,7 +43,6 @@ namespace ICSharpCode.SharpDevelop
[Obsolete] [Obsolete]
void ShowCompletionWindow(ICSharpCode.TextEditor.Gui.CompletionWindow.ICompletionDataProvider provider, char ch); void ShowCompletionWindow(ICSharpCode.TextEditor.Gui.CompletionWindow.ICompletionDataProvider provider, char ch);
void ShowCompletionWindow(ICompletionItemList data); void ShowCompletionWindow(ICompletionItemList data);
string GetWordBeforeCaret();
} }
public interface ITextEditorCaret public interface ITextEditorCaret

1
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/IDocument.cs

@ -20,6 +20,7 @@ namespace ICSharpCode.SharpDevelop.Dom.Refactoring
int TextLength { get; } int TextLength { get; }
int TotalNumberOfLines { get; } int TotalNumberOfLines { get; }
string Text { get; set; } string Text { get; set; }
event EventHandler TextChanged;
/// <summary> /// <summary>
/// Gets the document line with the specified number. /// Gets the document line with the specified number.

Loading…
Cancel
Save