From e5f66cf3ef1bd1cebb7efe19ea24a4a237591a75 Mon Sep 17 00:00:00 2001 From: Stella Huang Date: Tue, 21 Jul 2026 15:17:01 -0700 Subject: [PATCH] Honor parameter information setting for signature help Add a Python editor option backed by fAutoListParams and filter automatic LSP signature-help requests while preserving manual invocation and active-session retriggers. --- Python/Product/PythonTools/PythonTools.csproj | 1 + .../PythonTools/Editor/LanguagePreferences.cs | 5 +- .../PythonLanguageClient.cs | 7 +- .../PythonSignatureHelpMiddleLayer.cs | 70 +++++++++++++++++++ ...onAdvancedEditorOptionsControl.Designer.cs | 10 +++ .../PythonAdvancedEditorOptionsControl.cs | 5 +- .../PythonAdvancedEditorOptionsControl.resx | 37 +++++++++- .../PythonAdvancedEditorOptionsPage.cs | 20 +++++- 8 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs diff --git a/Python/Product/PythonTools/PythonTools.csproj b/Python/Product/PythonTools/PythonTools.csproj index 438a509243..29515d8543 100644 --- a/Python/Product/PythonTools/PythonTools.csproj +++ b/Python/Product/PythonTools/PythonTools.csproj @@ -433,6 +433,7 @@ + diff --git a/Python/Product/PythonTools/PythonTools/Editor/LanguagePreferences.cs b/Python/Product/PythonTools/PythonTools/Editor/LanguagePreferences.cs index 16b3384309..4bde5dbf5d 100644 --- a/Python/Product/PythonTools/PythonTools/Editor/LanguagePreferences.cs +++ b/Python/Product/PythonTools/PythonTools/Editor/LanguagePreferences.cs @@ -25,6 +25,7 @@ internal class LanguagePreferences : IVsTextManagerEvents2, IDisposable { private readonly IVsTextManager _textMgr; private readonly uint _cookie; private LANGPREFERENCES _preferences; + private volatile bool _autoListParams; private bool _isDisposed; public LanguagePreferences(IServiceProvider site, Guid languageGuid) { @@ -41,6 +42,7 @@ public LanguagePreferences(IServiceProvider site, Guid languageGuid) { langPrefs[0].fLineNumbers = 1; ErrorHandler.ThrowOnFailure(_textMgr.SetUserPreferences(null, null, langPrefs, null)); _preferences = langPrefs[0]; + _autoListParams = _preferences.fAutoListParams != 0; var guid = typeof(IVsTextManagerEvents2).GUID; IConnectionPoint connectionPoint = null; @@ -103,6 +105,7 @@ public int OnUserPreferencesChanged2(VIEWPREFERENCES2[] viewPrefs, FRAMEPREFEREN _preferences.IndentStyle = langPrefs[0].IndentStyle; _preferences.fAutoListMembers = langPrefs[0].fAutoListMembers; _preferences.fAutoListParams = langPrefs[0].fAutoListParams; + _autoListParams = langPrefs[0].fAutoListParams != 0; _preferences.fHideAdvancedAutoListMembers = langPrefs[0].fHideAdvancedAutoListMembers; _preferences.fDropdownBar = langPrefs[0].fDropdownBar; _preferences.fLineNumbers = langPrefs[0].fLineNumbers; @@ -122,7 +125,7 @@ public int OnUserPreferencesChanged2(VIEWPREFERENCES2[] viewPrefs, FRAMEPREFEREN public bool AutoListMembers => _preferences.fAutoListMembers != 0; - public bool AutoListParams => _preferences.fAutoListParams != 0; + public bool AutoListParams => _autoListParams; #endregion } diff --git a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs index 0c6f41747d..c5939c9e6e 100644 --- a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs +++ b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs @@ -98,6 +98,7 @@ internal sealed class PythonLanguageClient : ILanguageClient, ILanguageClientCus private List _workspaceFolders = new List(); private FileWatcher.Listener _fileListener; private static TaskCompletionSource _readyTcs = new TaskCompletionSource(); + private readonly PythonSignatureHelpMiddleLayer _signatureHelpMiddleLayer; private bool _loaded = false; // Set by the dispose action before any other cleanup; consulted by // TriggerWorkspaceUpdateConfig and GetSettings so teardown cannot @@ -106,6 +107,7 @@ internal sealed class PythonLanguageClient : ILanguageClient, ILanguageClientCus public PythonLanguageClient() { _disposables = new Common.Core.Disposables.DisposableBag(GetType().Name); + _signatureHelpMiddleLayer = new PythonSignatureHelpMiddleLayer(); } public string ContentTypeName => PythonCoreConstants.ContentType; @@ -118,7 +120,7 @@ public PythonLanguageClient() { public object InitializationOptions { get; private set; } public IEnumerable FilesToWatch => null; - public object MiddleLayer => null; + public object MiddleLayer => _signatureHelpMiddleLayer; public object CustomMessageTarget { get; private set; } public bool IsInitialized { get; private set; } public bool Loaded => this._loaded; @@ -187,7 +189,8 @@ public async Task OnLoadedAsync() { await JoinableTaskContext.Factory.SwitchToMainThreadAsync(); // Force the package to load, since this is a MEF component, // there is no guarantee it has been loaded. - Site.GetPythonToolsService(); + var pyService = Site.GetPythonToolsService(); + _signatureHelpMiddleLayer.Initialize(await pyService.GetLangPrefsAsync()); // Indicate to python tools service we've loaded. _loaded = true; diff --git a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs new file mode 100644 index 0000000000..d2c060fd7f --- /dev/null +++ b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs @@ -0,0 +1,70 @@ +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PythonTools.Editor; +using Microsoft.VisualStudio.LanguageServer.Client; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Newtonsoft.Json.Linq; + +namespace Microsoft.PythonTools.LanguageServerClient { +#if !DEV18_OR_LATER +#pragma warning disable CS0618 +#endif + internal sealed class PythonSignatureHelpMiddleLayer : +#if DEV18_OR_LATER + ILanguageClientMiddleLayer2 { +#else + ILanguageClientMiddleLayer { +#endif + private LanguagePreferences _languagePreferences; + + internal void Initialize(LanguagePreferences languagePreferences) { + Volatile.Write(ref _languagePreferences, languagePreferences ?? throw new ArgumentNullException(nameof(languagePreferences))); + } + + public bool CanHandle(string methodName) => + string.Equals(methodName, Methods.TextDocumentSignatureHelpName, StringComparison.Ordinal); + + public Task HandleRequestAsync(string methodName, JToken methodParam, Func> sendRequest) { + if (ShouldSuppressAutomaticSignatureHelp(methodParam)) { + return Task.FromResult(JValue.CreateNull()); + } + + return sendRequest(methodParam); + } + + public Task HandleNotificationAsync(string methodName, JToken methodParam, Func sendNotification) => + sendNotification(methodParam); + + private bool ShouldSuppressAutomaticSignatureHelp(JToken methodParam) { + var languagePreferences = Volatile.Read(ref _languagePreferences); + if (languagePreferences == null || languagePreferences.AutoListParams) { + return false; + } + + if (!(methodParam is JObject request) || + !(request["context"] is JObject context) || + context["triggerKind"]?.Type != JTokenType.Integer || + context["isRetrigger"]?.Type != JTokenType.Boolean || + context["isRetrigger"].Value() || + (context["activeSignatureHelp"] != null && context["activeSignatureHelp"].Type != JTokenType.Null)) { + return false; + } + + if (!int.TryParse( + context["triggerKind"].ToString(), + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var triggerKind)) { + return false; + } + + return triggerKind == (int)SignatureHelpTriggerKind.TriggerCharacter || + triggerKind == (int)SignatureHelpTriggerKind.ContentChange; + } + } +#if !DEV18_OR_LATER +#pragma warning restore CS0618 +#endif +} diff --git a/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.Designer.cs b/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.Designer.cs index cfe8c19fe2..933aba3510 100644 --- a/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.Designer.cs +++ b/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.Designer.cs @@ -27,6 +27,7 @@ private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PythonAdvancedEditorOptionsControl)); this._completeFunctionParens = new System.Windows.Forms.CheckBox(); this._autoImportCompletions = new System.Windows.Forms.CheckBox(); + this._parameterInformation = new System.Windows.Forms.CheckBox(); tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); tableLayoutPanel1.SuspendLayout(); this.SuspendLayout(); @@ -36,6 +37,7 @@ private void InitializeComponent() { resources.ApplyResources(tableLayoutPanel1, "tableLayoutPanel1"); tableLayoutPanel1.Controls.Add(this._autoImportCompletions, 0, 0); tableLayoutPanel1.Controls.Add(this._completeFunctionParens, 0, 1); + tableLayoutPanel1.Controls.Add(this._parameterInformation, 0, 2); tableLayoutPanel1.Name = "tableLayoutPanel1"; // // _completeFunctionParens @@ -52,6 +54,13 @@ private void InitializeComponent() { tableLayoutPanel1.SetColumnSpan(this._autoImportCompletions, 2); this._autoImportCompletions.Name = "_autoImportCompletions"; this._autoImportCompletions.UseVisualStyleBackColor = true; + // + // _parameterInformation + // + resources.ApplyResources(this._parameterInformation, "_parameterInformation"); + tableLayoutPanel1.SetColumnSpan(this._parameterInformation, 2); + this._parameterInformation.Name = "_parameterInformation"; + this._parameterInformation.UseVisualStyleBackColor = true; // // PythonAdvancedEditorOptionsControl // @@ -69,5 +78,6 @@ private void InitializeComponent() { #endregion private System.Windows.Forms.CheckBox _autoImportCompletions; private System.Windows.Forms.CheckBox _completeFunctionParens; + private System.Windows.Forms.CheckBox _parameterInformation; } } diff --git a/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.cs b/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.cs index e48ad93344..853cad4b37 100644 --- a/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.cs +++ b/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.cs @@ -22,9 +22,12 @@ public PythonAdvancedEditorOptionsControl() { InitializeComponent(); } - internal void SyncControlWithPageSettings(PythonToolsService pyService) { + internal bool ParameterInformation => _parameterInformation.Checked; + + internal void SyncControlWithPageSettings(PythonToolsService pyService, bool parameterInformation) { _autoImportCompletions.Checked = pyService.AdvancedEditorOptions.AutoImportCompletions; _completeFunctionParens.Checked = pyService.AdvancedEditorOptions.CompleteFunctionParens; + _parameterInformation.Checked = parameterInformation; } internal void SyncPageWithControlSettings(PythonToolsService pyService) { diff --git a/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.resx b/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.resx index a7dea5b27b..9d7b74b703 100644 --- a/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.resx +++ b/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsControl.resx @@ -201,6 +201,39 @@ 1 + + Left + + + True + + + 11, 88 + + + 11, 6, 11, 6 + + + 237, 29 + + + 3 + + + &Parameter information + + + _parameterInformation + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tableLayoutPanel1 + + + 2 + Fill @@ -211,7 +244,7 @@ 6, 6, 6, 6 - 3 + 4 699, 510 @@ -232,7 +265,7 @@ 0 - <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="_completeFunctionParens" Row="1" RowSpan="1" Column="0" ColumnSpan="2" /><Control Name="_autoImportCompletions" Row="0" RowSpan="1" Column="0" ColumnSpan="2" /></Controls><Columns Styles="Percent,100,Absolute,545" /><Rows Styles="AutoSize,0,AutoSize,0,Absolute,20" /></TableLayoutSettings> + <?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="_completeFunctionParens" Row="1" RowSpan="1" Column="0" ColumnSpan="2" /><Control Name="_autoImportCompletions" Row="0" RowSpan="1" Column="0" ColumnSpan="2" /><Control Name="_parameterInformation" Row="2" RowSpan="1" Column="0" ColumnSpan="2" /></Controls><Columns Styles="Percent,100,Absolute,545" /><Rows Styles="AutoSize,0,AutoSize,0,AutoSize,0,Absolute,20" /></TableLayoutSettings> True diff --git a/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsPage.cs b/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsPage.cs index 7b67acd6ed..3c6c4e8227 100644 --- a/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsPage.cs +++ b/Python/Product/PythonTools/PythonTools/Options/PythonAdvancedEditorOptionsPage.cs @@ -21,6 +21,7 @@ namespace Microsoft.PythonTools.Options { [ComVisible(true)] public class PythonAdvancedEditorOptionsPage : PythonDialogPage { private PythonAdvancedEditorOptionsControl _window; + private bool _parameterInformation = true; // replace the default UI of the dialog page w/ our own UI. protected override IWin32Window Window { @@ -38,18 +39,33 @@ protected override IWin32Window Window { /// a call to to commit the new /// values. /// - public override void ResetSettings() => PyService.AdvancedEditorOptions.Reset(); + public override void ResetSettings() { + PyService.AdvancedEditorOptions.Reset(); + _parameterInformation = true; + _window?.SyncControlWithPageSettings(PyService, _parameterInformation); + } public override void LoadSettingsFromStorage() { PyService.AdvancedEditorOptions.Load(); + _parameterInformation = PyService.GetLanguagePreferences().fAutoListParams != 0; // Synchronize UI with backing properties. - _window?.SyncControlWithPageSettings(PyService); + _window?.SyncControlWithPageSettings(PyService, _parameterInformation); } public override void SaveSettingsToStorage() { // Synchronize backing properties with UI. _window?.SyncPageWithControlSettings(PyService); + if (_window != null) { + _parameterInformation = _window.ParameterInformation; + } PyService.AdvancedEditorOptions.Save(); + + var languagePreferences = PyService.GetLanguagePreferences(); + var autoListParams = _parameterInformation ? 1u : 0u; + if (languagePreferences.fAutoListParams != autoListParams) { + languagePreferences.fAutoListParams = autoListParams; + PyService.SetLanguagePreferences(languagePreferences); + } } } }