diff --git a/AxialSqlTools/AxialSqlTools.csproj b/AxialSqlTools/AxialSqlTools.csproj index f5510d2..54ecaaa 100644 --- a/AxialSqlTools/AxialSqlTools.csproj +++ b/AxialSqlTools/AxialSqlTools.csproj @@ -113,6 +113,7 @@ + diff --git a/AxialSqlTools/Modules/KeypressCommandFilter.cs b/AxialSqlTools/Modules/KeypressCommandFilter.cs index 981dae7..8890618 100644 --- a/AxialSqlTools/Modules/KeypressCommandFilter.cs +++ b/AxialSqlTools/Modules/KeypressCommandFilter.cs @@ -2,11 +2,7 @@ using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.TextManager.Interop; using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; using System.Windows.Input; namespace AxialSqlTools diff --git a/AxialSqlTools/Modules/SqlStringSelectionMouseProcessor.cs b/AxialSqlTools/Modules/SqlStringSelectionMouseProcessor.cs new file mode 100644 index 0000000..ece85a1 --- /dev/null +++ b/AxialSqlTools/Modules/SqlStringSelectionMouseProcessor.cs @@ -0,0 +1,150 @@ +using System; +using System.ComponentModel.Composition; +using System.Windows; +using System.Windows.Input; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Utilities; + +namespace AxialSqlTools +{ + [Export(typeof(IMouseProcessorProvider))] + [Name("sql-string-double-click-selector")] + [ContentType("text")] + [ContentType("code")] + [Order(Before = PredefinedMouseProcessorNames.WordSelection)] + [TextViewRole(PredefinedTextViewRoles.Editable)] + public class SqlStringSelectionMouseProcessorProvider : IMouseProcessorProvider + { + public IMouseProcessor GetAssociatedProcessor(IWpfTextView wpfTextView) + { + return new SqlStringSelectionMouseProcessor(wpfTextView); + } + } + + public class SqlStringSelectionMouseProcessor : MouseProcessorBase + { + private readonly IWpfTextView view; + + public SqlStringSelectionMouseProcessor(IWpfTextView view) + { + this.view = view ?? throw new ArgumentNullException(nameof(view)); + } + + public override void PreprocessMouseLeftButtonDown(MouseButtonEventArgs e) + { + if (e.ChangedButton != MouseButton.Left) + { + return; + } + + if (e.ClickCount != 2) + { + return; + } + + if (Keyboard.Modifiers != ModifierKeys.None) + { + return; + } + + if (SelectQuotedString(e)) + { + e.Handled = true; + } + } + + private bool SelectQuotedString(MouseButtonEventArgs e) + { + SnapshotPoint? snapshotPoint = GetSnapshotAtCursor(e); + + if (snapshotPoint.HasValue && TryGetQuotedStringSpan(snapshotPoint.Value, out SnapshotSpan span)) + { + view.Selection.Select(span, isReversed: false); + view.Caret.MoveTo(span.End); + return true; + } + + return false; + } + + private SnapshotPoint? GetSnapshotAtCursor(MouseButtonEventArgs e) + { + if (view.TextViewLines == null || view.TextViewLines.IsEmpty) + { + return null; + } + + Point cursorPosition = GetPositionInViewport(e); + ITextViewLine textViewLine = view.TextViewLines.GetTextViewLineContainingYCoordinate(cursorPosition.Y); + + if (textViewLine != null) + { + return textViewLine.GetBufferPositionFromXCoordinate(cursorPosition.X, true); + } + + return null; + } + + private Point GetPositionInViewport(MouseButtonEventArgs e) + { + Point relativePosition = e.GetPosition(view.VisualElement); + return new Point(relativePosition.X + view.ViewportLeft, relativePosition.Y + view.ViewportTop); + } + + private bool TryGetQuotedStringSpan(SnapshotPoint point, out SnapshotSpan span) + { + string text = point.Snapshot.GetText(); + int position = point.Position; + + bool insideString = false; + int stringStart = -1; + + for (int index = 0; index < text.Length; index++) + { + char current = text[index]; + + if (current == '\'') + { + if (insideString) + { + if (index + 1 < text.Length && text[index + 1] == '\'') + { + index++; + continue; + } + + int stringEnd = index; + if (position >= stringStart && position <= stringEnd) + { + span = new SnapshotSpan(point.Snapshot, stringStart, stringEnd - stringStart + 1); + return true; + } + + insideString = false; + } + else + { + stringStart = index; + if (index > 0 && (text[index - 1] == 'N' || text[index - 1] == 'n')) + { + stringStart--; + } + + insideString = true; + } + } + } + + if (insideString && position >= stringStart) + { + span = new SnapshotSpan(point.Snapshot, stringStart, text.Length - stringStart); + return true; + } + + span = default; + return false; + } + } +} diff --git a/AxialSqlTools/source.extension.vsixmanifest b/AxialSqlTools/source.extension.vsixmanifest index d4f2490..668004d 100644 --- a/AxialSqlTools/source.extension.vsixmanifest +++ b/AxialSqlTools/source.extension.vsixmanifest @@ -19,5 +19,6 @@ +