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

github.com/microsoft/vs-editor-api.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/Text/Def/TextUI/MultiCaret/Selection.cs')
-rw-r--r--src/Text/Def/TextUI/MultiCaret/Selection.cs313
1 files changed, 313 insertions, 0 deletions
diff --git a/src/Text/Def/TextUI/MultiCaret/Selection.cs b/src/Text/Def/TextUI/MultiCaret/Selection.cs
new file mode 100644
index 0000000..7f27834
--- /dev/null
+++ b/src/Text/Def/TextUI/MultiCaret/Selection.cs
@@ -0,0 +1,313 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+using System;
+
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Manages the insertion, anchor, and active points for a single caret and its associated
+ /// selection.
+ /// </summary>
+ public struct Selection : IEquatable<Selection>
+ {
+ /// <summary>
+ /// A static instance of a selection that is invalid and can be used to check for instantiation.
+ /// </summary>
+ public static readonly Selection Invalid = new Selection();
+
+ /// <summary>
+ /// Instantiates a new Selection with a zero-width extent at the provided insertion point.
+ /// </summary>
+ /// <param name="insertionPoint">The location where a caret should be rendered and edits performed.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ public Selection(VirtualSnapshotPoint insertionPoint, PositionAffinity insertionPointAffinity = PositionAffinity.Successor)
+ : this(insertionPoint, insertionPoint, insertionPoint, insertionPointAffinity)
+ { }
+
+ /// <summary>
+ /// Instantiates a new Selection with a zero-width extent at the provided insertion point.
+ /// </summary>
+ /// <param name="insertionPoint">The location where a caret should be rendered and edits performed.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ public Selection(SnapshotPoint insertionPoint, PositionAffinity insertionPointAffinity = PositionAffinity.Successor)
+ : this(new VirtualSnapshotPoint(insertionPoint),
+ new VirtualSnapshotPoint(insertionPoint),
+ new VirtualSnapshotPoint(insertionPoint),
+ insertionPointAffinity)
+ { }
+
+ /// <summary>
+ /// Instantiates a new Selection with the given extent. Anchor and active points are defined by isReversed, and the
+ /// insertion point is located at the active point.
+ /// </summary>
+ /// <param name="extent">The span that the selection covers.</param>
+ /// <param name="isReversed">
+ /// True implies that <see cref="ActivePoint"/> comes before <see cref="AnchorPoint"/>.
+ /// The <see cref="InsertionPoint"/> is set to the <see cref="ActivePoint"/>.
+ /// <see cref="InsertionPointAffinity"/> is set to <see cref="PositionAffinity.Predecessor"/> when isReversed is true.
+ /// <see cref="PositionAffinity.Successor"/> otherwise.
+ /// </param>
+ public Selection(VirtualSnapshotSpan extent, bool isReversed = false)
+ {
+ if (isReversed)
+ {
+ AnchorPoint = extent.End;
+ ActivePoint = InsertionPoint = extent.Start;
+ InsertionPointAffinity = PositionAffinity.Successor;
+ }
+ else
+ {
+ AnchorPoint = extent.Start;
+ ActivePoint = InsertionPoint = extent.End;
+
+ // The goal here is to keep the caret with the selection box. If we're wordwrapped, and the
+ // box is at the end of a line, Predecessor will keep the caret on the previous line.
+ InsertionPointAffinity = PositionAffinity.Predecessor;
+ }
+ }
+
+ /// <summary>
+ /// Instantiates a new Selection with the given extent. Anchor and active points are defined by isReversed, and the
+ /// insertion point is located at the active point.
+ /// </summary>
+ /// <param name="extent">The span that the selection covers.</param>
+ /// <param name="isReversed">
+ /// True implies that <see cref="ActivePoint"/> comes before <see cref="AnchorPoint"/>.
+ /// The <see cref="InsertionPoint"/> is set to the <see cref="ActivePoint"/>.
+ /// <see cref="InsertionPointAffinity"/> is set to <see cref="PositionAffinity.Predecessor"/> when isReversed is true.
+ /// <see cref="PositionAffinity.Successor"/> otherwise.
+ /// </param>
+ public Selection(SnapshotSpan extent, bool isReversed = false)
+ : this(new VirtualSnapshotSpan(extent), isReversed)
+ { }
+
+ /// <summary>
+ /// Instantiates a new Selection with the given anchor and active points, and the
+ /// insertion point is located at the active point.
+ /// </summary>
+ /// <param name="anchorPoint">The location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is.</param>
+ /// <param name="activePoint">location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click.</param>
+ public Selection(VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint)
+ : this(insertionPoint: activePoint,
+ anchorPoint: anchorPoint,
+ activePoint: activePoint,
+ insertionPointAffinity: (anchorPoint < activePoint) ? PositionAffinity.Predecessor : PositionAffinity.Successor)
+ {
+ }
+
+ /// <summary>
+ /// Instantiates a new Selection with the given anchor and active points, and the
+ /// insertion point is located at the active point.
+ /// </summary>
+ /// <param name="anchorPoint">The location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is.</param>
+ /// <param name="activePoint">location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click.</param>
+ public Selection(SnapshotPoint anchorPoint, SnapshotPoint activePoint)
+ : this(anchorPoint: new VirtualSnapshotPoint(anchorPoint),
+ activePoint: new VirtualSnapshotPoint(activePoint))
+ {
+ }
+
+ /// <summary>
+ /// Instantiates a new Selection.
+ /// </summary>
+ /// <param name="insertionPoint">The location where a caret should be rendered and edits performed.</param>
+ /// <param name="anchorPoint">The location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is.</param>
+ /// <param name="activePoint">location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ public Selection(VirtualSnapshotPoint insertionPoint,
+ VirtualSnapshotPoint anchorPoint,
+ VirtualSnapshotPoint activePoint,
+ PositionAffinity insertionPointAffinity = PositionAffinity.Successor)
+ {
+ if (insertionPoint.Position.Snapshot != anchorPoint.Position.Snapshot || insertionPoint.Position.Snapshot != activePoint.Position.Snapshot)
+ {
+ throw new ArgumentException("All points must be on the same snapshot.");
+ }
+
+ InsertionPoint = insertionPoint;
+ AnchorPoint = anchorPoint;
+ ActivePoint = activePoint;
+ InsertionPointAffinity = insertionPointAffinity;
+ }
+
+ /// <summary>
+ /// Instantiates a new Selection.
+ /// </summary>
+ /// <param name="insertionPoint">The location where a caret should be rendered and edits performed.</param>
+ /// <param name="anchorPoint">The location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is.</param>
+ /// <param name="activePoint">location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ public Selection(SnapshotPoint insertionPoint,
+ SnapshotPoint anchorPoint,
+ SnapshotPoint activePoint,
+ PositionAffinity insertionPointAffinity = PositionAffinity.Successor)
+ : this(new VirtualSnapshotPoint(insertionPoint),
+ new VirtualSnapshotPoint(anchorPoint),
+ new VirtualSnapshotPoint(activePoint),
+ insertionPointAffinity)
+ { }
+
+ /// <summary>
+ /// Gets whether this selection contains meaningful data.
+ /// </summary>
+ public bool IsValid
+ {
+ get
+ {
+ return this != Invalid && this.InsertionPoint.Position.Snapshot != null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the location where a caret should be rendered and edits performed.
+ /// </summary>
+ public VirtualSnapshotPoint InsertionPoint { get; }
+
+ /// <summary>
+ /// Gets the location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is. If this is an empty selection, this will be at the
+ /// <see cref="InsertionPoint"/>.
+ /// </summary>
+ public VirtualSnapshotPoint AnchorPoint { get; }
+
+ /// <summary>
+ /// Gets the location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click. If this is an empty selection, this will be at the
+ /// <see cref="InsertionPoint"/>.
+ /// </summary>
+ public VirtualSnapshotPoint ActivePoint { get; }
+
+ /// <summary>
+ /// Gets the affinity of the insertion point.
+ /// This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </summary>
+ public PositionAffinity InsertionPointAffinity { get; }
+
+ /// <summary>
+ /// True if <see cref="AnchorPoint"/> is later in the document than <see cref="ActivePoint"/>. False otherwise.
+ /// </summary>
+ public bool IsReversed
+ {
+ get
+ {
+ return ActivePoint < AnchorPoint;
+ }
+ }
+
+ /// <summary>
+ /// True if <see cref="AnchorPoint"/> equals <see cref="ActivePoint"/>. False otherwise.
+ /// </summary>
+ public bool IsEmpty
+ {
+ get
+ {
+ return ActivePoint == AnchorPoint;
+ }
+ }
+
+ /// <summary>
+ /// Returns the smaller of <see cref="ActivePoint"/> and <see cref="AnchorPoint"/>.
+ /// </summary>
+ public VirtualSnapshotPoint Start
+ {
+ get
+ {
+ return IsReversed ? ActivePoint : AnchorPoint;
+ }
+ }
+
+ /// <summary>
+ /// Returns the larger of <see cref="ActivePoint"/> and <see cref="AnchorPoint"/>.
+ /// </summary>
+ public VirtualSnapshotPoint End
+ {
+ get
+ {
+ return IsReversed ? AnchorPoint : ActivePoint;
+ }
+ }
+
+ /// <summary>
+ /// Returns the span from <see cref="Start"/> to <see cref="End"/>.
+ /// </summary>
+ public VirtualSnapshotSpan Extent
+ {
+ get
+ {
+ return new VirtualSnapshotSpan(Start, End);
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ // We are fortunate enough to have 3 interesting points here. If you xor an even number of snapshot point hashcodes
+ // together, the snapshot component gets cancelled out.
+
+ // However, the common case is that ActivePoint and InsertionPoint are exactly equal, so we need to do something to change that.
+ // Invert the bytes in InsertionPoint.GetHashCode().
+ var insertionHash = (uint)InsertionPoint.GetHashCode();
+ insertionHash = (((0x0000FFFF & insertionHash) << 16) | ((0xFFFF0000 & insertionHash) >> 16));
+
+ int pointHashes = AnchorPoint.GetHashCode() ^ ActivePoint.GetHashCode() ^ (int)insertionHash;
+
+ // InsertionPointAffinity.GetHashCode() returns either 0 or 1 which can get stomped on by the rest of the hash codes.
+ // Generate more interesting hash code values below:
+ int affinityHash = InsertionPointAffinity == PositionAffinity.Predecessor
+ ? affinityHash = 04122013
+ : affinityHash = 10172014;
+
+ return pointHashes ^ affinityHash;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is Selection && Equals((Selection)obj);
+ }
+
+ public bool Equals(Selection other)
+ {
+ return this.ActivePoint == other.ActivePoint
+ && this.AnchorPoint == other.AnchorPoint
+ && this.InsertionPoint == other.InsertionPoint
+ && this.InsertionPointAffinity == other.InsertionPointAffinity;
+ }
+
+ public static bool operator ==(Selection left, Selection right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Selection left, Selection right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override string ToString()
+ {
+ return $"Ins:{InsertionPoint} Anc:{AnchorPoint} Act:{ActivePoint} Aff:{InsertionPointAffinity}";
+ }
+ }
+}