@@ -3409,12 +3409,118 @@ begin
34093409 Result := ProcessList <> ' ' ;
34103410end ;
34113411
3412+ { Scrollable process prompt. Buttons stay on-screen even with long lists.
3413+ Returns IDYES / IDNO / IDCANCEL (Cancel = "check again").
3414+ Form X / Escape returns IDNO so the user is never trapped in a re-prompt loop. }
3415+ function PromptBlockingProcessList (
3416+ const DialogTitle, IntroText, ProcessList, Footnote: String;
3417+ const YesCaption, NoCaption, CheckAgainCaption: String
3418+ ): Integer;
3419+ var
3420+ Form: TSetupForm;
3421+ IntroLabel: TNewStaticText;
3422+ ListMemo: TNewMemo;
3423+ FootLabel: TNewStaticText;
3424+ YesButton: TNewButton;
3425+ NoButton: TNewButton;
3426+ CheckAgainButton: TNewButton;
3427+ ButtonTop: Integer;
3428+ ButtonWidth: Integer;
3429+ Gap: Integer;
3430+ ContentWidth: Integer;
3431+ ModalResult: Integer;
3432+ begin
3433+ Form := CreateCustomForm(ScaleX(520 ), ScaleY(420 ), False, False);
3434+ try
3435+ Form.Caption := DialogTitle;
3436+
3437+ ContentWidth := Form.ClientWidth - ScaleX(24 );
3438+
3439+ IntroLabel := TNewStaticText.Create(Form);
3440+ IntroLabel.Parent := Form;
3441+ IntroLabel.Left := ScaleX(12 );
3442+ IntroLabel.Top := ScaleY(12 );
3443+ IntroLabel.Width := ContentWidth;
3444+ IntroLabel.AutoSize := False;
3445+ IntroLabel.WordWrap := True;
3446+ IntroLabel.Caption := IntroText;
3447+ IntroLabel.Height := ScaleY(48 );
3448+
3449+ FootLabel := TNewStaticText.Create(Form);
3450+ FootLabel.Parent := Form;
3451+ FootLabel.Left := ScaleX(12 );
3452+ FootLabel.Width := ContentWidth;
3453+ FootLabel.AutoSize := False;
3454+ FootLabel.WordWrap := True;
3455+ FootLabel.Caption := Footnote;
3456+ FootLabel.Height := ScaleY(52 );
3457+ FootLabel.Top := Form.ClientHeight - ScaleY(96 );
3458+
3459+ ButtonWidth := ScaleX(120 );
3460+ Gap := ScaleX(8 );
3461+ ButtonTop := Form.ClientHeight - ScaleY(40 );
3462+
3463+ YesButton := TNewButton.Create(Form);
3464+ YesButton.Parent := Form;
3465+ YesButton.Caption := YesCaption;
3466+ YesButton.ModalResult := mrYes;
3467+ YesButton.Default := True;
3468+ YesButton.Width := ButtonWidth;
3469+ YesButton.Height := ScaleY(24 );
3470+ YesButton.Left := Form.ClientWidth - ScaleX(12 ) - (ButtonWidth * 3 ) - (Gap * 2 );
3471+ YesButton.Top := ButtonTop;
3472+
3473+ NoButton := TNewButton.Create(Form);
3474+ NoButton.Parent := Form;
3475+ NoButton.Caption := NoCaption;
3476+ NoButton.ModalResult := mrNo;
3477+ NoButton.Cancel := True; { Escape / title-bar X → No (escape hatch) }
3478+ NoButton.Width := ButtonWidth;
3479+ NoButton.Height := ScaleY(24 );
3480+ NoButton.Left := YesButton.Left + ButtonWidth + Gap;
3481+ NoButton.Top := ButtonTop;
3482+
3483+ CheckAgainButton := TNewButton.Create(Form);
3484+ CheckAgainButton.Parent := Form;
3485+ CheckAgainButton.Caption := CheckAgainCaption;
3486+ CheckAgainButton.ModalResult := mrRetry;
3487+ CheckAgainButton.Width := ButtonWidth;
3488+ CheckAgainButton.Height := ScaleY(24 );
3489+ CheckAgainButton.Left := NoButton.Left + ButtonWidth + Gap;
3490+ CheckAgainButton.Top := ButtonTop;
3491+
3492+ ListMemo := TNewMemo.Create(Form);
3493+ ListMemo.Parent := Form;
3494+ ListMemo.Left := ScaleX(12 );
3495+ ListMemo.Top := IntroLabel.Top + IntroLabel.Height + ScaleY(8 );
3496+ ListMemo.Width := ContentWidth;
3497+ ListMemo.Height := FootLabel.Top - ListMemo.Top - ScaleY(8 );
3498+ ListMemo.ReadOnly := True;
3499+ ListMemo.ScrollBars := ssVertical;
3500+ ListMemo.WantReturns := True;
3501+ ListMemo.Text := ProcessList;
3502+
3503+ Form.FlipAndCenterIfNeeded(False, WizardForm, False);
3504+ ModalResult := Form.ShowModal();
3505+ finally
3506+ Form.Free();
3507+ end ;
3508+
3509+ case ModalResult of
3510+ mrYes:
3511+ Result := IDYES;
3512+ mrRetry:
3513+ Result := IDCANCEL; { keep prior "check again" loop semantics }
3514+ else
3515+ Result := IDNO; { mrNo, mrCancel, X, Escape }
3516+ end ;
3517+ end ;
3518+
34123519{ Returns True when install should continue (possibly after closing blockers). }
34133520function ConfirmAndCloseBlockingInstallProcesses (): Boolean;
34143521var
34153522 ProcessList: String;
34163523 Response: Integer;
3417- MessageText: String;
34183524begin
34193525 Result := True;
34203526
@@ -3433,15 +3539,17 @@ begin
34333539 if not ScanBlockingInstallProcesses(ProcessList) then
34343540 Break;
34353541
3436- MessageText :=
3437- ' These Node.js processes are still running and can block shim updates: ' + # 13 # 10 + # 13 # 10 +
3438- ProcessList + # 13 # 10 + # 13 # 10 +
3439- ' (Includes any node/npm/npx/corepack/pnpm/yarn process on this PC, plus NVM install folders.) ' + # 13 # 10 + # 13 # 10 +
3542+ Response := PromptBlockingProcessList(
3543+ ' {#Name} ' ,
3544+ ' These Node.js processes are still running and can block shim updates: ' ,
3545+ ProcessList,
34403546 ' Yes = close them and continue' + #13 #10 +
3441- ' No = continue without closing (shim rebuild may time out; you will need nvm reshim)' + #13 #10 +
3442- ' Cancel = check again' ;
3443-
3444- Response := MsgBox(MessageText, mbConfirmation, MB_YESNOCANCEL);
3547+ ' No = continue without closing (shim rebuild may time out; run nvm reshim later)' + #13 #10 +
3548+ ' Check again = rescan after you close apps manually' ,
3549+ ' &Yes' ,
3550+ ' &No' ,
3551+ ' Check &again'
3552+ );
34453553
34463554 if Response = IDYES then
34473555 begin
@@ -3998,7 +4106,6 @@ var
39984106 DataRoot: String;
39994107 ProcessList: String;
40004108 Response: Integer;
4001- MessageText: String;
40024109begin
40034110 Result := True;
40044111
@@ -4020,14 +4127,17 @@ begin
40204127 if not ScanBlockingUninstallProcesses(AppRoot, DataRoot, ProcessList) then
40214128 Break;
40224129
4023- MessageText :=
4024- ' Close these NVM processes before uninstall can continue:' + #13 #10 + #13 #10 +
4025- ProcessList + #13 #10 + #13 #10 +
4130+ Response := PromptBlockingProcessList(
4131+ ' {#Name} Uninstall' ,
4132+ ' Close these NVM processes before uninstall can continue:' ,
4133+ ProcessList,
40264134 ' Yes = close them and continue' + #13 #10 +
4027- ' No = cancel uninstall' + #13 #10 +
4028- ' Cancel = check again' ;
4029-
4030- Response := MsgBox(MessageText, mbConfirmation, MB_YESNOCANCEL);
4135+ ' No = cancel uninstall' + #13 #10 +
4136+ ' Check again = rescan after you close apps manually' ,
4137+ ' &Yes' ,
4138+ ' &No' ,
4139+ ' Check &again'
4140+ );
40314141
40324142 if Response = IDNO then
40334143 begin
0 commit comments