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:
authorKirill Osenkov <github@osenkov.com>2018-08-15 21:43:04 +0300
committerKirill Osenkov <github@osenkov.com>2018-08-15 21:43:04 +0300
commit21b22d2687687c4013d8e7873dd515518b06b386 (patch)
tree087647d48385e1639f41fe49a9405526a175f465
parent419aee36d47be0a340a04b55476b2ab9b4cf1b3b (diff)
Add SelectionState.cs and ExtensionMethods.cs.
-rw-r--r--src/Text/Util/TextUIUtil/ExtensionMethods.cs143
-rw-r--r--src/Text/Util/TextUIUtil/SelectionState.cs151
2 files changed, 294 insertions, 0 deletions
diff --git a/src/Text/Util/TextUIUtil/ExtensionMethods.cs b/src/Text/Util/TextUIUtil/ExtensionMethods.cs
new file mode 100644
index 0000000..c791f15
--- /dev/null
+++ b/src/Text/Util/TextUIUtil/ExtensionMethods.cs
@@ -0,0 +1,143 @@
+using System;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Formatting;
+using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
+
+namespace Microsoft.VisualStudio.Text.MultiSelection
+{
+ public static class ExtensionMethods
+ {
+ public static VirtualSnapshotPoint NormalizePoint(this ITextView view, VirtualSnapshotPoint point)
+ {
+ var line = view.GetTextViewLineContainingBufferPosition(point.Position);
+
+ //If point is at the end of the line, return it (including any virtual space offset)
+ if (point.Position >= line.End)
+ {
+ return new VirtualSnapshotPoint(line.End, point.VirtualSpaces);
+ }
+ else
+ {
+ //Otherwise align it with the begining of the containing text element &
+ //return that (losing any virtual space).
+ SnapshotSpan element = line.GetTextElementSpan(point.Position);
+ return new VirtualSnapshotPoint(element.Start);
+ }
+ }
+
+ public static Selection MapToSnapshot(this Selection region, ITextSnapshot snapshot, ITextView view)
+ {
+ var newInsertion = view.NormalizePoint(region.InsertionPoint.TranslateTo(snapshot));
+ var newActive = view.NormalizePoint(region.ActivePoint.TranslateTo(snapshot));
+ var newAnchor = view.NormalizePoint(region.AnchorPoint.TranslateTo(snapshot));
+
+ return new Selection(newInsertion, newAnchor, newActive, region.InsertionPointAffinity);
+ }
+
+ /// <summary>
+ /// Remaps a given x-coordinate to a valid point. If the provided x-coordinate is past the right end of the line, it will
+ /// be clipped to the correct position depending on the virtual space settings. If the ISmartIndent is providing indentation
+ /// settings, the x-coordinate will be changed based on that.
+ /// </summary>
+ public static double MapXCoordinate(this ITextViewLine textLine, ITextView textView,
+ double xCoordinate, ISmartIndentationService smartIndentationService, bool userSpecifiedXCoordinate)
+ {
+ if (textLine == null)
+ {
+ throw new ArgumentNullException(nameof(textLine));
+ }
+
+ if (textView == null)
+ {
+ throw new ArgumentNullException(nameof(textView));
+ }
+
+ // if the clicked point is to the right of the text and virtual space is disabled, the coordinate
+ // needs to be fixed
+ if ((xCoordinate > textLine.TextRight) && !textView.IsVirtualSpaceOrBoxSelectionEnabled())
+ {
+ double indentationWidth = 0.0;
+
+ // ask the ISmartIndent to see if any indentation is necessary for empty lines
+ if (textLine.End == textLine.Start)
+ {
+ int? indentation = smartIndentationService?.GetDesiredIndentation(textView, textLine.Start.GetContainingLine());
+ if (indentation.HasValue)
+ {
+ //The indentation specified by the smart indent service is desired column position of the caret. Find out how much virtual space
+ //need to be at the end of the line to satisfy that.
+ // TODO: need a way to determine column width in xplat scenarios, bug https://devdiv.visualstudio.com/DevDiv/_workitems/edit/637741
+ double columnWidth = 7;
+ indentationWidth = Math.Max(0.0, (((double)indentation.Value) * columnWidth - textLine.TextWidth));
+
+ // if the coordinate is specified by the user and the user has selected a coordinate to the left
+ // of the indentation suggested by ISmartIndent, overrule the ISmartIndent provided value and
+ // do not use any indentation.
+ if (userSpecifiedXCoordinate && (xCoordinate < (textLine.TextRight + indentationWidth)))
+ indentationWidth = 0.0;
+ }
+ }
+
+ xCoordinate = textLine.TextRight + indentationWidth;
+ }
+
+ return xCoordinate;
+ }
+
+ /// <summary>
+ /// If you are looking at this, you're likely maintaining selection code, and should be aware that
+ /// virtual whitespace allowances are not simply checking a flag.
+ ///
+ /// When dealing with virtual whitespace we have 3 major considerations:
+ /// 1) Is the editor option enabled that allows arbitrary virtual whitespace navigation?
+ /// 2) Is the current selection a box selection?
+ /// 3) Are we at the beginning of a line that is impacted by Auto-Indent.
+ ///
+ /// This method ignores the 3rd element, since the virtual whitespace added there is not usually based
+ /// on the previous whitespace, but on the auto-indent. This method is a convienence method that will return
+ /// whether either of the first two conditions apply, and should be used anywhere arbitrary virtual whitespace
+ /// is an option.
+ /// </summary>
+ public static bool IsVirtualSpaceOrBoxSelectionEnabled(this ITextView textView)
+ {
+ return textView.Options.IsVirtualSpaceEnabled() || textView.GetMultiSelectionBroker().IsBoxSelection;
+ }
+
+ public static bool TryGetClosestTextViewLine(this ITextView textView, double yCoordinate, out ITextViewLine closestLine)
+ {
+ if (textView == null)
+ {
+ throw new ArgumentNullException(nameof(textView));
+ }
+
+ if (textView.IsClosed || textView.InLayout)
+ {
+ closestLine = null;
+ return false;
+ }
+
+ ITextViewLine textLine = null;
+
+ ITextViewLineCollection textLines = textView.TextViewLines;
+
+ if (textLines != null && textLines.Count > 0)
+ {
+ textLine = textLines.GetTextViewLineContainingYCoordinate(yCoordinate);
+
+ if (textLine == null)
+ {
+ if (yCoordinate <= textLines.FirstVisibleLine.Bottom)
+ textLine = textLines.FirstVisibleLine;
+ else if (yCoordinate >= textLines.LastVisibleLine.Top)
+ textLine = textLines.LastVisibleLine;
+ }
+
+ closestLine = textLine;
+ return true;
+ }
+
+ closestLine = null;
+ return false;
+ }
+ }
+}
diff --git a/src/Text/Util/TextUIUtil/SelectionState.cs b/src/Text/Util/TextUIUtil/SelectionState.cs
new file mode 100644
index 0000000..0499b88
--- /dev/null
+++ b/src/Text/Util/TextUIUtil/SelectionState.cs
@@ -0,0 +1,151 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+// This file contain implementations details that are subject to change without notice.
+// Use at your own risk.
+//
+namespace Microsoft.VisualStudio.Text.Operations
+{
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Editor;
+
+ public class SelectionState
+ {
+ private readonly SingleSelection[] _selections;
+ private readonly SingleSelection _primary;
+ private bool _isBox;
+
+ public SelectionState(ITextView view)
+ {
+ var selectionBroker = view.GetMultiSelectionBroker();
+ var map = SelectionState.EditToDataMap(view);
+
+ if (selectionBroker.IsBoxSelection)
+ {
+ _primary = new SingleSelection(map, selectionBroker.BoxSelection);
+ _isBox = true;
+ }
+ else
+ {
+ if (selectionBroker.HasMultipleSelections)
+ {
+ var selections = selectionBroker.AllSelections;
+ _selections = new SingleSelection[selections.Count];
+ for (int i = 0; (i < _selections.Length); ++i)
+ {
+ _selections[i] = new SingleSelection(map, selections[i]);
+ }
+ }
+
+ _primary = new SingleSelection(map, selectionBroker.PrimarySelection);
+ }
+ }
+
+ public void Restore(ITextView view)
+ {
+ var selectionBroker = view.GetMultiSelectionBroker();
+ var map = SelectionState.EditToDataMap(view);
+
+ if (_isBox)
+ {
+ selectionBroker.SetBoxSelection(_primary.Rehydrate(map, selectionBroker.CurrentSnapshot));
+ }
+ else
+ {
+ Selection[] rehydradedSelections = null;
+ if (_selections != null)
+ {
+ rehydradedSelections = new Selection[_selections.Length];
+ for (int i = 0; (i < _selections.Length); ++i)
+ {
+ rehydradedSelections[i] = _selections[i].Rehydrate(map, selectionBroker.CurrentSnapshot);
+ }
+ }
+
+ selectionBroker.SetSelectionRange(rehydradedSelections, _primary.Rehydrate(map, selectionBroker.CurrentSnapshot));
+ }
+ }
+
+ public bool Matches(SelectionState other)
+ {
+ if ((_isBox == other._isBox) && _primary.Matches(other._primary))
+ {
+ if (_selections != null)
+ {
+ if ((other._selections != null) && (_selections.Length == other._selections.Length))
+ {
+ for (int i = 0; (i < _selections.Length); ++i)
+ {
+ if (!_selections[i].Matches(other._selections[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+ else if (other._selections == null)
+ return true;
+ }
+
+ return false;
+ }
+
+ public static IMapEditToData EditToDataMap(ITextView view)
+ {
+ return (view.TextViewModel.EditBuffer != view.TextViewModel.DataBuffer) && view.Properties.TryGetProperty(typeof(IMapEditToData), out IMapEditToData map) && (map != null)
+ ? map
+ : VacuousMapToEdit.Identity;
+ }
+
+ struct SingleSelection
+ {
+ public readonly VirtualPoint Anchor;
+ public readonly VirtualPoint Active;
+ public readonly VirtualPoint Insertion;
+ public readonly PositionAffinity Affinity;
+
+ public SingleSelection(IMapEditToData map, Selection selection)
+ {
+ this.Anchor = new VirtualPoint(map, selection.AnchorPoint);
+ this.Active = new VirtualPoint(map, selection.ActivePoint);
+ this.Insertion = new VirtualPoint(map, selection.InsertionPoint);
+ this.Affinity = selection.InsertionPointAffinity;
+ }
+
+ public Selection Rehydrate(IMapEditToData map, ITextSnapshot snapshot)
+ {
+ return new Selection(this.Insertion.Rehydrate(map, snapshot),
+ this.Anchor.Rehydrate(map, snapshot),
+ this.Active.Rehydrate(map, snapshot),
+ this.Affinity);
+ }
+
+ public bool Matches(SingleSelection other)
+ {
+ return this.Anchor.Matches(other.Anchor) && this.Active.Matches(other.Active) && this.Insertion.Matches(other.Insertion) && (this.Affinity == other.Affinity);
+ }
+ }
+
+ struct VirtualPoint
+ {
+ public readonly int Position;
+ public readonly int VirtualSpaces;
+ public VirtualPoint(IMapEditToData map, VirtualSnapshotPoint point) { this.Position = map.MapEditToData(point.Position); this.VirtualSpaces = point.VirtualSpaces; }
+
+ public VirtualSnapshotPoint Rehydrate(IMapEditToData map, ITextSnapshot snapshot) => new VirtualSnapshotPoint(new SnapshotPoint(snapshot, map.MapDataToEdit(this.Position)), this.VirtualSpaces);
+
+ public bool Matches(VirtualPoint other) => (this.Position == other.Position) && (this.VirtualSpaces == other.VirtualSpaces);
+ }
+
+ private class VacuousMapToEdit : IMapEditToData
+ {
+ public static readonly IMapEditToData Identity = new VacuousMapToEdit();
+
+ public int MapDataToEdit(int dataPoint) => dataPoint;
+ public int MapEditToData(int editPoint) => editPoint;
+ }
+ }
+}