Creating the Ultimate Developer Keyboard–Part 2

Mark Miller
26 August 2014

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

no comments
No Comments

Please login or register to post comments.