Here’s your Game Changer for 2014: IntelliRush in CodeRush 14.2

So you may have noticed, I only use the term “Game Changer” for features that dramatically change and improve the way you work as a developer. The last time I used the term was two years ago, when we introduced the Debug Visualizer for CodeRush.

The CodeRush team has been working on a revisualization of Intellisense designed to enhance existing functionality and add new abilities. The end result we’re calling IntelliRush, and it’s faster and more capable of exploring and entering code than anything you’ve seen before.

No Changes to the Way You Work

Well, we’re going to make you faster, but if you choose to, you can use IntelliRush exactly the same way and pressing exactly the same keys you’re already familiar with. Also, you can enable or disable IntelliRush at any time using the IntelliRush button on the DX toolbar or on the Editor\IntelliRush options page. However, if you want more…

See More

Visual Studio’s built-in Intellisense restricts you to a small window that shows only nine entries (sometimes that small window is a view into hundreds of symbols to scroll through). Small views into big data can be frustrating and take a long time to navigate. IntelliRush helps you find what you’re looking for faster by letting you see more entries without scrolling. And you are free to resize the window to suit your preferences and style of working:

IntelliRushShowsMore

Auto-sizing Width

Even though IntelliRush shows more entries, it continually works to only take up as much width as needed to show visible entries. This lets you see more code and places symbol hints closer to the entries, so your eyes don’t have to travel so far to the sides to read the hints.

In the comparison below, watch how IntelliRush automatically narrows its width depending on the contents in view:

IntelliRushAutoWidth

Filtering (by Symbol Kind)

One of the features we are most excited about is filtering. Have you ever tried to use the built-in Intellisense to find an event, property, interface, delegate, or class, only to realize you were spending a lot of time looking at everything else you didn’t want?

Now, with the smallest effort, you can see a list of in-scope entries that exactly match the kind of symbol you’re looking for.

Here’s how it works:

  1. Inside Visual Studio bring up IntelliRush (like you normally would bring up Visual Studio’s built-in Intellisense).
  2. Tap (quickly press and release) the Ctrl key.
  3. Press the highlighted letter of the filter you wish to apply.

For example, to see only Events, simply tap the Ctrl key and then press the letter “E” key. IntelliRush will show only events:

FilteringOnlyEvents

To see only Properties, simply tap the Ctrl key and then press the letter “P” key:”"

FilteringOnlyProperties

You can filter on:

  • Namespaces
  • Classes
  • Structs
  • Interfaces
  • Enums
  • Delegates
  • Methods
  • Properties
  • Events
  • Fields
  • Locals
  • Visual Studio Code Snippets

Got it? Simple. Easy. Fast. And it helps you narrow down on what you want, obliterating the visual noise caused by everything else.

Filtering by Symbol Text

Now when you filter the list by typing text in the editor, you can see your filter as it applies to each entry.

FilterVisualization1 FilterVisualization3

Overload Exploration

Many developers use Intellisense as an exploration tool, to learn about frameworks and classes declared in large solutions or in referenced assemblies. Here’s an example of what Visual Studio’s built-in Intellisense gives you when you’re on a method with overloads:

IntellisenseOverloads

IntelliRush provides this as well, and then takes it to the next level, giving you a syntax-highlighted submenu for the method overloads (available if you press the Right arrow key).

SubmenuDetail

Now you can easily compare the overloads side-by-side, and select the one you want. After selecting an overload, CodeRush can optionally insert TextFields for you into the code, so after specifying an argument, you can jump to the next argument by simply pressing Enter. It looks like this:

IntelliRushTextFields

More Enhancements Coming

Unfortunately we didn’t have enough time to squeeze all the IntelliRush features we wanted into this 14.2 release. The good news is that significant usability enhancements (to improve the speed and ease of exploration, and to widen IntelliRush scope to include CodeRush templates) are expected in future releases.

Try IntelliRush out and let us know what you think.

Creating the Ultimate Developer’s Keyboard–Part 5

Here’s what we’ve done so far:

Part 1 - the challenge, hardware, and the prototype.
Part 2 - first features - paired delimiters.
Part 3 – keyboard arrives, layout, keys labels, feature binding, plus new related features.
Part 4 – smarter brace keys, recent edits navigation, section navigation, and the pizza key.

Also, from a big picture perspective, we’ve taken a rough idea for making important features more accessible and we’ve taken a step in that direction, using off the shelf hardware. And while this post will likely be the last in this particular series, you can be assured that I’m thinking about taking this to the next level, with a full keyboard designed for developers everywhere (maybe a kickstarter campaign is in our future).

Super Sibling Nav Returns

One of my favorite features ever built in a CodeRush Feature Workshop was Super Sibling Nav. This navigation feature allows you to move up/down through neighboring methods or properties, maintaining relative cursor position as you move up and down. This is not something you need often, but when you need it (for example, examining or changing similar parts of adjacent methods), it can save a lot of time and keep your brain focused on what’s important. The problem with this feature was always the shortcut binding. What key can we bind it to that is easy to remember and easy to hit? After we built the feature we tried Ctrl+Alt+Page Up/Down. However that binding never really took off with me.

For me the good news is that I’ve dedicated two keys for this feature:

SuperSiblingNavKeys

If you want to use this feature too, download and install CR_SuperSiblingNav.

We’ll add the bindings for this later.

Repositioning the Pizza Key

In Part 4 we added the Pizza key. And while the key we replaced was a good one, I didn’t like its position on the keyboard. The Pizza key was likely a key that would be pressed once per day at most, and it was right next to a key that was used frequently. Also, the Pizza functionality is visually disruptive, ultimately replacing the code with pictures of yummy food. Not the thing you want to see when you’re deep in focus. So I moved it to the upper right, and moved the Run Last Test key to the upper left, and moved the the Fields key to the right where the Pizza key used to be. The new layout looks like this:

NewPizzaKeyPosition2

This repositioning required the following changes:

  • I needed to update the Layout options page to reflect the new keyboard layout.
  • I needed to update the Pizza, Field, and Last Test Run shortcuts so they were using the new key positions.

All the Bindings

Instead of talking you through setting up each of the bindings, instead I’ll document the functionality we have created (and we’re binding to) in the table below:

Key Feature
Pizza  It’s the Pizza key. Brings up the Nom Nom window that allows you to quickly choose from several built-in vendors of programmer’s fuel. You can also click the Find buttons to find a pizza vendor location near you. There’s also an option to go straight to the order page skipping the window altogether (if you check this option, hold down the Shift key the next time you hit the Pizza key to bring the window back).
Transporter The Transport key. This key brings up the “Jump to” menu, which allows you select a target location for the jump, such as method overrides, interface implementors, ancestor classes, etc., depending upon where you are in the code.
CodeRush The CodeRush key. This key brings up a Code/Refactor menu, that shows available refactorings, code declarations, and code modification wizards. The contents of this menu depend upon where you are in the code.
ArrowPlusEnter The arrow keys and the Enter key. These keys let you navigate through the Code/Refactor and the “Jump to” menus quickly without having to move your hand away from the keyboard.

The Enter key has an additional binding if the editor has focus (and no menus are active). It adds a new line beneath the current line regardless of the caret position on the current line (it’s the equivalent of pressing the End key followed by Enter).
Fields Navigates through all the field declaration sections in the active class. This is useful to quickly get the caret to the fields declaration section. This feature drops a marker so you can quickly get back to where you started with Escape.
AltKey + Fields Navigates through all the const declaration sections in the active class.
Properties Navigates through all the property declaration sections in the active class.
AltKey + Properties Navigates through all the event declaration sections in the active class.
Methods Navigates through all the method declaration sections in the active class.
AltKey + Methods Navigates through all the constructor declaration sections in the active class.
PreviousReferenceNextReference Navigates through all the references (inside the solution) of the active type, member, declaration, or string at the caret.
CamelCaseNavPreviousCamelCaseNavNext Camel Case Navigation – navigates to the next uppercase character inside a camel case identifier.
SelectionIncrease Extends the selection by a logical block (for each time it is pressed).
MinusKey Shrinks a previously-extended selection.
SwapAnchorWithActive Swap the selection’s anchor and active positions (useful for extending either side of a selection).
SelectToMarker Select from the caret position to the topmost marker in the file.
SpacerNodeUp
NodePreviousFirstChildNodeRight
Structure Navigation keys.

NodeUp takes you to the parent node.

FirstChild takes you to the first child node.
NodePrevious and NodeRight take you between adjacent sibling nodes.
AltKey + FirstChild Takes you to the last child node.
ShiftKey + NodePrevious Extends the selection to include the node preceding the start of the selection.
ShiftKey + NodeRight Extends the selection to include the node immediately following the end of the selection.
ClipboardHistory Brings up the Clipboard History.
LeftBraceRightBrace
OpenParenRightParen
OpenBracketCloseBracket
DoubleQuotesDoubleQuotes
LeftAngleBracketRightAngle
Paired Delimiter Keys. These keys insert the specified paired delimiters and position the caret in the desired location. For more details on this feature, see part 2 and part 3 of this series.

If a selection exists, the selection is wrapped in the delimiters. Otherwise the delimiters are inserted at the caret. Caret position is determined by which of the two key pairs is pressed.

Pressing the left (opening) key means the caret goes to the left if there is a selection. If there is not a selection pressing the left key means the caret will go between the delimiters (with a TextField between so you can get outside the delimiters by pressing Enter).

Pressing the right (closing) key will position the caret to the right of the delimiters after insertion.

Additionally, the brace keys LeftBrace & RightBrace can be used to add or remove redundant brace delimiters around child statements of a parenting block (such as if, for, while, etc.). Just place the caret at the start of the child statement, or place the caret after the opening redundant brace and then press one of these keys.
DropMarker Drop a navigation marker.
Escape

Cancels CodeRush menus (if they are up) and also collects any markers that have been dropped.

SwapMarkerWithCaret Swap the caret position with the last marker (and dropping a marker before jumping to the last position). This effectively allows you to work in two places at once, quickly switching back and forth between locations.
SuperSiblingNavNextSuperSiblingNavPrevious Navigate up and down between sibling members, maintaining the relative cursor position or selection between adjacent members.
NextViewEditPreviousViewEdits Navigate back and forward through previous edit points in the code.
BrowseRecentFiles Browse recently-opened file history
QuickNav Find Any Symbol
CtrlKey + QuickNav Find Any Member
AltKey + QuickNav Find Any Type – a filtered version of Find Any Symbol.
GotoDefinition Go to Declaration (press Escape to jump back)
Options CodeRush Options
ShiftKey + Options Visual Studio Options
CtrlKey + Options Project Properties (for the active project)
RunLastTests Run Last Test

You can see most of these shortcuts in action in STL Tech Talk episode 23.

Source & Settings Files

You can get all the shortcut bindings here. These should be copied to the CodeRush Settings folder.

Source code to the x-keys engine plug-in (for CodeRush in Visual Studio) is here.

Source code to the new features plug-in we built in this series is here.

Note: Before compiling the source code to the two plug-ins, specify the build output folder to match your CodeRush plug-ins folder.

You can open the settings and plug-in folders in Windows Explorer quickly by right-clicking the orange banner in the CodeRush About box and choosing the desired folder to open. After the DLLs have been built and the settings have been copied, restart Visual Studio.

Wrapping It Up

So this has been an interesting exploration. The custom keyboard is useful, and some of the new features are arguably essential. However, it’s not perfect. And I really want it to be perfect. Taking this to the next level, ideas to consider for my next attempt may include:

  1. A custom hardware solution with Cherry MX keys.
  2. Groups of keys dedicated to navigation, code generation, refactoring, and selection, placed to the right and left of the standard 88-key arrangement, with the ability to snap in securely on either side make a single solid keyboard, or float apart separately for more optimal positioning.
  3. Ergonomic and standard layouts.
  4. Possibly a group of keys dedicated to context-based operations (e.g., code entry, debugging, form layout & design) with embedded OLED screens that change content based on context.
  5. Other neat/fun ideas.

Keyboard2

Thanks for reading. Let me know what you think.

Creating the Ultimate Developer’s Keyboard–Part 4

Here’s what we’ve done so far:

  1. In part 1, we introduced the challenge, discussed hardware options, and revealed the prototype.
  2. In part 2, we wrote our first features (before the keyboard even arrived!) to help us enter paired delimiters like braces, parens, and quotes.
  3. In part 3, we reviewed the keyboard, created our layout, labeled the keys, bound the features we wrote in part 2, and added new related features that added or removed braces automatically for child nodes of if statements.

Making the Brace Keys Even Smarter

I believe I might have some kind of obsessive compulsive behavior. Once I start thinking about something I want to fix, I obsess about it more and more until I finally have to fix it. And yesterday we built a feature that added or removed surrounding braces to orphan child statements. And while functionality was good, accessibility was overly complicated. We essentially bound a toggling feature to two different shortcuts. That’s not good. It increases our mental burden because we have to remember which of the two keys adds or removes braces. I want to remove this burden and make it so either of the two brace keys can be hit, and I want the software to intelligently invoke the appropriate refactoring (if available), and if neither refactoring is available, we can fall back to the default paired delimiter behavior we built in part 2.

So let’s open our CR_KeyFeatures plug-in. Remember to delete the previously-built CR_KeyFeatures.dll before opening (see “Updating and Rebuilding in Future Sessions" in part 2 for steps on how to do this).

We’re going to add a ContextProvider so we can improve our shortcut binding, and make it appear even smarter. Here’s how to do it:

  1. Open up the designer for the Plugin1.cs file.
  2. In the Toolbox, find the ContextProvider control (tip – type a part of the control name into the search box at the top).

    ContextProvider
  3. Drop the ContextProvider on the Plugin1 design surface.
  4. Fill out the following properties for the new ContextProvider:

    Property Value
    (Name) ctxBraceRefactoringAvailable
    Description Satisfied if one of the two brace refactorings are available (Remove Redundant Block Delimiters or Add Block Delimiters).
    ProviderName System\Refactorings\Brace Refactoring is Available

  5. Double-click the ContextProvider to generate an event handler for the ContextSatisfied event. Add this code:

        const string STR_RemoveRedundantBlockDelimiters = "Remove Redundant Block Delimiters";
        const string STR_AddBlockDelimiters = "Add Block Delimiters";
        const string STR_SystemRefactoringIsAvailable = "System\\Refactoring is Available({0})";
        const string STR_Refactor = "Refactor";
    
        private bool RemoveRedundantBlockDelimitersIsAvailable
        {
          get
          {
            return CodeRush.Context.Satisfied(String.Format(STR_SystemRefactoringIsAvailable, STR_RemoveRedundantBlockDelimiters)) == ContextResult.Satisfied;
          }
        }
    
        private bool AddBlockDelimitersIsAvailable
        {
          get
          {
            return CodeRush.Context.Satisfied(String.Format(STR_SystemRefactoringIsAvailable, STR_AddBlockDelimiters)) == ContextResult.Satisfied;
          }
        }
    
        private void ctxBraceRefactoringAvailable_ContextSatisfied(ContextSatisfiedEventArgs ea)
        {
          CodeRush.Context.ClearCache();
          CodeRush.Source.ParseIfTextChanged();
          ea.Satisfied = RemoveRedundantBlockDelimitersIsAvailable || AddBlockDelimitersIsAvailable;
        }
  6. Next, let’s drop an Action onto the design surface. We’re going to create a single command that will apply the brace refactoring that is available.
  7. Fill out the following properties for the new Action:

    Property Value
    Name actSmartBraceRefactoring
    ActionName SmartBraceRefactoring
    Description Adds or removes braces as needed.

  8. Add the following code:

     private void actSmartBraceRefactoring_Execute(ExecuteEventArgs ea)
        {
          if (RemoveRedundantBlockDelimitersIsAvailable)
            CodeRush.Command.Execute(STR_Refactor, STR_RemoveRedundantBlockDelimiters);
          else if (AddBlockDelimitersIsAvailable)
          {
            CodeRush.Command.Execute(STR_Refactor, STR_AddBlockDelimiters);
            CodeRush.Command.Execute("Edit.LineUp");    // Put the caret on the previous line.
            CodeRush.Command.Execute("Edit.LineEnd");    // Put the caret after the opening brace (so Remove Redundant Block Delimiters is immediately available).
          }
        }
  9. Nice. Now, let’s try it out. Run your plug-in to start up a new instance of Visual Studio.

Now this works like I want it to. If I want to add or remove redundant braces, I simply hit either brace key. If I want to embed a selection in braces, I simply hit either brace key (the key I hit determines the active part of the selection). If I want to add new braces I simply hit either brace key (the key I hit determines whether I want the caret inside the braces or after them). Context differentiates.

Six different powerful but related features. Two keys. Simple.

Recent Edits Navigation

Two of the features I use in Visual Studio frequently are the View.NavigateForward and View.NavigateBackward commands. In my install these keys are bound to Ctrl+- (Ctrl+Minus key) and Ctrl+Shift+- (Ctrl+Shift+Minus key). I want to improve these features in a two small ways:

  1. I want to make them more accessible (improving both discoverability and efficiency) by featuring them prominently on the keyboard, with a dedicated key for each direction.
  2. I want to improve the feature with a LocatorBeacon so your eyes and brain find the target location with less cognitive effort.

So to do this we need two Actions that will effectively wrap Visual Studio’s view navigation commands. Here are the steps in detail:

  1. Open up the designer for the Plugin1.cs file.
  2. From the Visual Studio Toolbox window, drop an Action onto the Plugin1 design surface.

    DropAnAction
  3. Fill out the following properties for the new Action:

    Property Value
    Name actNavViewBack
    ActionName NavViewBack
    Description Jumps to previous edit points in the code.
  4. Double-click the Action to generate a handler for its Execute event. Add this code to the handler:

     private void actNavViewBack_Execute(ExecuteEventArgs ea)
     {
       DropMarkerIfNecessary();
       showBeaconAfterNextMove = true;
       CodeRush.Command.Execute("View.NavigateBackward");
     }
  5. Drop another Action onto the Plugin1.cs design surface.
  6. Fill out the following properties for this second Action:

    Property Value
    Name actNavViewForward
    ActionName NavViewForward
    Description Jumps to later edit points in the code.
  7. Double-click the Action to generate a handler for its Execute event. Add this code to the handler:

    private void actNavViewForward_Execute(ExecuteEventArgs ea)
    {
      DropMarkerIfNecessary();
      showBeaconAfterNextMove = true;
      CodeRush.Command.Execute("View.NavigateForward");
    }
  8. Activate the Plugin1.cs design surface.
  9. On the Toolbox, find and drop a LocatorBeacon control onto the design surface.

    LocatorBeaconControl

    Tip: Type “Locator” into the search box at the top of the Toolbox.

    The LocatorBeacon control draws those animated circles on the editor when collecting markers, and are useful for bringing your eyes into the right spot (especially when working with large monitors).
  10. Fill out the following properties for this LocatorBeacon:

    Property Value
    (Name)

    locatorBeacon1

    Duration 500

  11. I want this locatorBeacon to be green so it is distinctive. Inside the PlugIn1.cs source file, navigate to the InitializePlugIn method. Add the following line of code to the end of the method:

    public override void InitializePlugIn()
    {
      base.InitializePlugIn();
    
      locatorBeacon1.Color = DevExpress.DXCore.Platform.Drawing.Color.FromArgb(0x41, 0xBF, 0x79);
    }
  12. Activate and then Click the Plugin1.cs design surface. The Properties window should show the main form selected.

    PluginSurfaceActivated

  13. Now click the Events icon. Plugin design surfaces give you access to scores of Visual Studio and CodeRush events. There are two events we want to listen to.
  14. Double-click the CaretMoved event. Add the following code to show our new locatorBeacon when needed:

    private void PlugIn1_CaretMoved(CaretMovedEventArgs ea)
    {
      if (showBeaconAfterNextMove)
      {
        locatorBeacon1.Start(ea.NewPosition.TextView, ea.NewPosition.Line, ea.NewPosition.Offset);
        showBeaconAfterNextMove = false;
      }
      else if (justShowedBeacon)
        justShowedBeacon = false;
      else
        customerMovedCaret = true;
    }
  15. Activate the Plugin1.cs design surface. Make sure the Properties window is listing events.
  16. Double-click the CommandExecuted event. Add the following code to immediately show the locator beacon after either Visual Studio view navigation commands are invoked:

    private void PlugIn1_CommandExecuted(CommandExecutedEventArgs ea)
    {
      if (showBeaconAfterNextMove)
        if (ea.CommandName == "View.NavigateForward" || ea.CommandName == "View.NavigateBackward")
        {
          showBeaconAfterNextMove = false;
          justShowedBeacon = true;
          TextView active = CodeRush.TextViews.Active;
          if (active != null)
            locatorBeacon1.Start(active, active.Caret.Line, active.Caret.Offset);
        }
    }




Section Navigation

So at the top left of the keyboard, I have four keys with icons representing member sections for Fields, Methods, Properties, and Events. I want these keys to instantly take me to the corresponding section of the active class. I anticipate this will be useful for creating new classes and also examining and understanding classes built by others. So if I hit the Methods button, it should take me to the start of the first method in the active class. Hitting that key a second time should take me to the end of that group of methods. Hitting the method key a third time should take me to the start of the next group of methods found in the file. Holding down the Shift key and hitting the Methods button should take me in the reverse direction.

To build this feature, follow these steps:

 
  1. Open up the designer for the Plugin1.cs file.
  2. From the Visual Studio Toolbox window, drop an Action onto the Plugin1 design surface.

    DropAnAction


    DroppedAction
  3. Fill out the following properties for the new Action:


    Property

    Value
    (Name) actSectionJumpNext
    ActionName SectionJumpNext
    Description Jumps to the next specified section (pass the section name as a parameter – can be Methods, Properties, Fields, Events, or Constructors).

  4. Now we need to add a parameter. Double-click the Parameters (Collection)” value to bring up the Parameter Collection Editor.
  5. Click the Add button. Specify the following properties for the parameter and click OK:


    Property

    Value
    Description The section to jump to.
    Name section
    Optional False
    Type String

  6. Good. Now we need another Action to handle the jump back in the reverse direction. To save time, let’s copy this action and paste it back on the plug-in’s design surface. Change the following properties (text in red shows changes from the original Action):


    Property

    Value
    (Name) actSectionJumpPrevious
    ActionName SectionJumpPrevious
    Description Jumps to the previous specified section (pass the section name as a parameter – can be Methods, Properties, Fields, Events, or Constructors).

    The parameter remains the same.
  7. Double-click the actSectionJumpNext Action to generate an event handler for the Execute event. Add this code:

    private void actSectionJumpNext_Execute(ExecuteEventArgs ea)
    {
      var parameter = actSectionJumpNext.Parameters.GetString("section");
      TargetSections targetSection = GetTargetSection(InitialCase(parameter));
    
      JumpToNextSection(targetSection);
    }
  8. Reactivate the Plugin1.cs design surface. Double-click the actSectionJumpPrevious Action to generate an event handler for the Execute event. Add this code:

    private void actSectionJumpPrevious_Execute(ExecuteEventArgs ea)
    {
      var parameter = actSectionJumpPrevious.Parameters.GetString("section");
      TargetSections targetSection = GetTargetSection(InitialCase(parameter));
    
      JumpToPreviousSection(targetSection);
    }
  9. Add the following support code:

    public enum TargetSections
    {
      Unknown,
      Fields,
      Constants,
      Methods,
      Properties,
      Constructors,
      Events,
      Types
    }
    
    static string InitialCase(string targetSection)
    {
      if (targetSection == null || targetSection.Length < 1)
        return targetSection;
      return char.ToUpper(targetSection[0]) + targetSection.Substring(1).ToLower();
    }
    
    static TargetSections GetTargetSection(string targetStr)
    {
      if (targetStr.StartsWith("Field"))
        return TargetSections.Fields;
      else if (targetStr.StartsWith("Method"))
        return TargetSections.Methods;
      else if (targetStr.StartsWith("Constructor"))
        return TargetSections.Constructors;
      else if (targetStr.StartsWith("Event"))
        return TargetSections.Events;
      else if (targetStr.StartsWith("Propert"))
        return TargetSections.Properties;
      else if (targetStr.StartsWith("Const"))
        return TargetSections.Constants;
      else if (targetStr.StartsWith("Type"))
        return TargetSections.Types;
      else
        return TargetSections.Unknown;
    }
    
    static List<LanguageElementType > GetTypesToFind(TargetSections targetSection)
    {
      List<LanguageElementType > typesToFind = new List<LanguageElementType>();
      if (targetSection == TargetSections.Constructors)
        typesToFind.Add(LanguageElementType.Method);
      else if (targetSection == TargetSections.Events)
        typesToFind.Add(LanguageElementType.Event);
      else if (targetSection == TargetSections.Fields)
      {
        typesToFind.Add(LanguageElementType.Variable);
        typesToFind.Add(LanguageElementType.InitializedVariable);
      }
      else if (targetSection == TargetSections.Constants)
        typesToFind.Add(LanguageElementType.Const);
      else if (targetSection == TargetSections.Types)
      {
        typesToFind.Add(LanguageElementType.TypeDeclaration);
        typesToFind.Add(LanguageElementType.Delegate);
        typesToFind.Add(LanguageElementType.Enum);
      }
      else if (targetSection == TargetSections.Methods)
        typesToFind.Add(LanguageElementType.Method);
      else if (targetSection == TargetSections.Properties)
        typesToFind.Add(LanguageElementType.Property);
      return typesToFind;
    }
    
    static void AddRange(List<SourceRange> existingRanges, SourceRange sourceRange, SourcePoint end)
    {
      sourceRange.End = end;
      existingRanges.Add(sourceRange);
    }
    
    static List<SourceRange> GetExistingRanges(TargetSections targetSection, TypeDeclaration activeType)
    {
      bool lookingForConstructor = targetSection == TargetSections.Constructors;
    
      List<LanguageElementType> typesToFind = GetTypesToFind(targetSection);
    
      bool lookingForNextSectionStart = true;
      SourceRange sourceRange = SourceRange.Empty;
      Member lastMatchingMember = null;
      List<SourceRange > existingRanges = new List<SourceRange>();
      foreach (Member member in activeType.AllMembers)
      {
        bool foundMatchingMember = typesToFind.Contains(member.ElementType);
        if (foundMatchingMember && lookingForConstructor)
        {
          Method method = member as Method;
          if (method != null && method.IsConstructor)
            foundMatchingMember = false;
        }
        if (foundMatchingMember)
        {
          lastMatchingMember = member;
          if (lookingForNextSectionStart)
          {
            sourceRange.Start = member.Range.Start;
            lookingForNextSectionStart = false;
          }
        }
        else if (!lookingForNextSectionStart)
        {
          AddRange(existingRanges, sourceRange, lastMatchingMember.Range.End);
    
          lookingForNextSectionStart = true;
          sourceRange = SourceRange.Empty;
          lastMatchingMember = null;
        }
      }
    
      if (!lookingForNextSectionStart)
        AddRange(existingRanges, sourceRange, lastMatchingMember.Range.End);
      return existingRanges;
    }
    
    static SourcePoint GetPreviousTarget(List<SourceRange> existingRanges)
    {
      SourcePoint target = SourcePoint.Empty;
      int activeLine = CodeRush.Caret.SourcePoint.Line;
      bool targetIsInPreviousRange = false;
      for (int i = existingRanges.Count - 1; i >= 0; i--)
      {
        SourceRange thisRange = existingRanges[i];
        if (targetIsInPreviousRange)
          return thisRange.End;
    
        int startLine = thisRange.Start.Line;
        int endLine = thisRange.End.Line;
        if (activeLine == startLine)
          targetIsInPreviousRange = true;
        else if (activeLine <= endLine && activeLine > startLine)
          return thisRange.Start;
      }
    
      if (targetIsInPreviousRange || target == SourcePoint.Empty)
      {
        // We need to loop from the beginning back around to the end...
        if (existingRanges.Count > 0)
          return existingRanges[existingRanges.Count - 1].End;
      }
    
      return target;
    }
    
    static SourcePoint GetNextTarget(List<SourceRange> existingRanges)
    {
      SourcePoint target = SourcePoint.Empty;
      int activeLine = CodeRush.Caret.SourcePoint.Line;
      bool targetIsInNextRange = false;
      foreach (SourceRange existingRange in existingRanges)
      {
        if (targetIsInNextRange)
          return existingRange.Start;
    
        int startLine = existingRange.Start.Line;
        int endLine = existingRange.End.Line;
    
        if (activeLine == endLine)
          targetIsInNextRange = true;
        else if (activeLine >= startLine && activeLine < endLine)
          return existingRange.End;
      }
    
      if (targetIsInNextRange || target == SourcePoint.Empty)
      {
        // We need to loop back around to the beginning...
        if (existingRanges.Count > 0)
          return existingRanges[0].Start;
      }
    
      return target;
    }
    
    void SectionJump(SourcePoint target)
    {
      if (target != SourcePoint.Empty)
      {
        DropMarkerIfNecessary();
        showBeaconAfterNextMove = true;
        CodeRush.Caret.MoveTo(target);
      }
    }
    
    void JumpToNextSection(TargetSections targetSection)
    {
      TypeDeclaration activeType = CodeRush.Source.ActiveType as TypeDeclaration;
      if (activeType == null)
        return;
    
      SectionJump(GetNextTarget(GetExistingRanges(targetSection, activeType)));
    }
    
    
    void JumpToPreviousSection(TargetSections targetSection)
    {
      TypeDeclaration activeType = CodeRush.Source.ActiveType as TypeDeclaration;
      if (activeType == null)
        return;
    
      SectionJump(GetPreviousTarget(GetExistingRanges(targetSection, activeType)));
    }

This code is a bit sophisticated. It first collects the ranges of member groups inside the current class. So for example, there may be several groups of methods in a class – maybe organized by visibility, instance/static, functionality, or perhaps not organized at all. For the purposes of this code, a member range is defined as the distance between the start and end of a single member (or a group of two or more adjacent members of the same type). After collecting all those ranges, it then calculates the next position based on the desired movement direction (Previous or Next) and also based on the current position. Wrapping from the end of the last group back to the beginning of the first group is supported.

Bonus Actions

There are two more Actions I’ve added to the plug-in. One, called ShowURL, displays the specified web site inside the Visual Studio browser as a document.

The other, SendKeys, will send the keys in the specified key string to the active window. Key strings can contain individual characters (e.g., a-z, A-Z, 0-9, punctuation, etc.), and can also optionally include any of the elements of the Keys enum (placed in square brackets). For example, “// Hello World[Enter]”.

Last Minute Request – The Pizza Key

I’m not sure why this is, but often I get requests that some might consider crazy (or surely a joke). For example, this comment came in yesterday:

WhereIsThePizzaKey

Great question, Josh! Well, as I said from the beginning, this was always expected to be a work in progress – something I would refine over time. And thanks to you, my keyboard now has a pizza key:

ThePizzaKey

The pizza key now replaces the Events icon since it was unlikely to get frequent use. If you want to print this out, here’s the image:

PizzaKeyTemplate
(click picture above to get to full size image)

The first time you press the Pizza key, you will see this window:

NomNom

There are very likely some locale issues here in the Quick Links to food chains that may not be in your area, however you can get full source to this plug-in and change those Quick Links if you like. And there are always the Find buttons…. Ultimately, when you decide upon a place you want, you can specify its online ordering URL in the textbox here and always have it only a single click away.

Yummy!

Tomorrow

OK, that’s it for today. Next time we’ll wrap up all the feature shortcut binding and complete the series.

Creating the Ultimate Developer Keyboard–Part 3

The Keyboard Arrives.

The keyboard is here. We’ve already created a set of features to bind to it in part 2 (and this series starts with a prototype in part 1).

If you’ve seen me write code with an Xbox guitar before, you may have noticed how easy it was to navigate through the code. It turns out that guitars are way more intuitive at moving around code than regular keyboards are. The Xbox guitar has an up/down strum switch, and two groups of fret keys arranged in a straight line moving out from the strum key. So for navigation, I treated the fret keys like multipliers, from smallest distance moved to greatest distance moved. So to move toward the end of the file, I would strum down. With no fret keys pressed, the caret would simply move right. Depending on which fret keys were engaged, I could jump through the uppercase characters in a camel-cased identifier, through words, home/end, line up/down, up/down between methods, page up/down, as well as other special moves such as dropping and collecting markers or navigating through the history of edits. Similarly, creating and extending selections was easy. I just held down the blue fret key in combination with the other fret keys to extend the selection by the specified distance. As a result, navigating through code and refactoring with with the Xbox guitar was nirvana, and much easier and intuitive than trying to navigate with a standard keyboard.

Of course, for entering text, the guitar doesn’t work well. And the transition time to switch to a guitar every time I need to navigate through code is too great to make it practical. However with this keyboard… I’m hoping to recreate and approach some of that nirvana in something everyone can experience.

So at this point, I’m pretty excited.

Hardware Review

Upon taking the keyboard out of the package, there are few things I immediately notice. The keyboard is about 10-15% heavier and larger than I expected. This is not a showstopper for me, but something to keep in mind in case I ever collaborate on the design a custom programmer’s keyboard – something I’ve been thinking about a lot, lately.

The second thing I notice is that pressing the keys doesn’t feel the same as pressing the keys on my Microsoft Natural keyboard. It’s a subtle difference, and I don’t have great words to describe it, so here’s a totally subjective graph showing the different forces I feel against my fingertips as I press the keys down:

KeyForceComparison

With the X-keys professional, a little more force seems to be required to push the key. As the key approaches the down state, resistance increases, then seems to ease off and the key travels faster, followed by greater resistance as the key approaches its furthest down position. The Microsoft keys, by contrast, require slightly less force (and the force required is consistent pretty much throughout the entire stroke).

The yellow bands in the graphs above show the areas where switch contact appears to be made (and current flows through the switch below that key). With the Microsoft Natural Keyboard it feels like contact is always made in the same spot. But I don’t get the same feeling of precision with the X-keys Professional.

I should emphasize that the differences are subtle and most developers are unlikely to notice them.

I’m able to rationalize both of these issues as acceptable compromises in light of the productivity gains I’m anticipating, and knowing that I’m unlikely to be hitting these keys as frequently or as rapidly as the keys on my QWERTY keyboard from Microsoft.

The rest of the keyboard however, neatly meets expectations. I can pull out keys and replace them with tall keys, wide keys, or even large square keys. I can also replace existing keys with flat key blockers.

The keys themselves have transparent plastic covers that are easy to remove for labeling. Even though the key tops come off easily when pulled straight up, they don’t come off accidentally in response to the sheering force of my hand and fingers brushing rather aggressively against the keyboard, and they don’t come off from normal use. Nice.

KeyCaps

I immediately start on the layout designed in the first post in this series, adding key blockers, tall keys, and wide keys.

KeyExtraction2 KeyBlocker KeyBlockerInstalled

The black key blockers snap solidly into place and give me exactly that tactile feedback I am looking for.

KeyLayoutNoLabels

That was easy. Next I install the software….

Software/SDK Review

The included software is thorough, flexible, and ambitious, allowing you to bind multiple collections of recorded macros to the keys. Key binding collections are swapped in or out depending on which application has focus. Recorded macros can include individual key up or down transitions (not just key presses). However, I found the UI had some discoverability and clarity issues. The software also must be running to use the keyboard – a requirement I wasn’t really interested in.

So with the included software jettisoned, I opened up their SDK to build my own options/setup pages. I don’t want to bind recorded keyboard macros to each key. Instead I want to bind actual Visual Studio and CodeRush commands (including the new features we build as part of this blog series).

The SDK includes a C# example application that shows how to iterate through all the connected keyboards, connect to one, and setup two different callbacks: One that is called every time the data changes (a key is pressed or released), and another that is called when an error occurs (such as the keyboard getting unplugged from the USB port).

When data changes, your callback receives an array of nine bytes. Each byte corresponds to one of the nine columns of buttons on the device. Each of those bytes contain individual bits which are set corresponding to the seven keys in that column where a button is pressed.

I was up and running with the SDK in about an hour.

Labeling the Keys

Based on our design so far, the keys labels look like this (click this image for a full-size version):

KeyShapes

When printing this out, adjust the scale if necessary to ensure that the “1 cm” and/or “1 inch” labels are the correct length.

I put some effort into keeping the graphic conventions consistent. For example, keys with blue backgrounds directly manipulate selections. Keys with arrows are navigation keys. Keys with blue arrows can be combined with the Shift key to extend the selection in the corresponding direction and distance. You can shrink any extended selection with the minus key:

Minus

After some trial and error with the labels, I discovered that if I get the label shape to closely match the key shape (with the rounded corners and the lower curve), they will stay correctly oriented under the transparent tops without the need for any adhesive. This makes assembly and replacement/redesign easy.

After printing, you simply:

1. Cut out each key label…
Cutting

2. Remove the transparent cover…
Pulling

3. Position the label on the key….
Placing

4. Replace the transparent cover…
ReplacingTop  Pushing

If you want to create your own key labels, here’s a template you can open inside any image editor (copy and paste the individual key templates as needed to match your design):

EmptyKeys

(click the image above to see the full-size version)

When printing, scale if needed to ensure the red and/or blue reference rectangles match their indicated lengths.

 

Building the X-keys Plug-in

To make this all work stand-alone (eliminating the need to install the X-keys software), I built an X-keys plug-in for CodeRush in Visual Studio. The plug-in consists of three parts. The X-keys Engine, the Layout options page, and the Shortcuts options page.

X-keys Engine

This simple engine maps the input from the custom keyboard to CodeRush or Visual Studio commands. The engine holds a buffer of keys pressed and ensures those keys are evaluated in the UI thread. This engine also adds support for repeating keys when held down, something the original software and SDK do not support.

To customize keyboard behavior, I built two options pages….

The Layout Options Page

The Layout page lets you create a model of the physical key layout you decide to build. You can manually decide which keys will be blocker keys (the plastic black inserts that replace buttons), or you can click the Auto-detect Blockers button if the keyboard is plugged in which will scan the keyboard for any keys currently down and assign the Blocker attribute to each (blocked keys are always in the down position).

LayoutOptions

Any key that isn’t blocked can be given a Key Name. This makes the settings on the Shortcuts easier to read. If you don’t name the keys, then the shortcuts options page will show the column/row data code (e.g., you’ll see “0.0.64.64.0.0.0.00” instead of “CodeRush”).

The other important thing to do is to specify any tall, wide, or large square keys. This is necessary because when you install a tall or wide key, you are physically connecting the key to two adjacent button stems. In a perfect world, those two keys would always fire at the same time whenever the large button above them was pressed. But in our world, either of those buttons could fire first when the key is pushed down, and either one might fire individually when the key is released. The X-keys engine that runs as part of this CodeRush plug-in exploits your layout settings, converting noise caused by two or more grouped keys making contact (e.g., when a tall or wide button is being pressed and released) into a single solid signal.

You can download my layout settings here. Save to the X-keys subfolder inside your CodeRush settings folder. For example:

    C:\Users\YourName\AppData\Roaming\CodeRush for VS .NET\1.1\Settings.xml\X-keys

The Shortcuts Options Page

The X-keys Shortcuts options page is similar to the CodeRush Shortcuts dialog, allowing for organized shortcut folders, searching, and a rich context that permits a single key to have different behavior depending on where you are in the code.

OptionsShortcuts

Each binding includes an optional combination of shift key modifiers (Ctrl, Alt, and/or Shift). If you want a key to fire a command when any combination of the Ctrl, Alt, or Shift keys are down, click the “Any” button. Leaving all Shift Key buttons in the up (unchecked) position means you are matching only against the X-keys key.

Source to the X-keys engine, which includes the two options pages shown above, is available here:

https://github.com/MillerMark/UltimateDevKeyboard

Binding our First Features

With the X-keys CodeRush plug-in installed in Visual Studio, now it’s time to bind the Smart Paired Delimiters feature we wrote yesterday to the actual keyboard.

On the X-keys/Shortcuts page, I want to keep shortcut bindings organized, so I create a new shortcut folder called “Paired Keys”.

Inside this folder I create new shortcuts for each of the ten keys we have dedicated to our paired key feature. There are twelve shortcuts in all; I’m going to allow the Shift key to modify the double-quote keys so they behave like single-quote keys.

To create a new binding:

  1. Click the New Shortcut button on the Shortcuts toolbar.
  2. Press the key you want to bind this to on the X-keys keyboard. Optionally select any needed Shift Key options.
  3. Specify the Command and any needed parameters for the shortcut.
  4. Specify the Context under which this shortcut binding is valid.

The command for all of these features is the same: SmartPairedDelimiters

The context for all of these bindings is also the same: Focus must be in the Code Editor.

The parameters are all different. Refer to the screenshot below (click for full screen).

PairedKeyBindings

Pro Tip: Once you get the command and context specified for one shortcut, use the Duplicate Shortcut button. Then you simply press the new X-keys key, and change the parameters as needed.

Note: If you want to change a shortcut, just give focus to the mini keyboard layout control on this page, and then press the replacement key on the X-keys keyboard.

If you don’t want to enter all these by hand, you can download my shortcut settings for the Paired Key bindings here. Unzip to your CodeRush settings folder.

Now Let’s Give it a Try

So with our new bindings in Visual Studio, let’s try out the smart delimiter features.

Here’s an animated GIF showing some of my results:

TestingSmarterPairedDelimiters

And just as we designed, pressing the paired delimiter key on the left places the caret inside the delimiters (inside an orange rectangular field - pressing Enter gets you out of the field), and pressing the paired delimiter key on the right places the caret after the delimiter pair.

Let’s Take it to the Next Level

So far, it’s not bad. I’m definitely getting the sense I would be faster and less error prone using these dedicated feature keys instead of trying to make the same thing happen on my Microsoft keyboard. But I’m also getting the feeling that we’re not done with these keys yet. There are several features I’m considering, and since it’s so easy to try this out, instead of telling you what I’m would like to do sometime in the nebulous future (e.g., tomorrow perhaps?), I’m going to do it right now.

I’m a C# developer, and in C# an if-statement needs braces if it has more than one child statement to execute. However if it only has one child, those braces are optional. In addition to being a developer, I’m also a UI guy, so I like the code clean and easy to read. So when an if-statement has only one child, I don’t want any braces around it.

The challenge with the if-statement and maintaining clean code, is that frequently a single child statement will need sibling statements, or you might have several child statements that can be consolidated into a single line of code (such as through Extract Method), which means the previously-needed braces are now redundant.

So the feature I want to build right now (and bind to these dedicated brace keys) is something that instantly removes redundant braces or instantly wraps them around a single child statement. This will save time and keystrokes on an action I find myself taking regularly in the code.

  1. Bring up the X-keys/Shortcuts options page.
  2. Right-click the Paired Keys shortcut folder and select New Folder.
    NewFolder

  3. Enter a folder name of “Redundant Braces” and click OK.
    RedundantBracesFolder
  4. Click the New Keyboard Shortcut button.
    NewKeyboardShortcutButton
  5. Press the Close Brace (“}”) key on the X-keys keyboard.
    PressCloseBrace
  6. Enter the command “Refactor” with the parameters “Remove Redundant Block Delimiters”.

    CommandRefactorRemoveBlockDelimiters

    You can bind to any refactoring by putting its name in the Parameters text box like this.
  7. I want this binding to simply work when I press the “}“ key (when the Remove Redundant Block Delimiters refactoring is available). But we already have a different behavior associated with this key, so we’ll need to use context to determine which action to take. In the Context Picker, scroll down to the bottom of the available contexts and click the “Refactoring is Available” context so it has a single green check in it.

    RefactoringIsAvailableContext
  8. Right-click the “Refactoring is Available” context and choose Parameters

    RightClickRefactoringIsAvailable

  9. Enter “Remove Redundant Block Delimiters” and click OK.

    RefactoringIsAvailableParameters

    Now this shortcut binding will only work when it’s possible to remove redundant braces.
  10. Let’s also constrain this binding so it only works when the code editor has focus:

    CodeEditorFocus

  11. We have one more thing to do before we’re done. We need to disambiguate this new shortcut binding from the other “}“ shortcut added earlier. Fortunately this will be easy. First, right-click the context tree list and choose “Copy Context”.

    CopyContext
  12. Now select the other “}” binding.

    SelectOtherClosingBraceKey
  13. Right-click this binding and choose “Paste Context”.
  14. In the context picker, scroll down to the Refactoring is Available context, and click it once more to turn it into a red “X”.

    RefactoringIsNotAvailable
    This new context means our SmartPairedDelimiters binding will only work when the editor has focus but the Remove Redundant Block Delimiters refactoring is not available.
  15. Now, let’s create an alternate binding for the “{“ key in the same manner, this time bound to the “Add Block Delimiters” refactoring (essentially repeating steps 4-14, changing parameters to the command and the context to “Add Block Delimiters”).
  16. Click OK to save your changes.

Let’s try it out.

OK, so this is pretty cool. One key gives me access to three different useful functionalities, depending upon context.

SecondTestDithered

Next Time

Tomorrow we’ll bind a number of keys to existing CodeRush and Visual Studio features (and improve some existing Visual Studio features to make them even easier to use). See you tomorrow.

Creating the Ultimate Developer Keyboard–Part 2

In Part 1, I showed the hardware I intend to use and gave an preview of functionality we expect to add. I’ve ordered the keyboard and it’s on the way. While we wait for it to arrive, I’m going to create some features that we’ll bind once it gets here. I thought I would start with Paired Delimiter buttons on the left:

SmartBraces

This section of buttons will get us quick access to characters that often show up paired in programming. Pressing any of these keys gives you a complete pair, however the keys in the right column place the caret after the closing delimiter.

The keys in the left column place the caret between the two characters (with a field that gets you outside when you press Enter) so you can add the code you need and get out quickly.

Pressing either key when there is a selection embeds the selection in the pair, with the caret placed on the side of the modified text that corresponds to the column of the button that was pressed (left or right).

So the action we’re going to build is going have one of four behaviors, depending upon whether there is a selection or not and which key you press:

Behavior

Left key pressed

Right key pressed

No Selection

Inserts delimiters, caret between inside field

Inserts delimiters, caret after (no field)

Selection Exists

Wraps selection in delimiters, caret at start

Wraps selection in delimiters, caret at end


So to make this functionality work, we will need to create a plug-in for our IDE, and in the plug-in we need to register at least one command (that we can later bind to these shortcuts) that will add paired delimiters as we’ve spec’d out.

I’m using Visual Studio and CodeRush, but if you’re using something else, consult your extensibility documentation for how you can do the equivalent of what we’ve building here.

  1. From the DevExpress CodeRush menu, select “New Plug-in…”.
  2. Name the plug-in CR_KeyFeatures and click OK. This plug-in is going to hold all the new features we build.

    New Plug-in Project
  3. On the Project Settings dialog, click OK. No changes here. 

    ProjectSettings

    The wizard will generate the plug-in project and open it. Inside Visual Studio, you’ll see a design surface where you can drop IDE-enhancing controls for your plug-in.
  4. From the DXCore section of the toolbox, drop an Action onto the design surface.

    DXCoreAction

    Actions can do anything. You just provide the code. And Actions can be bound to shortcuts. You probably see where this is going. Next, we need to fill out some properties for our Action.
  5. Select the Action control if needed, and set the following properties:

    Property Value
    Name actSmartPairedDelimiters
    ActionName SmartPairedDelimiters
    Description Inserts or wraps code inside paired delimiters, passed as parameters to this action.

  6. Next, we need to add parameters to allow us to specify the leading and trailing delimiters, as well as which key was pressed – left or right. Select the Parameters property and click the “button.

    AddParameters
  7. We’re going to add three parameters. For each parameter, click “Add”, then change the Name and Description properties as follows:

    Parameter Name Parameter Description
    LeadingDelimiter The leading delimiter to insert.
    TrailingDelimiter The trailing delimiter to insert.
    CaretPosition The position of the caret after insertion. Can be either “Left” or “Right”.

    The Parameter Collection Editor should now look like this:

    Parameters

    Click OK.
  8. Nice. OK, our properties are all set. Now let’s click the Events button to see the events for this action.

    ClickEvents
  9. Double-click the Execute event to attach a handler to it.

    ClickExecute
  10. Add the following code inside your event handler:

    private void actSmartPairedDelimiters_Execute(ExecuteEventArgs ea)
    {
    string leadingDelimiter = GetParameter("LeadingDelimiter");
    string trailingDelimiter = GetParameter("TrailingDelimiter");
    CaretPlacement caretPosition = GetCaretPosition();
    if (CodeRush.Selection.Exists)
    WrapSelectionInDelimiters(leadingDelimiter, trailingDelimiter, caretPosition);
    else
    InsertDelimiters(leadingDelimiter, trailingDelimiter, caretPosition); }

  11. Add the following support methods:

    static void WrapSelectionInDelimiters(string leadingDelimiter, string trailingDelimiter, CaretPlacement caretPosition)
    {
      TextDocument activeTextDocument = CodeRush.Documents.ActiveTextDocument;
      if (activeTextDocument == null)
        return;

      SourcePoint savePoint = SourcePoint.Empty;
      if (caretPosition == CaretPlacement.Left)
      {
        TextView activeView = activeTextDocument.ActiveView;
        if (activeView != null)
          savePoint = activeView.Selection.Range.Top;
      }

      string undoMessage = String.Format("Embed {0}{1}", leadingDelimiter, trailingDelimiter);
      activeTextDocument.EmbedLineFragmentSelection(leadingDelimiter, trailingDelimiter, undoMessage, false);

      if (savePoint != SourcePoint.Empty)
        CodeRush.Caret.MoveTo(savePoint);
    }

    static string GetExpansion(string leadingDelimiter, string trailingDelimiter, CaretPlacement caretPosition)
    {
      string expansion;
      if (caretPosition == CaretPlacement.Left)
        expansion = String.Format("{0}«Caret»«Field()»{1}«FinalTarget»", leadingDelimiter, trailingDelimiter);
      else
        expansion = String.Format("{0}{1}«Caret»", leadingDelimiter, trailingDelimiter);
      return expansion;
    }

    static void InsertDelimiters(string leadingDelimiter, string trailingDelimiter, CaretPlacement caretPosition)
    {
      TextDocument activeTextDocument = CodeRush.Documents.ActiveTextDocument;
      if (activeTextDocument == null)
        return;

      CodeRush.UndoStack.BeginUpdate(String.Format("Smart {0}{1}", leadingDelimiter, trailingDelimiter));
      try
      {
        activeTextDocument.ExpandText(CodeRush.Caret.SourcePoint, GetExpansion(leadingDelimiter, trailingDelimiter, caretPosition));
      }
      finally
      {
        CodeRush.UndoStack.EndUpdate();
      }
    }

    string GetParameter(string paramName)
    {
      string parameterValue = actSmartPairedDelimiters.Parameters.GetString(paramName);
      if (parameterValue == "\\\"")    // un-escape quotes (convert \" to ").
        parameterValue = "\"";
      return parameterValue;
    }

    CaretPlacement GetCaretPosition()
    {
      if (GetParameter("CaretPosition") == "Right")
        return CaretPlacement.Right;
      else
        return CaretPlacement.Left;
    }
  12. And this enumeration:

    public enum CaretPlacement

      Right,
      Left
    }

  13. Now run the project. This will start a second instance of Visual Studio. Since we don’t have the keyboard yet, let’s test by setting up some temporary shortcut bindings to our new action. I used Ctrl+Shift+[ and Ctrl+Shift+]. Both of these shortcuts bind to the SmartPairedDelimiters command. Only the parameters change for the left and right keys.

    ShortcutBindings

    Here’s a list of the parameters I used to test:

    To Test: Left Key Parameters Right Key Parameters
    Smart Braces {,},Left {,},Right
    Smart Parens (,),Left

    (,),Right

    Smart Brackets [,],Left [,],Right
    Smart Quotes \",\",Left \",\",Right
    Smart Angle Brackets <,>,Left <,>,Right

    For testing I moved the caret through the editor and pressed the shortcuts (Ctrl+Shift+[ and Ctrl+Shift+]), testing with both selections and no selection. Then I went back into the options dialog and changed the parameters for the two shortcuts and repeated.

Updating and Rebuilding in Future Sessions

As we progress through this series we will add new features and make changes to existing features in this plug-in. When it’s time to compile, you may see errors that the DLL is locked. This can happen when you restart the instance of Visual Studio where you built the plug-in. Restarting causes CodeRush to load all the new plug-ins, which means the plug-in you just built will be loaded into memory (and locked).

Here’s how you can quickly delete the CR_KeyFeatures plug-in (useful whenever you need to recompile and it is locked):

  1. From inside Visual Studio’s DevExpress menu, select CodeRush | About.
  2. Right-click the Orange background and choose “Open Plug-ins folder…”.
    OpenPlugInsFolder[3]
    Two Explorer windows will appear, each containing plug-ins from two different directories.
  3. Close the About box and shut down all instances of Visual Studio.
  4. Inside the Explorer windows, find the CR_KeyFeatures.dll and delete it.

Now you can start Visual Studio and load the CR_KeyFeatures solution to compile, edit, or debug it.

Next Time

OK, we’ve made some good progress. We wrote a useful feature that will be bound to ten of the keys on our keyboard. According to UPS tracking, the keyboard should be here tomorrow. So it won’t be long before we’ll be setting up the keyboard and bind our new features to the new keys. See you tomorrow.

SmartBraces

More Posts