(cherry picked from commit 61cec1379b6dc44dbb3cd910d39dbcaf335ae7d4)
(cherry picked from commit 2795822ae6)
(cherry picked from commit 1c5a2e4844)
(cherry picked from commit 65566eaa91)
(cherry picked from commit 40cfdc43b8)
(cherry picked from commit b0138a5045360e83e95748ba8deec3b84f137bc0)
(cherry picked from commit afdb9e5d1f)
(cherry picked from commit 0cca83cacc)
(cherry picked from commit 5e200bbc4b)
(cherry picked from commit 3d26b38afc)
(cherry picked from commit 51b3d30ceafcc14c225dfef7710f6e6945f5c198)
(cherry picked from commit 51c6120cfb)
(cherry picked from commit 537434b1b5)
(cherry picked from commit 672701e9a0)
(cherry picked from commit 558ce1c806)
(cherry picked from commit 1f7cb2ed03217b272786e1aaebc0bfc802c31c76)
(cherry picked from commit c313f0721a)
(cherry picked from commit 31853d399e)
(cherry picked from commit cc90d4b476)
(cherry picked from commit d03ff467da)
(cherry picked from commit 5bc56a6a642bb0baba1a0434c50f6b45cae64cb9)
(cherry picked from commit fdfad7b125)
(cherry picked from commit b869d83490)
(cherry picked from commit 3c336d8050)
(cherry picked from commit 364de1bcba)
- Extract tooling libraries into modules
- Extract plugin descriptor module instead of using core module as both the content and descriptor
(cherry picked from commit 2edb04e444b424febb1099decdb44daf8848d909)
IJ-MR-222180
(cherry picked from commit 44f2dfa84bde686959e0704e777f640886349d41)
GitOrigin-RevId: 1dfe5ce6ce2e9aedb93c1a7cc6affb232468624c
ide.show.plugin.suggestions.on.open is the switch a user sets to say "do not offer me
plugins". The Markdown banner ignored it, so a user who set it to false and restarted still
met the offer over a README.
The platform reads the key before the project-open balloon
(PluginsAdvertiserStartupActivity.kt:50) and not before an editor banner, so every other
pluginSuggestionProvider ignores it. The divergence is deliberate, and the reason is an
asymmetry of costs. Honouring the switch too widely costs a user one suggestion they might
have wanted. Ignoring it shows a plugin offer to a user who asked to be shown none, and that
is what QA filed. The second mistake is the worse one, so the doubt is spent on the side of
the user's answer.
FigmaAdvertiserRegistry.isSuggestionAllowed is where both switches are read, so the provider
and FigmaLinkDocumentListener cannot answer differently. It reads the platform's key with the
one-argument Registry.is, which throws for a key that is not registered, and that is deliberate:
the two-argument form would make this module own a default for a key it does not own, and would
answer with that default before LoadingState.COMPONENTS_LOADED rather than with what the user
set. The key is declared at registry.properties:2239 and both callers need a running editor, so
the throw is unreachable.
The key's own description in registry.properties:2240 reads "Whether to show Suggested plugins
notifications on project open", which is the argument against this change. Reverting it is one
predicate and two call sites.
The place this really belongs is PluginAdvertiserEditorNotificationProvider, where one read
would answer for every provider and nobody would diverge. That is a Platform change and out of
scope for a bug fix.
MarkdownFigmaAdvertiserBannerTest gains two cases, one per caller. The module reports 28 tests,
28 passed.
Claude-Session: https://claude.ai/code/session_01ELVY5UTnoFX4drhGBHRVXK
(cherry picked from commit c71e6e9875f7490cf9c54f2a7ad0dace43bb574e)
GitOrigin-RevId: fe898cf81a38b0e9857bbb746d75923baf650b29
The banner over a Markdown file was answered only when the file was opened.
EditorNotificationsImpl recomputes on a file being opened, on dumb mode, on a root change and
on a plugin loading or unloading (EditorNotificationsImpl.kt:97-132). Editing a file is none
of those, so a link added to an open README waited for the file to be opened again.
FigmaLinkDocumentListener is registered on com.intellij.editorFactoryDocumentListener and
asks EditorNotifications to recompute that one file.
Every document in the IDE reaches it, so the questions are ordered by cost: the registry
switch, then the file's extension, then a search. The search reads a window around the change
and not the file, and it keeps a match only when the match meets the change. A link the change
created always does: a match that is there after a change and was not there before it holds a
character the change wrote, and a match a pure deletion produced spans the position the
deletion left behind. The pattern matches at most 29 characters, so such a match lies within
FIGMA_URL_MAX_MATCH of the change.
The overlap is what makes the ask one per link. Asking for every change with a link merely
near it charges one recompute per keystroke for the life of a file that holds a link, and the
platform's own coalescing sits after an EDT dispatch, so it would coalesce the recompute and
not the dispatch.
ProjectLocator.getProjectsForFile answers which projects to ask. It keeps a project whose file
index reports the file as in content or as excluded, so a file under no project root keeps the
old behaviour and is answered when it is opened.
The suppression of SplitModeApiUsage is written down at the class: the module is shared, and
the listener reads a document and asks for one file's notifications.
MarkdownFigmaAdvertiserBannerTest gains six cases. The URL is typed one character at a time,
and the recorded list is asserted whole, so forty-eight keystrokes have to produce exactly one
ask. The module reports 26 tests, 26 passed.
Claude-Session: https://claude.ai/code/session_01ELVY5UTnoFX4drhGBHRVXK
(cherry picked from commit 7cce24e103a18a6891a0b0fe70e2179d99945154)
GitOrigin-RevId: 66ca03042d6642d5e9520592753e1da4126e64c6
A Markdown file whose text contains a figma.com file, design or proto URL
draws the platform's editor banner with an offer to install Figma Connect.
The offer is shown once per project and it is answered once per project.
The new content module `intellij.markdown.figmaAdvertiser` registers
`com.intellij.pluginSuggestionProvider`. It depends on no `intellij.figma.*`
module, because that plugin loads only when it is installed, which is the one
case this offer does not exist for. The Figma URL rule and the tool window id
are therefore written again here, each with the reason in its KDoc. The URL
rule is deliberately the broader of the two copies: `FigmaUrlDetector` asks
which node to open and so needs a file key, while this asks whether the
author works with Figma, which a keyless link still answers.
The order of the questions follows the cost of answering them: the registry
switch, the file extension, the per-project dismissal, then
`buildSuggestionIfNeeded`, which drops a plugin id that is already loaded.
The file's own text is read last, and the answer is kept on the
`VirtualFile`, so a repaint reads it instead of the text.
The offer's word follows whether the plugin is on disk, not whether it is
switched off. The banner is drawn whenever the plugin is not loaded, and on
disk, switched on and not loaded is an ordinary state: `PluginEnabler.HEADLESS`
writes the flag and loads nothing, and every accepted plugin-advertiser offer
runs it. Such a user would otherwise be offered an install and sent to
Marketplace for a plugin they already have. The click asks the enabler in both
on-disk states, because `DynamicPluginEnabler.enable` loads a descriptor that
is switched on and not loaded rather than needing a restart.
The restart offer is the platform's own, and nothing is added here.
`installAndEnable` runs its success body through
`InstallAndEnableTask.runOnSuccess`, which is given the result of
`PluginInstaller.installAndLoadDynamicPlugin` — false when the plugin needs a
restart. On that branch `PluginsAdvertiserDialogPluginInstaller` has already
run `PluginManagerMain.notifyPluginsUpdated`, whose notification carries the
"Restart to activate plugin updates" action. The Figma tool window, which is
the setup wizard's entry point for a first-time user, is opened through a
safe call that does nothing when the plugin did not load.
`markdown.figma.advertiser` is a FUS group of its own, with a `trigger` field.
`figma.connect` is declared in a module that is absent exactly when this code
runs, and the platform's `plugins.advertiser` group has no field saying which
trigger fired.
Tests: `intellij.markdown.tests`, 20 tests over
`MarkdownFigmaAdvertiserDecisionTest` and `MarkdownFigmaAdvertiserBannerTest`.
Eleven mutation rows, 17 kills, every prediction matched. A UI test is added
to `intellij.figma.uiTests` and is not run; the phase batches one TeamCity
pass.
Claude-Session: https://claude.ai/code/session_01KRDwfXtkThVhoM8EKVGeMC
(cherry picked from commit 2f618d22f0c64a0ad6fb54da8008bf2bd1c34483)
GitOrigin-RevId: 9d6d437048859fb33e986126cb7c1c6bd7c60c46
The advertiser matches a plugin to an UnknownFeature by string equality on the
implementation name. A dependencySupport bean cannot express an offer that depends
on which version of a dependency the project declares. A plugin with a version-aware
offer therefore raises its own balloon on project open, and that balloon and the
advertiser's own can sit on the screen together.
PluginSuggestionNotificationProvider is a new extension point, next to
pluginSuggestionProvider, which does the same job for the editor banner. A provider
returns a ready Notification, or null. The advertiser decides whether to ask. The
provider decides what the balloon says, which notification group it belongs to, and
which actions it carries.
notifyUser now returns whether the advertiser has an offer of its own for the project.
It returns true on the suggest-or-enable branch, on the bundled Try Ultimate branch, and
on the includeIgnored branch that says no plugin was suggested. It returns false where
it built no offer, and run() asks the providers on that false. The return says the
advertiser reached notify, and notify raises no balloon while an earlier balloon of the
group is visible, and none at all for a group the user set to no popup.
The call site sits inside PluginAdvertiserServiceImpl.run, so a path where the
advertiser never reaches notifyUser asks nobody: the registry key
ide.show.plugin.suggestions.on.open switched off, an untrusted project, and headless.
HeadlessPluginAdvertiserServiceImpl.run stays an empty override, so the headless
service and the test service ask nobody either. RiderPluginAdvertiserService overrides
getAddressedMessagePresentation and inherits the new path.
The first answer wins. The advertiser asks to fill the one balloon it did not raise, so
a second balloon here recreates the stacking this extension point prevents.
A provider is called on a background thread. It may suspend and it may take a read
action, because it answers from project content. The advertiser publishes the
notification on the EDT, where a provider takes its own notification down, and it
publishes nothing the provider has already expired. A provider that fails is logged
under its own class and skipped, in the way ExtensionProcessingHelper.computeSafeIfAny
handles a provider that does not suspend. That helper takes a non-suspend function, so
the handling is written out here.
With no provider registered the extension list is empty, the advertiser publishes
nothing extra, and the IDE behaves as it did before.
Verified: bazel build of intellij.platform.ide.impl; 8 tests in
PluginSuggestionNotificationProviderTest and 23 over com.intellij.ide.plugins.advertiser,
all passing; lint_files clean over the new files. Six mutation rows, each turning one
case red: the call site in run() deleted, notifyUser returning true where it built no
offer, the expired-notification guard removed, the first-answer rule replaced by every
answer, the EDT hop removed, and a provider exception let out of the extension list.
Claude-Session: https://claude.ai/code/session_01XabGLGJyJQCmYSPE6kX9Dd
(cherry picked from commit acc9aab3a62f91fc78e81c353838b6a99dcf7ac4)
GitOrigin-RevId: b03f28925ec2772b4540ad38af20363d84f2e1e4
`str(x)` resolves to `builtins.str.__new__`. An explicit `str.__new__(cls)` and a `super().__new__(cls)`
in a `str` subclass resolve to the same declaration, and the inspection reported the type of `cls`.
A conversion never names `__new__`, so the callee name now selects it out.
(cherry picked from commit ca96135ee3a0a94aa0660a44715af07c3fa5b62c)
IJ-MR-222607
GitOrigin-RevId: 9a360a384680c95312ee06290d6522e7bf5af416
A type stub omits a `__str__` and a `__repr__` that only override the `object` ones. The flake8-pyi
rule Y029 removes them. `builtins.pyi` declares them on `object` and on `BaseException` only, and
`ipaddress.pyi` declares neither on `_BaseAddress`.
The inspection read the absence as proof, and reported the type. A generated skeleton hid the defect
on a local interpreter. WSL generates no skeleton, so the inspection reported every type there.
The lookup now gives three answers: DEFINED, NOT_DEFINED and UNKNOWN. It asks the runtime class of
every class of the MRO, one class at a time, so a `__str__` that an ancestor defines also counts. It
also searches the ancestors of each runtime class, because a stub often flattens a private base. It
answers UNKNOWN when a stub has no runtime class, and an UNKNOWN answer never reports.
`PyStringDunderUtil.TYPES_WITHOUT_USEFUL_STRING_CONVERSION` holds `zip`, `map`, `filter`, `enumerate`,
`reversed` and the generator types. The inspection reports them without a skeleton. A hit stops the
stubs after it from adding UNKNOWN, but the walk goes on, because a later base of a subclass can still
define `__str__`. The set replaces `TYPES_WITH_BUILTIN_STR`.
The mock SDK has no runtime module and no skeleton, so a unit test cannot cover the runtime lookup.
`PyStringConversionWithoutDunderMethodEnvTest` covers it with a real interpreter.
Also fixes PY-91292 and PY-89218.
(cherry picked from commit 944221d93ad86882af8b2c4070c2f4183f508083)
IJ-MR-222607
GitOrigin-RevId: 270848329319b6f87d0b9271a0e9e00a1c278f97
This is a combined commit of 2, squashed together
(cherry picked from commit a69aa2166b93267fcd84012a77b1e79a426ba5f0)
(cherry picked from commit 2c7c8313707fc49228a61e1433fbe7703400a89c)
IJ-CR-221035
GitOrigin-RevId: 6c75f6c316c850757f0250c2450054f35e584763