GExperts: New Rename Identifier Editor Expert

GExperts now has a new editor expert for renaming identifiers within a single unit. It’s accessible via Shift+F2. Yes that’s the same shortcut as the Rename Components, which only works in the form designer – Rename Identifier only works in the code editor, so there should be no conflict (But unfortunately there is, I’m working on it. In the mean time, just assign a different shortcut. Edit: Fixed in revision #5192. It’s now possible to assign the same shortcuts for experts in different contexts. Currently only Rename Identifier and Rename Component use this feature.).

Place your cursor on any identifier – a variable, parameter, field, property, type, constant, or method name – and press Shift+F2. A dialog shows what was found:

  • The current identifier name
  • The detected scope (e.g. “local in DoSomething”, “member of TMyClass”, “unit-level”)
  • How many occurrences will be renamed

Type the new name, press OK, and all occurrences are replaced with exactly the casing you typed. The change is undoable with a single Ctrl+Z.

Scope-Aware, Not Just Search-and-Replace

The key feature is that it’s scope-aware. It doesn’t just do a dumb text replacement – it understands Pascal scoping rules:

  • Local variables and parameters: Only renamed within the procedure where they’re declared
  • Class members: Renamed in the class declaration and all method implementations of that class, but not in unrelated code
  • Unit-level identifiers: Renamed throughout the entire unit
  • Shadowing protection: If a procedure declares its own local variable with the same name as a unit-level or class-level identifier, that procedure is excluded from the rename

For example, if you have two procedures that both have a local variable called i, renaming i in one procedure won’t touch the other. Similarly, renaming a class field won’t affect a method that happens to declare a local variable with the same name.

How It Works

The expert uses the TmwPasLex tokenizer to parse the source. This means:

  • Identifiers inside string literals are never touched
  • Identifiers inside comments are never touched
  • Case-insensitive matching (Pascal is case-insensitive), but all occurrences get the exact casing you type
  • Context-dependent directives like read, write, or name used as identifiers are handled correctly

It builds a lightweight scope model of procedures, classes, and records in a single pass, then uses that to determine which occurrences belong to the identifier under the cursor.

Warning: Early Days

This feature is mostly untested in real-world usage. While it has a DUnit test suite with 34 test cases covering various scenarios (local variables, parameters, class members, records, nested procedures, shadowing, etc.), it has not yet seen much use in practice. There will likely be bugs. Please use it carefully, and remember that Ctrl+Z is your friend if the result doesn’t look right.

Known limitations:

  • Only renames within the current unit (by design)
  • with statements are not tracked – members brought into scope via with are renamed if they match lexically
  • Generic type parameters are not specially tracked
  • Nested types inside a class (e.g. a record declared inside a class) are treated as part of the outer class scope

If you run into problems, I’d appreciate bug reports so I can add more test cases and fix them.

As usual, if you want to try this before the next official release, you can compile your own DLL.

Discussion about this in the corresponding post in the international Delphi Praxis forum.