diff --git a/.gitignore b/.gitignore index 2feec09..8467e85 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,6 @@ UpgradeLog*.htm # Microsoft Fakes FakesAssemblies/ +*.ide +/.vs/slnx.sqlite +/.vs/VSWorkspaceState.json diff --git a/EasyMotion/EasyMotion.vsct b/EasyMotion/EasyMotion.vsct index cda51e6..0328a0a 100644 --- a/EasyMotion/EasyMotion.vsct +++ b/EasyMotion/EasyMotion.vsct @@ -14,14 +14,40 @@ + + + + + + @@ -40,6 +66,9 @@ + + + diff --git a/EasyMotion/EasyMotionPackage.cs b/EasyMotion/EasyMotionPackage.cs index e95fc09..7423490 100644 --- a/EasyMotion/EasyMotionPackage.cs +++ b/EasyMotion/EasyMotionPackage.cs @@ -53,16 +53,23 @@ protected override void Initialize() _exportProvider = _componentModel.DefaultExportProvider; // Add our command handlers for menu (commands must exist in the .vsct file) - OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; + var mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if ( null != mcs ) { - // Create the command for the menu item. - CommandID menuCommandID = new CommandID(GuidList.guidEasyMotionCmdSet, (int)PkgCmdIDList.CmdEasyMotionNavigate); - MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID ); - mcs.AddCommand( menuItem ); + AddCommand(PkgCmdIDList.CmdEasyMotionNavigate, mcs); + AddCommand(PkgCmdIDList.CmdEasyMotionNavigateWord, mcs); + AddCommand(PkgCmdIDList.CmdEasyMotionNavigateExtend, mcs); + AddCommand(PkgCmdIDList.CmdEasyMotionNavigateWordExtend, mcs); } } + private void AddCommand(uint cmd, OleMenuCommandService mcs) + { + var menuCommandID = new CommandID(GuidList.guidEasyMotionCmdSet, (int)cmd); + var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); + mcs.AddCommand(menuItem); + } + private void MenuItemCallback(object sender, EventArgs e) { ITextView textView; @@ -81,7 +88,25 @@ private void MenuItemCallback(object sender, EventArgs e) } else { - easyMotionUtil.ChangeToLookingForChar(); + var command = sender as MenuCommand; + Debug.Assert(command != null); + var mode = EasyMotionSearchMode.Char; + switch (command.CommandID.ID) + { + case (int)PkgCmdIDList.CmdEasyMotionNavigate: + mode = EasyMotionSearchMode.Char; + break; + case (int)PkgCmdIDList.CmdEasyMotionNavigateWord: + mode = EasyMotionSearchMode.Word; + break; + case (int)PkgCmdIDList.CmdEasyMotionNavigateExtend: + mode = EasyMotionSearchMode.CharExtend; + break; + case (int)PkgCmdIDList.CmdEasyMotionNavigateWordExtend: + mode = EasyMotionSearchMode.WordExtend; + break; + } + easyMotionUtil.ChangeToLookingForChar(mode); } } diff --git a/EasyMotion/IEasyMotionUtil.cs b/EasyMotion/IEasyMotionUtil.cs index 12e972a..28e8a89 100644 --- a/EasyMotion/IEasyMotionUtil.cs +++ b/EasyMotion/IEasyMotionUtil.cs @@ -7,6 +7,31 @@ namespace EasyMotion { + internal enum EasyMotionSearchMode + { + /// + /// Developer is looking for characters anywhere + /// + Char, + + /// + /// Developer is looking for words starting with the typed character + /// + Word, + + /// + /// Same as Char + extending the selection + /// + CharExtend, + + /// + /// Same as Word + extending the selection + /// + WordExtend + } + + + internal enum EasyMotionState { /// @@ -37,6 +62,8 @@ internal interface IEasyMotionUtil EasyMotionState State { get; } + EasyMotionSearchMode SearchMode { get; } + /// /// During the LookingForDecision state this will be the character which /// the user has decided to make an easy motion for @@ -47,11 +74,13 @@ internal interface IEasyMotionUtil void ChangeToDisabled(); - void ChangeToLookingForChar(); + void ChangeToLookingForChar(EasyMotionSearchMode searchMode); void ChangeToLookingForDecision(char target); void ChangeToLookingCharNotFound(); + + bool IsInWordMode { get; } } internal interface IEasyMotionUtilProvider diff --git a/EasyMotion/Implementation/Adornment/EasyMotionAdornmentController.cs b/EasyMotion/Implementation/Adornment/EasyMotionAdornmentController.cs index 4a786a2..c483f58 100644 --- a/EasyMotion/Implementation/Adornment/EasyMotionAdornmentController.cs +++ b/EasyMotion/Implementation/Adornment/EasyMotionAdornmentController.cs @@ -10,6 +10,7 @@ using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; using System.Windows; +using Microsoft.VisualStudio.Text.Operations; namespace EasyMotion.Implementation.Adornment { @@ -24,16 +25,21 @@ internal sealed class EasyMotionAdornmentController : IEasyMotionNavigator private readonly IWpfTextView _wpfTextView; private readonly IEditorFormatMap _editorFormatMap; private readonly IClassificationFormatMap _classificationFormatMap; + private readonly ITextSearchService _TextSerachService; + private readonly IEditorOperations _editorOperations; private readonly Dictionary _navigateMap = new Dictionary(); private readonly object _tag = new object(); private IAdornmentLayer _adornmentLayer; - internal EasyMotionAdornmentController(IEasyMotionUtil easyMotionUtil, IWpfTextView wpfTextview, IEditorFormatMap editorFormatMap, IClassificationFormatMap classificationFormatMap) + internal EasyMotionAdornmentController(IEasyMotionUtil easyMotionUtil, IWpfTextView wpfTextview, IEditorFormatMap editorFormatMap, IClassificationFormatMap classificationFormatMap + , ITextSearchService textSerachService, IEditorOperations editorOperations) { _easyMotionUtil = easyMotionUtil; _wpfTextView = wpfTextview; _editorFormatMap = editorFormatMap; _classificationFormatMap = classificationFormatMap; + _TextSerachService = textSerachService; + _editorOperations = editorOperations; } internal void SetAdornmentLayer(IAdornmentLayer adornmentLayer) @@ -102,18 +108,33 @@ private void AddAdornments() var endPoint = textViewLines.LastVisibleLine.End; var snapshot = startPoint.Snapshot; int navigateIndex = 0; - for (int i = startPoint.Position; i < endPoint.Position; i++) + + if (_easyMotionUtil.IsInWordMode && !char.IsLetterOrDigit(_easyMotionUtil.TargetChar)) { - var point = new SnapshotPoint(snapshot, i); + _easyMotionUtil.ChangeToLookingCharNotFound(); + return; + } + var toSearch = _easyMotionUtil.TargetChar.ToString(); + var data = new FindData() + { + SearchString = _easyMotionUtil.IsInWordMode ? @"\b" + toSearch : toSearch, + TextSnapshotToSearch = snapshot, + FindOptions = _easyMotionUtil.IsInWordMode ? FindOptions.UseRegularExpressions : FindOptions.None + }; - if (Char.ToLower(point.GetChar()) == Char.ToLower(_easyMotionUtil.TargetChar) && navigateIndex < NavigationKeys.Length) + var startindex = startPoint.Position; + while (navigateIndex < NavigationKeys.Length) + { + var res = _TextSerachService.FindNext(startindex, false, data); + if (!res.HasValue || res.Value.Start.Position > endPoint.Position) { - string key = NavigationKeys[navigateIndex]; - navigateIndex++; - AddNavigateToPoint(textViewLines, point, key); + break; } + var key = NavigationKeys[navigateIndex]; + AddNavigateToPoint(textViewLines, res.Value.Start, key); + startindex = res.Value.Start.Position + 1; + navigateIndex++; } - if (navigateIndex == 0) { _easyMotionUtil.ChangeToLookingCharNotFound(); @@ -156,7 +177,14 @@ public bool NavigateTo(string key) return false; } - _wpfTextView.Caret.MoveTo(point); + if (_easyMotionUtil.SearchMode == EasyMotionSearchMode.CharExtend || _easyMotionUtil.SearchMode == EasyMotionSearchMode.WordExtend) + { + _editorOperations.ExtendSelection(point.Position); + } + else + { + _wpfTextView.Caret.MoveTo(point); + } return true; } } diff --git a/EasyMotion/Implementation/Adornment/EasyMotionAdornmentFactory.cs b/EasyMotion/Implementation/Adornment/EasyMotionAdornmentFactory.cs index 563d803..0c6bcf3 100644 --- a/EasyMotion/Implementation/Adornment/EasyMotionAdornmentFactory.cs +++ b/EasyMotion/Implementation/Adornment/EasyMotionAdornmentFactory.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.VisualStudio.Text.Operations; +using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; @@ -25,6 +26,8 @@ internal sealed class EasyMotionAdornmentFactory : IWpfTextViewCreationListener, private readonly IEasyMotionUtilProvider _easyMotionUtilProvider; private readonly IEditorFormatMapService _editorFormatMapService; private readonly IClassificationFormatMapService _classificationFormatMapService; + private readonly ITextSearchService _textSerachService; + private readonly IEditorOperationsFactoryService _editorOperationsFactory; #pragma warning disable 169 [Export(typeof(AdornmentLayerDefinition))] @@ -34,11 +37,14 @@ internal sealed class EasyMotionAdornmentFactory : IWpfTextViewCreationListener, #pragma warning restore 169 [ImportingConstructor] - internal EasyMotionAdornmentFactory(IEasyMotionUtilProvider easyMotionUtilProvider, IEditorFormatMapService editorFormatMapService, IClassificationFormatMapService classificationFormatMapService) + internal EasyMotionAdornmentFactory(IEasyMotionUtilProvider easyMotionUtilProvider, IEditorFormatMapService editorFormatMapService + , IClassificationFormatMapService classificationFormatMapService, ITextSearchService textSearchService, IEditorOperationsFactoryService editorOperations) { _easyMotionUtilProvider = easyMotionUtilProvider; _editorFormatMapService = editorFormatMapService; _classificationFormatMapService = classificationFormatMapService; + _textSerachService = textSearchService; + _editorOperationsFactory = editorOperations; } private EasyMotionAdornmentController GetOrCreate(IWpfTextView wpfTextView) @@ -50,7 +56,7 @@ private EasyMotionAdornmentController GetOrCreate(IWpfTextView wpfTextView) var easyMotionUtil = _easyMotionUtilProvider.GetEasyMotionUtil(wpfTextView); var editorFormatMap = _editorFormatMapService.GetEditorFormatMap(wpfTextView); var classificationFormatMap = _classificationFormatMapService.GetClassificationFormatMap(wpfTextView); - return new EasyMotionAdornmentController(easyMotionUtil, wpfTextView, editorFormatMap, classificationFormatMap); + return new EasyMotionAdornmentController(easyMotionUtil, wpfTextView, editorFormatMap, classificationFormatMap, _textSerachService, _editorOperationsFactory.GetEditorOperations(wpfTextView)); }); } diff --git a/EasyMotion/Implementation/EasyMotionUtil/EasyMotionUtil.cs b/EasyMotion/Implementation/EasyMotionUtil/EasyMotionUtil.cs index 46f8b77..dd55031 100644 --- a/EasyMotion/Implementation/EasyMotionUtil/EasyMotionUtil.cs +++ b/EasyMotion/Implementation/EasyMotionUtil/EasyMotionUtil.cs @@ -11,6 +11,7 @@ internal sealed class EasyMotionUtil : IEasyMotionUtil { private readonly ITextView _textView; private EasyMotionState _state; + private EasyMotionSearchMode _searchMode; private char _targetChar; public EasyMotionState State @@ -18,6 +19,11 @@ public EasyMotionState State get { return _state; } } + public EasyMotionSearchMode SearchMode + { + get { return _searchMode; } + } + public char TargetChar { get { return _targetChar; } @@ -28,6 +34,8 @@ public ITextView TextView get { return _textView; } } + public bool IsInWordMode => SearchMode == EasyMotionSearchMode.Word || SearchMode == EasyMotionSearchMode.WordExtend; + public event EventHandler StateChanged; internal EasyMotionUtil(ITextView textView) @@ -43,9 +51,10 @@ public void ChangeToDisabled() RaiseStateChanged(); } - public void ChangeToLookingForChar() + public void ChangeToLookingForChar(EasyMotionSearchMode mode) { _state = EasyMotionState.LookingForChar; + _searchMode = mode; _targetChar = (char)0; RaiseStateChanged(); } diff --git a/EasyMotion/PkgCmdID.cs b/EasyMotion/PkgCmdID.cs index 46690e4..4f22b08 100644 --- a/EasyMotion/PkgCmdID.cs +++ b/EasyMotion/PkgCmdID.cs @@ -6,8 +6,10 @@ namespace EasyMotion { static class PkgCmdIDList { - public const uint CmdEasyMotionNavigate = 0x100; + public const uint CmdEasyMotionNavigate = 0x100; + public const uint CmdEasyMotionNavigateWord = 0x101; + public const uint CmdEasyMotionNavigateExtend = 0x102; + public const uint CmdEasyMotionNavigateWordExtend = 0x103; - - }; + }; } \ No newline at end of file