Home > Plug-in Development > How to implement the Sync Edit feature from Delphi IDE inside Visual Studio IDE using DXCore

How to implement the Sync Edit feature from Delphi IDE inside Visual Studio IDE using DXCore

June 22nd, 2011

According to the Embarcadero’s docwiki:

Sync Edit mode in Delphi allows you to change all occurrences of an identifier when you change one instance of that identifier. When you enter Sync Edit mode, you can tab to each highlighted identifier in your current Code Editor window.

The difference of the Sync Edit feature in Delphi from the usual Rename refactoring from Refactor! (for example) is that a declaration of an identifier may not exist. In this case the Rename is simple not available.

As a sample, imagine the following code:

int CalculateMagicNumber(int x, int y, int z)
{
  int magicNumber = x * 3 + y * 2 - z;

  if (magicNumber < 0)
  {
    // TODO: fix it, invert value...
    magicNumber = 12345;
  }
  return magicNumber;
}

And then you paste and replace the following code instead of a comment and the next line of code:

result = Math.Abs(result) * result;

so the code become like this:

int CalculateMagicNumber(int x, int y, int z)
{
  int magicNumber = x * 3 + y * 2 - z;

  if (magicNumber < 0)
  {
    result = Math.Abs(result) * result;
  }
  return magicNumber;
}

Sync Edit allows you to select a block of code and rename identifiers inside a selection, so we can rename the “result” identifier to a “magicNumber” identifier. In Visual Studio, we have to manually change all the “result”s to “magicNumber”s.

Let’s code this functionality as a plug-in for the DXCore. We’ll use the Searcher Provider component which extends the existing Rename refactoring. To make it work, do the following steps:

1. Create a new DXCore plug-in
2. Drop the SearcherProvider control on the designer’s surface
3. Set the following SearcherProvider‘s properties:

  • ActiveFileOnly = true,
  • Description = something,
  • Provider Name = something,
  • UseForRenaming = true

New Searcher Provider component properties

4. Subscribe to the following SearcherProvider‘s events:

New DXCore Searcher Provider component events

The algorithm of the plug-in is the following:

When the Rename‘s refactoring availability is being checked, the refactoring also checks all Searcher providers registered. At this time, the CheckAvialability event of our SearcherProvider is fired, where we should specify whether it is available or not by setting the Available boolean property of the CheckSearchAvailabilityEventArgs parameter. In the event handler of this event we check if a text selection is set and if there’s at least one identifier existent inside this selection. To check if a text selection exists, we simply check the SelectionExists property of the CheckSearchAvailabilityEventArgs parameter. And to check if any identifiers exist inside the selection, we call the CodeRush.Source.IterateNodesInSelection method, which iterates all language elements inside the current selection’s range. Once there’s at least one identifier found, we return the value, indicating that the provider is available. After that, the Rename refactoring will appear in the Refactor! Popup menu:

Refactor! Pro Rename inside Popup Menu

If we perform the Rename refactoring, our SearcherProvider‘s SearchReferences event is fired. Inside this event handler we iterate all nodes inside the current selection and collect all necessary ranges for renaming. At the moment, SearcherProvider can provide a collection of ranges of similar code blocks, so all of them are linked together into a single linked identifers list. We pass this collection to the AddRanges method of the SearchEventArgs parameter.

The SearchPreviewReferences event handler is needed to provide ranges for a refactorings preview hint. In this handler, we filter all collected ranges from the SearchReferences event handler by visibility. If a range is visible on the screen, we can show it inside a preview hint.

The GetRangeToSelect event is fired at the end. This event handler should provide a single range that should be selected inside the code editor once Rename is executed, so we can start rename immediately, starting from the selected range (the first identifier).

Here’s the commented code of the plug-in. I hope the comments and the code itself is clear:

/// <summary>
/// Nodes iterator handler, collects necessary ranges of references
/// (identifiers) for renaming.
/// </summary>
/// <param name="ea"></param>
private void NodeIterationHandler(NodeIterationEventArgs ea)
{
  ElementReferenceExpression reference = ea.LanguageElement as ElementReferenceExpression;
  if (reference == null)
    return;

  if (String.IsNullOrEmpty(_ReferenceName))
    _ReferenceName = reference.Name;
  else if (_ReferenceName != reference.Name)
    return;

  _Ranges.Add(new FileSourceRange(reference.FileNode, reference.NameRange));
  ea.StopIteration = _StopIterationAtFirstElement;
}

/// <summary>
/// Returns true, if there is at least one identifier inside
/// a text selection exist.
/// </summary>
private bool IdentifiersExist()
{
  IterateNodesInSelection(true);
  return _Ranges.Count > 0;
}

/// <summary>
/// Iterates nodes inside a text selection.
/// </summary>
/// <param name="stopOnFirstFound">Specifies whether iteration
/// should stop when a first identifier is found. This may improve
/// performance for the availability checking.</param>
private void IterateNodesInSelection(bool stopOnFirstFound)
{
  // Clear ranges collection from a previous iteration if necessary.
  if (_Ranges != null)
    _Ranges.Clear();

  _ReferenceName = null;
  _StopIterationAtFirstElement = stopOnFirstFound;

  // Fire the nodes iteration process...
  CodeRush.Source.IterateNodesInSelection(new NodeIterationEventHandler(NodeIterationHandler));
}

// event handlers...
/// <summary>
/// Provides resulting references.
/// </summary>
private void searcherProvider1_SearchReferences(object sender, SearchEventArgs ea)
{
  IterateNodesInSelection(false);
  ea.AddRanges(_Ranges);
}

/// <summary>
/// Provides references necessary for a code preview.
/// </summary>
private void searcherProvider1_SearchPreviewReferences(object sender, SearchForPreviewEventArgs ea)
{
  IterateNodesInSelection(false);
  foreach (FileSourceRange fileSourceRange in _Ranges)
    if (ea.TextViewRange.Contains(fileSourceRange.Range))
      ea.AddRange(fileSourceRange.Range);
}

/// <summary>
/// Specifies whether this provider is availabile when it being tested.
/// </summary>
private void searcherProvider1_CheckAvailability(object sender, CheckSearchAvailabilityEventArgs ea)
{
  ea.Available = ea.SelectionExists && IdentifiersExist();
}

/// <summary>
/// Specifies the source code range that should be selected after the search is complete.
/// </summary>
private void searcherProvider1_GetRangeToSelect(object sender, GetRangeToSelectEventArgs ea)
{
  if (_Ranges.Count > 0)
    ea.RangeToSelect = _Ranges[0].Range;
}

The current implementation of the plug-in is rough enough, and you might need to adjust it according to your preference. The plug-in looks for all identifiers inside the current selection and links them together for a renaming:

Sync Edit DXCore Plug-in preview

The source and the binaries of the plug-in are attached (14,248 bytes, Visual Studio 2010, C#).

—–
Products: DXCore
Versions: 11.1 and up
VS IDEs: any
Updated: Jun/22/2011
ID: D095

Similar Posts:

  1. No comments yet. Be the first and leave a comment!