diff --git a/Build/18.0/packages.config b/Build/18.0/packages.config index 4e71b0f318..9ca1241570 100644 --- a/Build/18.0/packages.config +++ b/Build/18.0/packages.config @@ -85,6 +85,7 @@ + diff --git a/Python/Product/PythonTools/PythonTools.csproj b/Python/Product/PythonTools/PythonTools.csproj index 29515d8543..6648e150b6 100644 --- a/Python/Product/PythonTools/PythonTools.csproj +++ b/Python/Product/PythonTools/PythonTools.csproj @@ -243,6 +243,11 @@ True + + $(PackagesPath)\System.Text.Json.8.0.5\lib\net462\System.Text.Json.dll + false + false + diff --git a/Python/Product/PythonTools/PythonTools/LanguageServerClient/LanguageServerSettings.cs b/Python/Product/PythonTools/PythonTools/LanguageServerClient/LanguageServerSettings.cs index cd294d19cf..9b62f11d2a 100644 --- a/Python/Product/PythonTools/PythonTools/LanguageServerClient/LanguageServerSettings.cs +++ b/Python/Product/PythonTools/PythonTools/LanguageServerClient/LanguageServerSettings.cs @@ -53,91 +53,91 @@ public class PythonAnalysisSettings { /// /// Paths to look for typeshed modules. /// - public string[] typeshedPaths; + public string[] typeshedPaths { get; set; } /// /// Path to directory containing custom type stub files. /// - public string stubPath; + public string stubPath { get; set; } /// /// Allows a user to override the severity levels for individual diagnostics. /// Typically specified in mspythonconfig.json. /// - public Dictionary diagnosticSeverityOverrides; + public Dictionary diagnosticSeverityOverrides { get; set; } /// /// Analyzes and reports errors on only open files or the entire workspace. /// "enum": ["openFilesOnly", "workspace"] /// - public string diagnosticMode; + public string diagnosticMode { get; set; } /// /// Specifies the level of logging for the Output panel. /// "enum": ["Error", "Warning", "Information", "Trace"] /// - public string logLevel; + public string logLevel { get; set; } /// /// Automatically add common search paths like 'src'. /// - public bool? autoSearchPaths; + public bool? autoSearchPaths { get; set; } /// /// Defines the default rule set for type checking. /// - public string typeCheckingMode; + public string typeCheckingMode { get; set; } /// /// Use library implementations to extract type information when type stub is not present. /// - public bool? useLibraryCodeForTypes; + public bool? useLibraryCodeForTypes { get; set; } /// /// Additional import search resolution paths. /// - public string[] extraPaths; + public string[] extraPaths { get; set; } /// /// Automatically add brackets for functions. /// - public bool completeFunctionParens; + public bool completeFunctionParens { get; set; } /// /// Offer auto-import completions. /// - public bool autoImportCompletions; + public bool autoImportCompletions { get; set; } /// /// Index installed third party libraries and user files for language features such as auto-import, add import, workspace symbols and etc. /// - public bool? indexing; + public bool? indexing { get; set; } /// /// Allow using '.', '(' as commit characters when applicable. /// - public bool? extraCommitChars; + public bool? extraCommitChars { get; set; } - public PythonAnalysisInlayHintsSettings inlayHints; + public PythonAnalysisInlayHintsSettings inlayHints { get; set; } - public string importFormat; + public string importFormat { get; set; } /// /// Tokens that identify comments that should show up in the task list pane /// - public TaskListToken[] taskListTokens; + public TaskListToken[] taskListTokens { get; set; } public class PythonAnalysisInlayHintsSettings { /// /// Enable/disable inlay hints for variable types:\n```python\nfoo ' :list[str] ' = [\"a\"]\n \n```\n /// - public bool variableTypes; + public bool variableTypes { get; set; } /// /// Enable/disable inlay hints for function return types:\n```python\ndef foo(x:int) ' -> int ':\n\treturn x\n```\n" /// - public bool functionReturnTypes; + public bool functionReturnTypes { get; set; } } public class TaskListToken { @@ -145,34 +145,34 @@ public class TaskListToken { /// /// The text of the token. /// - public string text; + public string text { get; set; } /// /// The priority of the token. /// This comes from the CommentTaskPriority enum in Microsoft.VisualStudio.Shell /// - public string priority; + public string priority { get; set; } } } /// /// Analysis settings. /// - public PythonAnalysisSettings analysis; + public PythonAnalysisSettings analysis { get; set; } /// /// Path to Python, you can use a custom version of Python. /// - public string pythonPath; + public string pythonPath { get; set; } /// /// Path to folder with a list of Virtual Environments. /// - public string venvPath; + public string venvPath { get; set; } } /// /// Python section. /// - public PythonSettings python; + public PythonSettings python { get; set; } } } diff --git a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs index c5939c9e6e..0a0d00c377 100644 --- a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs +++ b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs @@ -258,8 +258,8 @@ private Task TriggerWorkspaceUpdateConfig() { Debug.WriteLine("Settings Changed"); return InvokeDidChangeConfigurationAsync(new LSP.DidChangeConfigurationParams() { // Pylance will ask us for per workspace configuration settings. We can send - // default workspace settings here. - Settings = GetSettings() + // section-keyed default settings here for clients without configuration support. + Settings = new { python = GetSettings() } }); } diff --git a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs index a09e6e6a00..5e2ef5b014 100644 --- a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs +++ b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs @@ -23,6 +23,7 @@ using Microsoft.PythonTools.Logging; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Threading; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using StreamJsonRpc; using Task = System.Threading.Tasks.Task; @@ -95,43 +96,39 @@ public PythonLanguageClientCustomTarget(IServiceProvider site, JoinableTaskConte /// internal event AsyncEventHandler WorkspaceFolders; - [JsonRpcMethod("telemetry/event")] - public void OnTelemetryEvent(JToken arg) { - if (!(arg is JObject telemetry)) { + [JsonRpcMethod("telemetry/event", UseSingleObjectParameterDeserialization = true)] + public void OnTelemetryEvent(object arg) { + PylanceTelemetryEvent telemetry; + try { + telemetry = Deserialize(arg); + } catch (JsonException) { return; } - Trace.WriteLine(telemetry.ToString()); - try { - var te = telemetry.ToObject(); - if (te == null) { - return; - } - - if (te.Exception == null) { - _logger.LogEvent(te.EventName, te.Properties, te.Measurements); - } else { - _logger.LogFault(new PylanceException(te.EventName, te.Exception.stack), te.EventName, false); - } + if (telemetry == null) { + return; + } - // Special case language_server/analysis_complete. We need this for testing so we - // know when it's okay to try to bring up intellisense - if (te.EventName == "language_server/analysis_complete") { - AnalysisComplete.Invoke(this, EventArgs.Empty); - } - } catch { + Trace.WriteLine(arg); + if (telemetry.Exception == null) { + _logger?.LogEvent(telemetry.EventName, telemetry.Properties, telemetry.Measurements); + } else { + _logger?.LogFault(new PylanceException(telemetry.EventName, telemetry.Exception.stack), telemetry.EventName, false); + } + // Special case language_server/analysis_complete. We need this for testing so we + // know when it's okay to try to bring up intellisense + if (telemetry.EventName == "language_server/analysis_complete") { +AnalysisComplete?.Invoke(this, EventArgs.Empty); } } [JsonRpcMethod("python/beginProgress")] -#pragma warning disable IDE0060 // Remove unused parameter - public void OnBeginProgressAsync(JToken arg) { -#pragma warning restore IDE0060 // Remove unused parameter + public void OnBeginProgressAsync() { } [JsonRpcMethod("python/reportProgress")] - public async Task OnReportProgressAsync(JToken arg) { + public async Task OnReportProgressAsync(object arg) { if (arg != null) { await _joinableTaskContext.Factory.SwitchToMainThreadAsync(); @@ -143,7 +140,7 @@ public async Task OnReportProgressAsync(JToken arg) { } [JsonRpcMethod("python/endProgress")] - public async Task OnEndProgressAsync(JToken arg) { + public async Task OnEndProgressAsync() { await _joinableTaskContext.Factory.SwitchToMainThreadAsync(); // TODO: localize text @@ -151,15 +148,18 @@ public async Task OnEndProgressAsync(JToken arg) { statusBar?.SetText("Python analysis done"); } - [JsonRpcMethod("client/registerCapability")] - public void OnRegisterCapability(JToken arg) { - var regParams = arg.ToObject(); + [JsonRpcMethod("client/registerCapability", UseSingleObjectParameterDeserialization = true)] + public void OnRegisterCapability(object arg) { + var regParams = Deserialize(arg); + if (regParams?.Registrations == null) { + return; + } + if (regParams.Registrations.Any(p => p.Method == "workspace/didChangeWorkspaceFolders")) { _joinableTaskContext.Factory.RunAsync(async () => this.WorkspaceFolderChangeRegistered.Invoke(this, EventArgs.Empty)); } var watchedFilesReg = regParams.Registrations.FirstOrDefault(p => p.Method == "workspace/didChangeWatchedFiles"); - if (watchedFilesReg != null) { - var optionsObj = watchedFilesReg.RegisterOptions as JObject; + if (watchedFilesReg?.RegisterOptions is JObject optionsObj) { var options = optionsObj.ToObject(); if (options != null) { _joinableTaskContext.Factory.RunAsync(async () => this.WatchedFilesRegistered.Invoke(this, options)); @@ -167,19 +167,26 @@ public void OnRegisterCapability(JToken arg) { } } - [JsonRpcMethod("workspace/configuration")] - public async Task OnWorkspaceConfiguration(JToken arg) { - try { - var reqParams = arg.ToObject(); - if (this.WorkspaceConfiguration != null && reqParams != null) { - var eventArgs = new ConfigurationArgs { requestParams = reqParams, requestResult = null }; - await this.WorkspaceConfiguration.InvokeAsync(this, eventArgs); - return eventArgs.requestResult; - } - return null; - } catch { + [JsonRpcMethod("workspace/configuration", UseSingleObjectParameterDeserialization = true)] + public async Task OnWorkspaceConfiguration(object arg) { + var reqParams = Deserialize(arg); + if (this.WorkspaceConfiguration != null && reqParams != null) { + var eventArgs = new ConfigurationArgs { requestParams = reqParams, requestResult = null }; + await this.WorkspaceConfiguration.InvokeAsync(this, eventArgs); + return eventArgs.requestResult; + } + return null; + } + + private static T Deserialize(object arg) where T : class { + if (arg == null) { return null; } + + // Dev18 supplies System.Text.Json values; older formatters supply JToken. + return arg is JToken token + ? token.ToObject() + : JsonConvert.DeserializeObject(arg.ToString()); } [JsonRpcMethod("workspace/workspaceFolders")] diff --git a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs index d2c060fd7f..bffdd2f3f1 100644 --- a/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs +++ b/Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs @@ -1,11 +1,17 @@ using System; +#if DEV18_OR_LATER +using System.Text.Json; +#else using System.Globalization; +#endif using System.Threading; using System.Threading.Tasks; using Microsoft.PythonTools.Editor; using Microsoft.VisualStudio.LanguageServer.Client; using Microsoft.VisualStudio.LanguageServer.Protocol; +#if !DEV18_OR_LATER using Newtonsoft.Json.Linq; +#endif namespace Microsoft.PythonTools.LanguageServerClient { #if !DEV18_OR_LATER @@ -13,7 +19,7 @@ namespace Microsoft.PythonTools.LanguageServerClient { #endif internal sealed class PythonSignatureHelpMiddleLayer : #if DEV18_OR_LATER - ILanguageClientMiddleLayer2 { + ILanguageClientMiddleLayer2 { #else ILanguageClientMiddleLayer { #endif @@ -26,6 +32,43 @@ internal void Initialize(LanguagePreferences languagePreferences) { public bool CanHandle(string methodName) => string.Equals(methodName, Methods.TextDocumentSignatureHelpName, StringComparison.Ordinal); +#if DEV18_OR_LATER + public Task HandleRequestAsync(string methodName, JsonDocument methodParam, Func> sendRequest) { + if (ShouldSuppressAutomaticSignatureHelp(methodParam)) { + return Task.FromResult(null); + } + + return sendRequest(methodParam); + } + + public Task HandleNotificationAsync(string methodName, JsonDocument methodParam, Func sendNotification) => + sendNotification(methodParam); + + private bool ShouldSuppressAutomaticSignatureHelp(JsonDocument methodParam) { + var languagePreferences = Volatile.Read(ref _languagePreferences); + if (languagePreferences == null || languagePreferences.AutoListParams || methodParam == null) { + return false; + } + + var request = methodParam.RootElement; + if (request.ValueKind != JsonValueKind.Object || + !request.TryGetProperty("context", out var context) || + context.ValueKind != JsonValueKind.Object || + !context.TryGetProperty("triggerKind", out var triggerKindElement) || + triggerKindElement.ValueKind != JsonValueKind.Number || + !context.TryGetProperty("isRetrigger", out var isRetrigger) || + (isRetrigger.ValueKind != JsonValueKind.True && isRetrigger.ValueKind != JsonValueKind.False) || + isRetrigger.GetBoolean() || + (context.TryGetProperty("activeSignatureHelp", out var activeSignatureHelp) && + activeSignatureHelp.ValueKind != JsonValueKind.Null) || + !triggerKindElement.TryGetInt32(out var triggerKind)) { + return false; + } + + return triggerKind == (int)SignatureHelpTriggerKind.TriggerCharacter || + triggerKind == (int)SignatureHelpTriggerKind.ContentChange; + } +#else public Task HandleRequestAsync(string methodName, JToken methodParam, Func> sendRequest) { if (ShouldSuppressAutomaticSignatureHelp(methodParam)) { return Task.FromResult(JValue.CreateNull()); @@ -63,6 +106,7 @@ private bool ShouldSuppressAutomaticSignatureHelp(JToken methodParam) { return triggerKind == (int)SignatureHelpTriggerKind.TriggerCharacter || triggerKind == (int)SignatureHelpTriggerKind.ContentChange; } +#endif } #if !DEV18_OR_LATER #pragma warning restore CS0618