From 0af3f648cb6c179a495b2102bb9527bd9bfe3fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Foghammar=20N=C3=B5mtak?= Date: Tue, 21 Feb 2023 18:21:55 +0100 Subject: [PATCH 1/2] Rework DuplicateSelection to fix bugs --- HotCommands/Commands/DuplicateSelection.cs | 146 ++++++++++++++------- 1 file changed, 96 insertions(+), 50 deletions(-) diff --git a/HotCommands/Commands/DuplicateSelection.cs b/HotCommands/Commands/DuplicateSelection.cs index 58400b2..7fbee98 100644 --- a/HotCommands/Commands/DuplicateSelection.cs +++ b/HotCommands/Commands/DuplicateSelection.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Windows; using Microsoft.VisualStudio; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; @@ -52,66 +51,71 @@ public static int HandleCommand_DuplicateLines(IWpfTextView textView, IClassifie ITextBufferUndoManager undoManager = undoManagerProvider.GetTextBufferUndoManager(textView.TextBuffer); ITextUndoTransaction transaction = undoManager.TextBufferUndoHistory.CreateTransaction("Duplicate Lines"); - List spans = textView.Selection.SelectedSpans.ToList(); - spans.Reverse(); // Hack: Work from the last selection upward, to avoid changing buffer positions with mutli-caret - foreach (SnapshotSpan span in spans) + // This bool is to handle an annoying edge case where the selection would be expanded + // by the inserted text because the insertion happens at the end of the selection. + bool isEdgeCase_InsertExpandsSelection = false; + + IMultiSelectionBroker broker = textView.GetMultiSelectionBroker(); + IReadOnlyList selections = broker.AllSelections; + Selection primarySelection = broker.PrimarySelection; + // Hack: Work from the last selection upward, to avoid changing buffer positions with mutli-caret + for (int i = selections.Count - 1; i >= 0; i--) { - // Select all the text from the start of the first line to the end of the last line - // Find the start of the first line - SnapshotPoint startPoint = new SnapshotPoint(span.Snapshot, span.Start); - SnapshotPoint startOfFirstLine = startPoint.GetContainingLine().Start; - - // Find the end of the last line - SnapshotPoint endPoint = new SnapshotPoint(span.Snapshot, span.End); - SnapshotPoint endOfLastLine = endPoint.GetContainingLine().End; - // Don't include the last line if the end point is at the very beginning! - bool endsAtLineStart = span.Length > 0 && (endPoint.GetContainingLine().Start.Position == endPoint.Position); - bool endsAtLineEnd = span.Length > 0 && (endPoint.GetContainingLine().End.Position == endPoint.Position); - if (endsAtLineStart) - { - // Return the text up to the actual endpoint, not the end of its line. - endOfLastLine = endPoint; - // Note: This means that this text contains a CRLF. Account for that later. - } + Selection selection = selections[i]; + + SnapshotSpan selectionSpan = selection.Extent.SnapshotSpan; + SnapshotSpan selectedLines = GetContainingLines(selectionSpan); + string textToInsert = selectedLines.GetText(); - // Fetch the text from the start of first to end of last - SnapshotSpan linesToCopy = new SnapshotSpan(startOfFirstLine, endOfLastLine); - string text = linesToCopy.GetText(); + bool isEndOfFile = selectedLines.End == textView.TextSnapshot.Length; - // Copy Lines Up? or Copy Lines Down? - if (isCopyUp) // ie. CopyLinesUp + SnapshotPoint insertPos; + if (isCopyUp) { - // Always start with a new line (CR/LF) - if (!endsAtLineStart) - { - text = Environment.NewLine + text; // Note: This does not detect the line endings of the current file. - } + insertPos = selectedLines.End; - // Insert the text on a new line after the last line - TODO (CopyLinesUp) - int insertPosn = endOfLastLine.Position; - textView.TextBuffer.Insert(insertPosn, text); // (CopyLinesUp) + isEdgeCase_InsertExpandsSelection |= selectionSpan.End == insertPos; - // Hack: Fix the selection, if the selection ended at the end of a line or start of new line. - if (endsAtLineStart || endsAtLineEnd) - { - // Hack: Only works for single-selection. TODO: Fix for multi-selection. - if (spans.Count < 2) - { - editorOperations.ExtendSelection(endPoint); - } - } + // Case when there is no trailing new-line chars in the selected lines. + if (isEndOfFile) textToInsert = Environment.NewLine + textToInsert; } - else // ie. CopyLinesDown + else // (isCopyDown) + { + insertPos = selectedLines.Start; + + // Case when there is no trailing new-line chars in the selected lines. + if (isEndOfFile) textToInsert = textToInsert + Environment.NewLine; + } + + textView.TextBuffer.Insert(insertPos, textToInsert); + } + + if (isEdgeCase_InsertExpandsSelection) + { + // Translate selections to newest snapshot with negative tracking mode for the + // end point so that they are not expanded due to the recent insertions. + var targetSnapshot = textView.TextSnapshot; + var newSelections = new Selection[selections.Count]; + int primarySelectionIndex = 0; + for (int i = 0; i < newSelections.Length; i++) { - // Always end with a new line (CR/LF) - if (!endsAtLineStart) + Selection selection = selections[i]; + if (primarySelection.Equals(selection)) primarySelectionIndex = i; + newSelections[i] = TranslateTo( + selection, targetSnapshot, + GetPointTrackingMode(selection.InsertionPoint), + GetPointTrackingMode(selection.AnchorPoint), + GetPointTrackingMode(selection.ActivePoint) + ); + + PointTrackingMode GetPointTrackingMode(VirtualSnapshotPoint point) { - text += Environment.NewLine; // Note: This does not detect the line endings of the current file. + if (point.Position == point.Position.Snapshot.Length) return PointTrackingMode.Negative; + return point <= selection.Extent.Start ? PointTrackingMode.Positive : PointTrackingMode.Negative; } - - // Insert the text at the start of the first line - textView.TextBuffer.Insert(startOfFirstLine.Position, text); // (CopyLinesDown) } + + broker.SetSelectionRange(newSelections, newSelections[primarySelectionIndex]); } // Complete the transaction @@ -120,6 +124,48 @@ public static int HandleCommand_DuplicateLines(IWpfTextView textView, IClassifie return VSConstants.S_OK; } + /// + /// Transforms a selection to a target snapshot using the provided tracking rules. + /// TODO: Move to utility class and make into extension? + /// + public static Selection TranslateTo(Selection selection, ITextSnapshot targetSnapshot, PointTrackingMode insertionPointTracking, PointTrackingMode anchorPointTracking, PointTrackingMode activePointTracking) + { + return new Selection + ( + selection.InsertionPoint.TranslateTo(targetSnapshot, insertionPointTracking), + selection.AnchorPoint.TranslateTo(targetSnapshot, anchorPointTracking), + selection.ActivePoint.TranslateTo(targetSnapshot, activePointTracking), + selection.InsertionPointAffinity + ); + } + + /// + /// Expands span to include all lines it touches. + /// Spans ending on first char of a new line does not count as touching that line. + /// Includes any trailing new-line chars (CR/LF). + /// TODO: Move to utility class and make into extension? + /// + /// The lines that contains this span. + public static SnapshotSpan GetContainingLines(SnapshotSpan span) + { + var firstLine = span.Start.GetContainingLine(); + SnapshotPoint linesStart = firstLine.Start; + SnapshotPoint linesEnd; + if (span.Length == 0) + { + linesEnd = firstLine.EndIncludingLineBreak; + } + else + { + var lastLine = span.End.GetContainingLine(); + linesEnd = span.End == lastLine.Start ? + lastLine.Start + : + lastLine.EndIncludingLineBreak; + } + return new SnapshotSpan(linesStart, linesEnd); + } + // Helped by source of Microsoft.VisualStudio.Text.Editor.DragDrop.DropHandlerBase.cs in assembly Microsoft.VisualStudio.Text.UI.Wpf, Version=14.0.0.0 public static int HandleCommand(IWpfTextView textView, IClassifier classifier, IOleCommandTarget commandTarget, IEditorOperations editorOperations, bool shiftPressed = false) { From f59388acc6f96c852c99b75df808fe85cd10f384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Foghammar=20N=C3=B5mtak?= Date: Tue, 21 Feb 2023 18:42:12 +0100 Subject: [PATCH 2/2] Make project use NuGet packages and update packages --- HotCommands/HotCommands.csproj | 259 ++++++++++----------------------- HotCommands/packages.config | 79 ---------- 2 files changed, 80 insertions(+), 258 deletions(-) delete mode 100644 HotCommands/packages.config diff --git a/HotCommands/HotCommands.csproj b/HotCommands/HotCommands.csproj index fc50445..5035b26 100644 --- a/HotCommands/HotCommands.csproj +++ b/HotCommands/HotCommands.csproj @@ -92,180 +92,10 @@ true Always - - Designer - Designer - - - ..\packages\Microsoft.CodeAnalysis.EditorFeatures.Text.1.0.0\lib\net45\Microsoft.CodeAnalysis.EditorFeatures.Text.dll - True - - - ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll - True - - - ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll - True - - - - ..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll - True - - - ..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - ..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll - True - - - False - ..\..\..\Program Files (x86)\Microsoft Visual Studio\Preview\Enterprise\Common7\IDE\CommonExtensions\Microsoft\Editor\Microsoft.VisualStudio.Editor.Implementation.dll - - - ..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll - True - - - ..\packages\Microsoft.VisualStudio.Language.StandardClassification.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.StandardClassification.dll - - - False - ..\..\..\Program Files (x86)\Microsoft Visual Studio\Preview\Enterprise\Common7\IDE\Microsoft.VisualStudio.Platform.WindowManagement.dll - - - False - ..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll - - - False - ..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll - - - False - ..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll - - - False - ..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll - - - ..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll - True - - - ..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll - True - - - ..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll - True - - - - ..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll - True - - - ..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - True - - - - ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - True - - - ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - True - - - ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - True - - - ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - True - - - ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - True - - - ..\packages\System.Console.4.0.0\lib\net46\System.Console.dll - True - - - - - ..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll - True - - - ..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll - True - - - - ..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll - True - - - ..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll - True - - - - ..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll - True - - - ..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll - True - - - ..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - - - ..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - - - ..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - True - - - ..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll - True - - - ..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll - True - - - - - - ..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll - True - - - ..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll - True - - - ..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll - True - - true @@ -292,25 +122,96 @@ Always - - - - - 4.0.1 + 4.4.0 + + + 4.4.0 + + + 4.4.0 + + + 17.4.255 + + + 17.4.255 + + + 17.4.255 + + + 17.4.33103.184 - 17.0.31902.203 + 17.4.33103.184 + + + 17.4.255 + + + + 17.4.255 + + + 17.4.255 + + + 17.4.255 - - + + 17.4.255 + + + 17.5.22 + + + 17.4.33103.184 + + + 17.0.65 + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + + 4.3.0 + + + 4.3.0 + + + 4.3.0 + + + 4.3.1 + + + 4.3.0 + + + 4.3.0 + + + 4.3.2 + + + 4.3.0 + + + 4.3.0 + + + 4.3.0 + + + 4.3.0 +