diff --git a/DPackRx.Tests/DPackRx.Tests.csproj b/DPackRx.Tests/DPackRx.Tests.csproj index 724e08c..a3d2c28 100644 --- a/DPackRx.Tests/DPackRx.Tests.csproj +++ b/DPackRx.Tests/DPackRx.Tests.csproj @@ -178,6 +178,7 @@ + diff --git a/DPackRx.Tests/Features/CodeBrowserFeatureTests.cs b/DPackRx.Tests/Features/CodeBrowserFeatureTests.cs index 850970b..e8fb59a 100644 --- a/DPackRx.Tests/Features/CodeBrowserFeatureTests.cs +++ b/DPackRx.Tests/Features/CodeBrowserFeatureTests.cs @@ -161,6 +161,16 @@ public void Execute(int commandId, CodeModelFilterFlags filter) _messageServiceMock.Verify(m => m.ShowError(It.IsNotNull(), false), Times.Never); } + [Test] + public void Execute_InvalidCommand() + { + var feature = GetFeature(); + + var result = feature.Execute(0); + + Assert.That(result, Is.False); + } + [Test] public void Execute_NoActiveFile() { diff --git a/DPackRx.Tests/Features/FileBrowserFeatureTests.cs b/DPackRx.Tests/Features/FileBrowserFeatureTests.cs index 93f45af..a2f34f8 100644 --- a/DPackRx.Tests/Features/FileBrowserFeatureTests.cs +++ b/DPackRx.Tests/Features/FileBrowserFeatureTests.cs @@ -138,6 +138,16 @@ public void Execute() _modalDialogServiceMock.Verify(d => d.ShowDialog(It.IsNotNull())); } + [Test] + public void Execute_InvalidCommand() + { + var feature = GetFeature(); + + var result = feature.Execute(0); + + Assert.That(result, Is.False); + } + #endregion } } \ No newline at end of file diff --git a/DPackRx.Tests/Features/MiscellaneousFeatureTests.cs b/DPackRx.Tests/Features/MiscellaneousFeatureTests.cs index f39068c..27907cc 100644 --- a/DPackRx.Tests/Features/MiscellaneousFeatureTests.cs +++ b/DPackRx.Tests/Features/MiscellaneousFeatureTests.cs @@ -308,6 +308,16 @@ public void Execute_CopyProjectFullPath(string path) _shellHelperServiceMock.Verify(s => s.GetCurrentProjectPath()); } + [Test] + public void Execute_InvalidCommand() + { + var feature = GetFeature(); + + var result = feature.Execute(0); + + Assert.That(result, Is.False); + } + #endregion } } \ No newline at end of file diff --git a/DPackRx.Tests/Features/SurroundWithFeatureTests.cs b/DPackRx.Tests/Features/SurroundWithFeatureTests.cs new file mode 100644 index 0000000..9a7ea77 --- /dev/null +++ b/DPackRx.Tests/Features/SurroundWithFeatureTests.cs @@ -0,0 +1,197 @@ +using System; +using System.Windows.Input; + +using Moq; +using NUnit.Framework; + +using DPackRx.CodeModel; +using DPackRx.Features; +using DPackRx.Features.SurroundWith; +using DPackRx.Language; +using DPackRx.Options; +using DPackRx.Package; +using DPackRx.Services; + +namespace DPackRx.Tests.Features +{ + /// + /// SurroundWithFeature tests. + /// + [TestFixture] + public class SurroundWithFeatureTests + { + #region Fields + + private Mock _serviceProviderMock; + private Mock _logMock; + private Mock _optionsServiceMock; + private Mock _shellHelperServiceMock; + private Mock _shellSelectionServiceMock; + private Mock _fileTypeResolverMock; + private Mock _keyboardServiceMock; + + #endregion + + #region Tests Setup + + [SetUp] + public void Setup() + { + _serviceProviderMock = new Mock(); + + _logMock = new Mock(); + _logMock.Setup(l => l.LogMessage(It.IsAny(), It.IsAny())).Verifiable(); + + _optionsServiceMock = new Mock(); + + _shellHelperServiceMock = new Mock(); + _shellHelperServiceMock.Setup(s => s.ExecuteCommand(It.IsNotNull(), null)).Verifiable(); + + _shellSelectionServiceMock = new Mock(); + _shellSelectionServiceMock.Setup(s => s.IsContextActive(It.IsAny())).Returns(true).Verifiable(); + _shellSelectionServiceMock.Setup(s => s.GetActiveProject()).Returns(new object()).Verifiable(); + + var webProject = false; + _fileTypeResolverMock = new Mock(); + _fileTypeResolverMock.Setup(f => f.GetCurrentLanguage(It.IsAny(), out webProject)) + .Returns(new LanguageSettings("C#", "C#") { Type = LanguageType.CSharp, SurroundWith = true }).Verifiable(); + + _keyboardServiceMock = new Mock(); + _keyboardServiceMock.Setup(k => k.Type(It.IsAny())).Verifiable(); + _keyboardServiceMock.Setup(k => k.Type(It.IsAny())).Verifiable(); + } + + [TearDown] + public void TearDown() + { + _serviceProviderMock = null; + _logMock = null; + _optionsServiceMock = null; + _shellHelperServiceMock = null; + _shellSelectionServiceMock = null; + _fileTypeResolverMock = null; + _keyboardServiceMock = null; + } + + #endregion + + #region Private Methods + + /// + /// Returns test feature instance. + /// + private IFeature GetFeature() + { + return new SurroundWithFeature(_serviceProviderMock.Object, _logMock.Object, _optionsServiceMock.Object, + _shellHelperServiceMock.Object, _shellSelectionServiceMock.Object, _fileTypeResolverMock.Object, + _keyboardServiceMock.Object); + } + + #endregion + + #region Tests + + [Test] + public void GetCommandIds() + { + var feature = GetFeature(); + + var commands = feature.GetCommandIds(); + + Assert.That(commands, Is.Not.Null); + Assert.That(commands.Count, Is.EqualTo(5)); + Assert.That(commands, Contains.Item(CommandIDs.SW_TRY_CATCH)); + Assert.That(commands, Contains.Item(CommandIDs.SW_TRY_FINALLY)); + Assert.That(commands, Contains.Item(CommandIDs.SW_FOR)); + Assert.That(commands, Contains.Item(CommandIDs.SW_FOR_EACH)); + Assert.That(commands, Contains.Item(CommandIDs.SW_REGION)); + } + + [TestCase(CommandIDs.SW_TRY_CATCH, true)] + [TestCase(CommandIDs.SW_TRY_FINALLY, true)] + [TestCase(CommandIDs.SW_FOR, true)] + [TestCase(CommandIDs.SW_FOR_EACH, true)] + [TestCase(CommandIDs.SW_REGION, true)] + [TestCase(0, false)] + public void IsValidContext(int commandId, bool expectedResult) + { + var feature = GetFeature(); + + var result = feature.IsValidContext(commandId); + + Assert.That(result, Is.EqualTo(expectedResult)); + if (expectedResult) + _shellSelectionServiceMock.Verify(s => s.IsContextActive(It.IsAny()), Times.AtLeast(2)); + else + _shellSelectionServiceMock.Verify(s => s.IsContextActive(It.IsAny()), Times.Never); + } + + [TestCase(CommandIDs.SW_TRY_CATCH, SurroundWithFeature.SNIPPET_TRY_CATCH)] + [TestCase(CommandIDs.SW_TRY_FINALLY, SurroundWithFeature.SNIPPET_TRY_FINALLY)] + [TestCase(CommandIDs.SW_FOR, SurroundWithFeature.SNIPPET_FOR)] + [TestCase(CommandIDs.SW_FOR_EACH, SurroundWithFeature.SNIPPET_FOR_EACH)] + [TestCase(CommandIDs.SW_REGION, SurroundWithFeature.SNIPPET_REGION)] + public void Execute(int commandId, string command) + { + var feature = GetFeature(); + var webProject = false; + + var result = feature.Execute(commandId); + + Assert.That(result, Is.True); + _shellSelectionServiceMock.Verify(s => s.GetActiveProject()); + _fileTypeResolverMock.Verify(f => f.GetCurrentLanguage(It.IsAny(), out webProject)); + _shellHelperServiceMock.Verify(s => s.ExecuteCommand(SurroundWithFeature.SURROUND_WITH_COMMAND, null)); + _keyboardServiceMock.Verify(k => k.Type(command)); + _keyboardServiceMock.Verify(k => k.Type(Key.Enter)); + } + + [Test] + public void Execute_InvalidCommand() + { + var feature = GetFeature(); + + var result = feature.Execute(0); + + Assert.That(result, Is.False); + } + + [Test] + public void Execute_UnknownLanguage() + { + var feature = GetFeature(); + var webProject = false; + _fileTypeResolverMock.Setup(f => f.GetCurrentLanguage(It.IsAny(), out webProject)) + .Returns(LanguageSettings.UnknownLanguage).Verifiable(); + + var result = feature.Execute(CommandIDs.SW_REGION); + + Assert.That(result, Is.True); + _shellSelectionServiceMock.Verify(s => s.GetActiveProject()); + _fileTypeResolverMock.Verify(f => f.GetCurrentLanguage(It.IsAny(), out webProject)); + _shellHelperServiceMock.Verify(s => s.ExecuteCommand(SurroundWithFeature.SURROUND_WITH_COMMAND, null), Times.Never); + _keyboardServiceMock.Verify(k => k.Type(It.IsAny()), Times.Never); + _keyboardServiceMock.Verify(k => k.Type(Key.Enter), Times.Never); + } + + [Test] + public void Execute_UnsupportedLanguage() + { + var feature = GetFeature(); + var webProject = false; + _fileTypeResolverMock.Setup(f => f.GetCurrentLanguage(It.IsAny(), out webProject)) + .Returns(new LanguageSettings("C#", "C#") { Type = LanguageType.CSharp, SurroundWith = false }).Verifiable(); + + var result = feature.Execute(CommandIDs.SW_REGION); + + Assert.That(result, Is.True); + _shellSelectionServiceMock.Verify(s => s.GetActiveProject()); + _fileTypeResolverMock.Verify(f => f.GetCurrentLanguage(It.IsAny(), out webProject)); + _shellHelperServiceMock.Verify(s => s.ExecuteCommand(SurroundWithFeature.SURROUND_WITH_COMMAND, null), Times.Never); + _keyboardServiceMock.Verify(k => k.Type(It.IsAny()), Times.Never); + _keyboardServiceMock.Verify(k => k.Type(Key.Enter), Times.Never); + } + + #endregion + } +} \ No newline at end of file diff --git a/DPackRx/DPackRx.csproj b/DPackRx/DPackRx.csproj index 2baec87..91ea208 100644 --- a/DPackRx/DPackRx.csproj +++ b/DPackRx/DPackRx.csproj @@ -67,6 +67,8 @@ + + @@ -76,6 +78,7 @@ + @@ -133,6 +136,7 @@ + diff --git a/DPackRx/Features/Bookmarks/BookmarksFeature.cs b/DPackRx/Features/Bookmarks/BookmarksFeature.cs index 92e8f33..3445f26 100644 --- a/DPackRx/Features/Bookmarks/BookmarksFeature.cs +++ b/DPackRx/Features/Bookmarks/BookmarksFeature.cs @@ -113,7 +113,7 @@ public override bool IsValidContext(int commandId) _shellSelectionService.IsContextActive(ContextType.HTMLSourceEditor) || _shellSelectionService.IsContextActive(ContextType.CSSTextEditor)); default: - return false; + return base.IsValidContext(commandId); } } diff --git a/DPackRx/Features/IFeature.cs b/DPackRx/Features/IFeature.cs index a5f2c50..fd090b3 100644 --- a/DPackRx/Features/IFeature.cs +++ b/DPackRx/Features/IFeature.cs @@ -72,7 +72,8 @@ public enum KnownFeature // TODO: finish other features Bookmarks, - //SurroundWith, + [Description("Surround With")] + SurroundWith, //SolutionStats, diff --git a/DPackRx/Features/SurroundWith/SurroundWithFeature.cs b/DPackRx/Features/SurroundWith/SurroundWithFeature.cs new file mode 100644 index 0000000..9d2e151 --- /dev/null +++ b/DPackRx/Features/SurroundWith/SurroundWithFeature.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Windows.Input; + +using DPackRx.CodeModel; +using DPackRx.Language; +using DPackRx.Options; +using DPackRx.Package; +using DPackRx.Services; + +namespace DPackRx.Features.SurroundWith +{ + /// + /// Surround with feature. + /// + [KnownFeature(KnownFeature.SurroundWith)] + [OptionsDefaults("Logging", false)] + public class SurroundWithFeature : Feature + { + #region Fields + + private readonly IShellHelperService _shellHelperService; + private readonly IShellSelectionService _shellSelectionService; + private readonly IFileTypeResolver _fileTypeResolver; + private readonly IKeyboardService _keyboardService; + + protected internal const string SURROUND_WITH_COMMAND = "Edit.SurroundWith"; + protected internal const string SNIPPET_TRY_CATCH = "try"; + protected internal const string SNIPPET_TRY_FINALLY = "tryf"; + protected internal const string SNIPPET_FOR = "for"; + protected internal const string SNIPPET_FOR_EACH = "foreach"; + protected internal const string SNIPPET_REGION = "#region"; + + #endregion + + public SurroundWithFeature(IServiceProvider serviceProvider, ILog log, IOptionsService optionsService, + IShellHelperService shellHelperService, IShellSelectionService shellSelectionService, + IFileTypeResolver fileTypeResolver, IKeyboardService keyboardService) : base(serviceProvider, log, optionsService) + { + _shellHelperService = shellHelperService; + _shellSelectionService = shellSelectionService; + _fileTypeResolver = fileTypeResolver; + _keyboardService = keyboardService; + } + + // Test constructor + protected internal SurroundWithFeature() : base() + { + } + + #region Feature Overrides + + /// + /// Returns all commands. + /// + /// Command Ids. + public override ICollection GetCommandIds() + { + return new List(new[] { + CommandIDs.SW_TRY_CATCH, + CommandIDs.SW_TRY_FINALLY, + CommandIDs.SW_FOR, + CommandIDs.SW_FOR_EACH, + CommandIDs.SW_REGION + }); + } + + /// + /// Checks if command is available or not. + /// + /// Command Id. + /// Command status. + public override bool IsValidContext(int commandId) + { + switch (commandId) + { + case CommandIDs.SW_TRY_CATCH: + case CommandIDs.SW_TRY_FINALLY: + case CommandIDs.SW_FOR: + case CommandIDs.SW_FOR_EACH: + case CommandIDs.SW_REGION: + return + _shellSelectionService.IsContextActive(ContextType.SolutionExists) && ( + _shellSelectionService.IsContextActive(ContextType.TextEditor) || + _shellSelectionService.IsContextActive(ContextType.XMLTextEditor) || + _shellSelectionService.IsContextActive(ContextType.XamlEditor) || + _shellSelectionService.IsContextActive(ContextType.NewXamlEditor) || + _shellSelectionService.IsContextActive(ContextType.HTMLSourceEditor) || + _shellSelectionService.IsContextActive(ContextType.CSSTextEditor)); + default: + return base.IsValidContext(commandId); + } + } + + /// + /// Executes a command. + /// + /// Command Id. + /// Execution status. + public override bool Execute(int commandId) + { + switch (commandId) + { + case CommandIDs.SW_TRY_CATCH: + return ExecuteSnippet(SNIPPET_TRY_CATCH); + case CommandIDs.SW_TRY_FINALLY: + return ExecuteSnippet(SNIPPET_TRY_FINALLY); + case CommandIDs.SW_FOR: + return ExecuteSnippet(SNIPPET_FOR); + case CommandIDs.SW_FOR_EACH: + return ExecuteSnippet(SNIPPET_FOR_EACH); + case CommandIDs.SW_REGION: + return ExecuteSnippet(SNIPPET_REGION); + default: + return base.Execute(commandId); + } + } + + #endregion + + #region Private Methods + + /// + /// Executes a given surround with snippet. + /// + private bool ExecuteSnippet(string snippet) + { + var project = _shellSelectionService.GetActiveProject(); + var languageSet = _fileTypeResolver.GetCurrentLanguage(project, out _); + if ((languageSet?.Type == LanguageType.Unknown) || !languageSet.SurroundWith) + return true; + + _shellHelperService.ExecuteCommand(SURROUND_WITH_COMMAND); + _keyboardService.Type(snippet); + _keyboardService.Type(Key.Enter); + return true; + } + + #endregion + } +} \ No newline at end of file diff --git a/DPackRx/Helpers/KeyboardHelper.cs b/DPackRx/Helpers/KeyboardHelper.cs new file mode 100644 index 0000000..d7ad58c --- /dev/null +++ b/DPackRx/Helpers/KeyboardHelper.cs @@ -0,0 +1,227 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Threading; +using System.Windows.Input; + +namespace DPackRx.Helpers +{ + /// + /// Keyboard auto-entry helper. + /// + /// Source: Node.js tools for Visual Studio. + public static class KeyboardHelper + { + #region Fields + + private const int VKEY_SHIFT_MASK = 0x0100; + private const int VKEY_CHAR_MASK = 0x00FF; + + private const int INPUT_MOUSE = 0; + private const int INPUT_KEYBOARD = 1; + + private const int KEY_EVENT_EXTENDED_KEY = 0x0001; + private const int KEY_EVENT_KEY_UP = 0x0002; + private const int KEY_EVENT_SCAN_CODE = 0x0008; + + private const char LEFT = '\u2190'; + private const char RIGHT = '\u2192'; + private const char UP = '\u2191'; + private const char DOWN = '\u2193'; + private const char CTRL_SPACE = '\u266B'; + private const char DELAY = '\u2026'; + + private static readonly Key[] _extendedKeys = new Key[] { + Key.RightAlt, Key.RightCtrl, + Key.NumLock, Key.Insert, Key.Delete, + Key.Home, Key.End, + Key.Prior, Key.Next, + Key.Up, Key.Down, Key.Left, Key.Right, + Key.Apps, Key.RWin, Key.LWin }; + + #endregion + + #region Exports + + [StructLayout(LayoutKind.Sequential)] + private struct INPUT + { + public int type; + public INPUTUNION union; + }; + + [StructLayout(LayoutKind.Explicit)] + private struct INPUTUNION + { + [FieldOffset(0)] + public MOUSEINPUT mouseInput; + [FieldOffset(0)] + public KEYBDINPUT keyboardInput; + }; + + [StructLayout(LayoutKind.Sequential)] + private struct MOUSEINPUT + { + public int dx; + public int dy; + public int mouseData; + public int dwFlags; + public int time; + public IntPtr dwExtraInfo; + }; + + [StructLayout(LayoutKind.Sequential)] + private struct KEYBDINPUT + { + public short wVk; + public short wScan; + public int dwFlags; + public int time; + public IntPtr dwExtraInfo; + }; + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern int MapVirtualKey(int nVirtKey, int nMapType); + + [DllImport("user32.dll", SetLastError = true)] + private static extern int SendInput(int nInputs, ref INPUT mi, int cbSize); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern short VkKeyScan(char ch); + + #endregion + + #region Public Methods + + /// + /// Types the specified text. + /// + public static void Type(string text) + { + if (string.IsNullOrEmpty(text)) + return; + + foreach (var ch in text) + { + switch (ch) + { + case LEFT: + Type(Key.Left); + break; + case RIGHT: + Type(Key.Right); + break; + case UP: + Type(Key.Up); + break; + case DOWN: + Type(Key.Down); + break; + case CTRL_SPACE: + PressAndRelease(Key.Space, Key.LeftCtrl); + break; + case DELAY: + Thread.Sleep(1000); + break; + default: + var keyValue = VkKeyScan(ch); + bool keyIsShifted = (keyValue & VKEY_SHIFT_MASK) == VKEY_SHIFT_MASK; + var key = KeyInterop.KeyFromVirtualKey(keyValue & VKEY_CHAR_MASK); + + if (keyIsShifted) + Type(key, new Key[] { Key.LeftShift }); + else + Type(key); + + break; + } + } + + Thread.Sleep(250); + } + + /// + /// Presses and releases the specified key. + /// + public static void Type(Key key) + { + PressKey(key); + ReleaseKey(key); + } + + #endregion + + #region Private Methods + + private static void PressAndRelease(Key key, params Key[] modifiers) + { + for (var index = 0; index < modifiers.Length; index++) + { + PressKey(modifiers[index]); + } + + PressKey(key); + ReleaseKey(key); + + for (var index = modifiers.Length - 1; index >= 0; index--) + { + ReleaseKey(modifiers[index]); + } + } + + private static void PressKey(Key key) + { + SendKeyboardInput(key, true); + } + + private static void ReleaseKey(Key key) + { + SendKeyboardInput(key, false); + } + + private static void SendKeyboardInput(Key key, bool press) + { + PermissionSet permissions = new PermissionSet(PermissionState.Unrestricted); + permissions.Demand(); + + var input = new INPUT { type = INPUT_KEYBOARD }; + input.union.keyboardInput.wVk = (short)KeyInterop.VirtualKeyFromKey(key); + input.union.keyboardInput.wScan = (short)MapVirtualKey(input.union.keyboardInput.wVk, 0); + int dwFlags = 0; + if (input.union.keyboardInput.wScan > 0) + dwFlags |= KEY_EVENT_SCAN_CODE; + if (!press) + dwFlags |= KEY_EVENT_KEY_UP; + input.union.keyboardInput.dwFlags = dwFlags; + if (_extendedKeys.Contains(key)) + input.union.keyboardInput.dwFlags |= KEY_EVENT_EXTENDED_KEY; + input.union.keyboardInput.time = 0; + input.union.keyboardInput.dwExtraInfo = new IntPtr(0); + + if (SendInput(1, ref input, Marshal.SizeOf(input)) == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Thread.Sleep(10); + } + + private static void Type(Key key, Key[] modifiers) + { + foreach (var modifier in modifiers) + { + PressKey(modifier); + } + + Type(key); + + foreach (var modifier in modifiers.Reverse()) + { + ReleaseKey(modifier); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/DPackRx/Language/LanguageRegistrationService.cs b/DPackRx/Language/LanguageRegistrationService.cs index ad85216..ec4a817 100644 --- a/DPackRx/Language/LanguageRegistrationService.cs +++ b/DPackRx/Language/LanguageRegistrationService.cs @@ -30,6 +30,7 @@ public class LanguageRegistrationService : ILanguageRegistrationService private const string IGNORE_CODE_TYPE = "IgnoreCodeType"; private const string CHECK_DUPLICATE_NAMES = "CheckDuplicateNames"; private const string PARENTLESS_FULL_NAME = "ParentlessFullName"; + private const string SURROUND_WITH_NAME = "SurroundWith"; #endregion @@ -81,6 +82,7 @@ public ICollection GetLanguages() var parentlessFullName = false; var designerFiles = LanguageDesignerFiles.NotSupported; var imports = LanguageImports.NotSupported; + var surroundWith = false; var langKey = dpackKey.OpenSubKey(id); if (langKey != null) @@ -103,6 +105,8 @@ public ICollection GetLanguages() (int)langKey.GetValue(CHECK_DUPLICATE_NAMES, Convert.ToInt32(checkDuplicateNames))); parentlessFullName = Convert.ToBoolean( (int)langKey.GetValue(PARENTLESS_FULL_NAME, Convert.ToInt32(parentlessFullName))); + surroundWith = Convert.ToBoolean( + (int)langKey.GetValue(SURROUND_WITH_NAME, Convert.ToInt32(surroundWith))); } } if (string.IsNullOrEmpty(friendlyName)) @@ -122,7 +126,8 @@ public ICollection GetLanguages() Imports = imports, IgnoreCodeType = ignoreCodeType, CheckDuplicateNames = checkDuplicateNames, - ParentlessFullName = parentlessFullName + ParentlessFullName = parentlessFullName, + SurroundWith = surroundWith }; var extKey = dpackKey.OpenSubKey(id + "\\" + EXTENSIONS_KEY); diff --git a/DPackRx/Language/LanguageSettings.cs b/DPackRx/Language/LanguageSettings.cs index f722903..b6fe079 100644 --- a/DPackRx/Language/LanguageSettings.cs +++ b/DPackRx/Language/LanguageSettings.cs @@ -153,6 +153,11 @@ public LanguageSettings(string language, string friendlyName, string xmlDoc = nu /// public bool SupportsGenerics { get; set; } + /// + /// Whether language supports surround with. + /// + public bool SurroundWith { get; set; } + #endregion } diff --git a/DPackRx/Package.cs b/DPackRx/Package.cs index 8b9be2a..144f2e3 100644 --- a/DPackRx/Package.cs +++ b/DPackRx/Package.cs @@ -9,6 +9,7 @@ using DPackRx.CodeModel; using DPackRx.Extensions; using DPackRx.Features.Bookmarks; +using DPackRx.Features.SurroundWith; using DPackRx.Language; using DPackRx.Options; using DPackRx.Package; @@ -45,7 +46,7 @@ namespace DPackRx [ProvideLanguage( "#101", EnvDTE.CodeModelLanguageConstants.vsCMLanguageCSharp, "C#", new[] { "cs" }, WebName = "Visual C#", WebLanguage = "CSharpCodeProvider", Comments = new[] { "//", "/*" }, XmlDoc = "/doc/summary", - DesignerFiles = LanguageDesignerFiles.FullySupported, Imports = LanguageImports.Supported)] + DesignerFiles = LanguageDesignerFiles.FullySupported, Imports = LanguageImports.Supported, SurroundWith = true)] [ProvideLanguage( "#101", EnvDTE.CodeModelLanguageConstants.vsCMLanguageVB, "VB", new[] { "bas", "vb", "frm" }, WebName = "Visual Basic", WebLanguage = "VBCodeProvider", Comments = new[] { "'" }, XmlDoc = "/summary", XmlDocSurround = true, @@ -53,7 +54,7 @@ namespace DPackRx [ProvideLanguage( "#101", EnvDTE.CodeModelLanguageConstants.vsCMLanguageVC, "C++", new[] { "c", "cpp", "h", "hpp", "inl", "cc", "hxx", "hh" }, Comments = new[] { "//", "/*" }, XmlDoc = "/summary,", XmlDocSurround = true, - CheckDuplicateNames = true, IgnoreCodeType = true, ParentlessFullName = true)] + CheckDuplicateNames = true, IgnoreCodeType = true, ParentlessFullName = true, SurroundWith = true)] [ProvideLanguage( "#101", LanguageConsts.vsLanguageJavaScript, "JavaScript", new[] { "js", "aspx", "ascx", "html", "htm", "asp", "master", "cshtml", "vbhtml" }, Comments = new[] { "//", "/*" }, SmartFormat = false)] @@ -217,6 +218,7 @@ private async Task ConfigureServicesAsync(CancellationToken cancellationTo _container.Register(new PerContainerLifetime()); _container.Register(new PerContainerLifetime()); _container.Register(new PerContainerLifetime()); + _container.Register(new PerContainerLifetime()); // Per request services _container.Register(); _container.Register(); @@ -240,6 +242,8 @@ private async Task ConfigureServicesAsync(CancellationToken cancellationTo // Bookmarks feature _container.Register(new PerContainerLifetime()); _container.Register(new PerContainerLifetime()); + // Surround With feature + _container.Register(new PerContainerLifetime()); _container.Compile(); _sharedServiceProvider?.Initialize(this, _container); diff --git a/DPackRx/Package.vsct b/DPackRx/Package.vsct index e9bf4aa..f70cc5a 100644 --- a/DPackRx/Package.vsct +++ b/DPackRx/Package.vsct @@ -627,143 +627,45 @@ - - - - - - - - @@ -1034,16 +936,8 @@ - - - - - - - - @@ -1125,16 +1019,8 @@ - - - - - - - - @@ -1217,17 +1103,9 @@ - - - - - - - - - - - + + + diff --git a/DPackRx/Package/CommandIDs.cs b/DPackRx/Package/CommandIDs.cs index 4443441..bf73775 100644 --- a/DPackRx/Package/CommandIDs.cs +++ b/DPackRx/Package/CommandIDs.cs @@ -57,17 +57,9 @@ public static class CommandIDs [CommandName("Edit.TryCatch", "Global::Ctrl+K, X")] public const int SW_TRY_CATCH = 600; [CommandName("Edit.TryFinally", "Global::Ctrl+K, T")] public const int SW_TRY_FINALLY = 601; - [CommandName("Edit.TryCatchFinally", "Global::Ctrl+K, Y")] public const int SW_TRY_CATCH_FINALLY = 602; - [CommandName("Edit.Using", "Global::Ctrl+K, U")] public const int SW_USING = 603; - [CommandName("Edit.For", "Global::Ctrl+K, F")] public const int SW_FOR = 604; - [CommandName("Edit.ForEach", "Global::Ctrl+K, E")] public const int SW_FOR_EACH = 605; - [CommandName("Edit.While", "Global::Ctrl+K, W")] public const int SW_WHILE = 606; - [CommandName("Edit.DoWhile", "Global::Ctrl+K, D")] public const int SW_DO_WHILE = 607; - [CommandName("Edit.If", "Global::Ctrl+K, I")] public const int SW_IF = 608; - [CommandName("Edit.IfElse", "Global::Ctrl+K, L")] public const int SW_IF_ELSE = 609; - [CommandName("Edit.Switch", "Global::Ctrl+K, S")] public const int SW_SWITCH = 610; - [CommandName("Edit.Property", "Global::Ctrl+K, O")] public const int SW_PROPERTY = 611; - [CommandName("Edit.Region", "Global::Ctrl+K, R")] public const int SW_REGION = 612; + [CommandName("Edit.For", "Global::Ctrl+K, F")] public const int SW_FOR = 602; + [CommandName("Edit.ForEach", "Global::Ctrl+K, E")] public const int SW_FOR_EACH = 603; + [CommandName("Edit.Region", "Global::Ctrl+K, R")] public const int SW_REGION = 604; [CommandName("Tools.DPackWebSite")] public const int PROJECT_HOME = 700; [CommandName("Tools.DPackEmailUs")] public const int SUPPORT_EMAIL = 701; diff --git a/DPackRx/Package/Registration/ProvideLanguageAttribute.cs b/DPackRx/Package/Registration/ProvideLanguageAttribute.cs index fc5c012..d4f2c44 100644 --- a/DPackRx/Package/Registration/ProvideLanguageAttribute.cs +++ b/DPackRx/Package/Registration/ProvideLanguageAttribute.cs @@ -80,6 +80,8 @@ public ProvideLanguageAttribute(string productName, string guid, string name, st public bool ParentlessFullName { get; set; } + public bool SurroundWith { get; set; } + internal string RegKeyName { get { return string.Format(@"{0}\Languages\{1}", _productName, _languageGuid); } @@ -125,6 +127,8 @@ public override void Register(RegistrationContext context) key.SetValue(nameof(this.SmartFormat), Convert.ToInt32(this.SmartFormat)); if (this.ParentlessFullName) key.SetValue(nameof(this.ParentlessFullName), Convert.ToInt32(this.ParentlessFullName)); + if (this.SurroundWith) + key.SetValue(nameof(this.SurroundWith), Convert.ToInt32(this.SurroundWith)); if ((_extensions != null) && (_extensions.Length > 0)) { diff --git a/DPackRx/Services/IKeyboardService.cs b/DPackRx/Services/IKeyboardService.cs new file mode 100644 index 0000000..65508fc --- /dev/null +++ b/DPackRx/Services/IKeyboardService.cs @@ -0,0 +1,20 @@ +using System.Windows.Input; + +namespace DPackRx.Services +{ + /// + /// Keyboard auto-entry service. + /// + public interface IKeyboardService + { + /// + /// Types the specified text. + /// + void Type(string text); + + /// + /// Presses and releases the specified key. + /// + void Type(Key key); + } +} \ No newline at end of file diff --git a/DPackRx/Services/IShellHelperService.cs b/DPackRx/Services/IShellHelperService.cs index dbfc859..125e785 100644 --- a/DPackRx/Services/IShellHelperService.cs +++ b/DPackRx/Services/IShellHelperService.cs @@ -59,5 +59,12 @@ public interface IShellHelperService /// Shows options setting page. /// void ShowOptions() where T : OptionsBase; + + /// + /// Executes built-in command. + /// + /// Internal command name. + /// Optional command arguments. + void ExecuteCommand(string command, string arguments = null); } } \ No newline at end of file diff --git a/DPackRx/Services/IShellSelectionService.cs b/DPackRx/Services/IShellSelectionService.cs index 1cda4fd..24252ed 100644 --- a/DPackRx/Services/IShellSelectionService.cs +++ b/DPackRx/Services/IShellSelectionService.cs @@ -12,6 +12,11 @@ public interface IShellSelectionService /// bool IsContextActive(ContextType context); + /// + /// Returns active document's untyped Project instance. + /// + object GetActiveProject(); + /// /// Returns active document's untyped ProjectItem instance. /// diff --git a/DPackRx/Services/KeyboardService.cs b/DPackRx/Services/KeyboardService.cs new file mode 100644 index 0000000..e1b833f --- /dev/null +++ b/DPackRx/Services/KeyboardService.cs @@ -0,0 +1,36 @@ +using System; +using System.Windows.Input; + +using DPackRx.Helpers; + +namespace DPackRx.Services +{ + /// + /// Keyboard auto-entry service. + /// + public class KeyboardService : IKeyboardService + { + #region IKeyboardService Members + + /// + /// Types the specified text. + /// + public void Type(string text) + { + if (string.IsNullOrEmpty(text)) + throw new ArgumentNullException(nameof(text)); + + KeyboardHelper.Type(text); + } + + /// + /// Presses and releases the specified key. + /// + public void Type(Key key) + { + KeyboardHelper.Type(key); + } + + #endregion + } +} \ No newline at end of file diff --git a/DPackRx/Services/ShellService.cs b/DPackRx/Services/ShellService.cs index a09e302..a80e98a 100644 --- a/DPackRx/Services/ShellService.cs +++ b/DPackRx/Services/ShellService.cs @@ -428,6 +428,29 @@ public void ShowOptions() where T : OptionsBase package.ShowOptionPage(typeof(T)); } + /// + /// Executes built-in command. + /// + /// Internal command name. + /// Optional command arguments. + public void ExecuteCommand(string command, string arguments = null) + { + if (string.IsNullOrEmpty(command)) + throw new ArgumentNullException(nameof(command)); + + ThreadHelper.ThrowIfNotOnUIThread(); + + var dte = GetDTEInternal(); + try + { + dte.ExecuteCommand(command); + } + catch (Exception ex) + { + _log.LogMessage($"Error executing {command} command: {ex.Message}", ex); + } + } + #endregion #region IShellStatusBarService Members @@ -601,6 +624,20 @@ public bool IsContextActive(ContextType context) } } + /// + /// Returns active document's untyped Project instance. + /// + public object GetActiveProject() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Document document = GetActiveDocument() as Document; + if (document == null) + return null; + + return document.ProjectItem?.ContainingProject; + } + /// /// Returns active document's untyped ProjectItem instance. ///