Give FSharpPackage its own VSPackage resource set - #20408
xperiandri wants to merge 3 commits into
Conversation
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev
|
This comment has been minimized.
This comment has been minimized.
0216897 to
f0edf06
Compare
T-Gro
left a comment
There was a problem hiding this comment.
Really nice piece of work — the diagnosis here is unusually precise and the fix is the minimal, correct one. A few things I verified and loved, plus two small notes.
What I checked
- Mechanism is sound.
FSharpPackagederives fromAbstractPackage<_,_>→ MPFPackage, whose shell-side string lookups go throughSVsResourceManagerwith the well-knownVSPackagebasename. Wiring the resx withManifestResourceName=VSPackageembeds it asVSPackage.resources, exactly matchingFSharp.ProjectSystem.FSharp.fsproj. SoResourceManager("VSPackage", FSharp.Editor)will now resolve. - Every referenced ID is covered. 100 (
ProvideLanguageService.languageResourceID), 101s (ProvideEditorFactory), 6000s/6001s (ProvideOptionPage+ProvideKeyBindingTable), and 6008–6012/6014 (the sixProvideLanguageEditorOptionPagepage-name IDs). I grepped the whole attribute block — no other numeric ID is left unbacked (ProvideToolWindowcarries no resource ID here). - The asymmetry actually confirms the diagnosis. The last arg of each
ProvideLanguageEditorOptionPage(IntelliSensePageKeywords, …) already lives inFSharp.Editor.resxand has worked for years — because those are resolved by the option-page automation via the assembly's default resource set, whereas the numeric page-name IDs are resolved by the shell via theVSPackageconvention. That's precisely why the numeric IDs threw while the keyword strings didn't. Great catch. - Loc pipeline lines up. 13
xlflanguages, matching the existingFSharp.Editor.resxset; each has all 10 trans-units withoriginal="../VSPackage.resx";state="new"is normal for fresh strings. NoGenerateSourceon the resx is correct (numeric names aren't valid identifiers).
Two small notes (non-blocking)
- The PR body says "every value is copied verbatim from the literal fallback string already passed alongside its resource ID." That's true for 6000/6001/6008–6014, but 100 (
F#) and 101 (F# Source File) have no literal fallback —ProvideLanguageService/ProvideEditorFactoryonly take the numeric ID.F#tracksstrLanguageName, butF# Source Fileis a genuinely new string. It's a reasonable editor-factory display name; just worth confirming that's the wording you want, since it's the one value not anchored to anything pre-existing. - Consider adding one line to the resx header comment explaining the split — i.e. why the page-name IDs belong in
VSPackage.resxwhile the sibling*PageKeywordsstay inFSharp.Editor.resx. The next person to touch this will absolutely wonder, and you've already worked out the answer.
Nice, surgical change with a real experimental-hive repro behind it. LGTM once you're happy with the 101 wording.
Address review feedback on dotnet#20408. Every one of the ten strings already exists, translated, elsewhere in vsintegration, so borrow those targets instead of shipping the new VSPackage.resx English-only: "F# Source File" and "F# Tools" from FSharp.ProjectSystem.FSharp's VSPackage.resx, the six option-page names from FSharp.UIResources' Strings.resx, "F# Interactive" from the FSI command table. Also record in the resx header why the page-name IDs live here while the sibling *PageKeywords stay in FSharp.Editor.resx: the shell resolves the former through SVsResourceManager against the fixed "VSPackage" basename, the option-page automation resolves the latter itself out of the assembly's default resource set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks for the careful read — both notes addressed in 694e0d2. 1. The
|
| IDs | Borrowed from |
|---|---|
101, 6000 |
FSharp.ProjectSystem.FSharp/xlf/VSPackage.*.xlf |
6001 |
FSharp.VS.FSI/xlf/MenusAndCommands.vsct.*.xlf |
6008–6012, 6014 |
FSharp.UIResources/xlf/Strings.*.xlf |
Only one source string had two candidate translations — zh-Hans Formatting, which is 格式设置 in FSharp.UIResources (the options-page label) and 正在格式化 in FSharp.Editor.resx's dead 6014 copy. The latter is the progress verb "formatting…", wrong for a page name, so 格式设置 won.
Verified in the built satellite rather than by eye:
> $asm = [Reflection.Assembly]::LoadFrom('artifacts/bin/FSharp.Editor/Debug/net472/ru/FSharp.Editor.resources.dll')
> $asm.GetManifestResourceNames()
VSPackage.ru.resources
LegacyResolver.ru.resources
FSharp.Editor.ru.resources
> # VSPackage.ru.resources
100 = F#
101 = Исходный файл F#
6000 = Инструменты F#
6001 = F# Interactive
6008 = IntelliSense
6009 = QuickInfo
6010 = Исправления кода
6011 = Производительность
6012 = Дополнительный
6014 = Форматирование
2. The resx header comment
Added, phrased as the rule for the next person rather than as history:
<!--
Only the strings the Visual Studio shell resolves on FSharpPackage's behalf belong here.
The shell goes through SVsResourceManager, which looks up the numeric ID against the fixed
basename "VSPackage" in the package assembly, so this file must embed as VSPackage.resources
(see ManifestResourceName in FSharp.Editor.fsproj). Strings the managed option-page code looks
up itself - the *PageKeywords named as the last argument of each ProvideLanguageEditorOptionPage
- come from the assembly's default resource set and stay in FSharp.Editor.resx.
-->One thing I found but deliberately did not touch
FSharp.Editor.resx already contains 6008–6012 and 6014 with the same values — someone added the page names to the default resource set, which is precisely where the shell never looks, and they've been dead ever since (the MissingManifestResourceException in the PR description is the proof: had anything resolved them, it wouldn't have thrown). Nothing but the ProvideLanguageEditorOptionPage attributes references those IDs, and those go through VSPackage.
Deleting them is correct but churns 13 more FSharp.Editor.*.xlf files for zero functional change, so I left it out of this PR rather than widening the diff. Happy to do it here or in a follow-up — your call.
🤖 Generated with Claude Code
Address review feedback on dotnet#20408. Every one of the ten strings already exists, translated, elsewhere in vsintegration, so borrow those targets instead of shipping the new VSPackage.resx English-only: "F# Source File" and "F# Tools" from FSharp.ProjectSystem.FSharp's VSPackage.resx, the six option-page names from FSharp.UIResources' Strings.resx, "F# Interactive" from the FSI command table. Also record in the resx header why the page-name IDs live here while the sibling *PageKeywords stay in FSharp.Editor.resx: the shell resolves the former through SVsResourceManager against the fixed "VSPackage" basename, the option-page automation resolves the latter itself out of the assembly's default resource set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
694e0d2 to
387eb55
Compare
Address review feedback on dotnet#20408. Every one of the ten strings already exists, translated, elsewhere in vsintegration, so borrow those targets instead of shipping the new VSPackage.resx English-only: "F# Source File" and "F# Tools" from FSharp.ProjectSystem.FSharp's VSPackage.resx, the six option-page names from FSharp.UIResources' Strings.resx, "F# Interactive" from the FSI command table. Also record in the resx header why the page-name IDs live here while the sibling *PageKeywords stay in FSharp.Editor.resx: the shell resolves the former through SVsResourceManager against the fixed "VSPackage" basename, the option-page automation resolves the latter itself out of the assembly's default resource set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
387eb55 to
453cbca
Compare
This comment has been minimized.
This comment has been minimized.
|
🔍 Tooling Safety Check — Affects-Build-Infra, Affects-Design-Time
|
T-Gro
left a comment
There was a problem hiding this comment.
🤖🕵️ Please shorten the description using this guidance. Focus on the problem and why the change is needed, in simplified technical English. Leave the implementation inventory to the Files tab and retain necessary caveats.
|
Moved out of the PR description per the pr-description guidance — implementation inventory and test plan for reference: IDs covered by the new resx
The values these IDs carry live in other assemblies today — 6000 ( Test plan
|
|
@T-Gro You should keep an eye on the Perf DDIRTs and OptProf tests after you insert (or during insertion), due to how VS loads resources, this can cause regressions in other extensions/packages. We've hit this before so many times. |
FSharpPackage's registration attributes in LanguageService.fs (ProvideOptionPage,
ProvideKeyBindingTable, ProvideEditorFactory, ProvideLanguageService, and six
ProvideLanguageEditorOptionPage entries) reference numeric resource IDs — 100, 101,
6000, 6001, 6008-6012, 6014 — that the shell resolves via a ResourceManager("VSPackage",
...) against FSharp.Editor's own assembly. No such resource set was ever embedded there,
so any lookup throws MissingManifestResourceException.
Add VSPackage.resx with those IDs, wired in with ManifestResourceName=VSPackage the same
way FSharp.ProjectSystem.FSharp.fsproj already backs its own VSPackage.resx. Every value
is copied verbatim from the literal fallback string already passed alongside its resource
ID in the attribute that references it.
Verified the rebuilt FSharp.Editor.dll's manifest now lists VSPackage.resources, which is
what the exception reported missing.
Address review feedback on dotnet#20408. Every one of the ten strings already exists, translated, elsewhere in vsintegration, so borrow those targets instead of shipping the new VSPackage.resx English-only: "F# Source File" and "F# Tools" from FSharp.ProjectSystem.FSharp's VSPackage.resx, the six option-page names from FSharp.UIResources' Strings.resx, "F# Interactive" from the FSI command table. Also record in the resx header why the page-name IDs live here while the sibling *PageKeywords stay in FSharp.Editor.resx: the shell resolves the former through SVsResourceManager against the fixed "VSPackage" basename, the option-page automation resolves the latter itself out of the assembly's default resource set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
453cbca to
ffa1b3f
Compare
FSharpPackage's registration attributes reference numeric resource IDs resolved throughResourceManager("VSPackage", ...), but FSharp.Editor's assembly has never embedded aVSPackage.resources— so the shell throwsMissingManifestResourceExceptionwhen it looks up the language name, the editor factory's display name, the Tools → Options page names, and the F# Interactive key binding table entry. Addedvsintegration/src/FSharp.Editor/VSPackage.resx, embedded the same wayFSharp.ProjectSystem.FSharp.fsprojalready backs its ownVSPackage.resx; every value matches the literal fallback string already passed alongside its resource ID, so no displayed text changes and these strings can now be localized through the normal xlf pipeline.FSharp.Editor.resxalready carries a dead, unreferenced copy of the page-name strings (6008–6012,6014) — left untouched here rather than churning 13 more.xlffiles for a no-op removal.