Skip to content

[VS] Rename (Ctrl+R, Ctrl+R) for X# symbols - #2154

Merged
RobertvanderHulst merged 15 commits into
X-Sharp:devfrom
hpetriffer:feature/rename
Nov 6, 2026
Merged

RobertvanderHulst merged 15 commits into
X-Sharp:devfrom
hpetriffer:feature/rename

Conversation

@hpetriffer

Copy link
Copy Markdown
Contributor

Rename (Ctrl+R, Ctrl+R) for X# symbols in Visual Studio, built on the Find All References engine (#2148).

What it does

  • Renames the symbol under the cursor in the whole solution: methods, properties (ACCESS/ASSIGN pairs), fields, classes (all parts of a partial class), functions, locals and parameters. A method is renamed with its override family; a constructor call renames its class.
  • Dialog: new name, "Include comments", "Include strings", "Include late-bound calls and symbols (:Name, #Name)" (members only, unchecked at the start of every rename), "Preview changes" (the VS Preview Changes dialog) and "Rename file" (when the file is named after the class).
  • The search runs while the dialog is open; the dialog shows how many places change and how many are only reported.
  • Files open in an editor window are changed in their buffer and stay unsaved. All other files are changed through an invisible editor and saved. One Ctrl+Z reverts everything; closed files are saved back after the undo.
  • Places that may refer to the symbol but are not sure (late bound unless asked for, unresolved, ambiguous overload, inactive #ifdef code, files that could not be read) are never changed. After Apply they are listed in the Find All References window.
  • Refused: interface members and their implementations, overrides of members declared in referenced assemblies (incl. System.Object members of classes without INHERIT), symbols not declared in the X# source.
  • Checks the new name (keywords need @@), warns about existing symbols with the new name, and stops before any change when a closed file is read-only or a file changed since the search.

Changes outside the new Rename folder

  • Find All References engine: search options (late bound, uncertain places, text in comments/strings/inactive code, transitive project scope), the uncertainty of each hit, its position in the file. The FAR command behaves as before.
  • The FAR window handling moved from FindReferencesCommand to ReferencesWindow so that Rename can list its places there; it includes the keep fix of [VS] Find References: Keep Results no longer clears the kept window, search does not block the UI #2152.
  • XFile.Rename(newPath) in the code model: after a file rename the open editor keeps its XFile; Rename moves it to the new path and parses the editor text. (A plain rename of an open, changed file in Solution Explorer still leaves the old class name in the code model until the file is saved; not changed here.)

Tests

  • LanguageService.Tests: 106 tests (name check, guard, conflicts, planner, text and uncertainty of the engine).
  • Checked in the VS 2022 experimental instance with a small test solution (method family + undo, overloads, ACCESS/ASSIGN, class + file rename and back, partial class, refusals, comments/strings, late-bound option, conflict warning, preview, read-only file) and with a large VO solution (39 changes in 13 files, search 2.4 s, apply 0.5 s, one Ctrl+Z restores all).
  • VS 2019 project compiles.

Known limitations

  • No inline rename (Roslyn only); the preview has no diff view.
  • #Name literals of classes and functions are reported, not changed.
  • Ctrl+Z does not rename the file back.
  • No progress or cancel while the changes are applied (fast in practice).

🤖 Generated with Claude Code

hpetriffer and others added 15 commits November 4, 2026 16:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tive scope

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of interface implementations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…undo, safe rollback, one search at a time

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…les stay unsaved

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hout INHERIT

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The project reads a renamed file from disk, but an open file keeps the
rename unsaved, so the code model still had the old class name under
the new path ("'Dog' already exists" when renaming back). The editor
also kept the XFile of the old path, so a second rename did not offer
to rename the file. XFile.Rename moves that object to the new path, and
the editor text is parsed afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new checkbox in the rename dialog (members only, unchecked at the
start of every rename) also changes the late-bound group that Find All
References lists: o:Name sends and #Name symbols; for a symbol only the
name behind the hash is replaced. Unresolved, ambiguous and inactive
places are still only reported. The dialog now says that the places
that are not changed are listed in the Find All References window
after Apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RobertvanderHulst
RobertvanderHulst merged commit cda1038 into X-Sharp:dev Nov 6, 2026
1 check passed
@RobertvanderHulst

Copy link
Copy Markdown
Member

Nice, although I would have preferred a XAML dialog window. That is also supported by VS2019
I do get an exception (that is captured) inside XSharpProjectTreePropertiesProvider.FindClassDeclaration() when I include the option "Rename File" because the original file cannot be found..

@hpetriffer

hpetriffer commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor Author

I will provide an PR with this

RobertvanderHulst pushed a commit that referenced this pull request Nov 8, 2026
…2156 (#2161)

The squash merge of #2156 mixed the SelectTarget method from #2154 (Rename)
with the old inline lookup, so FindReferencesCommand.cs did not compile.
SelectTarget now resolves the symbol through TargetUnderCursor.Resolve and
fills TargetSelection from it; messages are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
RobertvanderHulst pushed a commit that referenced this pull request Nov 8, 2026
…roject tree (#2163)

- The rename dialog is defined in XAML (RenameDialog.xaml) instead of being built in code;
  both LanguageService projects include it. It now uses the VS theme like the other X# windows.
- XSharpProjectTreePropertiesProvider: while a file is renamed the tree can still ask for the
  old path; return early when the file does not exist instead of throwing (and catching)
  a FileNotFoundException in FindClassDeclaration.

Follow-up to #2154 (review comments).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
RobertvanderHulst pushed a commit that referenced this pull request Nov 9, 2026
* [VS] Fix the build: SelectTarget after the Go To Implementation merge

The squash merge of #2156 on top of #2154 mixed the old body of
FindReferencesCommand.SelectTarget with the Go To Implementation version
(await in a non-async method, duplicate locals, undefined doc/name), so
the language service did not compile. SelectTarget now uses
TargetUnderCursor.Resolve, as Find References did in #2156, and keeps the
TargetSelection result that Rename (#2154) needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: thread-safe lookup state and walker suspend

XSharpLookup.nestedSearches is per thread now: lookups run in the
background (inlay hints, Find References) next to QuickInfo on the UI
thread and shared the recursion guard. ModelWalker.Suspend/Resume use
Interlocked, they are called from several threads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: candidates

HintCandidates finds calls with arguments (Name(...), Type{...}) and
implied declarations (VAR, LOCAL IMPLIED, FOREACH VAR, FOR VAR, USING VAR)
on lexer tokens. Declarations (FUNCTION Foo(...)) and OUT VAR are skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: rules

HintRules decides which hints show: parameter names only for arguments
that say nothing (literals, NIL, NULL_*, signed numbers, Type{...}), only
when exactly one overload fits the call, none beyond the last parameter
or after the first PARAMS argument; extension methods called on an
instance skip their first parameter. Type hints are hidden when the
initializer names the type. Type names are shown without namespaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: type rewrite

The double-click on a type hint writes the type: VAR x := e and
LOCAL IMPLIED x := e become LOCAL x := e AS T (X# puts the type after the
initializer), FOREACH VAR x IN c becomes FOREACH x AS T IN c. No edit for
FOR, USING, STATIC and statements with several variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: computer and queue

InlayHintComputer resolves the candidates of a document snapshot on a
private copy of its tokens (the classifier refills the live document),
inside one ModelWalker.Suspend/Resume, and logs the time per file. A type
hint is silent when the initializer ends in an ambiguous call or the type
is unknown. InlayHintQueue runs one computation at a time for the IDE;
a newer request for the same document replaces a queued one and cancels
a running one; RequestDebounced waits for a quiet second. The classifier
raises ParseCompleted after each parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: tagger, options, triggers

InlayHintTagger (IViewTaggerProvider, IntraTextAdornmentTag) shows the
hints of the last computed snapshot, translated to the current one; a
hint whose text was edited disappears until the next result. Hover shows
the parameter (with its XML doc and the member prototype) or the type;
a double-click on a type hint writes the type (one undo step). Colours
in Fonts and Colors (X# Inlay Hint). Options: Show parameter name hints,
Show inferred type hints (both on). Open documents are recomputed after
a quiet second when the code model walker is done (new
ModelWalker.WalkCompleted) or another file of the project was saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Overload filter: untyped parameters make an implicit CLIPPER method

VO METHOD Open(cName, lShared) accepts any number of arguments, but the
code model only marks CLIPPER when the keyword is written. Inlay hints
were missing for calls with fewer or more arguments (Find References
kept them only by the no-match fallback).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: draw an opaque stored background translucent

Fonts and Colors stores colours without alpha: the hint background came
back as opaque #808080, the same grey as the text, and the hints showed
as empty grey boxes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: no type hint when the lookup leaves the initializer text

The parser stores the initializer text as the type name of a VAR; when
the lookup cannot resolve it, that text stays. FOREACH VAR d IN
<DbFile>{o} showed ' AS <DbFile>{o}' and double-click wrote it out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Lookup: FOREACH VAR over a typed array literal and over a VAR local

FOREACH VAR d IN <DbFile>{o} now types d as DbFile. VAR list := s:Load()
kept List<T> instead of the declared List<STRING>, so FOREACH VAR item
IN list could not type item; implied assignments now keep the type
arguments of the declaration, as FOREACH variables already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: review fixes

- Type hint stays silent when the overloads of the final call return
  different types: the lookup takes the first overload, not the one the
  arguments pick (Math.Max(1, 2) said BYTE).
- Type hint stays silent when a type argument does not resolve
  (List<TSource> from ToList(), List<T> from a generic class).
- The double-click edit writes the namespace unless a USING or the
  enclosing namespace brings it in, so the edit compiles.
- The lookup's array type cache is a ConcurrentDictionary: the lookup
  now also runs in the background.
- The hint computation no longer suspends the code model walker; a
  suspend made the walker drop and restart its current project after
  every parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: hover tooltip as QuickInfo shows it

The tooltip of a hint was a plain WPF ToolTip with plain text: white
in every theme and without syntax colours. It now uses the editor's
IToolTipService with the same content elements as the X# QuickInfo
(icon + classified text), so it follows the theme and the
classification colours like the C# hints do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: show all hints while Alt+F1 is held down

As in C#: a third option "Show all hints while pressing Alt+F1" (on by
default) shows the switched off kinds while Alt+F1 is held down in an
X# editor. Releasing F1 or Alt, or moving the focus away, hides them
again at once. A key processor tracks the hold; the tagger computes the
missing kinds without the debounce and filters by the current setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Code model: no file lookup per member when a source type is built

XProject.GetType rebuilds a source type from the database on every lookup. For each member,
XDbResult.UpdateLocation looked the file up through XSolution.FindFile, which creates a new XFile
and reads it from the database; GetType then replaced that file with its own cached one. For a
VO class with many members this was most of the time of a lookup.

UpdateLocation now takes the file from the caller: GetType passes the file it resolves per file id,
the extension method path looks its single file up once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: compute the visible lines first

A large file was resolved as a whole before any hint appeared. The tagger now computes the visible
lines and one screen above and below, and when the view scrolls to lines that are not covered it
computes those after a short pause and merges them with the hints of the same snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: find the call arguments by position

HintCandidates.Calls looked each call name up in the token list to find its arguments. In the
editor the token index does not match the position in that list, so every call searched the whole
list: a 775 KB file spent about a second on this before any call was resolved.

OverloadFilter.Arguments now also takes the position, and the scan passes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: the colours of the C# inline hints

The hints were grey on a translucent grey background in every theme. They now take the colours of
the C# "inline hints" item in Fonts and Colors: the Visual Studio themes colour it, so the hints
follow light and dark like the C# hints, and a colour the user picks for C# applies to both.

Without that item (no Roslyn) the X# item is used, now with the C# defaults. It is no longer shown
in Fonts and Colors, where it would change nothing while the C# item exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: generic type arguments in VAR declarations

VAR d := Dictionary<STRING, INT>{} was split at the comma between the type
arguments into two declarators. The double-click edit then wrote the type
after STRING, in the middle of the type arguments, and the code no longer
compiled. The commas of Name<A, B>{...}, Name<A, B>(...) and <T>{...} now stay
in the initializer; a comparison such as VAR a := x < y, b := 2 still splits.

A generic constructor (VAR l := List<STRING>{}) now counts as a visible type,
like Customer{}, so it no longer gets a redundant type hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Lookup: FOREACH VAR over <T>{} only takes T when it resolves

The element type name of a typed array literal was stored as the type of the
loop variable before the lookup searched it, so an unknown T showed as an
inlay hint (and in QuickInfo). The name is now only taken when the type
resolves; otherwise the variable stays unresolved and gets no hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: a request cancels a pending debounce of the same document

A pending debounced computation was asked for an older state; after a direct
request for the same document it ran once more for nothing. Request now
cancels it. The CancellationTokenSources of the debounce are disposed when
their delay ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Find References, inlay hints: generic type arguments in call arguments

OverloadFilter.Arguments split Foo(Dictionary<STRING, INT>{}, 1) at the comma
between the type arguments and counted three arguments. Find References then
dropped the matching overload, and the inlay hints found no parameter for the
call or shifted the names of a CLIPPER method. The commas of Name<A, B>{...},
Name<A, B>(...) and <T>{...} now stay in their argument; a comparison such as
Foo(a < b, c > d, e) still counts three.

The type argument check moved from HintCandidates to OverloadFilter, where
both use it; it now skips tokens off the default channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Find References, inlay hints: calls with type arguments after the name

List<STRING>{10} and o:Foo<INT>(1, 2) had no argument list: the argument
reader and the inlay hint candidates expected ( or { right after the name,
and a generic creation was not narrowed to the constructors. So these calls
got no parameter hints, and Find References did not filter their overloads.

OverloadFilter.ListAfterName finds the ( or { after the name, also after
type arguments; Arguments, SymbolResolver.ForCreation and
HintCandidates.Calls use it. Foo < 3 and Foo < b, c > d stay no call.
Go To Definition on Foo in Foo<T>{1} now reaches the constructor, as it
already did for Foo{1}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Find References: generic source classes and their constructors

CLASS Box<T> is stored as Box`1. Find References searched the files for
"Box`1", which no source contains, and did not recognise the declaration,
so a generic class had no references at all. The search name and the
declaration check now drop the arity.

The constructor candidates only took Box{...}; Box<INT>{...} is now one too
(OverloadFilter.ListAfterName), so Find References on the CONSTRUCTOR of a
generic class finds its creations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Go To Definition: a keyword is no target

F12 on a keyword (the caret on CLASS) showed "Cannot navigate to the symbol
under the cursor"; when nothing is found F12 stays silent, a keyword is now
treated the same. Seen after F12 on Foo{1} through DTE.ExecuteCommand: the
shell sent the command twice, the first call opened the class with the caret
at the start of its line, the second one hit the CLASS keyword.

The deduplication moved into XSharpGotoDefinition.Targets, which also drops
the keywords.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: off by default, all hints while Alt+F1 is held down

As C# (Roslyn: EnabledForParameters and EnabledForTypes false,
DisplayAllHintsWhilePressingAltF1 true): parameter and type hints are off
until switched on in Tools - Options; holding Alt+F1 shows both. The
defaults are constants in InlayHintSettings, used by the options page and
as the start values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: an argument of only names and dots is no constructor

5df8da4 dropped the end check in HintRules.IsConstructor: for an argument
or initializer made only of IDs and dots (Foo(Color.Red), VAR e :=
Encoding.UTF8) it read past the end of the token list. The exception left
the computation, so the whole range showed no hints. Found in review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: a recompute drops the hints it no longer finds

Merging with the older hints of the same snapshot kept every old hint the
fresh result had no hint for at the same place, also inside the range just
recomputed. After the walker finished or another file was saved, a call
that had become ambiguous kept its stale parameter name until the next
edit. Inside the freshly covered range only the fresh hints count now.
Found in review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Inlay hints: generic VAR calls get the overload check; no fixed time limit in the linear-time test

FinalCallName took the token before the argument list as the call name, so for
VAR x := o:Foo<INT>(1) it found '>' and skipped the check that the call is
unambiguous; the type hint could come from the wrong overload. It now steps back
over the type arguments to the name (checked with OverloadFilter.ListAfterName).

ALargeFileIsScannedInLinearTime had a 1000 ms wall-clock limit that can fail on
the E-cores. It now compares 5000 with 20000 calls (fastest of three runs each):
linear is about 4x, the test allows less than 8x; the quadratic scan it guards
against measured 23x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [VS] Correction to the message of f80b56f (no code change)

f80b56f says: "Go To Definition on Foo in Foo<T>{1} now reaches the
constructor, as it already did for Foo{1}." That is wrong. Correct is:

Find References on Foo in Foo<T>{1} now resolves to the constructors, as it
already did for Foo{1}; Go To Definition still goes to the class, for Foo{1}
as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants