Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorMikayla Hutchinson <m.j.hutchinson@gmail.com>2018-06-29 01:47:05 +0300
committerMikayla Hutchinson <m.j.hutchinson@gmail.com>2018-07-06 01:23:58 +0300
commitf842739cb657bc02ddfd6d4f6e1de84f9fcd6df0 (patch)
tree4d1029783b13565d3274c46f4bd91411c1802728 /main
parent149eb1c5d0d1194247b9fc2dc15db415e037d757 (diff)
[Xml] Implement Expand/Shrink selection for XML
Diffstat (limited to 'main')
-rw-r--r--main/src/addins/Xml/Dom/XAttributeCollection.cs29
-rw-r--r--main/src/addins/Xml/Editor/BaseXmlEditorExtension.cs27
-rw-r--r--main/src/addins/Xml/Editor/XmlExpandSelectionHandler.cs384
-rw-r--r--main/src/addins/Xml/MonoDevelop.Xml.csproj1
-rw-r--r--main/src/addins/Xml/Tests/ExpandSelectionTests.cs160
-rw-r--r--main/src/addins/Xml/Tests/MonoDevelop.Xml.Tests.csproj9
6 files changed, 597 insertions, 13 deletions
diff --git a/main/src/addins/Xml/Dom/XAttributeCollection.cs b/main/src/addins/Xml/Dom/XAttributeCollection.cs
index 0a906f0ec9..d408f5b63f 100644
--- a/main/src/addins/Xml/Dom/XAttributeCollection.cs
+++ b/main/src/addins/Xml/Dom/XAttributeCollection.cs
@@ -31,9 +31,11 @@ namespace MonoDevelop.Xml.Dom
{
public class XAttributeCollection : IEnumerable<XAttribute>
{
- readonly XObject parent;
- XAttribute firstChild;
- XAttribute lastChild;
+ readonly XObject parent;
+
+ public XAttribute Last { get; private set; }
+ public XAttribute First { get; private set; }
+ public int Count { get; private set; }
public XAttributeCollection (XObject parent)
{
@@ -43,7 +45,7 @@ namespace MonoDevelop.Xml.Dom
public Dictionary<XName, XAttribute> ToDictionary ()
{
var dict = new Dictionary<XName,XAttribute> ();
- XAttribute current = firstChild;
+ XAttribute current = First;
while (current != null) {
dict.Add (current.Name, current);
current = current.NextSibling;
@@ -53,7 +55,7 @@ namespace MonoDevelop.Xml.Dom
public XAttribute this [XName name] {
get {
- XAttribute current = firstChild;
+ XAttribute current = First;
while (current != null) {
if (current.Name == name)
return current;
@@ -65,7 +67,7 @@ namespace MonoDevelop.Xml.Dom
public XAttribute this [int index] {
get {
- XAttribute current = firstChild;
+ XAttribute current = First;
while (current != null) {
if (index == 0)
return current;
@@ -78,7 +80,7 @@ namespace MonoDevelop.Xml.Dom
public XAttribute Get (XName name, bool ignoreCase)
{
- XAttribute current = firstChild;
+ XAttribute current = First;
while (current != null) {
if (XName.Equals (current.Name, name, ignoreCase))
return current;
@@ -96,17 +98,18 @@ namespace MonoDevelop.Xml.Dom
public void AddAttribute (XAttribute newChild)
{
newChild.Parent = parent;
- if (lastChild != null) {
- lastChild.NextSibling = newChild;
+ if (Last != null) {
+ Last.NextSibling = newChild;
}
- if (firstChild == null)
- firstChild = newChild;
- lastChild = newChild;
+ if (First == null)
+ First = newChild;
+ Last = newChild;
+ Count++;
}
public IEnumerator<XAttribute> GetEnumerator ()
{
- XAttribute current = firstChild;
+ XAttribute current = First;
while (current != null) {
yield return current;
current = current.NextSibling;
diff --git a/main/src/addins/Xml/Editor/BaseXmlEditorExtension.cs b/main/src/addins/Xml/Editor/BaseXmlEditorExtension.cs
index 0c2f3aedc5..6889969c84 100644
--- a/main/src/addins/Xml/Editor/BaseXmlEditorExtension.cs
+++ b/main/src/addins/Xml/Editor/BaseXmlEditorExtension.cs
@@ -1268,5 +1268,32 @@ namespace MonoDevelop.Xml.Editor
}
}
}
+
+ [CommandHandler (TextEditorCommands.ExpandSelection)]
+ public virtual void ExpandSelection ()
+ {
+ Tracker.UpdateEngine ();
+ XmlExpandSelectionHandler.ExpandSelection (Editor, Tracker.Engine.GetTreeParser);
+ }
+
+ [CommandUpdateHandler (TextEditorCommands.ExpandSelection)]
+ public void UpdateExpandSelection (CommandInfo info)
+ {
+ info.Enabled = XmlExpandSelectionHandler.CanExpandSelection (Editor);
+
+ }
+
+ [CommandHandler (TextEditorCommands.ShrinkSelection)]
+ public virtual void ShrinkSelection ()
+ {
+ Tracker.UpdateEngine ();
+ XmlExpandSelectionHandler.ShrinkSelection (Editor, Tracker.Engine.GetTreeParser);
+ }
+
+ [CommandUpdateHandler (TextEditorCommands.ShrinkSelection)]
+ public void UpdateShrinkSelection (CommandInfo info)
+ {
+ info.Enabled = XmlExpandSelectionHandler.CanShrinkSelection (Editor);
+ }
}
}
diff --git a/main/src/addins/Xml/Editor/XmlExpandSelectionHandler.cs b/main/src/addins/Xml/Editor/XmlExpandSelectionHandler.cs
new file mode 100644
index 0000000000..e4b2e79341
--- /dev/null
+++ b/main/src/addins/Xml/Editor/XmlExpandSelectionHandler.cs
@@ -0,0 +1,384 @@
+//
+// Copyright (c) Microsoft Corp
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MonoDevelop.Ide.Editor;
+using MonoDevelop.Xml.Dom;
+using MonoDevelop.Xml.Parser;
+
+namespace MonoDevelop.Xml.Editor
+{
+ class XmlExpandSelectionHandler
+ {
+ public static bool CanExpandSelection (TextEditor editor)
+ {
+ if (!editor.IsSomethingSelected) {
+ return true;
+ }
+ if (editor.Selections.Count () == 1) {
+ return editor.SelectionRange.Offset > 0 || editor.SelectionRange.Length != editor.Length;
+ }
+ return false;
+ }
+
+ internal static void ExpandSelection (TextEditor editor, Func<XmlParser> getTreeParser)
+ {
+ var selectionAnnotation = GetAnnotation (editor, getTreeParser);
+ if (selectionAnnotation.NodePath.Count == 0)
+ return;
+
+ var newRegion = selectionAnnotation.Grow ();
+ if (newRegion.HasValue) {
+ editor.SetSelection (newRegion.Value.Begin, newRegion.Value.End);
+ }
+ }
+
+ public static bool CanShrinkSelection (TextEditor editor)
+ {
+ return editor.IsSomethingSelected && editor.Selections.Count () == 1;
+ }
+
+ internal static void ShrinkSelection (TextEditor editor, Func<XmlParser> getTreeParser)
+ {
+ var selectionAnnotation = GetAnnotation (editor, getTreeParser);
+ if (selectionAnnotation.NodePath.Count == 0)
+ return;
+
+ var newRegion = selectionAnnotation.Shrink ();
+ if (newRegion.HasValue) {
+ editor.SetSelection (newRegion.Value.Begin, newRegion.Value.End);
+ } else {
+ editor.ClearSelection ();
+ }
+ }
+
+ static XmlExpandSelectionAnnotation GetAnnotation (TextEditor editor, Func<XmlParser> getTreeParser)
+ {
+ var result = editor.Annotation<XmlExpandSelectionAnnotation> ();
+ if (result == null) {
+ result = new XmlExpandSelectionAnnotation (editor, getTreeParser ());
+ editor.AddAnnotation (result);
+ }
+ return result;
+ }
+
+ enum SelectionLevel
+ {
+ Self,
+ Name,
+ Content,
+ WithClosing,
+ Document,
+ Attributes
+ }
+
+ class XmlExpandSelectionAnnotation
+ {
+ Stack<(int, SelectionLevel)> expansions = new Stack<(int, SelectionLevel)> ();
+
+ readonly IReadonlyTextDocument document;
+ readonly TextEditor editor;
+ readonly XmlParser parser;
+ public List<XObject> NodePath { get; }
+ public int Index { get; set; } = -1;
+ public SelectionLevel Level { get; set; }
+
+ public XmlExpandSelectionAnnotation (TextEditor editor, XmlParser parser)
+ {
+ this.parser = parser;
+ this.editor = editor;
+ document = editor.CreateDocumentSnapshot ();
+ editor.CaretPositionChanged += Editor_CaretPositionChanged;
+ NodePath = GetNodePath (parser, document);
+ }
+
+ void Editor_CaretPositionChanged (object sender, EventArgs e)
+ {
+ editor.CaretPositionChanged -= Editor_CaretPositionChanged;
+ editor.RemoveAnnotations<XmlExpandSelectionAnnotation> ();
+ }
+
+ DocumentRegion? GetCurrent ()
+ {
+ if (Index < 0) {
+ return null;
+ }
+ var current = NodePath [Index];
+ switch (Level) {
+ case SelectionLevel.Self:
+ return current.Region;
+ case SelectionLevel.WithClosing:
+ var element = (XElement)current;
+ return new DocumentRegion (element.Region.Begin, element.ClosingTag.Region.End);
+ case SelectionLevel.Name:
+ return current.TryGetNameRegion ().Value;
+ case SelectionLevel.Content:
+ if (current is XElement el) {
+ return new DocumentRegion (el.Region.End, el.ClosingTag.Region.Begin);
+ }
+ return ((XAttribute)current).GetAttributeValueRegion (document);
+ case SelectionLevel.Document:
+ return new DocumentRegion (new DocumentLocation (1, 1), document.OffsetToLocation (document.Length));
+ case SelectionLevel.Attributes:
+ return ((XElement)current).GetAttributesRegion ();
+ }
+ throw new InvalidOperationException ();
+ }
+
+ public DocumentRegion? Grow ()
+ {
+ var old = (Index, Level);
+ if (GrowStateInternal ()) {
+ expansions.Push (old);
+ return GetCurrent ();
+ }
+ return null;
+ }
+
+ bool GrowStateInternal ()
+ {
+ if (Index + 1 == NodePath.Count) {
+ return false;
+ }
+
+ //if an index is selected, we may need to transition level rather than transitioning index
+ if (Index >= 0) {
+ var current = NodePath [Index];
+ if (current is XElement element) {
+ switch (Level) {
+ case SelectionLevel.Self:
+ if (!element.IsSelfClosing) {
+ Level = SelectionLevel.WithClosing;
+ return true;
+ }
+ break;
+ case SelectionLevel.Content:
+ Level = SelectionLevel.WithClosing;
+ return true;
+ case SelectionLevel.Name:
+ Level = SelectionLevel.Self;
+ return true;
+ case SelectionLevel.Attributes:
+ Level = SelectionLevel.Self;
+ return true;
+ }
+ } else if (current is XAttribute att) {
+ switch (Level) {
+ case SelectionLevel.Name:
+ case SelectionLevel.Content:
+ Level = SelectionLevel.Self;
+ return true;
+ }
+ } else if (Level == SelectionLevel.Name) {
+ Level = SelectionLevel.Self;
+ return true;
+ } else if (Level == SelectionLevel.Document) {
+ return false;
+ }
+ }
+
+ //advance up the node path
+ Index++;
+ var newNode = NodePath [Index];
+
+ //determine the starting selection level for the new node
+ if (newNode is XDocument) {
+ Level = SelectionLevel.Document;
+ return true;
+ }
+
+ AdvanceUntilClosed (newNode, parser, document);
+
+ if (newNode.Region.ContainsOuter (editor.CaretLocation)) {
+ var nr = newNode.TryGetNameRegion ();
+ if (nr != null && nr.Value.ContainsOuter (editor.CaretLocation)) {
+ Level = SelectionLevel.Name;
+ return true;
+ }
+ if (newNode is XAttribute attribute) {
+ var valRegion = attribute.GetAttributeValueRegion (document);
+ if (valRegion.ContainsOuter (editor.CaretLocation)) {
+ Level = SelectionLevel.Content;
+ return true;
+ }
+ }
+ if (newNode is XElement xElement && xElement.Attributes.Count > 1) {
+ var attsRegion = xElement.GetAttributesRegion ();
+ if (attsRegion.ContainsOuter (editor.CaretLocation)) {
+ Level = SelectionLevel.Attributes;
+ return true;
+ }
+ }
+ Level = SelectionLevel.Self;
+ return true;
+ }
+
+ if (newNode is XElement el) {
+ if (el.IsSelfClosing) {
+ Level = SelectionLevel.Self;
+ return true;
+ }
+ if (el.ClosingTag.Region.ContainsOuter (editor.CaretLocation)) {
+ Level = SelectionLevel.WithClosing;
+ return true;
+ }
+ Level = SelectionLevel.Content;
+ return true;
+ }
+
+ Level = SelectionLevel.Self;
+ return true;
+ }
+
+ public DocumentRegion? Shrink ()
+ {
+ // if we have expansion state, pop it
+ if (expansions.Count > 0) {
+ var last = expansions.Pop ();
+ Index = last.Item1;
+ Level = last.Item2;
+ return GetCurrent ();
+ }
+
+ return null;
+ }
+
+ //advance the parser in chunks until the given node is complete
+ static void AdvanceUntilClosed (XObject ob, XmlParser parser, IReadonlyTextDocument document)
+ {
+ const int chunk = 200;
+ var el = ob as XElement;
+ while (parser.Position < document.Length) {
+ parser.Parse (document.CreateReader (parser.Position, Math.Min (document.Length - parser.Position, chunk)));
+ if (el?.IsClosed ?? ob.IsEnded || !parser.Nodes.Contains (ob.Parent)) {
+ break;
+ }
+ }
+ }
+
+ static List<XObject> GetNodePath (XmlParser parser, IReadonlyTextDocument document)
+ {
+ int offset = parser.Position;
+ var length = document.Length;
+ int i = offset;
+
+ var nodePath = parser.Nodes.ToList ();
+
+ //if inside body of unclosed element, capture whole body
+ if (parser.CurrentState is XmlRootState && parser.Nodes.Peek () is XElement unclosedEl) {
+ while (i < length && InRootOrClosingTagState () && !unclosedEl.IsClosed) {
+ parser.Push (document.GetCharAt (i++));
+ }
+ }
+
+ //if in potential start of a state, capture it
+ else if (parser.CurrentState is XmlRootState && GetStateTag() > 0) {
+ //eat until we figure out whether it's a state transition
+ while (i < length && GetStateTag () > 0) {
+ parser.Push (document.GetCharAt (i++));
+ }
+ //if it transitioned to another state, eat until we get a new node on the stack
+ if (NotInRootState ()) {
+ var newState = parser.CurrentState;
+ while (i < length && NotInRootState() && parser.Nodes.Count <= nodePath.Count) {
+ parser.Push (document.GetCharAt (i++));
+ }
+ if (parser.Nodes.Count > nodePath.Count) {
+ nodePath.Insert (0, parser.Nodes.Peek ());
+ }
+ }
+ }
+
+ //ensure any unfinished names are captured
+ while (i < length && InNameOrAttributeState ()) {
+ parser.Push (document.GetCharAt (i++));
+ }
+
+ //if nodes are incomplete, they won't get connected
+ //HACK: the only way to reconnect them is reflection
+ if (nodePath.Count > 1) {
+ for (int idx = 0; idx < nodePath.Count - 1; idx++) {
+ var node = nodePath [idx];
+ if (node.Parent == null) {
+ var parent = nodePath [idx + 1];
+ node.Parent = parent;
+ }
+ }
+ }
+
+ return nodePath;
+
+ bool InNameOrAttributeState () =>
+ parser.CurrentState is XmlNameState
+ || parser.CurrentState is XmlAttributeState
+ || parser.CurrentState is XmlAttributeValueState;
+
+ bool InRootOrClosingTagState () =>
+ parser.CurrentState is XmlRootState
+ || parser.CurrentState is XmlNameState
+ || parser.CurrentState is XmlClosingTagState;
+
+ int GetStateTag () => ((IXmlParserContext)parser).StateTag;
+
+ bool NotInRootState () => !(parser.CurrentState is XmlRootState);
+ }
+ }
+ }
+
+ internal static class XmlExtensions
+ {
+ public static DocumentRegion? TryGetNameRegion (this XObject xobject)
+ {
+ if (xobject is XElement element) {
+ var r = element.Region;
+ return new DocumentRegion (r.BeginLine, r.BeginColumn + 1, r.BeginLine, r.BeginColumn + 1 + element.Name.FullName.Length);
+ }
+ if (xobject is XClosingTag closingTag) {
+ var r = closingTag.Region;
+ return new DocumentRegion (r.BeginLine, r.BeginColumn + 2, r.BeginLine, r.BeginColumn + 2 + closingTag.Name.FullName.Length);
+ }
+ if (xobject is XAttribute attribute) {
+ var r = attribute.Region;
+ return new DocumentRegion (r.BeginLine, r.BeginColumn, r.BeginLine, r.BeginColumn + attribute.Name.FullName.Length);
+ }
+ return null;
+ }
+
+ public static DocumentRegion GetAttributeValueRegion (this XAttribute att, IReadonlyTextDocument doc)
+ {
+ int endOffset = doc.LocationToOffset (att.Region.End) - 1;
+ int startOffset = endOffset - att.Value.Length;
+ return new DocumentRegion (doc.OffsetToLocation (startOffset), doc.OffsetToLocation (endOffset));
+ }
+
+ public static DocumentRegion GetAttributesRegion (this XElement element)
+ {
+ return new DocumentRegion (element.Attributes.First.Region.Begin, element.Attributes.Last.Region.End);
+ }
+
+ public static bool ContainsOuter (this DocumentRegion region, DocumentLocation location)
+ {
+ return region.Begin <= location && location <= region.End;
+ }
+ }
+}
diff --git a/main/src/addins/Xml/MonoDevelop.Xml.csproj b/main/src/addins/Xml/MonoDevelop.Xml.csproj
index 54121aa38d..5859c6720c 100644
--- a/main/src/addins/Xml/MonoDevelop.Xml.csproj
+++ b/main/src/addins/Xml/MonoDevelop.Xml.csproj
@@ -176,6 +176,7 @@
<Compile Include="Completion\XmlElementPath.cs" />
<Compile Include="Editor\XmlCommands.cs" />
<Compile Include="Editor\EncodedStringWriter.cs" />
+ <Compile Include="Editor\XmlExpandSelectionHandler.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
diff --git a/main/src/addins/Xml/Tests/ExpandSelectionTests.cs b/main/src/addins/Xml/Tests/ExpandSelectionTests.cs
new file mode 100644
index 0000000000..5cc43fc238
--- /dev/null
+++ b/main/src/addins/Xml/Tests/ExpandSelectionTests.cs
@@ -0,0 +1,160 @@
+//
+// ExpandSelectionTests.cs
+//
+// Author:
+// Mikayla Hutchinson <m.j.hutchinson@gmail.com>
+//
+// Copyright (c) 2018 Microsoft Corp
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using MonoDevelop.Ide;
+using MonoDevelop.Ide.Editor;
+using MonoDevelop.Ide.Editor.Extension;
+using MonoDevelop.Xml.Editor;
+using NUnit.Framework;
+
+namespace MonoDevelop.Xml.Tests
+{
+ [TestFixture]
+ public class ExpandSelectionTests : TextEditorExtensionTestBase
+ {
+ public static EditorExtensionTestData XmlContentData = new EditorExtensionTestData (
+ fileName: "/a.xml",
+ language: "C#",
+ mimeType: "application/xml",
+ projectFileName: "test.csproj"
+ );
+
+ const string Document = @"<!-- this is
+a comment-->
+<foo hello=""hi"" goodbye=""bye"">
+ this is some text
+ <bar><baz thing=""done"" />
+ <!--another comment-->
+ </bar>
+</foo>
+";
+ const string CommentDoc = @"<!-- this is
+a comment-->";
+
+ const string ElementFoo = @"<foo hello=""hi"" goodbye=""bye"">";
+
+ const string ElementWithBodyFoo = @"<foo hello=""hi"" goodbye=""bye"">
+ this is some text
+ <bar><baz thing=""done"" />
+ <!--another comment-->
+ </bar>
+</foo>";
+
+ const string AttributesFoo = @"hello=""hi"" goodbye=""bye""";
+
+ const string AttributeHello = @"hello=""hi""";
+
+ const string AttributeGoodbye = @"goodbye=""bye""";
+
+ const string BodyFoo = @"
+ this is some text
+ <bar><baz thing=""done"" />
+ <!--another comment-->
+ </bar>
+";
+ const string ElementBar = @"<bar>";
+
+ const string ElementWithBodyBar = @"<bar><baz thing=""done"" />
+ <!--another comment-->
+ </bar>";
+
+ const string BodyBar = @"<baz thing=""done"" />
+ <!--another comment-->
+ ";
+
+ const string ElementBaz= @"<baz thing=""done"" />";
+
+ const string AttributeThing = @"thing=""done""";
+
+ const string CommentBar = @"<!--another comment-->";
+
+ protected override EditorExtensionTestData GetContentData () => XmlContentData;
+
+ protected override IEnumerable<TextEditorExtension> GetEditorExtensions ()
+ {
+ yield return new XmlTextEditorExtension ();
+ }
+
+ //args are line, col, then the expected sequence of expansions
+ [Test]
+ [TestCase (1, 2, CommentDoc)]
+ [TestCase (3, 2, "foo", ElementFoo, ElementWithBodyFoo)]
+ [TestCase (3, 3, "foo", ElementFoo, ElementWithBodyFoo)]
+ [TestCase (3, 15, "hi", AttributeHello, AttributesFoo, ElementFoo, ElementWithBodyFoo)]
+ [TestCase (3, 7, "hello", AttributeHello, AttributesFoo, ElementFoo, ElementWithBodyFoo)]
+ [TestCase (4, 7, BodyFoo, ElementWithBodyFoo)]
+ [TestCase (5, 22, "done", AttributeThing, ElementBaz, BodyBar, ElementWithBodyBar, BodyFoo, ElementWithBodyFoo)]
+ [TestCase (6, 12, CommentBar, BodyBar, ElementWithBodyBar, BodyFoo, ElementWithBodyFoo)]
+ public async Task TestExpandShrink (object[] args)
+ {
+ var loc = new DocumentLocation ((int)args [0], (int)args[1]);
+ using (var testCase = await SetupTestCase (Document)) {
+ var doc = testCase.Document;
+ doc.Editor.SetCaretLocation (loc);
+ var ext = doc.GetContent<BaseXmlEditorExtension> ();
+
+ //check initial state
+ Assert.IsFalse (doc.Editor.IsSomethingSelected);
+ Assert.AreEqual (loc, doc.Editor.CaretLocation);
+
+ //check expanding causes correct selections
+ for (int i = 2; i < args.Length; i++) {
+ ext.ExpandSelection ();
+ Assert.AreEqual (args [i], doc.Editor.SelectedText);
+ }
+
+ //check entire doc is selected
+ ext.ExpandSelection ();
+ var sel = doc.Editor.SelectionRange;
+ Assert.AreEqual (0, sel.Offset);
+ Assert.AreEqual (Document.Length, sel.Length);
+
+ //check expanding again does not change it
+ ext.ExpandSelection ();
+ Assert.AreEqual (0, sel.Offset);
+ Assert.AreEqual (Document.Length, sel.Length);
+
+ //check shrinking causes correct selections
+ for (int i = args.Length - 1; i >= 2; i--) {
+ ext.ShrinkSelection ();
+ Assert.AreEqual (args [i], doc.Editor.SelectedText);
+ }
+
+ //final shrink back to a caret
+ ext.ShrinkSelection ();
+ Assert.IsFalse (doc.Editor.IsSomethingSelected);
+ Assert.AreEqual (loc, doc.Editor.CaretLocation);
+
+ //check shrinking again does not change it
+ ext.ShrinkSelection ();
+ Assert.IsFalse (doc.Editor.IsSomethingSelected);
+ Assert.AreEqual (loc, doc.Editor.CaretLocation);
+ }
+ }
+ }
+}
diff --git a/main/src/addins/Xml/Tests/MonoDevelop.Xml.Tests.csproj b/main/src/addins/Xml/Tests/MonoDevelop.Xml.Tests.csproj
index 3484ee1fa2..95af752b1d 100644
--- a/main/src/addins/Xml/Tests/MonoDevelop.Xml.Tests.csproj
+++ b/main/src/addins/Xml/Tests/MonoDevelop.Xml.Tests.csproj
@@ -32,6 +32,14 @@
<Name>MonoDevelop.Xml</Name>
<Private>False</Private>
</ProjectReference>
+ <ProjectReference Include="..\..\..\..\tests\IdeUnitTests\IdeUnitTests.csproj">
+ <Project>{F7B2B155-7CF4-42C4-B5AF-63C0667D2E4F}</Project>
+ <Name>IdeUnitTests</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\..\tests\UnitTests\UnitTests.csproj">
+ <Project>{1497D0A8-AFF1-4938-BC22-BE79B358BA5B}</Project>
+ <Name>UnitTests</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
@@ -105,6 +113,7 @@
<Compile Include="Formatting\XmlFormatterTests.cs" />
<Compile Include="Schema\SchemaValidationTests.cs" />
<Compile Include="Utils\XsltTransformTest.cs" />
+ <Compile Include="ExpandSelectionTests.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\schemas\XMLSchema.xsd">